# ADR 0002: official MCP SDK, low-level server Status: accepted (2026-10-14) ## Context The mission asks for the official MCP Python SDK (FastMCP). In `mcp` 2.x (current stable 2.3.0) FastMCP was renamed `MCPServer`. Its `add_tool()` always derives the input schema from a Python function signature and cannot accept a raw JSON schema. We register 538 tools whose schemas come from the Gitea OpenAPI spec. The SDK's low-level `mcp.server.Server` takes raw `Tool(input_schema=...)` objects and offers the same `streamable_http_app(auth=..., token_verifier=..., auth_server_provider=..., custom_starlette_routes=...)` wiring as `MCPServer`. Details: `docs/research/sdk-oauth-gitea.md` section 2. ## Decision - Depend on `mcp>=2.3,<3`. - Build one low-level `Server` from the generated catalog with a single `on_list_tools` and a single `on_call_tool` dispatcher. - Serve it over `stdio_server()` and over `server.streamable_http_app(...)` run by uvicorn. The FastAPI app, the hand-rolled JSON-RPC protocol and the `[server]` extra are removed; `mcp` already brings Starlette and uvicorn. - Validate arguments ourselves with `jsonschema` (v2 does not). - Tool failures are returned as `CallToolResult(is_error=True)` so the model can read them; only protocol problems raise `MCPError`. ## Consequences Same package, same transports and auth stack as `MCPServer`, without private API hacks. The core no longer needs a web-free boundary test: both transports come from the SDK.