サンプル
@modelcontextprotocol/sdk 1.30.0: Test an MCP server in-process over InMemoryTransport, and find out which tool failures arrive as an error result and which as a JSON-RPC error
検証済みサンプル — npm @modelcontextprotocol/sdk 1.30.0: Test an MCP server in-process over InMemoryTransport, and find out which tool failures arrive as an error…
sha256:d133f23612d4a391e0f2b96f76d38e57a55c2c65d48d5e32fc0099002b56ccfd
このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。
合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。
MIT-0
実行証拠
宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。
- 証拠の基準
- 署名済みコントラクト合格
- 検証レシート
- 3
- ビルドした署名鍵
- 3
宣言された環境
node 22 linux x64 node 22 javascript npm
検証実行環境
| 環境 | コントラクト | ステージ | 実行日 |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-14 |
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
| node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5… |
2026-09-07 |
ケース
HOW- ゴール
- Test an MCP server in-process over InMemoryTransport, and find out which tool failures arrive as an error result and which as a JSON-RPC error
- シンボル
-
- InMemoryTransport.createLinkedPair
- McpServer.registerTool
- McpServer.registerResource
- Client.connect
- Client.callTool
- Client.listTools
- Client.readResource
- CallToolResult.isError
- McpError
- ErrorCode
- LATEST_PROTOCOL_VERSION
- DEFAULT_REQUEST_TIMEOUT_MSEC
- 環境
- node 22
- 作成日
- 2026-08-14T13:24:49Z
コントラクト
- assert the SDK version cannot be read by requiring its package.json, and that the transport subpath does not resolve without its .js extension, both because the exports map ends in a wildcard onto the built output
- assert connecting a Client over a linked pair is exactly three messages, initialize out, its response in, and notifications/initialized out carrying no id
- assert 1.30.0 negotiates the protocol version string 2025-11-25, and that no accessor and no own property of the Client or the transport retains it, so the wire is the only place to read it
- assert connect() overwrites the onmessage and onclose already sitting on a transport, which is why recording the wire needs an accessor rather than an assignment
- assert the capabilities the client sees are derived from what was registered, tools and resources each with listChanged and no prompts key at all
- assert the server echoes an older supported version but answers a version it does not know with its own latest instead of an error, and that the SDK's own client refuses such an answer with a plain Error carrying no code
- assert listTools publishes the zod shape as draft-07 JSON Schema with $schema inside it, required omitting the defaulted key, and an execution block the server never asked for
- assert a valid call returns a content array of {type:'text',text} with no isError key at all, and that the handler receives the default already filled in
- assert arguments violating the input schema come back as an isError result rather than a rejected promise, with the JSON-RPC code present only as text, and the handler never running
- assert a missing key, an out-of-range value and omitted arguments all fail that same way, while an undeclared extra property is stripped and the call succeeds
- assert an unknown tool name is also an isError result, while an unknown resource URI and an unregistered method are thrown McpErrors carrying -32602 and -32601
- assert the unknown-resource message carries the 'MCP error -32602' prefix twice, because both the server and the client prepend it
- assert a handler that throws becomes an isError result carrying the bare Error message with no MCP prefix, and that the connection keeps working afterwards
- assert the registered resource is listed with its title and mime type and reads back as the JSON text its handler produced
- assert the linked pair does not serialise, so a Date placed in _meta arrives as a Date and an undefined-valued key survives as an own property
- assert connecting the client first queues initialize on the server half and leaves the promise pending instead of failing fast, then completes the handshake once the server connects
- assert that wait is bounded after all, because initialize is an ordinary request carrying the SDK's default 60 second timeout, and rejects with an McpError of ErrorCode.RequestTimeout that puts the timeout in data
- assert closing either half tears down both, the client's close firing the server's own onclose and leaving it disconnected, and the server's close leaving the client's next call to fail with a plain Error 'Not connected' that carries no code, not an McpError with ConnectionClosed
ファイル
- csx.json
- package-lock.json
- package.json
- src/server.mjs
- test/contract.mjs
ソース
{"case":{"caseId":"case:sha256:86708dd21892459b9c91748d82327156cd61db83bc618366a38290161b239fb2","constraints":{"moduleSystem":"esm","runtime":"node"},"contract":["assert the SDK version cannot be read by requiring its package.json, and that the transport subpath does not resolve without its .js extension, both because the exports map ends in a wildcard onto the built output","assert connecting a Client over a linked pair is exactly three messages, initialize out, its response in, and notifications/initialized out carrying no id","assert 1.30.0 negotiates the protocol version string 2025-11-25, and that no accessor and no own property of the Client or the transport retains it, so the wire is the only place to read it","assert connect() overwrites the onmessage and onclose already sitting on a transport, which is why recording the wire needs an accessor rather than an assignment","assert the capabilities the client sees are derived from what was registered, tools and resources each with listChanged and no prompts key at all","assert the server echoes an older supported version but answers a version it does not know with its own latest instead of an error, and that the SDK's own client refuses such an answer with a plain Error carrying no code","assert listTools publishes the zod shape as draft-07 JSON Schema with $schema inside it, required omitting the defaulted key, and an execution block the server never asked for","assert a valid call returns a content array of {type:'text',text} with no isError key at all, and that the handler receives the default already filled in","assert arguments violating the input schema come back as an isError result rather than a rejected promise, with the JSON-RPC code present only as text, and the handler never running","assert a missing key, an out-of-range value and omitted arguments all fail that same way, while an undeclared extra property is stripped and the call succeeds","assert an unknown tool name is also an isError result, while an unknown resource URI and an unregistered method are thrown McpErrors carrying -32602 and -32601","assert the unknown-resource message carries the 'MCP error -32602' prefix twice, because both the server and the client prepend it","assert a handler that throws becomes an isError result carrying the bare Error message with no MCP prefix, and that the connection keeps working afterwards","assert the registered resource is listed with its title and mime type and reads back as the JSON text its handler produced","assert the linked pair does not serialise, so a Date placed in _meta arrives as a Date and an undefined-valued key survives as an own property","assert connecting the client first queues initialize on the server half and leaves the promise pending instead of failing fast, then completes the handshake once the server connects","assert that wait is bounded after all, because initialize is an ordinary request carrying the SDK's default 60 second timeout, and rejects with an McpError of ErrorCode.RequestTimeout that puts the timeout in data","assert closing either half tears down both, the client's close firing the server's own onclose and leaving it disconnected, and the server's close leaving the client's next call to fail with a plain Error 'Not connected' that carries no code, not an McpError with ConnectionClosed"],"goal":"Test an MCP server in-process over InMemoryTransport, and find out which tool failures arrive as an error result and which as a JSON-RPC error","kind":"HOW","packages":["pkg:npm/@modelcontextprotocol/sdk@1.30.0","pkg:npm/zod@4.4.3"],"schemaVersion":1,"symbols":["InMemoryTransport.createLinkedPair","McpServer.registerTool","McpServer.registerResource","Client.connect","Client.callTool","Client.listTools","Client.readResource","CallToolResult.isError","McpError","ErrorCode","LATEST_PROTOCOL_VERSION","DEFAULT_REQUEST_TIMEOUT_MSEC"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"javascript","moduleSystem":"esm","os":"linux","packageManager":"npm","runtime":"node","runtimeVersion":"22","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/@modelcontextprotocol/sdk@1.30.0","pkg:npm/zod@4.4.3"],"schemaVersion":1,"symbols":["InMemoryTransport.createLinkedPair","McpServer.registerTool","McpServer.registerResource","Client.connect","Client.callTool","Client.listTools","Client.readResource","CallToolResult.isError","McpError","ErrorCode","LATEST_PROTOCOL_VERSION","DEFAULT_REQUEST_TIMEOUT_MSEC"],"verifierAdapter":"node-typescript@1"}
{
"name": "csx-mcp-sdk-inmemory-transport",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "csx-mcp-sdk-inmemory-transport",
"version": "1.0.0",
"license": "MIT-0",
"dependencies": {
"@modelcontextprotocol/sdk": "1.30.0",
"zod": "4.4.3"
}
},
"node_modules/@hono/node-server": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz",
"integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"hono": "^4"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
"integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.9 || ^2.0.5",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"hono": "^4.11.4",
"jose": "^6.1.3",
"json-schema-typed": "^8.0.2",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.25 || ^4.0",
"zod-to-json-schema": "^3.25.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@cfworker/json-schema": "^4.1.1",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"@cfworker/json-schema": {
"optional": true
},
"zod": {
"optional": false
}
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^2.0.0",
"debug": "^4.4.3",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
"qs": "^6.15.2",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
"integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "8.6.2",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz",
"integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hono": {
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.2.tgz",
"integrity": "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jose": {
"version": "6.2.8",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz",
"integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/json-schema-typed": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
"license": "BSD-2-Clause"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
"integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/pkce-challenge": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
"integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.15.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
"license": "BSD-3-Clause",
"dependencies": {
"es-define-property": "^1.0.1",
"side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
"integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4",
"side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
"integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT",
"dependencies": {
"content-type": "^2.0.0",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/type-is/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.25.28 || ^4"
}
}
}
}
{
"name": "csx-mcp-sdk-inmemory-transport",
"version": "1.0.0",
"private": true,
"type": "module",
"license": "MIT-0",
"dependencies": {
"@modelcontextprotocol/sdk": "1.30.0",
"zod": "4.4.3"
}
}
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
/**
* Testing an MCP server without a process, a pipe, or a port.
*
* The SDK ships the transport for this itself:
* `InMemoryTransport.createLinkedPair()` returns two halves that hand messages
* straight to each other. One goes to a `Server`/`McpServer`, the other to a
* `Client`, and the whole initialize handshake runs in the same event loop as
* the test. There is no stdio to drain, no child process to kill, and nothing
* to await except the calls themselves.
*
* The subpath is `@modelcontextprotocol/sdk/inMemory.js`, with the `.js` — the
* package's exports map is a wildcard onto the built files, so the extension is
* part of the specifier and `.../inMemory` does not resolve.
*/
export const SCALES = ["fahrenheit", "kelvin"];
/**
* One tool and one resource, which is enough to pin down every error channel
* the protocol has.
*
* Registering them is also what declares the capabilities: `registerTool` turns
* on `tools.listChanged` and `registerResource` turns on
* `resources.listChanged`, so the capabilities the client sees during
* initialize are derived from what you registered, not from anything you pass
* to the constructor. A server with no prompts advertises no `prompts`
* capability at all, and the difference is load-bearing — see the contract,
* where an unregistered method is the only thing in this sample that produces a
* JSON-RPC MethodNotFound.
*
* `handlerCalls` is returned alongside so the contract can prove the handler
* did not run for a rejected call. Whether the handler ran is the whole
* question when the failure comes back looking like a successful response.
*/
export function createUnitsServer() {
const handlerCalls = [];
const server = new McpServer({ name: "units", version: "1.4.2" });
server.registerTool(
"convert_temperature",
{
title: "Convert temperature",
description: "Convert a celsius reading into another scale.",
inputSchema: {
celsius: z.number(),
scale: z.enum(SCALES),
// A key with a default is optional on the wire and always present in
// the handler: it is absent from the JSON Schema's `required` list and
// arrives filled in, so the handler never has to re-apply the default.
precision: z.number().int().min(0).max(6).default(1),
},
},
async (args) => {
handlerCalls.push(args);
const { celsius, scale, precision } = args;
// A domain failure the schema cannot express. Throwing from a tool
// handler is the ordinary way to report one; the contract measures what
// the client receives.
if (celsius < -273.15) {
throw new Error(`${celsius} C is below absolute zero`);
}
const converted = scale === "kelvin" ? celsius + 273.15 : (celsius * 9) / 5 + 32;
return { content: [{ type: "text", text: converted.toFixed(precision) }] };
},
);
server.registerResource(
"supported-scales",
"config://units/scales",
{ title: "Supported scales", mimeType: "application/json" },
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(SCALES),
},
],
// A Date on purpose. Over stdio or HTTP this is JSON, so the client would
// read the string "2024-01-01T00:00:00.000Z"; across the linked pair the
// Date object itself arrives. The contract measures that, because it is
// the way an in-memory test passes while the real transport fails.
_meta: { revisedAt: new Date("2024-01-01T00:00:00.000Z") },
}),
);
return { server, handlerCalls };
}
/**
* Records every message crossing one half of the pair.
*
* Worth having because the SDK's `Client` gives you no way to ask what protocol
* version was negotiated — there is no `getProtocolVersion()`, and the hook the
* HTTP transports use to stash it, `transport.setProtocolVersion`, is not
* implemented by `InMemoryTransport`. The negotiated version exists only in the
* initialize response, so reading the wire is how you see it.
*
* `onmessage` has to be intercepted through an accessor rather than wrapped:
* `client.connect()` takes ownership of the transport and overwrites the
* callbacks that are already on it, so anything assigned beforehand is thrown
* away. Defining the property means the overwrite goes through the setter.
*/
export function recordWire(transport) {
const wire = [];
const send = transport.send.bind(transport);
transport.send = async (message, options) => {
wire.push({ direction: "out", message });
return send(message, options);
};
let handler;
Object.defineProperty(transport, "onmessage", {
configurable: true,
get: () => handler,
set: (fn) => {
handler = (message, extra) => {
wire.push({ direction: "in", message });
return fn(message, extra);
};
},
});
return wire;
}
/**
* Connects a client to a server over a linked pair, server side first.
*
* The order is not stylistic. `client.connect()` sends initialize and waits for
* the response; if nothing is listening on the other half the request is parked
* on that half's queue and nothing reports that the peer is missing. The wait
* is not infinite — initialize is an ordinary request and carries the SDK's
* default 60 second timeout — but a suite that connects the client too early
* stalls for a minute and then fails with `Request timed out`, which names
* neither the transport nor the missing server. Connecting the server first, or
* starting both and awaiting after, is what avoids it. The contract measures
* the queueing, the stall, and the timeout.
*/
export async function connectInMemory(server, clientInfo = { name: "units-test-client", version: "0.0.0" }) {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const wire = recordWire(clientTransport);
const client = new Client(clientInfo);
await server.connect(serverTransport);
await client.connect(clientTransport);
return { client, clientTransport, serverTransport, wire };
}
/**
* Drives the raw JSON-RPC side of the pair with no `Client` involved, which is
* the only way to ask the server for a protocol version the SDK's own client
* would never request.
*/
export async function rawExchange(server, request) {
const [rawTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const replies = [];
rawTransport.onmessage = (message) => replies.push(message);
await server.connect(serverTransport);
await rawTransport.start();
await rawTransport.send(request);
// Delivery is a direct call into the peer's `onmessage`, so the reply lands
// as soon as the handler's promise chain drains. One macrotask is plenty.
await new Promise((resolve) => setTimeout(resolve, 0));
return replies;
}
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from "@modelcontextprotocol/sdk/shared/protocol.js";
import {
ErrorCode,
LATEST_PROTOCOL_VERSION,
McpError,
SUPPORTED_PROTOCOL_VERSIONS,
} from "@modelcontextprotocol/sdk/types.js";
import { connectInMemory, createUnitsServer, rawExchange, SCALES } from "../src/server.mjs";
// The version this was measured against. It cannot be read the usual way: the
// package's exports map ends in a `"./*"` wildcard onto the built output, and
// package.json is not exempted from it, so `require(".../sdk/package.json")`
// resolves to dist/cjs/package.json and hands back the two-line stub that marks
// that directory as CommonJS. Version-gating code that reads it sees undefined.
const require = createRequire(import.meta.url);
assert.deepEqual(require("@modelcontextprotocol/sdk/package.json"), { type: "commonjs" });
const sdkManifest = JSON.parse(
readFileSync(new URL("../node_modules/@modelcontextprotocol/sdk/package.json", import.meta.url), "utf8"),
);
assert.equal(sdkManifest.version, "1.30.0");
// The same wildcard is why every subpath keeps its file extension. Dropping it
// asks for dist/esm/inMemory, which is not a file, and the error names a path
// inside the package rather than the exports map that produced it.
await assert.rejects(import("@modelcontextprotocol/sdk/inMemory"), (error) => {
assert.equal(error.code, "ERR_MODULE_NOT_FOUND");
assert.ok(error.message.includes("@modelcontextprotocol/sdk/dist/esm/inMemory"));
return true;
});
const { server, handlerCalls } = createUnitsServer();
const { client, clientTransport, wire } = await connectInMemory(server);
// ---------------------------------------------------------------------------
// The handshake
// ---------------------------------------------------------------------------
// Connecting a Client is the initialize round trip. Three messages, in this
// order, before any of your own code runs: the request, the response, and the
// notification that acknowledges it.
assert.deepEqual(
wire.map((entry) => [entry.direction, entry.message.method ?? "(response)"]),
[
["out", "initialize"],
["in", "(response)"],
["out", "notifications/initialized"],
],
);
const [initialize, initialized, ack] = wire.map((entry) => entry.message);
// The client asks for the newest version it knows and this server, being the
// same SDK build, agrees. That string is the answer to "which protocol version
// does 1.30.0 speak": 2025-11-25.
assert.equal(LATEST_PROTOCOL_VERSION, "2025-11-25");
assert.equal(initialize.params.protocolVersion, "2025-11-25");
assert.equal(initialized.result.protocolVersion, "2025-11-25");
assert.equal(initialized.id, initialize.id);
// The third message is a notification, so it carries no id and nothing replies
// to it. A client therefore has no way to know when the server finished
// reacting to it, only that it went out before connect() resolved.
assert.equal(ack.method, "notifications/initialized");
assert.equal("id" in ack, false);
// Reading it off the wire is not fastidiousness. The Client keeps the server's
// identity and capabilities but not the negotiated version: there is no
// getProtocolVersion(), the hook the HTTP transports use to record it,
// transport.setProtocolVersion, is not implemented by InMemoryTransport, and no
// own property of the client or the transport holds the string either.
assert.equal(client.getProtocolVersion, undefined);
assert.equal(clientTransport.setProtocolVersion, undefined);
assert.equal(
[...Object.values(client), ...Object.values(clientTransport)].includes("2025-11-25"),
false,
);
assert.deepEqual(client.getServerVersion(), { name: "units", version: "1.4.2" });
assert.equal(client.getInstructions(), undefined);
// Which is why recording the wire has to intercept `onmessage` through an
// accessor. connect() takes the transport over and overwrites the callbacks
// that are already sitting on it, so a plain assignment made beforehand is
// gone by the time the first message arrives.
const [spyClientSide, spyServerSide] = InMemoryTransport.createLinkedPair();
const marker = () => {};
spyClientSide.onmessage = marker;
spyClientSide.onclose = marker;
const spyClient = new Client({ name: "spy", version: "0.0.0" });
await createUnitsServer().server.connect(spyServerSide);
await spyClient.connect(spyClientSide);
assert.notEqual(spyClientSide.onmessage, marker);
assert.notEqual(spyClientSide.onclose, marker);
await spyClient.close();
// Capabilities come from what was registered, not from what the constructor was
// told. One registerTool and one registerResource produce exactly these two,
// each with listChanged, and no `prompts` key because no prompt was registered.
assert.deepEqual(client.getServerCapabilities(), {
tools: { listChanged: true },
resources: { listChanged: true },
});
assert.deepEqual(initialized.result.capabilities, client.getServerCapabilities());
// What the server does with a version it has never heard of: it does not
// refuse, it answers with its own latest and leaves the client to notice. An
// old client that only speaks 2024-11-05 therefore gets 2025-11-25 back and has
// to reject it itself — the SDK's client does, with a plain Error, but a
// hand-written one that trusts the response is now speaking the wrong protocol.
const askOld = await rawExchange(createUnitsServer().server, {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "raw", version: "0" } },
});
assert.equal(askOld[0].result.protocolVersion, "2024-11-05");
assert.ok(SUPPORTED_PROTOCOL_VERSIONS.includes("2024-11-05"));
const askUnknown = await rawExchange(createUnitsServer().server, {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: { protocolVersion: "1999-01-01", capabilities: {}, clientInfo: { name: "raw", version: "0" } },
});
assert.equal(askUnknown[0].error, undefined);
assert.equal(askUnknown[0].result.protocolVersion, LATEST_PROTOCOL_VERSION);
// The refusal the SDK's client performs, against a peer that answers with a
// version it has never heard of: connect() throws a plain Error carrying no
// code, so this failure is not distinguishable by an error code either.
const [oddClientSide, oddServerSide] = InMemoryTransport.createLinkedPair();
oddServerSide.onmessage = (message) => {
oddServerSide.send({
jsonrpc: "2.0",
id: message.id,
result: {
protocolVersion: "1999-01-01",
capabilities: {},
serverInfo: { name: "odd", version: "0" },
},
});
};
await oddServerSide.start();
await assert.rejects(new Client({ name: "odd", version: "0.0.0" }).connect(oddClientSide), (error) => {
assert.equal(error.constructor, Error);
assert.equal(error.message, "Server's protocol version is not supported: 1999-01-01");
assert.equal(error.code, undefined);
return true;
});
// ---------------------------------------------------------------------------
// tools/list
// ---------------------------------------------------------------------------
const { tools } = await client.listTools();
assert.equal(tools.length, 1);
const [tool] = tools;
assert.equal(tool.name, "convert_temperature");
assert.equal(tool.title, "Convert temperature");
assert.equal(tool.description, "Convert a celsius reading into another scale.");
// The zod shape is published as draft-07 JSON Schema, with the `$schema` key
// inside it. A key with a .default() is absent from `required` and carries its
// default, so `required` is the two keys that have none.
assert.deepEqual(tool.inputSchema, {
$schema: "http://json-schema.org/draft-07/schema#",
type: "object",
properties: {
celsius: { type: "number" },
scale: { type: "string", enum: SCALES },
precision: { type: "integer", minimum: 0, maximum: 6, default: 1 },
},
required: ["celsius", "scale"],
});
// 1.30.0 stamps every tool definition with an `execution` block whose default
// forbids task augmentation. It is not something this server asked for, and a
// test that deep-equals a whole tool definition against a hand-written literal
// fails on it.
assert.deepEqual(tool.execution, { taskSupport: "forbidden" });
// ---------------------------------------------------------------------------
// A call that works
// ---------------------------------------------------------------------------
const boiling = await client.callTool({
name: "convert_temperature",
arguments: { celsius: 100, scale: "fahrenheit" },
});
// The result shape: `content` is an array of typed blocks, and a text block is
// exactly {type, text}. `isError` is not present-and-false on success, it is
// absent, so the check is `result.isError` and never `result.isError === false`.
assert.deepEqual(Object.keys(boiling), ["content"]);
assert.deepEqual(boiling.content, [{ type: "text", text: "212.0" }]);
assert.equal(Object.hasOwn(boiling, "isError"), false);
// The default arrived filled in rather than as undefined, which is why the
// handler can format with it directly.
assert.deepEqual(handlerCalls.at(-1), { celsius: 100, scale: "fahrenheit", precision: 1 });
// ---------------------------------------------------------------------------
// A call the schema rejects. This is the part people get wrong.
// ---------------------------------------------------------------------------
// The expectation going in was that a schema violation comes back as a JSON-RPC
// error and that `await client.callTool(...)` rejects. It does not. In 1.30.0
// the McpServer tools/call handler wraps its whole body in a try/catch and
// converts every McpError it raises into a CallToolResult with isError: true,
// so the transport-level answer is a successful response. A test written as
// `await assert.rejects(client.callTool(...))` fails, and worse, a caller that
// only checks for a thrown error treats the validation failure as success and
// reads content[0].text as if it were the tool's output.
const callsBefore = handlerCalls.length;
const badType = await client.callTool({
name: "convert_temperature",
arguments: { celsius: "hot", scale: "fahrenheit" },
});
assert.equal(badType.isError, true);
assert.equal(badType.content[0].type, "text");
assert.equal(
badType.content[0].text,
"MCP error -32602: Input validation error: Invalid arguments for tool convert_temperature: " +
"Invalid input: expected number, received string at celsius",
);
// The JSON-RPC code survives only as text inside that message. There is no
// `code` field on the result to switch on, so telling a validation failure
// apart from a tool that legitimately failed means parsing English.
assert.equal(badType.code, undefined);
assert.equal(badType.error, undefined);
assert.ok(badType.content[0].text.includes(`MCP error ${ErrorCode.InvalidParams}`));
assert.equal(ErrorCode.InvalidParams, -32602);
// It really was rejected: the handler did not run. `callsBefore` was taken
// ahead of this call and is checked again after the three that follow, so all
// four schema failures are covered by it. That counter is the whole difference
// between a rejected call and a successful one, and it exists only inside the
// server — a client holding the result cannot see it.
// A missing required key and an out-of-range value come back the same way.
const missing = await client.callTool({ name: "convert_temperature", arguments: { celsius: 5 } });
assert.equal(missing.isError, true);
assert.ok(missing.content[0].text.endsWith('Invalid option: expected one of "fahrenheit"|"kelvin" at scale'));
const outOfRange = await client.callTool({
name: "convert_temperature",
arguments: { celsius: 5, scale: "kelvin", precision: 9 },
});
assert.equal(outOfRange.isError, true);
assert.ok(outOfRange.content[0].text.endsWith("Too big: expected number to be <=6 at precision"));
// Omitting `arguments` entirely is a validation failure too, not a call with no
// arguments: the schema sees undefined where it wanted an object.
const noArguments = await client.callTool({ name: "convert_temperature" });
assert.equal(noArguments.isError, true);
assert.ok(noArguments.content[0].text.endsWith("Invalid input: expected object, received undefined"));
assert.equal(handlerCalls.length, callsBefore);
// An extra property is not a violation. The object schema strips what it does
// not declare, the call succeeds, and the handler never sees the key — so a
// client sending a stale argument name gets silence rather than an error, and
// the tool runs with the field it was supposed to be setting left at default.
const extra = await client.callTool({
name: "convert_temperature",
arguments: { celsius: 0, scale: "kelvin", percision: 4 },
});
assert.deepEqual(extra.content, [{ type: "text", text: "273.1" }]);
assert.equal(Object.hasOwn(extra, "isError"), false);
assert.deepEqual(handlerCalls.at(-1), { celsius: 0, scale: "kelvin", precision: 1 });
// ---------------------------------------------------------------------------
// An unknown tool name, and where a real JSON-RPC error does come from
// ---------------------------------------------------------------------------
// Same channel as a validation failure: an isError result, not a rejection. The
// -32602 is again only inside the prose.
const unknownTool = await client.callTool({ name: "no_such_tool", arguments: {} });
assert.equal(unknownTool.isError, true);
assert.equal(unknownTool.content[0].text, "MCP error -32602: Tool no_such_tool not found");
// Resources answer on the other channel. An unregistered URI raises the same
// class of McpError inside the server, but the ReadResource handler has no
// try/catch over it, so it travels as a JSON-RPC error and the client's promise
// rejects. One server, two error conventions, decided by which handler you hit.
await assert.rejects(
client.readResource({ uri: "config://units/nope" }),
(error) => {
assert.ok(error instanceof McpError);
assert.equal(error.code, ErrorCode.InvalidParams);
// The prefix appears twice: the server serialises an McpError by copying
// its already-prefixed `message` into the JSON-RPC error, and the client
// prefixes again when it rebuilds the McpError. Matching on the exact
// string is therefore a trap of its own.
assert.equal(
error.message,
"MCP error -32602: MCP error -32602: Resource config://units/nope not found",
);
return true;
},
);
// So is an unregistered method. This server declared no prompts capability and
// prompts/list has no handler, so it comes back as MethodNotFound. Everything
// off the tools/call path reports its code as a code; only tools/call demotes
// one to prose.
await assert.rejects(client.listPrompts(), (error) => {
assert.ok(error instanceof McpError);
assert.equal(error.code, ErrorCode.MethodNotFound);
assert.equal(error.code, -32601);
assert.equal(error.message, "MCP error -32601: Method not found");
return true;
});
// ---------------------------------------------------------------------------
// A handler that throws
// ---------------------------------------------------------------------------
// The throw is caught and turned into an isError result, and the message is the
// bare Error message with no "MCP error" prefix — which is how you can tell a
// tool that failed from a call the framework rejected, and the only way you can.
const belowZero = await client.callTool({
name: "convert_temperature",
arguments: { celsius: -300, scale: "kelvin" },
});
assert.equal(belowZero.isError, true);
assert.deepEqual(belowZero.content, [{ type: "text", text: "-300 C is below absolute zero" }]);
assert.equal(belowZero.content[0].text.startsWith("MCP error"), false);
// The connection is untouched. The next call works, on the same client, over
// the same transport, with the handler running again.
const after = await client.callTool({
name: "convert_temperature",
arguments: { celsius: 0, scale: "kelvin", precision: 2 },
});
assert.deepEqual(after.content, [{ type: "text", text: "273.15" }]);
assert.equal(handlerCalls.at(-1).precision, 2);
// ---------------------------------------------------------------------------
// The resource, and the fact that the pair is not a wire
// ---------------------------------------------------------------------------
const { resources } = await client.listResources();
assert.deepEqual(resources, [
{
name: "supported-scales",
title: "Supported scales",
uri: "config://units/scales",
mimeType: "application/json",
},
]);
const read = await client.readResource({ uri: "config://units/scales" });
assert.deepEqual(read.contents, [
{
uri: "config://units/scales",
mimeType: "application/json",
text: '["fahrenheit","kelvin"]',
},
]);
// InMemoryTransport calls the peer's onmessage with the object itself. Nothing
// is stringified anywhere on this path, so values JSON cannot carry survive:
// the Date the resource put in _meta arrives as a Date. Run the same server
// over stdio and the client reads the ISO string instead, and any code that
// called .getTime() on it starts throwing in production only.
assert.ok(read._meta.revisedAt instanceof Date);
assert.equal(read._meta.revisedAt.toISOString(), "2024-01-01T00:00:00.000Z");
assert.equal(JSON.parse(JSON.stringify(read))._meta.revisedAt, "2024-01-01T00:00:00.000Z");
// The same absence of a JSON round trip keeps undefined-valued keys alive. This
// server set no annotations, and over a real transport the key would be dropped
// by JSON.stringify; here it is an own property holding undefined, so
// `"annotations" in tool` is true and `Object.keys` reports it.
assert.equal(Object.hasOwn(tool, "annotations"), true);
assert.equal(tool.annotations, undefined);
assert.ok(Object.keys(tool).includes("annotations"));
assert.equal(Object.hasOwn(JSON.parse(JSON.stringify(tool)), "annotations"), false);
// ---------------------------------------------------------------------------
// Connecting in the wrong order, and closing
// ---------------------------------------------------------------------------
// The client half can be started with nothing on the other end: the initialize
// request lands on the server half's queue instead of being lost, and nothing
// fails fast to tell you the peer is missing. Two hundred milliseconds in, the
// promise is still pending.
const late = createUnitsServer().server;
const [lateClientSide, lateServerSide] = InMemoryTransport.createLinkedPair();
const lateClient = new Client({ name: "late", version: "0.0.0" });
const connecting = lateClient.connect(lateClientSide);
let settled = false;
connecting.then(() => { settled = true; }, () => { settled = true; });
await new Promise((resolve) => setTimeout(resolve, 200));
assert.equal(settled, false);
assert.equal(lateClient.getServerCapabilities(), undefined);
// Connecting the server drains the queue and the handshake finishes, which is
// what makes "start both, await afterwards" a legitimate pattern.
await late.connect(lateServerSide);
await connecting;
assert.deepEqual(lateClient.getServerCapabilities(), {
tools: { listChanged: true },
resources: { listChanged: true },
});
// What it does not do is wait indefinitely, and that is the part worth getting
// right: every request the Protocol sends carries a timeout, initialize
// included, so a client connected to nothing eventually rejects with
// RequestTimeout rather than hanging the run. Sitting out the default takes a
// minute — measured once, the rejection lands just past 60 s — so the same
// path is driven here with an explicit 300 ms. The rejection is an McpError, so
// this failure does carry a code to switch on, and the timeout it gave up on is
// in `data`.
assert.equal(DEFAULT_REQUEST_TIMEOUT_MSEC, 60000);
const [orphanSide] = InMemoryTransport.createLinkedPair();
await assert.rejects(
new Client({ name: "orphan", version: "0.0.0" }).connect(orphanSide, { timeout: 300 }),
(error) => {
assert.ok(error instanceof McpError);
assert.equal(error.code, ErrorCode.RequestTimeout);
assert.equal(error.code, -32001);
assert.equal(error.message, "MCP error -32001: Request timed out");
assert.deepEqual(error.data, { timeout: 300 });
return true;
},
);
// Tear-down runs in both directions, and closing the client half is the one
// that is easy to miss: close() drops the link and closes the peer, so the
// server's own onclose fires and it goes disconnected without anyone having
// called close() on it.
let lateServerClosed = false;
late.server.onclose = () => { lateServerClosed = true; };
assert.equal(late.isConnected(), true);
await lateClient.close();
assert.equal(lateServerClosed, true);
assert.equal(late.isConnected(), false);
// The other direction, from the server this time, and the failure the client
// then gives you is a plain Error with no code on it — not an McpError
// carrying ErrorCode.ConnectionClosed, which is what a catch block written
// against the error codes would be looking for, and not the RequestTimeout
// above either.
await server.close();
await assert.rejects(
client.callTool({ name: "convert_temperature", arguments: { celsius: 0, scale: "kelvin" } }),
(error) => {
assert.equal(error.constructor, Error);
assert.ok(!(error instanceof McpError));
assert.equal(error.message, "Not connected");
assert.equal(error.code, undefined);
assert.notEqual(ErrorCode.ConnectionClosed, undefined);
return true;
},
);
await client.close();
console.log("contract ok: mcp sdk 1.30.0, protocol", LATEST_PROTOCOL_VERSION);