Spaces:
Running
Running
| /** | |
| * Self-contained MCP server demonstrating JSON Schema 2020-12 tool inputs, | |
| * enabled by SEP-2106 (tool inputSchema/outputSchema conform to JSON Schema | |
| * 2020-12). Each tool in `demo-tools.ts` exercises a different 2020-12 keyword | |
| * that was not permitted in a tool input before SEP-2106 (enum varieties, | |
| * top-level oneOf, discriminated unions, $ref/$defs, if/then/else, prefixItems | |
| * tuples). | |
| * | |
| * Built on the 2.0 SDK's `fromJsonSchema` adapter, which advertises each raw | |
| * JSON Schema verbatim in tools/list and strictly validates tools/call | |
| * arguments against it (via @cfworker/json-schema). | |
| * | |
| * Confirmed against @modelcontextprotocol/server@2.0.0 (GA). In 2.0, | |
| * `StdioServerTransport` and `CfWorkerJsonSchemaValidator` live on subpath | |
| * exports (see the imports below); `McpServer`, `fromJsonSchema`, and | |
| * `registerTool` are unchanged from the 2.0 alpha. | |
| * | |
| * SEP-2106: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2106-json-schema-2020-12.md | |
| * | |
| * Run over stdio: npm start (then point MCP Inspector or any client at it) | |
| * Run over HTTP: npm run start:http (Streamable HTTP at /mcp — see http.ts) | |
| */ | |
| import { McpServer, fromJsonSchema } from "@modelcontextprotocol/server"; | |
| import type { CallToolResult } from "@modelcontextprotocol/server"; | |
| // Since 2.0 beta these two live on subpath exports, not the package root | |
| // (alpha.2 imported both from "@modelcontextprotocol/server"): | |
| import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; | |
| import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/server/validators/cf-worker"; | |
| import { pathToFileURL } from "node:url"; | |
| import { DEMO_TOOLS } from "./demo-tools.js"; | |
| // Validate against JSON Schema 2020-12 explicitly. The SDK's default validator | |
| // does NOT use the 2020-12 dialect, so newer keywords like `prefixItems` are | |
| // misinterpreted (e.g. `items: false` gets applied to every element). Passing a | |
| // 2020-12 validator is required for full SEP-2106 fidelity. | |
| const validator = new CfWorkerJsonSchemaValidator({ draft: "2020-12" }); | |
| // A canned `structuredContent` per tool that CONFORMS to that tool's outputSchema, | |
| // so the server demonstrates a real 2020-12 outputSchema round-trip (advertise + | |
| // return + validate). Mirrors the Python sibling's _OUTPUTS. | |
| const OUTPUTS: Record<string, unknown> = { | |
| "get-enum-selections": { result: "applied", appliedCount: 3 }, | |
| "lookup-record": { found: true, name: "Acme Corp" }, | |
| "create-payment": { | |
| receiptId: "rcpt_001", | |
| outcome: { status: "settled", settledAt: "2026-06-25T12:00:00Z" }, | |
| }, | |
| "create-shipment": { | |
| shipmentId: "shp_001", | |
| origin: { city: "San Francisco", eta: "2026-06-26" }, | |
| destination: { city: "London", eta: "2026-06-28" }, | |
| }, | |
| "register-address": { status: "active", validUntil: "2027-06-25" }, | |
| "plot-point": { plotted: [3, 7], ok: true }, | |
| }; | |
| /** Shared handler: echo the validated arguments AND return a 2020-12-conforming | |
| * structuredContent for the tool's outputSchema. */ | |
| function echo(name: string, args: Record<string, unknown> | undefined): CallToolResult { | |
| const entries = Object.entries(args ?? {}); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: | |
| entries.length > 0 | |
| ? `You provided:\n${entries | |
| .map(([k, v]) => `- ${k}: ${JSON.stringify(v)}`) | |
| .join("\n")}` | |
| : "No arguments provided.", | |
| }, | |
| ], | |
| structuredContent: OUTPUTS[name] as Record<string, unknown> | undefined, | |
| }; | |
| } | |
| /** Build the demo server (exported so it can be driven in-process by tests). */ | |
| export function createServer(): McpServer { | |
| const server = new McpServer( | |
| { | |
| // Identity mirrors the public HF Space name (huggingface.co/spaces/ | |
| // olaservo/json-schema-2020-12-ts). Keep `version` in sync with | |
| // package.json. | |
| name: "json-schema-2020-12-ts", | |
| title: "JSON Schema 2020-12 tool inputs (TypeScript)", | |
| version: "1.0.0", | |
| description: | |
| "Demo MCP server: six tools whose inputSchemas exercise the JSON Schema 2020-12 keywords SEP-2106 allows (top-level oneOf, const unions, $ref/$defs, if/then, prefixItems, SEP-1330 enum varieties).", | |
| websiteUrl: | |
| "https://olaservo-sandyland.static.hf.space/mcp/tool-schemas/index.html", | |
| }, | |
| { | |
| capabilities: { tools: {} }, | |
| // The six tools are static per deploy — let 2026-07-28 clients cache | |
| // tools/list for a day, shared caches included (keyless public demo). | |
| cacheHints: { "tools/list": { ttlMs: 86_400_000, cacheScope: "public" } }, | |
| } | |
| ); | |
| for (const tool of DEMO_TOOLS) { | |
| server.registerTool( | |
| tool.name, | |
| { | |
| title: tool.title, | |
| description: tool.description, | |
| inputSchema: fromJsonSchema(tool.jsonSchema, validator), | |
| outputSchema: tool.outputSchema | |
| ? fromJsonSchema(tool.outputSchema, validator) | |
| : undefined, | |
| annotations: { | |
| readOnlyHint: true, | |
| destructiveHint: false, | |
| idempotentHint: true, | |
| openWorldHint: false, | |
| }, | |
| }, | |
| async (args): Promise<CallToolResult> => | |
| echo(tool.name, args as Record<string, unknown>) | |
| ); | |
| } | |
| return server; | |
| } | |
| async function main(): Promise<void> { | |
| const server = createServer(); | |
| const transport = new StdioServerTransport(); | |
| await server.connect(transport); | |
| console.error( | |
| `json-schema-2020-12-ts running on stdio (${DEMO_TOOLS.length} tools)` | |
| ); | |
| process.on("SIGINT", async () => { | |
| await server.close(); | |
| process.exit(0); | |
| }); | |
| } | |
| // Only launch the stdio server when this module is run directly (npm start / | |
| // selftest spawn `tsx src/server.ts`). When src/http.ts imports `createServer` | |
| // to mount the same server over Streamable HTTP, this guard keeps stdio from | |
| // starting and stealing the process's stdin/stdout. | |
| const isEntrypoint = | |
| process.argv[1] !== undefined && | |
| import.meta.url === pathToFileURL(process.argv[1]).href; | |
| if (isEntrypoint) { | |
| main().catch((error) => { | |
| console.error("Server error:", error); | |
| process.exit(1); | |
| }); | |
| } | |