fix: replace schema-based handlers with string method names (#1446)
This commit is contained in:
parent
a5670de388
commit
f6e8204b86
3 changed files with 490 additions and 878 deletions
|
|
@ -36,23 +36,14 @@ import type {
|
|||
import {
|
||||
assertCompleteRequestPrompt,
|
||||
assertCompleteRequestResourceTemplate,
|
||||
CallToolRequestSchema,
|
||||
CompleteRequestSchema,
|
||||
ErrorCode,
|
||||
getLiteralValue,
|
||||
getObjectShape,
|
||||
getParseErrorMessage,
|
||||
GetPromptRequestSchema,
|
||||
getSchemaDescription,
|
||||
isSchemaOptional,
|
||||
ListPromptsRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListResourceTemplatesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
McpError,
|
||||
normalizeObjectSchema,
|
||||
objectFromShape,
|
||||
ReadResourceRequestSchema,
|
||||
safeParseAsync,
|
||||
toJsonSchemaCompat,
|
||||
UriTemplate,
|
||||
|
|
@ -128,8 +119,8 @@ export class McpServer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(ListToolsRequestSchema));
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(CallToolRequestSchema));
|
||||
this.server.assertCanSetRequestHandler('tools/list');
|
||||
this.server.assertCanSetRequestHandler('tools/call');
|
||||
|
||||
this.server.registerCapabilities({
|
||||
tools: {
|
||||
|
|
@ -138,7 +129,7 @@ export class McpServer {
|
|||
});
|
||||
|
||||
this.server.setRequestHandler(
|
||||
ListToolsRequestSchema,
|
||||
'tools/list',
|
||||
(): ListToolsResult => ({
|
||||
tools: Object.entries(this._registeredTools)
|
||||
.filter(([, tool]) => tool.enabled)
|
||||
|
|
@ -176,7 +167,7 @@ export class McpServer {
|
|||
})
|
||||
);
|
||||
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra): Promise<CallToolResult | CreateTaskResult> => {
|
||||
this.server.setRequestHandler('tools/call', async (request, extra): Promise<CallToolResult | CreateTaskResult> => {
|
||||
try {
|
||||
const tool = this._registeredTools[request.params.name];
|
||||
if (!tool) {
|
||||
|
|
@ -404,13 +395,13 @@ export class McpServer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(CompleteRequestSchema));
|
||||
this.server.assertCanSetRequestHandler('completion/complete');
|
||||
|
||||
this.server.registerCapabilities({
|
||||
completions: {}
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(CompleteRequestSchema, async (request): Promise<CompleteResult> => {
|
||||
this.server.setRequestHandler('completion/complete', async (request): Promise<CompleteResult> => {
|
||||
switch (request.params.ref.type) {
|
||||
case 'ref/prompt': {
|
||||
assertCompleteRequestPrompt(request);
|
||||
|
|
@ -490,9 +481,9 @@ export class McpServer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(ListResourcesRequestSchema));
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(ListResourceTemplatesRequestSchema));
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(ReadResourceRequestSchema));
|
||||
this.server.assertCanSetRequestHandler('resources/list');
|
||||
this.server.assertCanSetRequestHandler('resources/templates/list');
|
||||
this.server.assertCanSetRequestHandler('resources/read');
|
||||
|
||||
this.server.registerCapabilities({
|
||||
resources: {
|
||||
|
|
@ -500,7 +491,7 @@ export class McpServer {
|
|||
}
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
|
||||
this.server.setRequestHandler('resources/list', async (_request, extra) => {
|
||||
const resources = Object.entries(this._registeredResources)
|
||||
.filter(([_, resource]) => resource.enabled)
|
||||
.map(([uri, resource]) => ({
|
||||
|
|
@ -528,7 +519,7 @@ export class McpServer {
|
|||
return { resources: [...resources, ...templateResources] };
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
||||
this.server.setRequestHandler('resources/templates/list', async () => {
|
||||
const resourceTemplates = Object.entries(this._registeredResourceTemplates).map(([name, template]) => ({
|
||||
name,
|
||||
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
|
||||
|
|
@ -538,7 +529,7 @@ export class McpServer {
|
|||
return { resourceTemplates };
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
|
||||
this.server.setRequestHandler('resources/read', async (request, extra) => {
|
||||
const uri = new URL(request.params.uri);
|
||||
|
||||
// First check for exact resource match
|
||||
|
|
@ -571,8 +562,8 @@ export class McpServer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(ListPromptsRequestSchema));
|
||||
this.server.assertCanSetRequestHandler(getMethodValue(GetPromptRequestSchema));
|
||||
this.server.assertCanSetRequestHandler('prompts/list');
|
||||
this.server.assertCanSetRequestHandler('prompts/get');
|
||||
|
||||
this.server.registerCapabilities({
|
||||
prompts: {
|
||||
|
|
@ -581,7 +572,7 @@ export class McpServer {
|
|||
});
|
||||
|
||||
this.server.setRequestHandler(
|
||||
ListPromptsRequestSchema,
|
||||
'prompts/list',
|
||||
(): ListPromptsResult => ({
|
||||
prompts: Object.entries(this._registeredPrompts)
|
||||
.filter(([, prompt]) => prompt.enabled)
|
||||
|
|
@ -596,7 +587,7 @@ export class McpServer {
|
|||
})
|
||||
);
|
||||
|
||||
this.server.setRequestHandler(GetPromptRequestSchema, async (request, extra): Promise<GetPromptResult> => {
|
||||
this.server.setRequestHandler('prompts/get', async (request, extra): Promise<GetPromptResult> => {
|
||||
const prompt = this._registeredPrompts[request.params.name];
|
||||
if (!prompt) {
|
||||
throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
|
||||
|
|
@ -1263,22 +1254,6 @@ function promptArgumentsFromSchema(schema: AnyObjectSchema): PromptArgument[] {
|
|||
});
|
||||
}
|
||||
|
||||
function getMethodValue(schema: AnyObjectSchema): string {
|
||||
const shape = getObjectShape(schema);
|
||||
const methodSchema = shape?.method as AnySchema | undefined;
|
||||
if (!methodSchema) {
|
||||
throw new Error('Schema is missing a method literal');
|
||||
}
|
||||
|
||||
// Extract literal value - works for both v3 and v4
|
||||
const value = getLiteralValue(methodSchema);
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error('Schema method literal must be a string');
|
||||
}
|
||||
|
||||
function createCompletionResult(suggestions: string[]): CompleteResult {
|
||||
return {
|
||||
completion: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type {
|
||||
AnyObjectSchema,
|
||||
ClientCapabilities,
|
||||
CreateMessageRequest,
|
||||
CreateMessageRequestParamsBase,
|
||||
|
|
@ -22,18 +21,17 @@ import type {
|
|||
ProtocolOptions,
|
||||
Request,
|
||||
RequestHandlerExtra,
|
||||
RequestMethod,
|
||||
RequestOptions,
|
||||
RequestTypeMap,
|
||||
ResourceUpdatedNotification,
|
||||
Result,
|
||||
SchemaOutput,
|
||||
ServerCapabilities,
|
||||
ServerNotification,
|
||||
ServerRequest,
|
||||
ServerResult,
|
||||
ToolResultContent,
|
||||
ToolUseContent,
|
||||
ZodV3Internal,
|
||||
ZodV4Internal
|
||||
ToolUseContent
|
||||
} from '@modelcontextprotocol/core';
|
||||
import {
|
||||
AjvJsonSchemaValidator,
|
||||
|
|
@ -47,10 +45,6 @@ import {
|
|||
ElicitResultSchema,
|
||||
EmptyResultSchema,
|
||||
ErrorCode,
|
||||
getObjectShape,
|
||||
InitializedNotificationSchema,
|
||||
InitializeRequestSchema,
|
||||
isZ4Schema,
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
ListRootsResultSchema,
|
||||
LoggingLevelSchema,
|
||||
|
|
@ -58,7 +52,6 @@ import {
|
|||
mergeCapabilities,
|
||||
Protocol,
|
||||
safeParse,
|
||||
SetLevelRequestSchema,
|
||||
SUPPORTED_PROTOCOL_VERSIONS
|
||||
} from '@modelcontextprotocol/core';
|
||||
|
||||
|
|
@ -162,11 +155,11 @@ export class Server<
|
|||
this._instructions = options?.instructions;
|
||||
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
|
||||
|
||||
this.setRequestHandler(InitializeRequestSchema, request => this._oninitialize(request));
|
||||
this.setNotificationHandler(InitializedNotificationSchema, () => this.oninitialized?.());
|
||||
this.setRequestHandler('initialize', request => this._oninitialize(request));
|
||||
this.setNotificationHandler('notifications/initialized', () => this.oninitialized?.());
|
||||
|
||||
if (this._capabilities.logging) {
|
||||
this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
|
||||
this.setRequestHandler('logging/setLevel', async (request, extra) => {
|
||||
const transportSessionId: string | undefined =
|
||||
extra.sessionId || (extra.requestInfo?.headers.get('mcp-session-id') as string) || undefined;
|
||||
const { level } = request.params;
|
||||
|
|
@ -222,39 +215,16 @@ export class Server<
|
|||
/**
|
||||
* Override request handler registration to enforce server-side validation for tools/call.
|
||||
*/
|
||||
public override setRequestHandler<T extends AnyObjectSchema>(
|
||||
requestSchema: T,
|
||||
public override setRequestHandler<M extends RequestMethod>(
|
||||
method: M,
|
||||
handler: (
|
||||
request: SchemaOutput<T>,
|
||||
request: RequestTypeMap[M],
|
||||
extra: RequestHandlerExtra<ServerRequest | RequestT, ServerNotification | NotificationT>
|
||||
) => ServerResult | ResultT | Promise<ServerResult | ResultT>
|
||||
): void {
|
||||
const shape = getObjectShape(requestSchema);
|
||||
const methodSchema = shape?.method;
|
||||
if (!methodSchema) {
|
||||
throw new Error('Schema is missing a method literal');
|
||||
}
|
||||
|
||||
// Extract literal value using type-safe property access
|
||||
let methodValue: unknown;
|
||||
if (isZ4Schema(methodSchema)) {
|
||||
const v4Schema = methodSchema as unknown as ZodV4Internal;
|
||||
const v4Def = v4Schema._zod?.def;
|
||||
methodValue = v4Def?.value ?? v4Schema.value;
|
||||
} else {
|
||||
const v3Schema = methodSchema as unknown as ZodV3Internal;
|
||||
const legacyDef = v3Schema._def;
|
||||
methodValue = legacyDef?.value ?? v3Schema.value;
|
||||
}
|
||||
|
||||
if (typeof methodValue !== 'string') {
|
||||
throw new TypeError('Schema method literal must be a string');
|
||||
}
|
||||
const method = methodValue;
|
||||
|
||||
if (method === 'tools/call') {
|
||||
const wrappedHandler = async (
|
||||
request: SchemaOutput<T>,
|
||||
request: RequestTypeMap[M],
|
||||
extra: RequestHandlerExtra<ServerRequest | RequestT, ServerNotification | NotificationT>
|
||||
): Promise<ServerResult | ResultT> => {
|
||||
const validatedRequest = safeParse(CallToolRequestSchema, request);
|
||||
|
|
@ -293,11 +263,11 @@ export class Server<
|
|||
};
|
||||
|
||||
// Install the wrapped handler
|
||||
return super.setRequestHandler(requestSchema, wrappedHandler as unknown as typeof handler);
|
||||
return super.setRequestHandler(method, wrappedHandler as unknown as typeof handler);
|
||||
}
|
||||
|
||||
// Other handlers use default behavior
|
||||
return super.setRequestHandler(requestSchema, handler);
|
||||
return super.setRequestHandler(method, handler);
|
||||
}
|
||||
|
||||
protected assertCapabilityForMethod(method: RequestT['method']): void {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ beforeEach(() => {
|
|||
|
||||
outputBuffer = new ReadBuffer();
|
||||
output = new Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
write(chunk, _encoding, callback) {
|
||||
outputBuffer.append(chunk);
|
||||
callback();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue