feat: add create_label write tool
Adds a create_label write-mode tool so labels can be created in a repository
through the MCP server (previously there was no way to define labels, which
blocked attaching labels to issues). Follows the full tool checklist:
- arguments.py: CreateLabelArgs (name, hex color, optional description/exclusive),
with extra=forbid and a hex-color pattern.
- gitea_client.py: create_label() POSTing to /repos/{owner}/{repo}/labels with
url-encoded path segments.
- write_tools.py: create_label_tool handler; normalizes the color to a leading
'#', bounds text output, and lets auth/authz errors surface.
- mcp_protocol.py: register create_label (write_operation=True).
- server.py: wire create_label into TOOL_HANDLERS.
- docs/api-reference.md: document create_label.
- tests: success path, color normalization, and invalid-color rejection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from aegis_gitea_mcp.tools.write_tools import (
|
||||
assign_issue_tool,
|
||||
create_issue_comment_tool,
|
||||
create_issue_tool,
|
||||
create_label_tool,
|
||||
create_pr_comment_tool,
|
||||
update_issue_tool,
|
||||
)
|
||||
@@ -97,6 +98,9 @@ class StubGitea:
|
||||
async def assign_issue(self, owner, repo, index, assignees):
|
||||
return {"assignees": [{"login": user} for user in assignees]}
|
||||
|
||||
async def create_label(self, owner, repo, *, name, color, description="", exclusive=False):
|
||||
return {"id": 5, "name": name, "color": color, "description": description, "url": "u"}
|
||||
|
||||
|
||||
class ErrorGitea(StubGitea):
|
||||
"""Stub that raises backend errors for failure-mode coverage."""
|
||||
@@ -169,9 +173,42 @@ async def test_extended_read_tools_failure_mode() -> None:
|
||||
{"owner": "acme", "repo": "app", "issue_number": 1, "assignees": ["alice"]},
|
||||
"assignees",
|
||||
),
|
||||
(
|
||||
create_label_tool,
|
||||
{"owner": "acme", "repo": "app", "name": "bug", "color": "#ff0000"},
|
||||
"id",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_write_tools_success(tool, args, expected_key):
|
||||
"""Write tools should normalize successful backend responses."""
|
||||
result = await tool(StubGitea(), args)
|
||||
assert expected_key in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_label_normalizes_color_without_hash() -> None:
|
||||
"""A hex color without a leading '#' is normalized before hitting Gitea."""
|
||||
captured: dict = {}
|
||||
|
||||
class CaptureStub(StubGitea):
|
||||
async def create_label(self, owner, repo, *, name, color, description="", exclusive=False):
|
||||
captured["color"] = color
|
||||
return {"id": 7, "name": name, "color": color}
|
||||
|
||||
result = await create_label_tool(
|
||||
CaptureStub(),
|
||||
{"owner": "acme", "repo": "app", "name": "bug", "color": "ff0000"},
|
||||
)
|
||||
assert captured["color"] == "#ff0000"
|
||||
assert result["id"] == 7
|
||||
|
||||
|
||||
def test_create_label_args_reject_invalid_color() -> None:
|
||||
"""Non-hex color values are rejected at the argument layer."""
|
||||
import pydantic
|
||||
|
||||
from aegis_gitea_mcp.tools.arguments import CreateLabelArgs
|
||||
|
||||
with pytest.raises(pydantic.ValidationError):
|
||||
CreateLabelArgs(owner="o", repo="r", name="bug", color="red")
|
||||
|
||||
Reference in New Issue
Block a user