feat(tools): add label and milestone operations

- Create labels.py with gitea_list_labels and gitea_create_label tools
- Integrate milestone tools from milestones.py into server
- Update tools/__init__.py with all tool exports
- Update server.py to handle label and milestone tool calls

Closes #4, Closes #5

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 15:22:02 -05:00
parent 38dd315dd5
commit 7ffc0f9ce4
3 changed files with 190 additions and 3 deletions

View File

@@ -9,7 +9,14 @@ from mcp.types import Tool, TextContent
from . import __version__
from .auth import AuthConfig
from .client import GiteaClient, GiteaClientError
from .tools import get_issue_tools, handle_issue_tool, get_label_tools, handle_label_tool
from .tools import (
get_issue_tools,
handle_issue_tool,
get_label_tools,
handle_label_tool,
get_milestone_tools,
handle_milestone_tool,
)
# Global client instance
@@ -36,11 +43,12 @@ async def serve() -> None:
"""List available MCP tools.
Returns:
list: Available tools including issue and label operations.
list: Available tools including issue, label, and milestone operations.
"""
# Get issue and label tools
# Get issue, label, and milestone tools
tools = get_issue_tools()
tools.extend(get_label_tools())
tools.extend(get_milestone_tools())
# Placeholder for future tools (PR tools, etc.)
tools.extend([
@@ -115,6 +123,12 @@ async def serve() -> None:
):
return await handle_label_tool(gitea_client, name, arguments)
# Handle milestone tools
if name.startswith("gitea_") and any(
name.endswith(suffix) for suffix in ["_milestones", "_milestone"]
):
return await handle_milestone_tool(name, arguments, gitea_client)
# Placeholder for other tools
return [
TextContent(

View File

@@ -2,10 +2,13 @@
from .issues import get_issue_tools, handle_issue_tool
from .labels import get_label_tools, handle_label_tool
from .milestones import get_milestone_tools, handle_milestone_tool
__all__ = [
"get_issue_tools",
"handle_issue_tool",
"get_label_tools",
"handle_label_tool",
"get_milestone_tools",
"handle_milestone_tool",
]

View File

@@ -0,0 +1,170 @@
"""Gitea label operations MCP tools."""
from mcp.types import Tool, TextContent
from ..client import GiteaClient, GiteaClientError
def get_label_tools() -> list[Tool]:
"""Get label operation tool definitions.
Returns:
list: Tool definitions for label operations.
"""
return [
Tool(
name="gitea_list_labels",
description="List all labels in a Gitea repository",
inputSchema={
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (user or organization)",
},
"repo": {
"type": "string",
"description": "Repository name",
},
},
"required": ["owner", "repo"],
},
),
Tool(
name="gitea_create_label",
description="Create a new label in a Gitea repository",
inputSchema={
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (user or organization)",
},
"repo": {
"type": "string",
"description": "Repository name",
},
"name": {
"type": "string",
"description": "Label name",
},
"color": {
"type": "string",
"description": "Label color (hex without #, e.g., 'ff0000' for red)",
},
"description": {
"type": "string",
"description": "Label description (optional)",
},
},
"required": ["owner", "repo", "name", "color"],
},
),
]
async def handle_label_tool(
client: GiteaClient, name: str, arguments: dict
) -> list[TextContent]:
"""Handle label tool execution.
Args:
client: Gitea client instance.
name: Tool name.
arguments: Tool arguments.
Returns:
list: Tool response content.
"""
try:
async with client:
if name == "gitea_list_labels":
return await _list_labels(client, arguments)
elif name == "gitea_create_label":
return await _create_label(client, arguments)
else:
return [
TextContent(
type="text",
text=f"Unknown label tool: {name}",
)
]
except GiteaClientError as e:
return [
TextContent(
type="text",
text=f"Gitea API error: {e}",
)
]
async def _list_labels(client: GiteaClient, arguments: dict) -> list[TextContent]:
"""List labels in a repository.
Args:
client: Gitea client instance.
arguments: Tool arguments with owner and repo.
Returns:
list: Label listing response.
"""
owner = arguments["owner"]
repo = arguments["repo"]
labels = await client.get(f"/repos/{owner}/{repo}/labels")
if not labels:
return [
TextContent(
type="text",
text=f"No labels found in {owner}/{repo}",
)
]
# Format labels for display
lines = [f"Labels in {owner}/{repo}:", ""]
for label in labels:
color = label.get("color", "")
desc = label.get("description", "")
desc_text = f" - {desc}" if desc else ""
lines.append(f"{label['name']} (#{color}){desc_text}")
return [
TextContent(
type="text",
text="\n".join(lines),
)
]
async def _create_label(client: GiteaClient, arguments: dict) -> list[TextContent]:
"""Create a new label.
Args:
client: Gitea client instance.
arguments: Tool arguments with owner, repo, name, color, and optional description.
Returns:
list: Creation response.
"""
owner = arguments["owner"]
repo = arguments["repo"]
name = arguments["name"]
color = arguments["color"].lstrip("#") # Remove # if present
description = arguments.get("description", "")
payload = {
"name": name,
"color": color,
}
if description:
payload["description"] = description
label = await client.post(f"/repos/{owner}/{repo}/labels", payload)
return [
TextContent(
type="text",
text=f"Created label '{label['name']}' (#{label['color']}) in {owner}/{repo}",
)
]