Skip to content

Commit 9a0eb39

Browse files
floatingIce91DevRev
andauthored
Feature/stage tool (#26)
## Add Valid Stage Transition Tool https://app.devrev.ai/devrev/works/ISS-187541 ### Overview Added `mcp_devrev_valid_stage_transition` tool to retrieve available stage transitions for DevRev work items and parts. ### Features - **Multi-type Support**: Works with issues (ISS-), tickets (TKT-), and enhancements (ENH-) - **Workflow Validation**: Returns only valid stage transitions from current stage - **Complete Transition Data**: Provides both stage names and internal IDs - **Support Subtypes**: Works with custom types ### Usage ```python # Get valid transitions for an issue valid_transitions = mcp_devrev_valid_stage_transition(type="issue", id="ISS-12345") ``` ### Benefits - Prevents invalid stage transition attempts - Enables workflow automation with valid transition data - Provides clear visibility into available next steps ### Returns List of valid transitions with: - Target stage name (e.g., "completed", "in_review") - Target stage ID for API operations This tool enhances DevRev workflow management by ensuring users only attempt valid stage transitions. --------- Co-authored-by: DevRev <[email protected]>
1 parent 22093c0 commit 9a0eb39

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

src/devrev_mcp/server.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async def handle_list_tools() -> list[types.Tool]:
8585
"applies_to_part": {"type": "string", "description": "The DevRev ID of the part to which the work item applies"},
8686
"modified_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who modified the work item"},
8787
"owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who are assigned to the work item"},
88+
"stage": {"type": "string", "description": "The stage name of the work item. Use valid_stage_transition tool to get the list of valid stages you an update to."},
8889
},
8990
"required": ["id", "type"],
9091
},
@@ -212,6 +213,7 @@ async def handle_list_tools() -> list[types.Tool]:
212213
"description": {"type": "string", "description": "The description of the part"},
213214
"target_close_date": {"type": "string", "description": "The target closed date of the part, for example: 2025-06-03T00:00:00Z"},
214215
"target_start_date": {"type": "string", "description": "The target start date of the part, for example: 2025-06-03T00:00:00Z"},
216+
"stage": {"type": "string", "description": "The stage DevRev ID of the part. Use valid_stage_transition tool to get the list of valid stages you an update to."},
215217
},
216218
"required": ["id", "type"],
217219
},
@@ -365,6 +367,18 @@ async def handle_list_tools() -> list[types.Tool]:
365367
}
366368
}
367369
),
370+
types.Tool(
371+
name="valid_stage_transition",
372+
description="gets a list of valid stage transition for a given work item (issue, ticket) or part (enhancement). Use this before updating stage of the work item or part to ensure the transition is valid.",
373+
inputSchema={
374+
"type": "object",
375+
"properties": {
376+
"type": {"type": "string", "enum": ["issue", "ticket", "enhancement"]},
377+
"id": {"type": "string", "description": "The DevRev ID of the work item (issue, ticket) or part (enhancement)"},
378+
},
379+
"required": ["type", "id"]
380+
}
381+
)
368382
]
369383

370384
@server.call_tool()
@@ -540,6 +554,10 @@ async def handle_call_tool(
540554
if applies_to_part:
541555
payload["applies_to_part"] = applies_to_part
542556

557+
stage = arguments.get("stage")
558+
if stage:
559+
payload["stage"] = {"name": stage}
560+
543561
response = make_devrev_request(
544562
"works.update",
545563
payload
@@ -778,6 +796,10 @@ async def handle_call_tool(
778796
if target_start_date:
779797
payload["target_start_date"] = target_start_date
780798

799+
stage = arguments.get("stage")
800+
if stage:
801+
payload["stage_v2"] = stage
802+
781803
response = make_devrev_request(
782804
"parts.update",
783805
payload
@@ -962,6 +984,128 @@ async def handle_call_tool(
962984
text=f"Meetings listed successfully: {response.json()}"
963985
)
964986
]
987+
elif name == "valid_stage_transition":
988+
if not arguments:
989+
raise ValueError("Missing arguments")
990+
991+
payload = {}
992+
993+
id = arguments.get("id")
994+
if not id:
995+
raise ValueError("Missing id parameter")
996+
payload["id"] = id
997+
998+
type = arguments.get("type")
999+
if not type:
1000+
raise ValueError("Missing type parameter")
1001+
payload["type"] = type
1002+
1003+
current_stage_id = None
1004+
leaf_type = None
1005+
subtype = None
1006+
1007+
if(type == "issue" or type == "ticket"):
1008+
response = make_devrev_request(
1009+
"works.get",
1010+
{
1011+
"id": id
1012+
}
1013+
)
1014+
1015+
if response.status_code != 200:
1016+
error_text = response.text
1017+
return [
1018+
types.TextContent(
1019+
type="text",
1020+
text=f"Get work item failed with status {response.status_code}: {error_text}"
1021+
)
1022+
]
1023+
1024+
current_stage_id = response.json().get("work", {}).get("stage", {}).get("stage", {}).get("id", {})
1025+
leaf_type = response.json().get("work", {}).get("type", {})
1026+
subtype = response.json().get("work", {}).get("subtype", {})
1027+
1028+
elif(type == "enhancement"):
1029+
response = make_devrev_request(
1030+
"parts.get",
1031+
{
1032+
"id": id
1033+
}
1034+
)
1035+
1036+
if response.status_code != 200:
1037+
error_text = response.text
1038+
return [
1039+
types.TextContent(
1040+
type="text",
1041+
text=f"Get part failed with status {response.status_code}: {error_text}"
1042+
)
1043+
]
1044+
1045+
current_stage_id = response.json().get("part", {}).get("stage_v2", {}).get("stage", {}).get("id", {})
1046+
leaf_type = response.json().get("part", {}).get("type", {})
1047+
subtype = response.json().get("part", {}).get("subtype", {})
1048+
else:
1049+
raise ValueError("Invalid type parameter")
1050+
1051+
if(current_stage_id == {} or leaf_type == {}):
1052+
raise ValueError("Could not get current stage or leaf type")
1053+
1054+
schema_payload = {}
1055+
if(leaf_type != {}):
1056+
schema_payload["leaf_type"] = leaf_type
1057+
if(subtype != {}):
1058+
schema_payload["custom_schema_spec"] = {"subtype": subtype}
1059+
1060+
schema_response = make_devrev_request(
1061+
"schemas.aggregated.get",
1062+
schema_payload
1063+
)
1064+
1065+
if schema_response.status_code != 200:
1066+
error_text = schema_response.text
1067+
return [
1068+
types.TextContent(
1069+
type="text",
1070+
text=f"Get schema failed with status {schema_response.status_code}: {error_text}"
1071+
)
1072+
]
1073+
1074+
stage_diagram_id = schema_response.json().get("schema", {}).get("stage_diagram_id", {}).get("id", {})
1075+
if stage_diagram_id == None:
1076+
raise ValueError("Could not get stage diagram id")
1077+
1078+
stage_transitions_response = make_devrev_request(
1079+
"stage-diagrams.get",
1080+
{"id": stage_diagram_id}
1081+
)
1082+
1083+
if stage_transitions_response.status_code != 200:
1084+
error_text = stage_transitions_response.text
1085+
return [
1086+
types.TextContent(
1087+
type="text",
1088+
text=f"Get stage diagram for Get stage transitions failed with status {stage_transitions_response.status_code}: {error_text}"
1089+
)
1090+
]
1091+
1092+
stages = stage_transitions_response.json().get("stage_diagram", {}).get("stages", [])
1093+
for stage in stages:
1094+
if stage.get("stage", {}).get("id") == current_stage_id:
1095+
transitions = stage.get("transitions", [])
1096+
return [
1097+
types.TextContent(
1098+
type="text",
1099+
text=f"Valid Transitions for '{id}' from current stage:\n{transitions}"
1100+
)
1101+
]
1102+
1103+
return [
1104+
types.TextContent(
1105+
type="text",
1106+
text=f"No valid transitions found for '{id}' from current stage"
1107+
),
1108+
]
9651109
else:
9661110
raise ValueError(f"Unknown tool: {name}")
9671111

0 commit comments

Comments
 (0)