olaservo's picture
Upload folder using huggingface_hub
f341f90 verified
Raw
History Blame Contribute Delete
6.28 kB
/**
* 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);
});
}