fix: React to upstream RC schema changes for form mode elicitation requests (#1164)
Co-authored-by: Nate Barbettini <nate@arcade.dev>
This commit is contained in:
parent
2a55dfd5f2
commit
b59a2bde7d
6 changed files with 91 additions and 42 deletions
|
|
@ -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(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,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.
|
||||
|
|
@ -375,33 +373,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': {
|
||||
|
|
@ -416,10 +394,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue