// Minimal remote MCP server (Streamable HTTP, JSON-RPC 2.0) exposing ONE tool, `echo`, written with Node built-ins only // (no @modelcontextprotocol/sdk dependency so it runs with `node --experimental-strip-types`). // // STATUS: LIVE_VERIFIED locally 2026-09-18 (initialize / tools/list / tools/call over HTTP on 127.0.0.1) — NOT yet connected through the // Anthropic MCP connector, which only reaches PUBLIC https URLs. To let Claude use it: // 1. node --experimental-strip-types examples/anthropic/mcp/mini-mcp-server.ts (listens on :8788, path /mcp) // 2. expose it publicly with a tunnel, e.g. `cloudflared tunnel --url http://localhost:8788` (or ngrok, or on this cluster: // `mlt add mcp-demo. :8788` on the MacLustr Tunnel gateway) -> https:///mcp // 3. call the Messages API with header anthropic-beta: mcp-client-2025-11-20 and // mcp_servers: [{type:"url", url:"https:///mcp", name:"mini"}], tools: [{type:"mcp_toolset", mcp_server_name:"mini"}] // (see mcp_connector.sh / .py / .ts — swap the DeepWiki URL for yours). Anthropic then performs initialize + tools/list + tools/call. // Optional bearer auth: set MCP_TOKEN=… and pass authorization_token in the server definition. import { createServer } from "node:http"; const PORT = Number(process.env.PORT ?? 8788); const TOKEN = process.env.MCP_TOKEN; // optional shared secret const TOOLS = [{ name: "echo", description: "Echoes back the provided text. Use it when the user asks you to echo or repeat something through the MCP server.", inputSchema: { type: "object", properties: { text: { type: "string", description: "Text to echo" } }, required: ["text"] }, }]; function rpcResult(id: unknown, result: unknown) { return { jsonrpc: "2.0", id, result }; } function rpcError(id: unknown, code: number, message: string) { return { jsonrpc: "2.0", id, error: { code, message } }; } function handle(msg: any): unknown { switch (msg.method) { case "initialize": return rpcResult(msg.id, { protocolVersion: msg.params?.protocolVersion ?? "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "mini-mcp", version: "0.1.0" } }); case "notifications/initialized": case "ping": return msg.id === undefined ? undefined : rpcResult(msg.id, {}); case "tools/list": return rpcResult(msg.id, { tools: TOOLS }); case "tools/call": { const { name, arguments: args } = msg.params ?? {}; if (name !== "echo") return rpcResult(msg.id, { content: [{ type: "text", text: `Unknown tool ${name}` }], isError: true }); return rpcResult(msg.id, { content: [{ type: "text", text: `echo: ${args?.text ?? ""}` }], isError: false }); } default: return msg.id === undefined ? undefined : rpcError(msg.id, -32601, `Method not found: ${msg.method}`); } } createServer((req, res) => { if (req.url !== "/mcp") { res.writeHead(404).end(); return; } if (TOKEN && req.headers.authorization !== `Bearer ${TOKEN}`) { res.writeHead(401).end(); return; } if (req.method === "GET") { res.writeHead(405).end(); return; } // no server->client SSE stream in this mini server let body = ""; req.on("data", (c) => (body += c)); req.on("end", () => { try { const parsed = JSON.parse(body); const out = Array.isArray(parsed) ? parsed.map(handle).filter(Boolean) : handle(parsed); if (out === undefined || (Array.isArray(out) && out.length === 0)) { res.writeHead(202).end(); return; } res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(out)); } catch { res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify(rpcError(null, -32700, "Parse error"))); } }); }).listen(PORT, () => console.log(`mini MCP server on http://127.0.0.1:${PORT}/mcp (tools: echo)`));