Introduce a GiteaOAuthValidator for JWT and userinfo validation and fallbacks, add /oauth/token proxy, and thread per-user tokens through the request context and automation paths. Update config and .env.example for OAuth-first mode, add OpenAPI, extensive unit/integration tests, GitHub/Gitea CI workflows, docs, and lint/test enforcement (>=80% cov).
386 lines
13 KiB
Python
386 lines
13 KiB
Python
"""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 configured bot account.",
|
|
{"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(
|
|
"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": []},
|
|
},
|
|
"required": ["owner", "repo", "title"],
|
|
"additionalProperties": False,
|
|
},
|
|
write_operation=True,
|
|
),
|
|
_tool(
|
|
"update_issue",
|
|
"Update issue title/body/state (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"]},
|
|
},
|
|
"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,
|
|
),
|
|
]
|
|
|
|
|
|
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
|