# sh0 MCP Server — Architecture & Implementation Plan ## Context sh0 already has AI chat in the dashboard with 10 hand-curated tools executing client-side via SSE. The OpenAPI spec (`/api/v1/openapi.json`) documents 50+ endpoints but only a fraction is exposed to AI. The goal is to let AI **fully understand and interact with sh0** — both for developers using external AI tools (Claude Desktop, Cursor, Claude Code) and for the embedded dashboard chat. **Key discovery:** Claude's API has a native **MCP Connector** (beta) that can connect to remote MCP servers directly. This means the sh0-website AI gateway can pass the user's sh0 instance URL as an MCP server to Claude, and Claude handles tool calling natively — no more client-side tool execution dance. --- ## Architecture: Three Access Paths, One MCP Server ``` Path 1: External AI Clients Path 2: Embedded Dashboard Chat (Claude Desktop, Cursor, Claude Code) (existing sh0 dashboard) | | | MCP (Streamable HTTP) | POST /api/ai/chat (local) v v ┌─────────────┐ sh0-core gateway │ sh0-core │ (same process, direct calls) │ /mcp │ │ endpoint │ Path 3: sh0.dev Website AI Chat └─────────────┘ (sh0.dev/account/ai) | | Direct Rust calls | POST /api/ai/chat (shared AppState) v sh0-website gateway | Claude API + MCP Connector | connects to user's sh0 instance (user provides URL + API key) ``` **Path 1 — Direct MCP:** Developers configure their AI IDE to connect to `https://myserver.com/api/v1/mcp` with an API key. Each sh0 instance serves its own MCP endpoint — no centralized `mcp.sh0.dev` needed (unlike SaaS products like Slack/Box that have one endpoint). **Path 2 — Embedded Dashboard:** The dashboard chat talks to sh0-core directly (same process). No external gateway needed for the local experience. **Path 3 — Website AI Chat:** Users on `sh0.dev/account` provide their instance URL + API key. The sh0-website gateway uses Claude API's MCP Connector to reach the user's sh0 instance. Requires the instance to be publicly accessible via HTTPS. **Why no `mcp.sh0.dev`?** sh0 is self-hosted. Each instance is its own MCP server at its own domain. A centralized proxy/tunnel could come later but adds infrastructure complexity that isn't needed for MVP. --- ## Implementation Phases ### Phase 1: MCP Server in sh0-core (MVP) **New module:** `crates/sh0-api/src/mcp/` | File | Purpose | |------|---------| | `mod.rs` | Module root | | `transport.rs` | Streamable HTTP handler (Axum POST endpoint + SSE) | | `protocol.rs` | JSON-RPC 2.0 framing, MCP initialize/tools/list/tools/call | | `tools.rs` | Tool definitions — auto-generated from OpenAPI spec at startup | | `auth.rs` | API key extraction from Authorization header | **Route:** `POST /api/v1/mcp` — single endpoint, MCP protocol handles multiplexing **Auth:** Reuse existing `sh0_` API keys via `Authorization: Bearer sh0_xxxxx` **MVP Tools (read-only, curated via `x-mcp-enabled` OpenAPI extension):** - `list_apps`, `get_app`, `get_app_metrics`, `get_app_logs` - `get_server_status`, `get_server_metrics` - `list_deployments`, `get_deployment_logs` - `list_databases`, `list_cron_jobs`, `list_backups` **No new Cargo dependencies.** MCP Streamable HTTP is JSON-RPC 2.0 over HTTP POST + SSE — sh0 already has `axum`, `serde_json`, `tokio`, and SSE support. **Key files to modify:** - `crates/sh0-api/src/router.rs` — add `/mcp` route, add `x-mcp-enabled` extensions to utoipa paths - `crates/sh0-api/src/handlers/*.rs` — add `x-mcp-enabled` extension to relevant `#[utoipa::path]` annotations ### Phase 2: OpenAPI-Driven Dynamic Tools Instead of manually listing tools, generate them from the OpenAPI spec at startup: 1. `utoipa::OpenApi::openapi()` already produces the spec in-memory 2. New function `mcp::tools::from_openapi(spec)` iterates paths, filters by `x-mcp-enabled: true` 3. Each operation becomes an MCP tool: `operationId` → tool name, `summary` → description, request schema → `inputSchema` 4. Add `x-mcp-risk: read | write | destructive` extension for security classification 5. Tool executor routes calls to the corresponding handler function directly (shared `AppState`) **Result:** Adding `x-mcp-enabled = true` to any new `#[utoipa::path]` annotation auto-exposes it to AI. Zero additional code per endpoint. ### Phase 3: Write Operations + Safety Add write tools with risk-based guardrails: - **`write` risk:** `restart_app`, `deploy_app`, `set_env_var`, `scale_app`, `create_backup`, `trigger_cron` - **`destructive` risk:** `delete_app`, `delete_database`, `delete_backup` **Safety layers:** 1. **API key scopes:** Add `scope` field to API keys (`read`, `standard`, `admin`). MCP checks scope before executing. 2. **Confirmation tokens:** Destructive operations return a token + description. AI must call `confirm_action(token)` to proceed. 3. **Audit logging:** Every MCP tool call logged with `source: "mcp"` in existing audit system. **Modify:** `crates/sh0-auth/src/api_key.rs` — add scope field; `crates/sh0-db/` — migration for scope column. ### Phase 4: Gateway MCP Connector Integration Upgrade sh0-website's AI gateway to use Claude's MCP Connector instead of client-side tool execution: ```typescript // sh0-website /api/ai/chat endpoint const response = await anthropic.messages.create({ model: selectedModel, messages: conversationMessages, mcp_servers: [{ url: userInstance.mcpUrl, // e.g. https://myserver.com/api/v1/mcp name: "sh0", authorization_token: userApiKey // user's sh0_ API key }], tools: [{ type: "mcp_toolset", mcp_server_name: "sh0" }], // beta header: "mcp-client-2025-11-20" }); ``` **This eliminates:** - `ai-tools.ts` in the gateway (tool definitions) - `ai-tools.ts` in the dashboard (tool executor) - The entire client-side SSE tool execution loop - The `tool_call` / `tool_call_done` event protocol **Dashboard changes:** Remove agentic loop from `ai.svelte.ts`. The gateway streams a single response — Claude handles all tool iterations internally via MCP. **Files to modify:** - `sh0-website/src/routes/api/ai/chat/+server.ts` — use MCP Connector - `sh0-website/src/lib/server/ai-tools.ts` — remove (replaced by MCP) - `sh0-core/dashboard/src/lib/ai-tools.ts` — remove (no more client-side execution) - `sh0-core/dashboard/src/lib/stores/ai.svelte.ts` — simplify (no agentic loop) - `sh0-core/dashboard/src/lib/ai.ts` — simplify SSE handling **sh0.dev/account settings:** Add instance configuration page where users enter: - Instance URL (e.g., `https://myserver.com`) - API key (`sh0_xxxxx`) - Gateway validates connectivity before saving **Prerequisite:** User's sh0 instance must be accessible via HTTPS from Claude's API servers. For local-only instances, the embedded dashboard (Path 2) still works via direct local calls — no gateway needed. ### Phase 5: AI Sandbox Container (Future) Auto-create an "AI debugger" sidecar container per deployed app: - Shares app's Docker network (can `curl localhost:PORT`) - Read-only access to app logs and mounted volumes - Common debugging tools pre-installed - Non-root, no internet access, command allowlist New MCP tool: `exec_in_sandbox(app_name, command)` — runs diagnostic commands in the sidecar. **Leverages:** `sh0-docker` crate's existing `exec_interactive` / container creation logic. --- ## Dashboard UX: "Connect AI" Page New page in dashboard settings: `/settings/ai-connect` 1. Generate a scoped API key for MCP 2. Show copy-paste config for Claude Desktop, Cursor, Claude Code 3. Display MCP connection status (last request timestamp) 4. Test connection button --- ## Verification Plan 1. **Phase 1:** Start sh0-core, use MCP Inspector (`npx @modelcontextprotocol/inspector`) to connect to `http://localhost:9000/api/v1/mcp`, verify tool listing and execution 2. **Phase 2:** Add `x-mcp-enabled` to a new endpoint, restart, verify it appears in MCP tool list automatically 3. **Phase 3:** Test scoped keys — verify read-only key can't execute write tools, confirmation flow works 4. **Phase 4:** Connect dashboard chat, verify tool calls go through MCP Connector, no more client-side execution 5. **Phase 5:** Deploy a test app, verify sandbox container creation, run diagnostic command via MCP --- ## Key Decisions | Decision | Choice | Why | |----------|--------|-----| | MCP server location | Inside sh0-core (Rust) | Single-binary philosophy, shared AppState | | Transport | Streamable HTTP | sh0-core is a server, not a CLI. Works with Claude API MCP Connector | | Tool source | Auto-generated from OpenAPI | DRY — one source of truth, zero maintenance per endpoint | | New dependencies | None | JSON-RPC 2.0 + SSE already covered by axum + serde_json | | Current dashboard chat | Keep → migrate to MCP Connector in Phase 4 | Non-breaking, incremental | | Destructive ops | Confirmation token pattern | Defense-in-depth without making common ops painful | | Centralized MCP endpoint | No `mcp.sh0.dev` for MVP | Self-hosted = each instance is its own MCP server. Tunnel/proxy can come later | | External access model | Direct (user provides URL) | Simple, no infrastructure. User's server must be HTTPS-accessible | | Sandbox container | Planned, deferred to after MCP is stable | MCP tools for logs/metrics/status may suffice initially |