"""MCP protocol models and tool registry.""" from __future__ import annotations from typing import Any from pydantic import BaseModel, ConfigDict, Field class MCPTool(BaseModel): """MCP tool definition.""" name: str = Field(..., description="Unique tool identifier") description: str = Field(..., description="Human-readable tool description") input_schema: dict[str, Any] = Field( ..., alias="inputSchema", serialization_alias="inputSchema", description="JSON schema describing input arguments", ) write_operation: bool = Field(default=False, description="Whether tool mutates data") model_config = ConfigDict(populate_by_name=True) class MCPToolCallRequest(BaseModel): """Request to invoke an MCP tool.""" tool: str = Field(..., description="Name of the tool to invoke") arguments: dict[str, Any] = Field(default_factory=dict, description="Tool argument payload") correlation_id: str | None = Field(default=None, description="Request correlation ID") model_config = ConfigDict(extra="forbid") class MCPToolCallResponse(BaseModel): """Response returned from MCP tool invocation.""" success: bool = Field(..., description="Whether invocation succeeded") result: Any | None = Field(default=None, description="Tool result payload") error: str | None = Field(default=None, description="Error message for failed request") correlation_id: str = Field(..., description="Correlation ID for request tracing") class MCPListToolsResponse(BaseModel): """Response listing available tools.""" tools: list[MCPTool] = Field(..., description="Available tool definitions") def _tool( name: str, description: str, schema: dict[str, Any], write_operation: bool = False ) -> MCPTool: """Construct tool metadata entry.""" return MCPTool( name=name, description=description, inputSchema=schema, write_operation=write_operation, ) AVAILABLE_TOOLS: list[MCPTool] = [ _tool( "list_repositories", "List repositories visible to the authenticated Gitea API token.", {"type": "object", "properties": {}, "required": []}, ), _tool( "get_repository_info", "Get metadata for a repository.", { "type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_file_tree", "Get repository tree at a selected ref.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "ref": {"type": "string", "default": "main"}, "recursive": {"type": "boolean", "default": False}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_file_contents", "Read a repository file with size-limited content.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "filepath": {"type": "string"}, "ref": {"type": "string", "default": "main"}, }, "required": ["owner", "repo", "filepath"], "additionalProperties": False, }, ), _tool( "search_code", "Search code in a repository.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "query": {"type": "string"}, "ref": {"type": "string", "default": "main"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}, }, "required": ["owner", "repo", "query"], "additionalProperties": False, }, ), _tool( "list_commits", "List commits for a repository ref.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "ref": {"type": "string", "default": "main"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_commit_diff", "Get commit metadata and file diffs.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "sha": {"type": "string"}, }, "required": ["owner", "repo", "sha"], "additionalProperties": False, }, ), _tool( "compare_refs", "Compare two repository refs.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "base": {"type": "string"}, "head": {"type": "string"}, }, "required": ["owner", "repo", "base", "head"], "additionalProperties": False, }, ), _tool( "list_issues", "List repository issues.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "state": {"type": "string", "enum": ["open", "closed", "all"], "default": "open"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}, "labels": {"type": "array", "items": {"type": "string"}, "default": []}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_issue", "Get repository issue details.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, }, "required": ["owner", "repo", "issue_number"], "additionalProperties": False, }, ), _tool( "list_pull_requests", "List repository pull requests.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "state": {"type": "string", "enum": ["open", "closed", "all"], "default": "open"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_pull_request", "Get pull request details.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "pull_number": {"type": "integer", "minimum": 1}, }, "required": ["owner", "repo", "pull_number"], "additionalProperties": False, }, ), _tool( "list_labels", "List labels defined on a repository.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "list_tags", "List repository tags.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "list_releases", "List repository releases.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "list_pull_request_files", "List files changed in a pull request.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "pull_number": {"type": "integer", "minimum": 1}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo", "pull_number"], "additionalProperties": False, }, ), _tool( "list_pull_request_commits", "List commits in a pull request.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "pull_number": {"type": "integer", "minimum": 1}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo", "pull_number"], "additionalProperties": False, }, ), _tool( "list_issue_comments", "List comments on an issue or pull request.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo", "issue_number"], "additionalProperties": False, }, ), _tool( "list_branches", "List repository branches.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_branch", "Get a single branch.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "branch": {"type": "string"}, }, "required": ["owner", "repo", "branch"], "additionalProperties": False, }, ), _tool( "get_release", "Get a release by id.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "release_id": {"type": "integer", "minimum": 1}, }, "required": ["owner", "repo", "release_id"], "additionalProperties": False, }, ), _tool( "get_latest_release", "Get the latest published release.", { "type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "list_milestones", "List repository milestones.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "state": {"type": "string", "enum": ["open", "closed", "all"], "default": "open"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "get_commit_status", "Get the combined commit status for a ref or sha.", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "sha": {"type": "string"}, }, "required": ["owner", "repo", "sha"], "additionalProperties": False, }, ), _tool( "list_org_repositories", "List repositories belonging to an organization.", { "type": "object", "properties": { "org": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": ["org"], "additionalProperties": False, }, ), _tool( "list_organizations", "List organizations the authenticated user belongs to.", { "type": "object", "properties": { "page": {"type": "integer", "minimum": 1, "default": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, }, "required": [], "additionalProperties": False, }, ), _tool( "get_repo_languages", "Get the language breakdown for a repository.", { "type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "list_repo_topics", "List the topics assigned to a repository.", { "type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}, "required": ["owner", "repo"], "additionalProperties": False, }, ), _tool( "create_issue", "Create a repository issue (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "title": {"type": "string"}, "body": {"type": "string", "default": ""}, "labels": {"type": "array", "items": {"type": "string"}, "default": []}, "assignees": {"type": "array", "items": {"type": "string"}, "default": []}, "milestone": { "type": ["integer", "string"], "description": "Milestone id or title to assign the issue to", }, }, "required": ["owner", "repo", "title"], "additionalProperties": False, }, write_operation=True, ), _tool( "update_issue", "Update issue title/body/state/milestone (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "title": {"type": "string"}, "body": {"type": "string"}, "state": {"type": "string", "enum": ["open", "closed"]}, "milestone": { "type": ["integer", "string"], "description": "Milestone id or title to assign; 0 clears the milestone", }, }, "required": ["owner", "repo", "issue_number"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_issue_comment", "Create issue comment (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "body": {"type": "string"}, }, "required": ["owner", "repo", "issue_number", "body"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_pr_comment", "Create pull request comment (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "pull_number": {"type": "integer", "minimum": 1}, "body": {"type": "string"}, }, "required": ["owner", "repo", "pull_number", "body"], "additionalProperties": False, }, write_operation=True, ), _tool( "add_labels", "Add labels to an issue or PR (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "labels": {"type": "array", "items": {"type": "string"}, "minItems": 1}, }, "required": ["owner", "repo", "issue_number", "labels"], "additionalProperties": False, }, write_operation=True, ), _tool( "assign_issue", "Assign users to issue or PR (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "assignees": {"type": "array", "items": {"type": "string"}, "minItems": 1}, }, "required": ["owner", "repo", "issue_number", "assignees"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_label", "Create a repository label (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "name": {"type": "string"}, "color": {"type": "string", "description": "Hex color, e.g. #00aabb"}, "description": {"type": "string", "default": ""}, "exclusive": {"type": "boolean", "default": False}, }, "required": ["owner", "repo", "name", "color"], "additionalProperties": False, }, write_operation=True, ), _tool( "update_label", "Update an existing repository label, located by its current name (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "name": {"type": "string", "description": "Current label name"}, "new_name": {"type": "string"}, "color": {"type": "string", "description": "Hex color, e.g. #00aabb"}, "description": {"type": "string"}, }, "required": ["owner", "repo", "name"], "additionalProperties": False, }, write_operation=True, ), _tool( "remove_labels", "Remove labels (by name) from an issue or pull request (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "issue_number": {"type": "integer", "minimum": 1}, "labels": {"type": "array", "items": {"type": "string"}, "minItems": 1}, }, "required": ["owner", "repo", "issue_number", "labels"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_pull_request", "Open a pull request from head into base (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "title": {"type": "string"}, "head": {"type": "string", "description": "Source branch"}, "base": {"type": "string", "description": "Target branch"}, "body": {"type": "string", "default": ""}, }, "required": ["owner", "repo", "title", "head", "base"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_release", "Create a release for a tag (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "tag_name": {"type": "string"}, "name": {"type": "string", "default": ""}, "body": {"type": "string", "default": ""}, "draft": {"type": "boolean", "default": False}, "prerelease": {"type": "boolean", "default": False}, "target": {"type": "string", "description": "Target commitish/branch"}, }, "required": ["owner", "repo", "tag_name"], "additionalProperties": False, }, write_operation=True, ), _tool( "edit_release", "Edit an existing release (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "release_id": {"type": "integer", "minimum": 1}, "name": {"type": "string"}, "body": {"type": "string"}, "draft": {"type": "boolean"}, "prerelease": {"type": "boolean"}, }, "required": ["owner", "repo", "release_id"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_branch", "Create a branch, optionally from an existing branch (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "new_branch_name": {"type": "string"}, "old_branch_name": {"type": "string", "description": "Source branch (optional)"}, }, "required": ["owner", "repo", "new_branch_name"], "additionalProperties": False, }, write_operation=True, ), _tool( "create_milestone", "Create a repository milestone (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "title": {"type": "string"}, "description": {"type": "string", "default": ""}, "due_on": {"type": "string", "description": "ISO8601 due date (optional)"}, }, "required": ["owner", "repo", "title"], "additionalProperties": False, }, write_operation=True, ), _tool( "edit_issue_comment", "Edit an existing issue or PR comment (write-mode only).", { "type": "object", "properties": { "owner": {"type": "string"}, "repo": {"type": "string"}, "comment_id": {"type": "integer", "minimum": 1}, "body": {"type": "string"}, }, "required": ["owner", "repo", "comment_id", "body"], "additionalProperties": False, }, write_operation=True, ), _tool( "gitea_request", ( "Generic escape hatch that calls an arbitrary Gitea REST endpoint " "(method + path). Prefer the dedicated tools; use this only for " "endpoints they do not cover. Subject to policy, write-mode and the " "sensitive-path denylist. Methods other than GET/HEAD are writes and " "require write-mode plus a whitelisted repository." ), { "type": "object", "properties": { "method": { "type": "string", "enum": ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"], }, "path": { "type": "string", "description": "Gitea REST path, e.g. /repos/{owner}/{repo}/pulls/1/merge", }, "query": {"type": "object", "description": "Optional query-string parameters"}, "body": {"type": "object", "description": "Optional JSON request body"}, }, "required": ["method", "path"], "additionalProperties": False, }, # write_operation is intentionally False: a static flag cannot describe a # tool that is read OR write depending on the method. Setting it True # would force the central write-mode gate on GETs and break reads. The # handler is authoritative via its own per-method authorize() call. write_operation=False, ), ] def get_tool_by_name(tool_name: str) -> MCPTool | None: """Get tool definition by name.""" for tool in AVAILABLE_TOOLS: if tool.name == tool_name: return tool return None