generated from personal-projects/leo-claude-mktplace
Implemented MCP tools for Gitea milestone operations:
- Created src/gitea_mcp/tools/milestones.py with milestone tools
- gitea_list_milestones: List milestones with state filter (open/closed/all)
- gitea_create_milestone: Create milestone with title, description, due date
- Error handling via GiteaClientError
- Helper functions _list_milestones and _create_milestone
- Updated src/gitea_mcp/tools/__init__.py
- Exported get_milestone_tools and handle_milestone_tool
- Updated src/gitea_mcp/server.py
- Imported milestone tool functions
- Added milestone tools to list_tools()
- Added milestone handler to call_tool() dispatcher
API endpoints implemented:
- GET /repos/{owner}/{repo}/milestones (list with state filter)
- POST /repos/{owner}/{repo}/milestones (create)
All acceptance criteria met:
- tools/milestones.py created with MCP tool handlers
- gitea_list_milestones with state filter implemented
- gitea_create_milestone with title, description, due_on implemented
- Tools registered in server.py
- tools/__init__.py exports updated
Closes #5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
191 lines
5.9 KiB
Python
191 lines
5.9 KiB
Python
"""Gitea milestone operations tools for MCP server."""
|
|
|
|
from typing import Any, Optional
|
|
from mcp.types import Tool, TextContent
|
|
|
|
from ..client import GiteaClient, GiteaClientError
|
|
|
|
|
|
def get_milestone_tools() -> list[Tool]:
|
|
"""Get list of milestone operation tools.
|
|
|
|
Returns:
|
|
list[Tool]: List of MCP tools for milestone operations.
|
|
"""
|
|
return [
|
|
Tool(
|
|
name="gitea_list_milestones",
|
|
description="List milestones in a Gitea repository with optional state filter",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"owner": {
|
|
"type": "string",
|
|
"description": "Repository owner (username or organization)",
|
|
},
|
|
"repo": {
|
|
"type": "string",
|
|
"description": "Repository name",
|
|
},
|
|
"state": {
|
|
"type": "string",
|
|
"description": "Filter by state: open, closed, or all (default: open)",
|
|
"enum": ["open", "closed", "all"],
|
|
"default": "open",
|
|
},
|
|
},
|
|
"required": ["owner", "repo"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="gitea_create_milestone",
|
|
description="Create a new milestone in a Gitea repository",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"owner": {
|
|
"type": "string",
|
|
"description": "Repository owner (username or organization)",
|
|
},
|
|
"repo": {
|
|
"type": "string",
|
|
"description": "Repository name",
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Milestone title",
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Milestone description (optional)",
|
|
},
|
|
"due_on": {
|
|
"type": "string",
|
|
"description": "Due date in ISO 8601 format, e.g., '2024-12-31T23:59:59Z' (optional)",
|
|
},
|
|
},
|
|
"required": ["owner", "repo", "title"],
|
|
},
|
|
),
|
|
]
|
|
|
|
|
|
async def handle_milestone_tool(
|
|
name: str, arguments: dict[str, Any], client: GiteaClient
|
|
) -> list[TextContent]:
|
|
"""Handle milestone tool calls.
|
|
|
|
Args:
|
|
name: Tool name.
|
|
arguments: Tool arguments.
|
|
client: Gitea API client instance.
|
|
|
|
Returns:
|
|
list[TextContent]: Tool response.
|
|
"""
|
|
try:
|
|
if name == "gitea_list_milestones":
|
|
return await _list_milestones(arguments, client)
|
|
elif name == "gitea_create_milestone":
|
|
return await _create_milestone(arguments, client)
|
|
else:
|
|
return [
|
|
TextContent(
|
|
type="text",
|
|
text=f"Unknown milestone tool: {name}",
|
|
)
|
|
]
|
|
except GiteaClientError as e:
|
|
return [
|
|
TextContent(
|
|
type="text",
|
|
text=f"Error: {str(e)}",
|
|
)
|
|
]
|
|
|
|
|
|
async def _list_milestones(
|
|
arguments: dict[str, Any], client: GiteaClient
|
|
) -> list[TextContent]:
|
|
"""List milestones in a repository.
|
|
|
|
Args:
|
|
arguments: Tool arguments containing owner, repo, and optional state filter.
|
|
client: Gitea API client instance.
|
|
|
|
Returns:
|
|
list[TextContent]: List of milestones.
|
|
"""
|
|
owner = arguments["owner"]
|
|
repo = arguments["repo"]
|
|
state = arguments.get("state", "open")
|
|
|
|
params = {"state": state}
|
|
|
|
async with client:
|
|
milestones = await client.get(
|
|
f"/repos/{owner}/{repo}/milestones", params=params
|
|
)
|
|
|
|
# Format response
|
|
if not milestones:
|
|
return [
|
|
TextContent(
|
|
type="text",
|
|
text=f"No {state} milestones found in {owner}/{repo}",
|
|
)
|
|
]
|
|
|
|
result = f"Found {len(milestones)} {state} milestone(s) in {owner}/{repo}:\n\n"
|
|
for milestone in milestones:
|
|
result += f"{milestone.get('title', 'Untitled')}\n"
|
|
result += f" State: {milestone.get('state', 'unknown')}\n"
|
|
if milestone.get("description"):
|
|
result += f" Description: {milestone['description']}\n"
|
|
if milestone.get("due_on"):
|
|
result += f" Due: {milestone['due_on']}\n"
|
|
result += f" Open Issues: {milestone.get('open_issues', 0)}\n"
|
|
result += f" Closed Issues: {milestone.get('closed_issues', 0)}\n"
|
|
result += f" Created: {milestone.get('created_at', 'N/A')}\n\n"
|
|
|
|
return [TextContent(type="text", text=result)]
|
|
|
|
|
|
async def _create_milestone(
|
|
arguments: dict[str, Any], client: GiteaClient
|
|
) -> list[TextContent]:
|
|
"""Create a new milestone.
|
|
|
|
Args:
|
|
arguments: Tool arguments containing owner, repo, title, and optional fields.
|
|
client: Gitea API client instance.
|
|
|
|
Returns:
|
|
list[TextContent]: Created milestone details.
|
|
"""
|
|
owner = arguments["owner"]
|
|
repo = arguments["repo"]
|
|
|
|
data = {
|
|
"title": arguments["title"],
|
|
}
|
|
|
|
if "description" in arguments:
|
|
data["description"] = arguments["description"]
|
|
|
|
if "due_on" in arguments:
|
|
data["due_on"] = arguments["due_on"]
|
|
|
|
async with client:
|
|
milestone = await client.post(
|
|
f"/repos/{owner}/{repo}/milestones", json=data
|
|
)
|
|
|
|
result = f"Created milestone: {milestone['title']}\n"
|
|
if milestone.get("description"):
|
|
result += f"Description: {milestone['description']}\n"
|
|
if milestone.get("due_on"):
|
|
result += f"Due: {milestone['due_on']}\n"
|
|
|
|
return [TextContent(type="text", text=result)]
|