diff --git a/src/client/index.test.ts b/src/client/index.test.ts index e9e52ea58..4c26c796c 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -14,6 +14,7 @@ import { CallToolRequestSchema, CreateMessageRequestSchema, ElicitRequestSchema, + ElicitResultSchema, ListRootsRequestSchema, ErrorCode } from '../types.js'; @@ -917,6 +918,64 @@ test('should reject form-mode elicitation when client only supports URL mode', a await client.close(); }); +test('should reject missing-mode elicitation when client only supports URL mode', async () => { + const server = new Server( + { + name: 'test server', + version: '1.0' + }, + { + capabilities: {} + } + ); + + const client = new Client( + { + name: 'test client', + version: '1.0' + }, + { + capabilities: { + elicitation: { + url: {} + } + } + } + ); + + const handler = vi.fn().mockResolvedValue({ + action: 'cancel' + }); + client.setRequestHandler(ElicitRequestSchema, handler); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]); + + await expect( + server.request( + { + method: 'elicitation/create', + params: { + message: 'Please provide data', + requestedSchema: { + type: 'object', + properties: { + username: { + type: 'string' + } + } + } + } + }, + ElicitResultSchema + ) + ).rejects.toThrow('Client does not support form-mode elicitation requests'); + + expect(handler).not.toHaveBeenCalled(); + + await Promise.all([client.close(), server.close()]); +}); + test('should reject URL-mode elicitation when client only supports form mode', async () => { const client = new Client( { diff --git a/src/client/index.ts b/src/client/index.ts index 694ae4a1a..823aa790e 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -267,13 +267,14 @@ export class Client< } const { params } = validatedRequest.data; + const mode = params.mode ?? 'form'; const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation); - if (params.mode === 'form' && !supportsFormMode) { + if (mode === 'form' && !supportsFormMode) { throw new McpError(ErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests'); } - if (params.mode === 'url' && !supportsUrlMode) { + if (mode === 'url' && !supportsUrlMode) { throw new McpError(ErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests'); } @@ -288,9 +289,9 @@ export class Client< } const validatedResult = validationResult.data; - const requestedSchema = params.mode === 'form' ? (params.requestedSchema as JsonSchemaType) : undefined; + const requestedSchema = mode === 'form' ? (params.requestedSchema as JsonSchemaType) : undefined; - if (params.mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) { + if (mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) { if (this._capabilities.elicitation?.form?.applyDefaults) { try { applyElicitationDefaults(requestedSchema, validatedResult.content); diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 86eaf6d9e..682dd4e22 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -700,7 +700,7 @@ test('should include form mode when sending elicitation form requests', async () const receivedModes: string[] = []; client.setRequestHandler(ElicitRequestSchema, request => { - receivedModes.push(request.params.mode); + receivedModes.push(request.params.mode ?? ''); return { action: 'accept', content: { @@ -764,7 +764,7 @@ test('should include url mode when sending elicitation URL requests', async () = const receivedModes: string[] = []; const receivedIds: string[] = []; client.setRequestHandler(ElicitRequestSchema, request => { - receivedModes.push(request.params.mode); + receivedModes.push(request.params.mode ?? ''); if (request.params.mode === 'url') { receivedIds.push(request.params.elicitationId); } diff --git a/src/server/index.ts b/src/server/index.ts index 8ec838e51..e8d5c6ca5 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -35,8 +35,6 @@ import { import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js'; import type { JsonSchemaType, jsonSchemaValidator } from '../validation/types.js'; -type LegacyElicitRequestFormParams = Omit; - export type ServerOptions = ProtocolOptions & { /** * Capabilities to advertise as being supported by this server. @@ -331,33 +329,13 @@ export class Server< /** * Creates an elicitation request for the given parameters. - * @param params The parameters for the form elicitation request (explicit mode: 'form'). + * For backwards compatibility, `mode` may be omitted for form requests and will default to `'form'`. + * @param params The parameters for the elicitation request. * @param options Optional request options. * @returns The result of the elicitation request. */ - async elicitInput(params: ElicitRequestFormParams, options?: RequestOptions): Promise; - /** - * Creates an elicitation request for the given parameters. - * @param params The parameters for the URL elicitation request (with url and elicitationId). - * @param options Optional request options. - * @returns The result of the elicitation request. - */ - async elicitInput(params: ElicitRequestURLParams, options?: RequestOptions): Promise; - /** - * Creates an elicitation request for the given parameters. - * @deprecated Use the overloads with explicit `mode: 'form' | 'url'` instead. - * @param params The parameters for the form elicitation request (legacy signature without mode). - * @param options Optional request options. - * @returns The result of the elicitation request. - */ - async elicitInput(params: LegacyElicitRequestFormParams, options?: RequestOptions): Promise; - - // Implementation (not visible to callers) - async elicitInput( - params: LegacyElicitRequestFormParams | ElicitRequestFormParams | ElicitRequestURLParams, - options?: RequestOptions - ): Promise { - const mode = ('mode' in params ? params.mode : 'form') as 'form' | 'url'; + async elicitInput(params: ElicitRequestFormParams | ElicitRequestURLParams, options?: RequestOptions): Promise { + const mode = (params.mode ?? 'form') as 'form' | 'url'; switch (mode) { case 'url': { @@ -372,10 +350,9 @@ export class Server< if (!this._clientCapabilities?.elicitation?.form) { throw new Error('Client does not support form elicitation.'); } + const formParams: ElicitRequestFormParams = - 'mode' in params - ? (params as ElicitRequestFormParams) - : ({ ...(params as LegacyElicitRequestFormParams), mode: 'form' } as ElicitRequestFormParams); + params.mode === 'form' ? (params as ElicitRequestFormParams) : { ...(params as ElicitRequestFormParams), mode: 'form' }; const result = await this.request({ method: 'elicitation/create', params: formParams }, ElicitResultSchema, options); diff --git a/src/spec.types.ts b/src/spec.types.ts index 6ce24059e..49f2457ce 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -3,7 +3,7 @@ * * Source: https://github.com/modelcontextprotocol/modelcontextprotocol * Pulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts - * Last updated from commit: 4528444698f76e6d0337e58d2941d5d3485d779d + * Last updated from commit: 7dcdd69262bd488ddec071bf4eefedabf1742023 * * DO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates. * To update this file, run: npm run fetch:spec-types @@ -469,6 +469,15 @@ export interface BaseMetadata { export interface Implementation extends BaseMetadata, Icons { version: string; + /** + * An optional human-readable description of what this implementation does. + * + * This can be used by clients or servers to provide context about their purpose + * and capabilities. For example, a server might describe the types of resources + * or tools it provides, while a client might describe its intended use case. + */ + description?: string; + /** * An optional URL of the website for this implementation. * @@ -613,7 +622,7 @@ export interface ResourceRequestParams extends RequestParams { * @category `resources/read` */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface ReadResourceRequestParams extends ResourceRequestParams {} +export interface ReadResourceRequestParams extends ResourceRequestParams { } /** * Sent from the client to the server, to read a specific resource URI. @@ -650,7 +659,7 @@ export interface ResourceListChangedNotification extends JSONRPCNotification { * @category `resources/subscribe` */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface SubscribeRequestParams extends ResourceRequestParams {} +export interface SubscribeRequestParams extends ResourceRequestParams { } /** * Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. @@ -668,7 +677,7 @@ export interface SubscribeRequest extends JSONRPCRequest { * @category `resources/unsubscribe` */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UnsubscribeRequestParams extends ResourceRequestParams {} +export interface UnsubscribeRequestParams extends ResourceRequestParams { } /** * Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. @@ -1377,7 +1386,7 @@ export type SamplingMessageContentBlock = */ export interface Annotations { /** - * Describes who the intended customer of this object or data is. + * Describes who the intended audience of this object or data is. * * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). */ @@ -1833,7 +1842,7 @@ export interface ElicitRequestFormParams extends RequestParams { /** * The elicitation mode. */ - mode: "form"; + mode?: "form"; /** * The message to present to the user describing what information is being requested. @@ -1845,6 +1854,7 @@ export interface ElicitRequestFormParams extends RequestParams { * Only top-level properties are allowed, without nesting. */ requestedSchema: { + $schema?: string; type: "object"; properties: { [key: string]: PrimitiveSchemaDefinition; diff --git a/src/types.ts b/src/types.ts index 4ff815ceb..5f34ed1b1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1479,8 +1479,10 @@ export const PrimitiveSchemaDefinitionSchema = z.union([EnumSchemaSchema, Boolea export const ElicitRequestFormParamsSchema = BaseRequestParamsSchema.extend({ /** * The elicitation mode. + * + * Optional for backward compatibility. Clients MUST treat missing mode as "form". */ - mode: z.literal('form'), + mode: z.literal('form').optional(), /** * The message to present to the user describing what information is being requested. */