diff --git a/.changeset/green-carpets-trade.md b/.changeset/green-carpets-trade.md new file mode 100644 index 0000000..4840739 --- /dev/null +++ b/.changeset/green-carpets-trade.md @@ -0,0 +1,5 @@ +--- +'@contextvm/sdk': minor +--- + +Add CEP-15 common schema support for `tools/list`, including schema hash metadata for compatible tools and `i`/`k` discovery tags in public announcement events. diff --git a/bun.lock b/bun.lock index eda63dc..5695c38 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@noble/hashes": "^2.2.0", "applesauce-relay": "^5.2.0", + "canonicalize": "^2.1.0", "nostr-tools": "~2.18.2", "pino": "^10.3.1", "rxjs": "^7.8.2", @@ -84,11 +85,13 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="], + "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], - "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], @@ -132,27 +135,27 @@ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/type-utils": "8.59.0", "@typescript-eslint/utils": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.0", "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.1", "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.59.1", "", {}, "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.1", "@typescript-eslint/tsconfig-utils": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -160,7 +163,7 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], @@ -202,7 +205,7 @@ "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -210,7 +213,7 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -218,6 +221,8 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "canonicalize": ["canonicalize@2.1.0", "", { "bin": { "canonicalize": "bin/canonicalize.js" } }, "sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], @@ -228,7 +233,7 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -270,7 +275,7 @@ "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], - "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], + "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -318,11 +323,11 @@ "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], + "eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], "express": ["express@5.2.1", "", { "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" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "express-rate-limit": ["express-rate-limit@8.3.2", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="], + "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], "extendable-error": ["extendable-error@0.1.7", "", {}, "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg=="], @@ -400,9 +405,9 @@ "hash-sum": ["hash-sum@2.0.0", "", {}, "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], - "hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="], + "hono": ["hono@4.12.16", "", {}, "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -484,7 +489,7 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], + "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -532,7 +537,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="], + "nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -624,7 +629,7 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], @@ -658,7 +663,7 @@ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], - "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], @@ -688,7 +693,7 @@ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], @@ -730,7 +735,7 @@ "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -756,11 +761,11 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.59.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.0", "@typescript-eslint/parser": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw=="], + "typescript-eslint": ["typescript-eslint@8.59.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.1", "@typescript-eslint/parser": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], @@ -788,7 +793,7 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "zod": ["zod@4.4.2", "", {}, "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], @@ -808,15 +813,15 @@ "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "@noble/curves/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], "@scure/bip32/@noble/curves": ["@noble/curves@1.1.0", "", { "dependencies": { "@noble/hashes": "1.3.1" } }, "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA=="], - "@scure/bip32/@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="], + "@scure/bip32/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], - "@scure/bip39/@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="], + "@scure/bip39/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -824,7 +829,7 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "applesauce-core/nostr-tools": ["nostr-tools@2.19.4", "", { "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.1", "@scure/base": "1.1.1", "@scure/bip32": "1.3.1", "@scure/bip39": "1.2.1", "nostr-wasm": "0.1.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-qVLfoTpZegNYRJo5j+Oi6RPu0AwLP6jcvzcB3ySMnIT5DrAGNXfs5HNBspB/2HiGfH3GY+v6yXkTtcKSBQZwSg=="], @@ -856,6 +861,8 @@ "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@scure/bip32/@noble/curves/@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], diff --git a/package.json b/package.json index d9fc9b8..e3beff0 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@noble/hashes": "^2.2.0", "applesauce-relay": "^5.2.0", + "canonicalize": "^2.1.0", "nostr-tools": "~2.18.2", "pino": "^10.3.1", "rxjs": "^7.8.2", diff --git a/src/core/constants.ts b/src/core/constants.ts index bd6d450..f954dc7 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -134,3 +134,9 @@ export const announcementMethods: AnnouncementMethods = { export const INITIALIZE_METHOD = 'initialize'; export const NOTIFICATIONS_INITIALIZED_METHOD = 'notifications/initialized'; + +/** + * Namespace for CEP-15 common schema metadata in tool definitions. + */ +export const COMMON_SCHEMA_META_NAMESPACE = 'io.contextvm/common-schema'; + diff --git a/src/core/index.ts b/src/core/index.ts index e6b3786..ef0b398 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -2,4 +2,5 @@ export * from './constants.js'; export * from './interfaces.js'; export * from './utils/websocket.js'; export * from './utils/serializers.js'; +export * from './utils/common-schema.js'; export * from './encryption.js'; diff --git a/src/core/utils/common-schema.test.ts b/src/core/utils/common-schema.test.ts new file mode 100644 index 0000000..f8b14b7 --- /dev/null +++ b/src/core/utils/common-schema.test.ts @@ -0,0 +1,274 @@ +import { describe, expect, test } from 'bun:test'; +import { + computeCommonSchemaHash, + normalizeSchema, +} from './common-schema.js'; + +describe('normalizeSchema', () => { + test('recursively removes title and description fields', () => { + const schema = { + title: 'Top Level', + description: 'Top description', + type: 'object', + properties: { + city: { + type: 'string', + title: 'City', + description: 'City name', + }, + nested: { + type: 'object', + description: 'Nested object', + properties: { + value: { + type: 'number', + title: 'Value', + }, + }, + }, + }, + anyOf: [ + { + type: 'string', + description: 'Variant A', + }, + { + type: 'number', + title: 'Variant B', + }, + ], + }; + + const normalized: unknown = normalizeSchema(schema); + + expect(normalized).toEqual({ + type: 'object', + properties: { + city: { + type: 'string', + }, + nested: { + type: 'object', + properties: { + value: { + type: 'number', + }, + }, + }, + }, + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }); + }); + + test('recursively removes all documentation and vendor-extension fields', () => { + const schema = { + title: 'Top Level', + description: 'Top description', + default: 'foo', + examples: ['foo', 'bar'], + deprecated: true, + readOnly: false, + writeOnly: true, + 'x-custom-meta': 'some value', + type: 'object', + properties: { + city: { + type: 'string', + title: 'City', + 'x-internal-id': 123, + default: 'New York', + }, + }, + }; + + const normalized: unknown = normalizeSchema(schema); + + expect(normalized).toEqual({ + type: 'object', + properties: { + city: { + type: 'string', + }, + }, + }); + }); + + test('throws an error if an external $ref is encountered', () => { + const schema = { + type: 'object', + properties: { + location: { + $ref: 'http://example.com/schema.json', + }, + }, + }; + + expect(() => normalizeSchema(schema)).toThrow( + 'External $ref pointers must be resolved before computing common schema hash' + ); + }); + + test('preserves local $ref pointers', () => { + const schema = { + type: 'object', + properties: { + location: { + $ref: '#/definitions/Location', + }, + }, + }; + + const normalized: unknown = normalizeSchema(schema); + + expect(normalized).toEqual(schema); + }); +}); + +describe('computeCommonSchemaHash', () => { + test('produces the same hash when only documentation text changes', () => { + const first = computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: { + type: 'object', + properties: { + text: { + type: 'string', + description: 'Text to translate', + }, + }, + required: ['text'], + }, + outputSchema: { + type: 'object', + properties: { + translated_text: { + type: 'string', + title: 'Translated text', + }, + }, + required: ['translated_text'], + }, + }); + + const second = computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: { + title: 'Translate input', + type: 'object', + properties: { + text: { + type: 'string', + description: 'User content', + }, + }, + required: ['text'], + }, + outputSchema: { + type: 'object', + description: 'Translate output', + properties: { + translated_text: { + type: 'string', + title: 'Output', + }, + }, + required: ['translated_text'], + }, + }); + + expect(first).toBe(second); + }); + + test('changes when schema structure changes', () => { + const first = computeCommonSchemaHash({ + name: 'get_weather', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' }, + }, + required: ['location'], + }, + }); + + const second = computeCommonSchemaHash({ + name: 'get_weather', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' }, + units: { type: 'string' }, + }, + required: ['location'], + }, + }); + + expect(first).not.toBe(second); + }); + + test('changes when outputSchema presence changes', () => { + const withoutOutput = computeCommonSchemaHash({ + name: 'get_weather', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' }, + }, + required: ['location'], + }, + }); + + const withOutput = computeCommonSchemaHash({ + name: 'get_weather', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' }, + }, + required: ['location'], + }, + outputSchema: { + type: 'object', + properties: { + temperature: { type: 'number' }, + }, + required: ['temperature'], + }, + }); + + expect(withoutOutput).not.toBe(withOutput); + }); + + test('changes when tool name changes', () => { + const first = computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + }, + }); + + const second = computeCommonSchemaHash({ + name: 'translate_message', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + }, + }); + + expect(first).not.toBe(second); + }); +}); diff --git a/src/core/utils/common-schema.ts b/src/core/utils/common-schema.ts new file mode 100644 index 0000000..227bbd4 --- /dev/null +++ b/src/core/utils/common-schema.ts @@ -0,0 +1,94 @@ +import canonicalizePackage from 'canonicalize'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { sha256 } from '@noble/hashes/sha2.js'; +import { bytesToHex } from '@noble/hashes/utils.js'; + +export interface CommonToolSchemaDefinition { + name: Tool['name']; + inputSchema: Tool['inputSchema']; + outputSchema?: Tool['outputSchema']; +} + +type CanonicalizeFn = (input: unknown) => string | undefined; +const canonicalize = canonicalizePackage as unknown as CanonicalizeFn; + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Recursively removes documentation-only JSON Schema fields used by CEP-15. + * + * The normalization rule explicitly strips non-functional fields such as `title`, + * `description`, `examples`, `default`, `deprecated`, `readOnly`, `writeOnly`, + * and any vendor extensions (`x-*`) while preserving compatibility-relevant structure. + * + * @param schema The JSON Schema value to normalize. + * @returns A normalized copy of the schema. + */ +export function normalizeSchema(schema: T): T { + if (Array.isArray(schema)) { + return schema.map((item) => normalizeSchema(item)) as T; + } + + if (!isPlainObject(schema)) { + return schema; + } + + const normalized: Record = {}; + + Object.keys(schema).forEach((key) => { + if ( + key === 'title' || + key === 'description' || + key === 'default' || + key === 'examples' || + key === 'deprecated' || + key === 'readOnly' || + key === 'writeOnly' || + key.startsWith('x-') + ) { + return; + } + + if ( + key === '$ref' && + typeof schema[key] === 'string' && + !(schema[key] as string).startsWith('#') + ) { + throw new Error( + 'External $ref pointers must be resolved before computing common schema hash', + ); + } + + normalized[key] = normalizeSchema(schema[key]); + }); + + return normalized as T; +} + +/** + * Computes the CEP-15 schema hash for a common tool definition. + * + * @param definition Tool name and JSON Schemas participating in compatibility. + * @returns A deterministic SHA-256 hash of the normalized schema payload. + */ +export function computeCommonSchemaHash( + definition: CommonToolSchemaDefinition, +): string { + const payload: CommonToolSchemaDefinition = { + name: definition.name, + inputSchema: normalizeSchema(definition.inputSchema), + }; + + if (definition.outputSchema != null) { + payload.outputSchema = normalizeSchema(definition.outputSchema); + } + + const canonicalPayload = canonicalize(payload); + if (canonicalPayload === undefined) { + throw new Error('Failed to canonicalize common schema payload'); + } + + return bytesToHex(sha256(new TextEncoder().encode(canonicalPayload))); +} diff --git a/src/transport/index.ts b/src/transport/index.ts index c828dce..ae872b8 100644 --- a/src/transport/index.ts +++ b/src/transport/index.ts @@ -2,3 +2,4 @@ export * from './nostr-client-transport.js'; export * from './nostr-server-transport.js'; export * from './nostr-server/announcement-manager.js'; export * from './base-nostr-transport.js'; +export * from './server-transport-common-schemas.js'; diff --git a/src/transport/nostr-server-transport.test.ts b/src/transport/nostr-server-transport.test.ts index 2d05aaf..32566a3 100644 --- a/src/transport/nostr-server-transport.test.ts +++ b/src/transport/nostr-server-transport.test.ts @@ -25,9 +25,11 @@ import { RESOURCETEMPLATES_LIST_KIND, SERVER_ANNOUNCEMENT_KIND, TOOLS_LIST_KIND, + COMMON_SCHEMA_META_NAMESPACE, } from '../core/constants.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { EncryptionMode, GiftWrapMode } from '../core/interfaces.js'; +import { computeCommonSchemaHash } from '../core/index.js'; import { ApplesauceRelayPool } from '../relay/applesauce-relay-pool.js'; import { z } from 'zod'; import { injectClientPubkey } from '../core/utils/utils.js'; @@ -36,6 +38,8 @@ import { JSONRPCMessage, } from '@modelcontextprotocol/sdk/types.js'; import { withServerPayments } from '../payments/server-transport-payments.js'; + +import { withCommonToolSchemas } from './server-transport-common-schemas.js'; import { FakePaymentProcessor } from '../payments/fake-payment-processor.js'; import { spawnMockRelay, @@ -1247,4 +1251,144 @@ describe.serial('NostrServerTransport', () => { await server.close(); await relayPool.disconnect(); }, 15000); + + test.serial( + 'withCommonToolSchemas injects schema hashes into direct and announced tools/list payloads', + async () => { + const serverPrivateKey = bytesToHex(generateSecretKey()); + const serverPublicKey = getPublicKey(hexToBytes(serverPrivateKey)); + const clientPrivateKey = bytesToHex(generateSecretKey()); + const uniqueSuffix = Math.random().toString(36).substring(2, 8); + const commonToolName = `translate_text_${uniqueSuffix}`; + const bespokeToolName = `bespoke_tool_${uniqueSuffix}`; + + const server = new McpServer({ + name: 'Common Schema Server', + version: '1.0.0', + }); + + server.registerTool( + commonToolName, + { + title: 'Translate Text', + description: 'Translate text between languages', + inputSchema: { + text: z.string(), + targetLanguage: z.string(), + }, + }, + async ({ text, targetLanguage }) => ({ + content: [ + { + type: 'text', + text: `${targetLanguage}: ${text}`, + }, + ], + }), + ); + + server.registerTool( + bespokeToolName, + { + title: 'Bespoke Tool', + description: 'Do something custom', + inputSchema: { + query: z.string(), + }, + }, + async ({ query }) => ({ + content: [{ type: 'text', text: query.toUpperCase() }], + }), + ); + + const transport = new NostrServerTransport({ + signer: new PrivateKeySigner(serverPrivateKey), + relayHandler: new ApplesauceRelayPool([relayUrl]), + serverInfo: { name: 'Common Schema Server' }, + isPublicServer: true, + encryptionMode: EncryptionMode.DISABLED, + }); + + withCommonToolSchemas(transport, { + tools: [{ name: commonToolName }], + }); + + await server.connect(transport); + + const relayPool = new ApplesauceRelayPool([relayUrl]); + await relayPool.connect(); + + const toolsListEvent = await waitForNostrEvent({ + relayPool, + filters: [{ kinds: [TOOLS_LIST_KIND], authors: [serverPublicKey] }], + where: () => true, + }); + + const announcedToolsList = JSON.parse(toolsListEvent.content) as { + tools: Array>; + }; + const announcedCommonTool = announcedToolsList.tools.find( + (tool) => tool.name === commonToolName, + ) as Record | undefined; + const announcedBespokeTool = announcedToolsList.tools.find( + (tool) => tool.name === bespokeToolName, + ) as Record | undefined; + + const { client, clientNostrTransport } = createClientAndTransport( + clientPrivateKey, + 'Common-Schema Client', + serverPublicKey, + EncryptionMode.DISABLED, + ); + + await client.connect(clientNostrTransport); + const listToolsResult = await client.listTools(); + + const directCommonTool = listToolsResult.tools.find( + (tool) => tool.name === commonToolName, + ); + const directBespokeTool = listToolsResult.tools.find( + (tool) => tool.name === bespokeToolName, + ); + + const expectedSchemaHash = computeCommonSchemaHash({ + name: directCommonTool!.name, + inputSchema: directCommonTool!.inputSchema, + outputSchema: directCommonTool!.outputSchema ?? undefined, + }); + const iTags = toolsListEvent.tags.filter((tag) => tag[0] === 'i'); + const kTags = toolsListEvent.tags.filter((tag) => tag[0] === 'k'); + + expect(directCommonTool?._meta).toMatchObject({ + [COMMON_SCHEMA_META_NAMESPACE]: { + schemaHash: expectedSchemaHash, + }, + }); + expect( + directBespokeTool?._meta?.[COMMON_SCHEMA_META_NAMESPACE], + ).toBeUndefined(); + + expect(announcedCommonTool?.['_meta']).toMatchObject({ + [COMMON_SCHEMA_META_NAMESPACE]: { + schemaHash: expectedSchemaHash, + }, + }); + expect( + (announcedBespokeTool?.['_meta'] as Record | undefined)?.[ + COMMON_SCHEMA_META_NAMESPACE + ], + ).toBeUndefined(); + + expect(iTags).toEqual( + expect.arrayContaining([['i', expectedSchemaHash, commonToolName]]), + ); + expect(iTags.some((tag) => tag[2] === bespokeToolName)).toBe(false); + expect(kTags).toEqual([['k', COMMON_SCHEMA_META_NAMESPACE]]); + + await client.close(); + await server.close(); + await relayPool.disconnect(); + }, + 15000, + ); }); diff --git a/src/transport/nostr-server-transport.ts b/src/transport/nostr-server-transport.ts index c549f86..da9635b 100644 --- a/src/transport/nostr-server-transport.ts +++ b/src/transport/nostr-server-transport.ts @@ -4,6 +4,7 @@ import { ListResourcesResultSchema, ListResourceTemplatesResultSchema, ListToolsResultSchema, + type ListToolsResult, isJSONRPCRequest, isJSONRPCNotification, type JSONRPCMessage, @@ -137,6 +138,14 @@ export interface NostrServerTransportOptions extends BaseNostrTransportOptions { }; } +export type ListToolsResultTransformer = ( + result: ListToolsResult, +) => ListToolsResult; + +export type ListToolsAnnouncementTagsProducer = ( + result: ListToolsResult, +) => string[][]; + export type InboundMiddlewareFn = ( message: JSONRPCMessage, ctx: { clientPubkey: string; clientPmis?: readonly string[] }, @@ -173,6 +182,8 @@ export class NostrServerTransport }) => void | Promise) | undefined; private readonly inboundMiddlewares: InboundMiddlewareFn[] = []; + private readonly listToolsResultTransformers: ListToolsResultTransformer[] = []; + private readonly listToolsAnnouncementTagsProducers: ListToolsAnnouncementTagsProducer[] = []; /** * Deduplicate inbound events to avoid redundant work. @@ -267,6 +278,10 @@ export class NostrServerTransport publishRelayList: options.publishRelayList, relayListUrls: options.relayListUrls, bootstrapRelayUrls: options.bootstrapRelayUrls, + transformListToolsResult: (result) => + this.applyListToolsResultTransformers(result), + getListToolsAnnouncementTags: (result) => + this.buildListToolsAnnouncementTags(result), onDispatchMessage: (message) => this.onmessage?.(message), onPublishEvent: (event) => this.publishEvent(event), onPublishEventToRelays: (event, relayUrls) => @@ -323,6 +338,26 @@ export class NostrServerTransport this.inboundMiddlewares.push(middleware); } + /** + * Adds a transformer for `tools/list` results emitted by the server. + * + * Transformers are applied to direct responses and public announcement payloads. + */ + public addListToolsResultTransformer( + transformer: ListToolsResultTransformer, + ): void { + this.listToolsResultTransformers.push(transformer); + } + + /** + * Adds a provider for extra tags on public tools/list announcement events. + */ + public addListToolsAnnouncementTagsProducer( + producer: ListToolsAnnouncementTagsProducer, + ): void { + this.listToolsAnnouncementTagsProducers.push(producer); + } + /** * Starts the transport, connecting to the relay and setting up event listeners * to receive incoming MCP requests. @@ -581,6 +616,23 @@ export class NostrServerTransport * Handles response messages by finding the original request and routing back to client. * @param response The JSON-RPC response or error to send. */ + private applyListToolsResultTransformers( + result: ListToolsResult, + ): ListToolsResult { + return this.listToolsResultTransformers.reduce( + (currentResult, transformer) => transformer(currentResult), + result, + ); + } + + private buildListToolsAnnouncementTags( + result: ListToolsResult, + ): string[][] { + return this.listToolsAnnouncementTagsProducers.flatMap((producer) => + producer(result), + ); + } + private async handleResponse( response: JSONRPCResponse | JSONRPCErrorResponse, ): Promise { @@ -615,8 +667,19 @@ export class NostrServerTransport return; } + const parsedListToolsResult = isJSONRPCResultResponse(response) + ? ListToolsResultSchema.safeParse(response.result) + : null; + + const responseToSend = parsedListToolsResult?.success + ? { + ...response, + result: this.applyListToolsResultTransformers(parsedListToolsResult.data), + } + : response; + // Restore the original request ID in the response - response.id = route.originalRequestId; + responseToSend.id = route.originalRequestId; // CEP-22 Oversized Transfer (proactive path for server responses) if ( @@ -625,7 +688,7 @@ export class NostrServerTransport session.supportsOversizedTransfer ) { // Serialize before restoring id so the client receives the correct id. - const serialized = JSON.stringify(response); + const serialized = JSON.stringify(responseToSend); const byteLength = new TextEncoder().encode(serialized).byteLength; if (byteLength > this.oversizedThreshold) { const continuationFrameTags = this.createResponseTags( @@ -675,8 +738,8 @@ export class NostrServerTransport }); // Attach pricing tags to capability list responses so clients can access CEP-8 pricing - if (isJSONRPCResultResponse(response)) { - const result = response.result; + if (isJSONRPCResultResponse(responseToSend)) { + const result = responseToSend.result; if ( ListToolsResultSchema.safeParse(result).success || ListResourcesResultSchema.safeParse(result).success || @@ -689,7 +752,7 @@ export class NostrServerTransport try { await this.sendMcpMessage( - response, + responseToSend, route.clientPubkey, CTXVM_MESSAGES_KIND, tags, diff --git a/src/transport/nostr-server/announcement-manager.ts b/src/transport/nostr-server/announcement-manager.ts index 8f43ea6..4156a05 100644 --- a/src/transport/nostr-server/announcement-manager.ts +++ b/src/transport/nostr-server/announcement-manager.ts @@ -13,6 +13,7 @@ import { ListResourceTemplatesResultSchema, ListToolsResultSchema, type JSONRPCMessage, + type ListToolsResult, type JSONRPCResponse, isJSONRPCResultResponse, } from '@modelcontextprotocol/sdk/types.js'; @@ -111,6 +112,10 @@ export interface AnnouncementManagerOptions { ) => Promise; /** Callback to inspect the transport's operational relay URLs. */ onGetRelayUrls?: () => string[] | undefined; + /** Optional transformer for tools/list results before publication. */ + transformListToolsResult?: (result: ListToolsResult) => ListToolsResult; + /** Optional producer for extra tags on public tools/list announcements. */ + getListToolsAnnouncementTags?: (result: ListToolsResult) => string[][]; /** Logger for debug output */ logger: { info: (message: string, meta?: unknown) => void; @@ -166,6 +171,12 @@ export class AnnouncementManager { onEvent: (event: NostrEvent) => void, ) => Promise; private readonly onGetRelayUrls: (() => string[] | undefined) | undefined; + private readonly transformListToolsResult: + | ((result: ListToolsResult) => ListToolsResult) + | undefined; + private readonly getListToolsAnnouncementTags: + | ((result: ListToolsResult) => string[][]) + | undefined; private readonly logger: AnnouncementManagerOptions['logger']; private isInitialized = false; @@ -194,6 +205,8 @@ export class AnnouncementManager { this.onGetPublicKey = options.onGetPublicKey; this.onSubscribe = options.onSubscribe; this.onGetRelayUrls = options.onGetRelayUrls; + this.transformListToolsResult = options.transformListToolsResult; + this.getListToolsAnnouncementTags = options.getListToolsAnnouncementTags; this.logger = options.logger; } @@ -570,13 +583,27 @@ export class AnnouncementManager { try { const recipientPubkey = await this.onGetPublicKey(); const announcementMapping = this.getAnnouncementMapping(); + const parsedListToolsResult = ListToolsResultSchema.safeParse(result); + const announcementListToolsResult = + this.transformListToolsResult && parsedListToolsResult.success + ? this.transformListToolsResult(parsedListToolsResult.data) + : parsedListToolsResult.success + ? parsedListToolsResult.data + : undefined; + const announcementResult = announcementListToolsResult ?? result; + const listToolsAnnouncementTags = announcementListToolsResult + ? this.getListToolsAnnouncementTags?.(announcementListToolsResult) ?? [] + : []; for (const mapping of announcementMapping) { - if (mapping.schema.safeParse(result).success) { + if (mapping.schema.safeParse(announcementResult).success) { const eventTemplate = { kind: mapping.kind, - content: JSON.stringify(result), - tags: mapping.tags, + content: JSON.stringify(announcementResult), + tags: + mapping.kind === TOOLS_LIST_KIND + ? [...mapping.tags, ...listToolsAnnouncementTags] + : mapping.tags, created_at: Math.floor(Date.now() / 1000), pubkey: recipientPubkey, }; diff --git a/src/transport/server-transport-common-schemas.test.ts b/src/transport/server-transport-common-schemas.test.ts new file mode 100644 index 0000000..943bada --- /dev/null +++ b/src/transport/server-transport-common-schemas.test.ts @@ -0,0 +1,244 @@ +import { describe, expect, test } from 'bun:test'; +import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'; +import { computeCommonSchemaHash } from '../core/utils/common-schema.js'; +import { COMMON_SCHEMA_META_NAMESPACE } from '../core/constants.js'; +import { + createCommonSchemaAnnouncementTagsProducer, + createCommonSchemaToolsResultTransformer, +} from './server-transport-common-schemas.js'; + +describe('createCommonSchemaToolsResultTransformer', () => { + test('injects schema hashes into opted-in tools and preserves existing metadata', () => { + const result: ListToolsResult = { + tools: [ + { + name: 'translate_text', + title: 'Translate Text', + description: 'Translate text between languages', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string', description: 'Input text' }, + targetLanguage: { type: 'string', title: 'Target language' }, + }, + required: ['text', 'targetLanguage'], + }, + _meta: { + existing: true, + [COMMON_SCHEMA_META_NAMESPACE]: { + note: 'preserved', + }, + }, + }, + { + name: 'bespoke_tool', + title: 'Bespoke Tool', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string' }, + }, + }, + }, + ], + }; + + const transform = createCommonSchemaToolsResultTransformer({ + tools: [{ name: 'translate_text' }], + }); + + const transformed = transform(result); + const translateTool = transformed.tools.find( + (tool) => tool.name === 'translate_text', + ); + const bespokeTool = transformed.tools.find( + (tool) => tool.name === 'bespoke_tool', + ); + + expect(transformed).not.toBe(result); + expect(translateTool?._meta).toMatchObject({ + existing: true, + [COMMON_SCHEMA_META_NAMESPACE]: { + note: 'preserved', + schemaHash: computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: result.tools[0]!.inputSchema, + }), + }, + }); + expect(bespokeTool).toBe(result.tools[1]); + expect(bespokeTool?._meta?.[COMMON_SCHEMA_META_NAMESPACE]).toBeUndefined(); + }); + + + + test('returns the original result when opted-in tools already carry the matching schema hash', () => { + const schemaHash = computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + }, + }); + + const tool = { + name: 'translate_text', + title: 'Translate Text', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + }, + _meta: { + [COMMON_SCHEMA_META_NAMESPACE]: { + schemaHash, + note: 'already present', + }, + }, + } satisfies ListToolsResult['tools'][number]; + + const result: ListToolsResult = { + tools: [tool], + }; + + const transform = createCommonSchemaToolsResultTransformer({ + tools: [{ name: 'translate_text' }], + }); + + expect(transform(result)).toBe(result); + }); + + test('returns the original result when no configured tools match', () => { + const result: ListToolsResult = { + tools: [ + { + name: 'weather_lookup', + title: 'Weather Lookup', + inputSchema: { + type: 'object', + properties: { + city: { type: 'string' }, + }, + required: ['city'], + }, + }, + ], + }; + + const transform = createCommonSchemaToolsResultTransformer({ + tools: [{ name: 'translate_text' }], + }); + + expect(transform(result)).toBe(result); + }); +}); + +describe('createCommonSchemaAnnouncementTagsProducer', () => { + test('creates NIP-73 i/k tags for opted-in common-schema tools only', () => { + const result: ListToolsResult = { + tools: [ + { + name: 'translate_text', + title: 'Translate Text', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + targetLanguage: { type: 'string' }, + }, + required: ['text', 'targetLanguage'], + }, + }, + { + name: 'bespoke_tool', + title: 'Bespoke Tool', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string' }, + }, + required: ['query'], + }, + }, + ], + }; + + const produceTags = createCommonSchemaAnnouncementTagsProducer({ + tools: [{ name: 'translate_text' }], + }); + + expect(produceTags(result)).toEqual([ + [ + 'i', + computeCommonSchemaHash({ + name: 'translate_text', + inputSchema: result.tools[0]!.inputSchema, + }), + 'translate_text', + ], + ['k', COMMON_SCHEMA_META_NAMESPACE], + ]); + }); + + + + test('reuses existing schemaHash metadata when producing announcement tags', () => { + const result: ListToolsResult = { + tools: [ + { + name: 'translate_text', + title: 'Translate Text', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + }, + _meta: { + [COMMON_SCHEMA_META_NAMESPACE]: { + schemaHash: 'precomputed-hash', + }, + }, + }, + ], + }; + + const produceTags = createCommonSchemaAnnouncementTagsProducer({ + tools: [{ name: 'translate_text' }], + }); + + expect(produceTags(result)).toEqual([ + ['i', 'precomputed-hash', 'translate_text'], + ['k', COMMON_SCHEMA_META_NAMESPACE], + ]); + }); + + test('returns no tags when no common-schema tools are present', () => { + const result: ListToolsResult = { + tools: [ + { + name: 'bespoke_tool', + title: 'Bespoke Tool', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string' }, + }, + }, + }, + ], + }; + + const produceTags = createCommonSchemaAnnouncementTagsProducer({ + tools: [{ name: 'translate_text' }], + }); + + expect(produceTags(result)).toEqual([]); + }); +}); diff --git a/src/transport/server-transport-common-schemas.ts b/src/transport/server-transport-common-schemas.ts new file mode 100644 index 0000000..e89892b --- /dev/null +++ b/src/transport/server-transport-common-schemas.ts @@ -0,0 +1,175 @@ +import type { ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js'; +import { computeCommonSchemaHash } from '../core/utils/common-schema.js'; +import type { NostrServerTransport } from './nostr-server-transport.js'; + +import { COMMON_SCHEMA_META_NAMESPACE } from '../core/constants.js'; +export interface CommonSchemaToolConfig { + name: Tool['name']; +} + +export interface CommonToolSchemasOptions { + tools: CommonSchemaToolConfig[]; +} + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function getCurrentSchemaHash(meta: Tool['_meta']): string | undefined { + const commonSchemaMeta = meta?.[COMMON_SCHEMA_META_NAMESPACE]; + + if (!isPlainObject(commonSchemaMeta)) { + return undefined; + } + + return typeof commonSchemaMeta.schemaHash === 'string' + ? commonSchemaMeta.schemaHash + : undefined; +} + +function getToolSchemaHash( + tool: Pick, +): string { + return ( + getCurrentSchemaHash(tool._meta) ?? + computeCommonSchemaHash({ + name: tool.name, + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + }) + ); +} + +function mergeCommonSchemaMeta( + meta: Tool['_meta'], + schemaHash: string, +): { meta: NonNullable; didChange: boolean } { + if (getCurrentSchemaHash(meta) === schemaHash && meta) { + return { + meta, + didChange: false, + }; + } + + const existingMeta = meta ?? {}; + const existingNamespace = existingMeta[COMMON_SCHEMA_META_NAMESPACE]; + + return { + meta: { + ...existingMeta, + [COMMON_SCHEMA_META_NAMESPACE]: { + ...(isPlainObject(existingNamespace) ? existingNamespace : {}), + schemaHash, + }, + }, + didChange: true, + }; +} + +function getCommonToolNames( + options: CommonToolSchemasOptions, +): Set { + return new Set(options.tools.map((tool) => tool.name)); +} + +/** + * Creates a pure transformer that enriches opted-in `tools/list` results with CEP-15 schema hashes. + */ +export function createCommonSchemaToolsResultTransformer( + options: CommonToolSchemasOptions, +): (result: ListToolsResult) => ListToolsResult { + const commonToolNames = getCommonToolNames(options); + + return (result: ListToolsResult): ListToolsResult => { + if (!commonToolNames.size) { + return result; + } + + let nextTools: Tool[] | undefined; + + result.tools.forEach((tool, index) => { + if (!commonToolNames.has(tool.name)) { + if (nextTools) { + nextTools.push(tool); + } + return; + } + + const schemaHash = getToolSchemaHash(tool); + const mergedMeta = mergeCommonSchemaMeta(tool._meta, schemaHash); + + if (!mergedMeta.didChange) { + if (nextTools) { + nextTools.push(tool); + } + return; + } + + if (!nextTools) { + nextTools = result.tools.slice(0, index); + } + + nextTools.push({ + ...tool, + _meta: mergedMeta.meta, + }); + }); + + if (!nextTools) { + return result; + } + + return { + ...result, + tools: nextTools, + }; + }; +} + +/** + * Creates NIP-73 `i` / `k` tags for tools/list announcements of opted-in CEP-15 tools. + */ +export function createCommonSchemaAnnouncementTagsProducer( + options: CommonToolSchemasOptions, +): (result: ListToolsResult) => string[][] { + const commonToolNames = getCommonToolNames(options); + + return (result: ListToolsResult): string[][] => { + if (!commonToolNames.size) { + return []; + } + + const iTags = result.tools.flatMap((tool) => { + if (!commonToolNames.has(tool.name)) { + return []; + } + + return [['i', getToolSchemaHash(tool), tool.name]]; + }); + + if (!iTags.length) { + return []; + } + + return [...iTags, ['k', COMMON_SCHEMA_META_NAMESPACE]]; + }; +} + +/** + * Attaches CEP-15 common-schema metadata injection to a NostrServerTransport. + * Apply this decorator before connecting the transport so direct and announced `tools/list` + * payloads stay consistent from the first announcement onward. + */ +export function withCommonToolSchemas( + transport: NostrServerTransport, + options: CommonToolSchemasOptions, +): NostrServerTransport { + transport.addListToolsResultTransformer( + createCommonSchemaToolsResultTransformer(options), + ); + transport.addListToolsAnnouncementTagsProducer( + createCommonSchemaAnnouncementTagsProducer(options), + ); + + return transport; +}