Files
labyricorn-site/labyricorn_mcp/server.py
T
Labyricorn fe6025fa63
Deploy production / deploy (push) Successful in 21s
Add local repository MCP server
2026-08-23 10:48:33 -07:00

152 lines
6.1 KiB
Python

"""Dependency-free MCP stdio transport for the local Labyricorn repository."""
from __future__ import annotations
import json
import logging
import sys
from typing import Any, TextIO
from . import __version__
from .errors import LabyricornMcpError
from .safety import RepositoryGuard
from .tools import ToolRegistry
SUPPORTED_PROTOCOLS = ("2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05")
LATEST_PROTOCOL = SUPPORTED_PROTOCOLS[0]
class StdioMcpServer:
def __init__(self, guard: RepositoryGuard, *, input_stream: TextIO | None = None, output_stream: TextIO | None = None):
self.guard = guard
self.registry = ToolRegistry(guard)
self.input = input_stream or sys.stdin
self.output = output_stream or sys.stdout
self.initialized = False
self.client_initialized = False
self.protocol_version: str | None = None
self.log = logging.getLogger("labyricorn_mcp")
def serve_forever(self) -> int:
self.log.info("server started for repository %s", self.guard.root)
for raw_line in self.input:
line = raw_line.rstrip("\r\n")
if not line:
continue
try:
message = json.loads(line)
except json.JSONDecodeError:
self._write_error(None, -32700, "Parse error")
continue
if not isinstance(message, dict) or isinstance(message.get("id"), (dict, list)):
self._write_error(message.get("id") if isinstance(message, dict) else None, -32600, "Invalid Request")
continue
self._handle(message)
self.log.info("server stopped after stdin closed")
return 0
def _handle(self, message: dict[str, Any]) -> None:
method = message.get("method")
request_id = message.get("id")
is_notification = "id" not in message
if message.get("jsonrpc") != "2.0" or not isinstance(method, str):
if not is_notification:
self._write_error(request_id, -32600, "Invalid Request")
return
params = message.get("params", {})
if not isinstance(params, dict):
if not is_notification:
self._write_error(request_id, -32602, "Invalid params")
return
if is_notification:
if method == "notifications/initialized" and self.initialized:
self.client_initialized = True
return
if method == "initialize":
self._initialize(request_id, params)
return
if not self.initialized:
self._write_error(request_id, -32002, "Server is not initialized")
return
if method == "ping":
self._write_result(request_id, {})
elif method == "tools/list":
self._write_result(request_id, {"tools": self.registry.tools})
elif method == "tools/call":
self._call_tool(request_id, params)
else:
self._write_error(request_id, -32601, "Method not found")
def _initialize(self, request_id: Any, params: dict[str, Any]) -> None:
if self.initialized:
self._write_error(request_id, -32600, "Server is already initialized")
return
requested = params.get("protocolVersion")
self.protocol_version = requested if requested in SUPPORTED_PROTOCOLS else LATEST_PROTOCOL
self.initialized = True
self._write_result(
request_id,
{
"protocolVersion": self.protocol_version,
"capabilities": {"tools": {"listChanged": False}},
"serverInfo": {"name": "labyricorn-mcp", "version": __version__},
"instructions": (
"This server manages only the configured local Labyricorn Git checkout. "
"It has no production administration capability. Commits and pushes are separate explicit tools."
),
},
)
def _call_tool(self, request_id: Any, params: dict[str, Any]) -> None:
name = params.get("name")
if not isinstance(name, str):
self._write_error(request_id, -32602, "Tool name is required")
return
self.log.info("tool invocation: %s", name)
try:
result = self.registry.call(name, params.get("arguments"))
except LabyricornMcpError as exc:
self.log.warning("tool rejected: %s code=%s", name, exc.code)
payload = {"error": exc.as_dict()}
self._write_result(
request_id,
{
"content": [{"type": "text", "text": json.dumps(payload, ensure_ascii=False)}],
"structuredContent": payload,
"isError": True,
},
)
except Exception:
self.log.exception("unexpected tool failure: %s", name)
payload = {"error": {"code": "INTERNAL_ERROR", "message": "The tool failed unexpectedly; see local stderr diagnostics."}}
self._write_result(
request_id,
{
"content": [{"type": "text", "text": json.dumps(payload)}],
"structuredContent": payload,
"isError": True,
},
)
else:
self._write_result(
request_id,
{
"content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False, default=str)}],
"structuredContent": result,
"isError": False,
},
)
def _write_result(self, request_id: Any, result: dict[str, Any]) -> None:
self._write({"jsonrpc": "2.0", "id": request_id, "result": result})
def _write_error(self, request_id: Any, code: int, message: str) -> None:
self._write({"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}})
def _write(self, message: dict[str, Any]) -> None:
self.output.write(json.dumps(message, ensure_ascii=False, separators=(",", ":")) + "\n")
self.output.flush()