diff --git a/README.md b/README.md index f1839845c..87f60dd89 100644 --- a/README.md +++ b/README.md @@ -1040,8 +1040,11 @@ server.tool( type: "string", title: "Date flexibility", description: "How flexible are your dates?", - enum: ["next_day", "same_week", "next_week"], - enumNames: ["Next day", "Same week", "Next week"] + oneOf: [ + { const: "next_day", title: "Next day" }, + { const: "same_week", title: "Same week" }, + { const: "next_week", title: "Next week" } + ], } }, required: ["checkAlternatives"] diff --git a/package-lock.json b/package-lock.json index dd45ff05d..dcd12c379 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.17.1", "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "ajv": "^8.17.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", @@ -1037,6 +1037,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.13.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", @@ -2223,15 +2247,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "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" + "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", @@ -3241,6 +3265,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", @@ -3508,7 +3556,8 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -3523,6 +3572,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -4897,9 +4962,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "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-stable-stringify-without-jsonify": { @@ -5574,6 +5639,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5679,6 +5745,15 @@ "node": ">=0.10.0" } }, + "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/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -6461,6 +6536,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index 7bbb0f173..ee44d0de3 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "client": "tsx src/cli.ts client" }, "dependencies": { - "ajv": "^6.12.6", + "ajv": "^8.17.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", diff --git a/src/client/index.ts b/src/client/index.ts index 3e8d8ec80..c64b6d005 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -43,7 +43,7 @@ import { ErrorCode, McpError, } from "../types.js"; -import Ajv from "ajv"; +import { Ajv } from 'ajv'; import type { ValidateFunction } from "ajv"; export type ClientOptions = ProtocolOptions & { @@ -92,7 +92,7 @@ export class Client< private _capabilities: ClientCapabilities; private _instructions?: string; private _cachedToolOutputValidators: Map = new Map(); - private _ajv: InstanceType; + private _ajv: Ajv; /** * Initializes this client with the given name and version information. diff --git a/src/examples/client/simpleStreamableHttp.ts b/src/examples/client/simpleStreamableHttp.ts index ddb274196..12b7426a3 100644 --- a/src/examples/client/simpleStreamableHttp.ts +++ b/src/examples/client/simpleStreamableHttp.ts @@ -20,7 +20,7 @@ import { ReadResourceResultSchema, } from '../../types.js'; import { getDisplayName } from '../../shared/metadataUtils.js'; -import Ajv from "ajv"; +import { Ajv } from 'ajv'; // Create readline interface for user input const readline = createInterface({ @@ -373,7 +373,7 @@ async function connect(url?: string): Promise { if (!isValid) { console.log('❌ Validation errors:'); validate.errors?.forEach(error => { - console.log(` - ${error.dataPath || 'root'}: ${error.message}`); + console.log(` - ${error.instancePath || 'root'}: ${error.message}`); }); if (attempts < maxAttempts) { diff --git a/src/examples/server/simpleStreamableHttp.ts b/src/examples/server/simpleStreamableHttp.ts index 98f9d351c..7db1db4bb 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -141,8 +141,11 @@ const getServer = () => { type: 'string', title: 'Theme', description: 'Choose your preferred theme', - enum: ['light', 'dark', 'auto'], - enumNames: ['Light', 'Dark', 'Auto'], + oneOf: [ + { const: 'light', title: 'Light' }, + { const: 'dark', title: 'Dark' }, + { const: 'auto', title: 'Auto' } + ], }, notifications: { type: 'boolean', @@ -154,8 +157,11 @@ const getServer = () => { type: 'string', title: 'Notification Frequency', description: 'How often would you like notifications?', - enum: ['daily', 'weekly', 'monthly'], - enumNames: ['Daily', 'Weekly', 'Monthly'], + oneOf: [ + { const: 'daily', title: 'Daily' }, + { const: 'weekly', title: 'Weekly' }, + { const: 'monthly', title: 'Monthly' } + ], }, }, required: ['theme'], diff --git a/src/server/index.ts b/src/server/index.ts index 10ae2fadc..8250bd4d2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -33,7 +33,7 @@ import { ServerResult, SUPPORTED_PROTOCOL_VERSIONS, } from "../types.js"; -import Ajv from "ajv"; +import { Ajv } from 'ajv'; export type ServerOptions = ProtocolOptions & { /** @@ -85,6 +85,7 @@ export class Server< private _clientVersion?: Implementation; private _capabilities: ServerCapabilities; private _instructions?: string; + private _ajv: Ajv; /** * Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification). @@ -101,6 +102,7 @@ export class Server< super(options); this._capabilities = options?.capabilities ?? {}; this._instructions = options?.instructions; + this._ajv = new Ajv({ code: { source: false } }); this.setRequestHandler(InitializeRequestSchema, (request) => this._oninitialize(request), @@ -323,15 +325,13 @@ export class Server< // Validate the response content against the requested schema if action is "accept" if (result.action === "accept" && result.content) { try { - const ajv = new Ajv(); - - const validate = ajv.compile(params.requestedSchema); + const validate = this._ajv.compile(params.requestedSchema); const isValid = validate(result.content); if (!isValid) { throw new McpError( ErrorCode.InvalidParams, - `Elicitation response content does not match requested schema: ${ajv.errorsText(validate.errors)}`, + `Elicitation response content does not match requested schema: ${this._ajv.errorsText(validate.errors)}`, ); } } catch (error) { diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 10e550df4..b63c0afee 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -4119,8 +4119,11 @@ describe("elicitInput()", () => { type: "string", title: "Date flexibility", description: "How flexible are your dates?", - enum: ["next_day", "same_week", "next_week"], - enumNames: ["Next day", "Same week", "Next week"] + oneOf: [ + { const: "next_day", title: "Next day" }, + { const: "same_week", title: "Same week" }, + { const: "next_week", title: "Next week" } + ], } }, required: ["checkAlternatives"] diff --git a/src/types.ts b/src/types.ts index 323e37389..89d87c3b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1212,8 +1212,10 @@ export const EnumSchemaSchema = z type: z.literal("string"), title: z.optional(z.string()), description: z.optional(z.string()), - enum: z.array(z.string()), - enumNames: z.optional(z.array(z.string())), + oneOf: z.array(z.object({ + const: z.string(), + title: z.string() + })), }) .passthrough();