From d37acb6705d58e17afcc7c1a4cc8a05f32c86e0b Mon Sep 17 00:00:00 2001 From: DevRev Date: Thu, 26 Jun 2025 17:15:15 +0530 Subject: [PATCH 1/5] added tool to get valid stage transitions from issue, ticket or enhanement --- src/devrev_mcp/server.py | 134 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/devrev_mcp/server.py b/src/devrev_mcp/server.py index 4db7270..680461d 100644 --- a/src/devrev_mcp/server.py +++ b/src/devrev_mcp/server.py @@ -365,6 +365,18 @@ async def handle_list_tools() -> list[types.Tool]: } } ), + types.Tool( + name="valid_stage_transition", + 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.", + inputSchema={ + "type": "object", + "properties": { + "type": {"type": "string", "enum": ["issue", "ticket", "enhancement"]}, + "id": {"type": "string", "description": "The DevRev ID of the work item (issue, ticket) or part (enhancement)"}, + }, + "required": ["type", "id"] + } + ) ] @server.call_tool() @@ -962,6 +974,128 @@ async def handle_call_tool( text=f"Meetings listed successfully: {response.json()}" ) ] + elif name == "valid_stage_transition": + if not arguments: + raise ValueError("Missing arguments") + + payload = {} + + id = arguments.get("id") + if not id: + raise ValueError("Missing id parameter") + payload["id"] = id + + type = arguments.get("type") + if not type: + raise ValueError("Missing type parameter") + payload["type"] = type + + current_stage_id = None + leaf_type = None + subtype = None + + if(type == "issue" or type == "ticket"): + response = make_devrev_request( + "works.get", + { + "id": id + } + ) + + if response.status_code != 200: + error_text = response.text + return [ + types.TextContent( + type="text", + text=f"Get work item failed with status {response.status_code}: {error_text}" + ) + ] + + current_stage_id = response.json().get("work", {}).get("stage", {}).get("stage", {}).get("id", {}) + leaf_type = response.json().get("work", {}).get("type", {}) + subtype = response.json().get("work", {}).get("subtype", {}) + + elif(type == "enhancement"): + response = make_devrev_request( + "parts.get", + { + "id": id + } + ) + + if response.status_code != 200: + error_text = response.text + return [ + types.TextContent( + type="text", + text=f"Get part failed with status {response.status_code}: {error_text}" + ) + ] + + current_stage_id = response.json().get("part", {}).get("stage", {}).get("stage", {}).get("id", {}) + leaf_type = response.json().get("part", {}).get("type", {}) + subtype = response.json().get("part", {}).get("subtype", {}) + else: + raise ValueError("Invalid type parameter") + + if(current_stage_id == {} or leaf_type == {}): + raise ValueError("Could not get current stage or leaf type") + + schema_payload = {} + if(leaf_type != {}): + schema_payload["leaf_type"] = leaf_type + if(subtype != {}): + schema_payload["custom_schema_spec"] = {"subtype": subtype} + + schema_response = make_devrev_request( + "schemas.aggregated.get", + schema_payload + ) + + if schema_response.status_code != 200: + error_text = schema_response.text + return [ + types.TextContent( + type="text", + text=f"Get schema failed with status {schema_response.status_code}: {error_text}" + ) + ] + + stage_diagram_id = schema_response.json().get("schema", {}).get("stage_diagram_id", {}).get("id", {}) + if stage_diagram_id == None: + raise ValueError("Could not get stage diagram id") + + stage_transitions_response = make_devrev_request( + "stage-diagrams.get", + {"id": stage_diagram_id} + ) + + if stage_transitions_response.status_code != 200: + error_text = stage_transitions_response.text + return [ + types.TextContent( + type="text", + text=f"Get stage diagram for Get stage transitions failed with status {stage_transitions_response.status_code}: {error_text}" + ) + ] + + stages = stage_transitions_response.json().get("stage_diagram", {}).get("stages", []) + for stage in stages: + if stage.get("stage", {}).get("id") == current_stage_id: + transitions = stage.get("transitions", []) + return [ + types.TextContent( + type="text", + text=f"Valid Transitions for '{id}' from current stage:\n{transitions}" + ) + ] + + return [ + types.TextContent( + type="text", + text=f"No valid transitions found for '{id}' from current stage" + ), + ] else: raise ValueError(f"Unknown tool: {name}") From cc9110f6569db7c8a737fab18994f3a495faf6b2 Mon Sep 17 00:00:00 2001 From: DevRev Date: Thu, 26 Jun 2025 17:15:27 +0530 Subject: [PATCH 2/5] corrected a bug --- src/devrev_mcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devrev_mcp/server.py b/src/devrev_mcp/server.py index 680461d..2fabc03 100644 --- a/src/devrev_mcp/server.py +++ b/src/devrev_mcp/server.py @@ -1032,7 +1032,7 @@ async def handle_call_tool( ) ] - current_stage_id = response.json().get("part", {}).get("stage", {}).get("stage", {}).get("id", {}) + current_stage_id = response.json().get("part", {}).get("stage_v2", {}).get("stage", {}).get("id", {}) leaf_type = response.json().get("part", {}).get("type", {}) subtype = response.json().get("part", {}).get("subtype", {}) else: From 13aa87bc9b699f6758b3b301fbc36ca944a22ec3 Mon Sep 17 00:00:00 2001 From: DevRev Date: Mon, 30 Jun 2025 12:42:59 +0530 Subject: [PATCH 3/5] added stage field to update work --- src/devrev_mcp/server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/devrev_mcp/server.py b/src/devrev_mcp/server.py index 2fabc03..a6ceae6 100644 --- a/src/devrev_mcp/server.py +++ b/src/devrev_mcp/server.py @@ -85,6 +85,7 @@ async def handle_list_tools() -> list[types.Tool]: "applies_to_part": {"type": "string", "description": "The DevRev ID of the part to which the work item applies"}, "modified_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who modified the work item"}, "owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who are assigned to the work item"}, + "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."}, }, "required": ["id", "type"], }, @@ -552,6 +553,10 @@ async def handle_call_tool( if applies_to_part: payload["applies_to_part"] = applies_to_part + stage = arguments.get("stage") + if stage: + payload["stage"] = {"name": stage} + response = make_devrev_request( "works.update", payload From 67980efe330c4ff7543f38f8ddf755c4f7d5bc80 Mon Sep 17 00:00:00 2001 From: DevRev Date: Mon, 30 Jun 2025 16:08:12 +0530 Subject: [PATCH 4/5] added support stage field in update part --- src/devrev_mcp/server.py | 9 +++++++-- src/devrev_mcp/utils.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/devrev_mcp/server.py b/src/devrev_mcp/server.py index a6ceae6..03185f1 100644 --- a/src/devrev_mcp/server.py +++ b/src/devrev_mcp/server.py @@ -14,7 +14,7 @@ from mcp.server import NotificationOptions, Server from pydantic import AnyUrl import mcp.server.stdio -from .utils import make_devrev_request +from .utils import make_devrev_request, make_devrev_beta_request server = Server("devrev_mcp") @@ -213,6 +213,7 @@ async def handle_list_tools() -> list[types.Tool]: "description": {"type": "string", "description": "The description of the part"}, "target_close_date": {"type": "string", "description": "The target closed date of the part, for example: 2025-06-03T00:00:00Z"}, "target_start_date": {"type": "string", "description": "The target start date of the part, for example: 2025-06-03T00:00:00Z"}, + "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."}, }, "required": ["id", "type"], }, @@ -795,7 +796,11 @@ async def handle_call_tool( if target_start_date: payload["target_start_date"] = target_start_date - response = make_devrev_request( + stage = arguments.get("stage") + if stage: + payload["stage_v2"] = stage + + response = make_devrev_beta_request( "parts.update", payload ) diff --git a/src/devrev_mcp/utils.py b/src/devrev_mcp/utils.py index cfa6a37..876e35d 100644 --- a/src/devrev_mcp/utils.py +++ b/src/devrev_mcp/utils.py @@ -37,3 +37,33 @@ def make_devrev_request(endpoint: str, payload: Dict[str, Any]) -> requests.Resp headers=headers, json=payload ) + +def make_devrev_beta_request(endpoint: str, payload: Dict[str, Any]) -> requests.Response: + """ + Make an authenticated request to the DevRev API. + + Args: + endpoint: The API endpoint path (e.g., "works.get" or "search.hybrid") + payload: The JSON payload to send + + Returns: + requests.Response object + + Raises: + ValueError: If DEVREV_API_KEY environment variable is not set + """ + api_key = os.environ.get("DEVREV_API_KEY") + if not api_key: + raise ValueError("DEVREV_API_KEY environment variable is not set") + + headers = { + "Authorization": f"{api_key}", + "Content-Type": "application/json", + "X-Devrev-Scope": "beta" + } + + return requests.post( + f"https://api.devrev.ai/{endpoint}", + headers=headers, + json=payload + ) From 24fcac54aeb10a62e28e75da5fe68b4d26b9fd81 Mon Sep 17 00:00:00 2001 From: DevRev Date: Mon, 30 Jun 2025 16:18:06 +0530 Subject: [PATCH 5/5] no need for beta request --- src/devrev_mcp/server.py | 4 ++-- src/devrev_mcp/utils.py | 30 ------------------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/src/devrev_mcp/server.py b/src/devrev_mcp/server.py index 03185f1..b8deac5 100644 --- a/src/devrev_mcp/server.py +++ b/src/devrev_mcp/server.py @@ -14,7 +14,7 @@ from mcp.server import NotificationOptions, Server from pydantic import AnyUrl import mcp.server.stdio -from .utils import make_devrev_request, make_devrev_beta_request +from .utils import make_devrev_request server = Server("devrev_mcp") @@ -800,7 +800,7 @@ async def handle_call_tool( if stage: payload["stage_v2"] = stage - response = make_devrev_beta_request( + response = make_devrev_request( "parts.update", payload ) diff --git a/src/devrev_mcp/utils.py b/src/devrev_mcp/utils.py index 876e35d..cfa6a37 100644 --- a/src/devrev_mcp/utils.py +++ b/src/devrev_mcp/utils.py @@ -37,33 +37,3 @@ def make_devrev_request(endpoint: str, payload: Dict[str, Any]) -> requests.Resp headers=headers, json=payload ) - -def make_devrev_beta_request(endpoint: str, payload: Dict[str, Any]) -> requests.Response: - """ - Make an authenticated request to the DevRev API. - - Args: - endpoint: The API endpoint path (e.g., "works.get" or "search.hybrid") - payload: The JSON payload to send - - Returns: - requests.Response object - - Raises: - ValueError: If DEVREV_API_KEY environment variable is not set - """ - api_key = os.environ.get("DEVREV_API_KEY") - if not api_key: - raise ValueError("DEVREV_API_KEY environment variable is not set") - - headers = { - "Authorization": f"{api_key}", - "Content-Type": "application/json", - "X-Devrev-Scope": "beta" - } - - return requests.post( - f"https://api.devrev.ai/{endpoint}", - headers=headers, - json=payload - )