Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/client/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CallToolRequestSchema,
CreateMessageRequestSchema,
ElicitRequestSchema,
ElicitResultSchema,
ListRootsRequestSchema,
ErrorCode
} from '../types.js';
Expand Down Expand Up @@ -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(
{
Expand Down
9 changes: 5 additions & 4 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/server/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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);
}
Expand Down
35 changes: 6 additions & 29 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ import {
import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js';
import type { JsonSchemaType, jsonSchemaValidator } from '../validation/types.js';

type LegacyElicitRequestFormParams = Omit<ElicitRequestFormParams, 'mode'>;

export type ServerOptions = ProtocolOptions & {
/**
* Capabilities to advertise as being supported by this server.
Expand Down Expand Up @@ -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<ElicitResult>;
/**
* 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<ElicitResult>;
/**
* 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<ElicitResult>;

// Implementation (not visible to callers)
async elicitInput(
params: LegacyElicitRequestFormParams | ElicitRequestFormParams | ElicitRequestURLParams,
options?: RequestOptions
): Promise<ElicitResult> {
const mode = ('mode' in params ? params.mode : 'form') as 'form' | 'url';
async elicitInput(params: ElicitRequestFormParams | ElicitRequestURLParams, options?: RequestOptions): Promise<ElicitResult> {
const mode = (params.mode ?? 'form') as 'form' | 'url';

switch (mode) {
case 'url': {
Expand All @@ -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);

Expand Down
22 changes: 16 additions & 6 deletions src/spec.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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"]`).
*/
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Loading