@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
TypeScript
/**
* 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;