// @ts-nocheck // TypeScript is suppressed because @modelcontextprotocol/sdk@1.x expects // Zod schemas for tool definitions, but we pass plain JSON Schema objects. // The runtime contract is identical; there is no type-safety regression - // the nocheck just avoids a blanket of incorrect Zod-vs-object type errors // that would obscure real mistakes. Remove once the SDK adds a JSON Schema // overload or we migrate to a Zod-based schema builder. // // `od mcp` - stdio MCP server that proxies read-only tool calls to the // running daemon's HTTP API. Lets a coding agent in a *different* repo // (Claude Code, Cursor, Zed) pull files from a local Open Design // project without the export-zip-import dance. // // The server itself holds no state and never touches the filesystem; // every tool resolves to a fetch() against `OD_DAEMON_URL`. Spawn the // MCP server with no daemon running and tool calls return a clear // "daemon not reachable" error - the server itself still launches so // the client can list its tool schema. import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; const SERVER_NAME = 'open-design'; const SERVER_VERSION = '0.2.0'; // Mimes whose body we surface as MCP `text` content. Everything else // returns a clear error directing the caller at list_files for // metadata, until phase 2 adds binary support. const TEXTUAL_MIME_PATTERNS = [ /^text\//i, /^application\/json\b/i, /^application\/javascript\b/i, /^application\/typescript\b/i, /^application\/xml\b/i, /^application\/x-(yaml|toml|httpd-php|sh)\b/i, /\+json\b/i, /\+xml\b/i, /^image\/svg\+xml\b/i, ]; // Every tool here is a read against a local daemon owned by the // current user, so they're all read-only, idempotent, and operate on // a closed (project-scoped) namespace. Pull these into one constant // so each tool def doesn't repeat them. const READ_ANNOTATIONS = { readOnlyHint: true, idempotentHint: true, openWorldHint: false, }; // Description style: short, one purpose-line per tool. Active-context // fallback is documented once in the server `instructions` block, so // per-tool descriptions just say "project optional" and don't repeat // the rationale - that saves ~150 tokens per tools/list response, // shipped to the model on every session. const PROJECT_ARG = { type: 'string', description: 'Project id (UUID) or name substring. Optional; defaults to the active project (expires after ~5 minutes of no Open Design activity).', } as const; const TOOL_DEFS = [ { name: 'list_projects', description: 'List every Open Design project on this daemon.', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, annotations: { ...READ_ANNOTATIONS, title: 'List Open Design projects' }, }, { name: 'get_active_context', description: 'Project + file the user has open in Open Design right now. Returns {active:false, hint:"..."} when no project is active so the agent can ask the user to interact with Open Design (the active context expires ~5 minutes after the last user interaction). Most tools default to this when project is omitted, so you rarely need to call this directly.', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, annotations: { ...READ_ANNOTATIONS, title: 'What is the user looking at?' }, }, { name: 'get_artifact', description: 'PREFER THIS over multiple get_file calls. Bundles the entry file plus every sibling it references (HTML