Skip to content

added tools for parts in devrev #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 213 additions & 33 deletions src/devrev_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,41 +38,85 @@ async def handle_list_tools() -> list[types.Tool]:
},
),
types.Tool(
name="get_object",
description="Get all information about a DevRev issue and ticket using its ID",
name="get_work",
description="Get all information about a DevRev work item using its ID",
inputSchema={
"type": "object",
"properties": {
"id": {"type": "string"},
"id": {"type": "string", "description": "The DevRev ID of the work item"},
},
"required": ["id"],
},
),
types.Tool(
name="create_object",
description="Create a new isssue or ticket in DevRev",
name="create_work",
description="Create a new work item (issue and ticket) in DevRev",
inputSchema={
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["issue", "ticket"]},
"title": {"type": "string"},
"body": {"type": "string"},
"applies_to_part": {"type": "string"},
"owned_by": {"type": "array", "items": {"type": "string"}}
"applies_to_part": {"type": "string", "description": "The DevRev ID of the part to which the work item applies"},
"owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who are assigned to the work item"}
},
"required": ["type", "title", "applies_to_part"],
},
),
types.Tool(
name="update_object",
description="Update an existing issue or ticket in DevRev",
name="update_work",
description="Update an existing work item (issue and ticket) in DevRev",
inputSchema={
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["issue", "ticket"]},
"id": {"type": "string"},
"title": {"type": "string"},
"body": {"type": "string"},
"applies_to_part": {"type": "string", "description": "The DevRev ID of the part to which the work item applies"},
"owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users who are assigned to the work item"},
"sprint": {"type": "string", "description": "The sprint ID to associate the issue or ticket with."},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the tool to fetch the relevant sprint as a part of this PR itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add once the API is public, removing sprint field from update_work tool for now

},
"required": ["id", "type"],
},
),
types.Tool(
name="get_part",
description="Get information about a part in DevRev using its ID",
inputSchema={
"type": "object",
"properties": {"id": {"type": "string", "description": "The DevRev ID of the part"}},
"required": ["id"],
},
),
types.Tool(
name="create_part",
description="Create a new part (enhancement) in DevRev",
inputSchema={
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["enhancement"]},
"name": {"type": "string"},
"owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users assigned to the part"},
"parent_part": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the parent parts"},
"description": {"type": "string", "description": "The description of the part"},
},
"required": ["type", "name", "owned_by", "parent_part"],
},
),
types.Tool(
name="update_part",
description="Update an existing part (enhancement) in DevRev",
inputSchema={
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["enhancement"]},
"id": {"type": "string", "description": "The DevRev ID of the part"},
"name": {"type": "string", "description": "The name of the part"},
"owned_by": {"type": "array", "items": {"type": "string"}, "description": "The DevRev IDs of the users assigned to the part"},
"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"},
},
"required": ["id", "type"],
},
Expand Down Expand Up @@ -101,7 +145,10 @@ async def handle_call_tool(

response = make_devrev_request(
"search.hybrid",
{"query": query, "namespace": namespace}
{
"query": query,
"namespace": namespace
}
)
if response.status_code != 200:
error_text = response.text
Expand All @@ -119,7 +166,7 @@ async def handle_call_tool(
text=f"Search results for '{query}':\n{search_results}"
)
]
elif name == "get_object":
elif name == "get_work":
if not arguments:
raise ValueError("Missing arguments")

Expand All @@ -129,7 +176,9 @@ async def handle_call_tool(

response = make_devrev_request(
"works.get",
{"id": id}
{
"id": id
}
)
if response.status_code != 200:
error_text = response.text
Expand All @@ -140,20 +189,18 @@ async def handle_call_tool(
)
]

object_info = response.json()
return [
types.TextContent(
type="text",
text=f"Object information for '{id}':\n{object_info}"
text=f"Object information for '{id}':\n{response.json()}"
)
]
elif name == "create_object":
elif name == "create_work":
if not arguments:
raise ValueError("Missing arguments")

# Mandatory fields
object_type = arguments.get("type")
if not object_type:
type = arguments.get("type")
if not type:
raise ValueError("Missing type parameter")

title = arguments.get("title")
Expand All @@ -164,14 +211,13 @@ async def handle_call_tool(
if not applies_to_part:
raise ValueError("Missing applies_to_part parameter")

# Optional fields
body = arguments.get("body", "")
owned_by = arguments.get("owned_by", [])

response = make_devrev_request(
"works.create",
{
"type": object_type,
"type": type,
"title": title,
"body": body,
"applies_to_part": applies_to_part,
Expand All @@ -193,37 +239,35 @@ async def handle_call_tool(
text=f"Object created successfully: {response.json()}"
)
]
elif name == "update_object":
# Update mandatory fields
elif name == "update_work":
if not arguments:
raise ValueError("Missing arguments")

id = arguments.get("id")
if not id:
raise ValueError("Missing id parameter")

object_type = arguments.get("type")
if not object_type:
type = arguments.get("type")
if not type:
raise ValueError("Missing type parameter")

# Update title and body
title = arguments.get("title")
body = arguments.get("body")

# Build request payload with only the fields that have values
update_payload = {"id": id, "type": object_type}
sprint = arguments.get("sprint")

payload = {"id": id, "type": type}
if title:
update_payload["title"] = title
payload["title"] = title
if body:
update_payload["body"] = body
payload["body"] = body
if sprint:
payload["sprint"] = sprint

# Make devrev request to update the object
response = make_devrev_request(
"works.update",
update_payload
payload
)

# Check if the request was successful
if response.status_code != 200:
error_text = response.text
return [
Expand All @@ -239,6 +283,142 @@ async def handle_call_tool(
text=f"Object updated successfully: {id}"
)
]
elif name == "get_part":
if not arguments:
raise ValueError("Missing arguments")

id = arguments.get("id")
if not id:
raise ValueError("Missing id parameter")

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}"
)
]

return [
types.TextContent(
type="text",
text=f"Part information for '{id}':\n{response.json()}"
)
]
elif name == "create_part":
if not arguments:
raise ValueError("Missing arguments")

payload = {}

type = arguments.get("type")
if not type:
raise ValueError("Missing type parameter")
payload["type"] = type

part_name = arguments.get("name")
if not part_name:
raise ValueError("Missing name parameter")
payload["name"] = part_name

owned_by = arguments.get("owned_by")
if not owned_by:
raise ValueError("Missing owned_by parameter")
payload["owned_by"] = owned_by

parent_part = arguments.get("parent_part")
if not parent_part:
raise ValueError("Missing parent_part parameter")
payload["parent_part"] = parent_part

description = arguments.get("description")
if description:
payload["description"] = description

response = make_devrev_request(
"parts.create",
payload
)

if response.status_code != 201:
error_text = response.text
return [
types.TextContent(
type="text",
text=f"Create part failed with status {response.status_code}: {error_text}"
)
]

return [
types.TextContent(
type="text",
text=f"Part created successfully: {response.json()}"
)
]
elif name == "update_part":
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

part_name = arguments.get("name")
if part_name:
payload["name"] = part_name

owned_by = arguments.get("owned_by")
if owned_by:
payload["owned_by"] = owned_by

description = arguments.get("description")
if description:
payload["description"] = description

target_close_date = arguments.get("target_close_date")
if target_close_date:
payload["target_close_date"] = target_close_date

target_start_date = arguments.get("target_start_date")
if target_start_date:
payload["target_start_date"] = target_start_date

response = make_devrev_request(
"parts.update",
payload
)

if response.status_code != 200:
error_text = response.text
return [
types.TextContent(
type="text",
text=f"Update part failed with status {response.status_code}: {error_text}"
)
]

return [
types.TextContent(
type="text",
text=f"Part updated successfully: {id}"
)
]
else:
raise ValueError(f"Unknown tool: {name}")

Expand Down
Loading