UNPKG

@mnemoverse/mcp-memory-server

Version:

Hosted AI agent memory over MCP that learns from outcomes — feedback re-ranks recall — with shared rooms for multi-agent work; one key or OAuth across Claude Code, Cursor, VS Code & ChatGPT, no infra to run

88 lines (87 loc) • 5.59 kB
/** * Tool schemas in JSON Schema 2020-12, the dialect MCP defaults to (#189). * * WHAT WAS WRONG. `@modelcontextprotocol/sdk` 1.x builds the `tools/list` * answer itself. For each registered tool it converts the zod input and output * schemas with `toJsonSchemaCompat(obj, { strictUnions, pipeStrategy })` * (dist/esm/server/mcp.js) and passes no `target`, and that function's target * mapping falls back to `draft-7` when none is given. So every `inputSchema` * and `outputSchema` this package served said * `"$schema": "http://json-schema.org/draft-07/schema#"` (0.15.0 with SDK * 1.30.0 and with 1.32.1, the newest 1.x, checked 2026-10-11). MCP 2025-11-25 * ("JSON Schema Usage") makes 2020-12 the default dialect, and a client whose * validator accepts only 2020-12 refuses such a tool before calling it, with * the message quoted in #189: "JSON Schema declares an unsupported dialect * (draft-07). The default validator supports JSON Schema 2020-12 only". That * message comes from the `AjvJsonSchemaValidator` of * `@modelcontextprotocol/client` 2.0.0-alpha.4 to 2.0.0-beta.5, which * rejected all 22 schemas of 0.15.0 and accepts all 22 served through this * file. The SDK side is modelcontextprotocol/typescript-sdk#2084, open on * 2026-10-11. * * WHAT THIS DOES. It wraps the SDK's own `tools/list` handler. For each listed * tool it converts the registered zod schemas again, with the SDK's own * converter and the same options plus `target: "draft-2020-12"` (the change * #2084 proposes), and then removes the `$schema` key. Everything else on a * listed tool (name, title, description, annotations, execution, `_meta`) is * what the SDK built. For this package's eleven tools the 2020-12 conversion * and the draft-07 one differ in the `$schema` line alone; the bodies are * byte-identical (test/schema-dialect.test.ts compares them). * * WHY NO `$schema` KEY, rather than the 2020-12 URI. Both mean 2020-12 under * the spec: "When a schema does not include a `$schema` field, it defaults to * JSON Schema 2020-12." The difference is in older clients. A draft-07 Ajv * instance in its default configuration (`new Ajv()`) cannot compile a schema * that names the 2020-12 meta-schema (`no schema with key or ref * "https://json-schema.org/draft/2020-12/schema"`), and it compiles the same * schema without the key. Without the key, a 2020-12-only validator reads * 2020-12 and a draft-07 one still works. The test compiles every schema * under both. * * WHICH TOOLS. Every tool on the server, not only the eleven registered by * `registerMemoryTools`: a server that registers its own tools beside these * gets the same dialect for them. A tool whose schema the SDK's converter * cannot write in 2020-12 (a zod 3 schema: its converter has no 2020-12 * target and returns draft-07 whatever it is asked) is left exactly as the SDK * built it, since dropping `$schema` from a draft-07 body would claim a * dialect that body was not written in. * * WHEN TO CALL. Before the first tool is registered on the server. The SDK * installs its `tools/list` handler at the first `registerTool` and never * again, so this function watches for that one installation and wraps it. * `registerMemoryTools` calls it first thing; calling it again on the same * server does nothing. Called after a tool is already registered, it throws, * because it could not do what its name says. * * WHAT IT READS FROM THE SDK THAT IS NOT DOCUMENTED API, AND WHAT HAPPENS * WHEN A LATER SDK CHANGES IT. Three things: the registered tools, * `McpServer._registeredTools` (the map the SDK's own handler lists from; the * hosted connector's host-surface check reads it too), and the converter * modules `server/zod-json-schema-compat.js` and `server/zod-compat.js`, which * the SDK's `./*` export makes importable. None of them is needed for the * server to start or for its tools to work, so none of them is imported * statically: `npx` resolves `^1.32.1` to the newest 1.x, and a static import * of a module that release had moved would stop the process before it * answered `initialize`. The converter modules are loaded with a dynamic * `import()` inside a `try`, and the map is looked up when this function * runs. When any of them is missing, or the SDK has installed `tools/list` * before any tool was registered, the SDK's own `tools/list` answer goes out * unchanged (draft-07, as in 0.15.0) and one line on stderr says why, once * per process. stdout is the MCP channel, so nothing is written there. * test/schema-dialect-fallback.test.ts takes each of these away in turn and * checks that the server still lists its eleven tools. When the SDK passes * the target itself, the bare-SDK test in test/schema-dialect.test.ts fails. * The SDK would then write the 2020-12 URI, which this wrapper leaves out, so * the choice at that point is whether to keep the wrapper for that reason * alone. */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; /** The `$schema` URI the SDK's converter writes for a 2020-12 schema. Used to * confirm the conversion really produced 2020-12 before the key is dropped. */ export declare const JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema"; /** * Make `server`'s `tools/list` describe every tool schema in JSON Schema * 2020-12 with no `$schema` key. See the file header for why, for the calling * order it requires, and for what it does when the SDK lacks a part it needs. */ export declare function serveToolSchemasIn2020_12(server: McpServer): void;