v2 ESlint fixes (#1403)

Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
This commit is contained in:
Konstantin Konstantinov 2026-01-20 14:21:53 +02:00 • committed by GitHub
commit 9fc7e28f48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1672 additions and 1346 deletions

View file

@ -9,5 +9,5 @@
export * from './interfaces.js';
// Wrapper classes
export * from './mcp-server.js';
export * from './mcpServer.js';
export * from './server.js';

View file

@ -0,0 +1,142 @@
/**
* Experimental McpServer task features for MCP SDK.
* WARNING: These APIs are experimental and may change without notice.
*
* @experimental
*/
import type { AnySchema, TaskToolExecution, ToolAnnotations, ToolExecution, ZodRawShapeCompat } from '@modelcontextprotocol/core';
import type { AnyToolHandler, McpServer, RegisteredTool } from '../../server/mcp.js';
import type { ToolTaskHandler } from './interfaces.js';
/**
* Internal interface for accessing McpServer's private _createRegisteredTool method.
* @internal
*/
interface McpServerInternal {
_createRegisteredTool(
name: string,
title: string | undefined,
description: string | undefined,
inputSchema: ZodRawShapeCompat | AnySchema | undefined,
outputSchema: ZodRawShapeCompat | AnySchema | undefined,
annotations: ToolAnnotations | undefined,
execution: ToolExecution | undefined,
_meta: Record<string, unknown> | undefined,
handler: AnyToolHandler<ZodRawShapeCompat | undefined>
): RegisteredTool;
}
/**
* Experimental task features for McpServer.
*
* Access via `server.experimental.tasks`:
* ```typescript
* server.experimental.tasks.registerToolTask('long-running', config, handler);
* ```
*
* @experimental
*/
export class ExperimentalMcpServerTasks {
constructor(private readonly _mcpServer: McpServer) {}
/**
* Registers a task-based tool with a config object and handler.
*
* Task-based tools support long-running operations that can be polled for status
* and results. The handler must implement `createTask`, `getTask`, and `getTaskResult`
* methods.
*
* @example
* ```typescript
* server.experimental.tasks.registerToolTask('long-computation', {
* description: 'Performs a long computation',
* inputSchema: { input: z.string() },
* execution: { taskSupport: 'required' }
* }, {
* createTask: async (args, extra) => {
* const task = await extra.taskStore.createTask({ ttl: 300000 });
* startBackgroundWork(task.taskId, args);
* return { task };
* },
* getTask: async (args, extra) => {
* return extra.taskStore.getTask(extra.taskId);
* },
* getTaskResult: async (args, extra) => {
* return extra.taskStore.getTaskResult(extra.taskId);
* }
* });
* ```
*
* @param name - The tool name
* @param config - Tool configuration (description, schemas, etc.)
* @param handler - Task handler with createTask, getTask, getTaskResult methods
* @returns RegisteredTool for managing the tool's lifecycle
*
* @experimental
*/
registerToolTask<OutputArgs extends undefined | ZodRawShapeCompat | AnySchema>(
name: string,
config: {
title?: string;
description?: string;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
execution?: TaskToolExecution;
_meta?: Record<string, unknown>;
},
handler: ToolTaskHandler<undefined>
): RegisteredTool;
registerToolTask<InputArgs extends ZodRawShapeCompat | AnySchema, OutputArgs extends undefined | ZodRawShapeCompat | AnySchema>(
name: string,
config: {
title?: string;
description?: string;
inputSchema: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
execution?: TaskToolExecution;
_meta?: Record<string, unknown>;
},
handler: ToolTaskHandler<InputArgs>
): RegisteredTool;
registerToolTask<
InputArgs extends undefined | ZodRawShapeCompat | AnySchema,
OutputArgs extends undefined | ZodRawShapeCompat | AnySchema
>(
name: string,
config: {
title?: string;
description?: string;
inputSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
execution?: TaskToolExecution;
_meta?: Record<string, unknown>;
},
handler: ToolTaskHandler<InputArgs>
): RegisteredTool {
// Validate that taskSupport is not 'forbidden' for task-based tools
const execution: ToolExecution = { taskSupport: 'required', ...config.execution };
if (execution.taskSupport === 'forbidden') {
throw new Error(`Cannot register task-based tool '${name}' with taskSupport 'forbidden'. Use registerTool() instead.`);
}
// Access McpServer's internal _createRegisteredTool method
const mcpServerInternal = this._mcpServer as unknown as McpServerInternal;
return mcpServerInternal._createRegisteredTool(
name,
config.title,
config.description,
config.inputSchema,
config.outputSchema,
config.annotations,
execution,
config._meta,
handler as AnyToolHandler<ZodRawShapeCompat | undefined>
);
}
}

View file

@ -61,7 +61,7 @@ import {
import { ZodOptional } from 'zod';
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcp-server.js';
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
import { getCompleter, isCompletable } from './completable.js';
import type { ServerOptions } from './server.js';
import { Server } from './server.js';
@ -224,10 +224,8 @@ export class McpServer {
await this.validateToolOutput(tool, result, request.params.name);
return result;
} catch (error) {
if (error instanceof McpError) {
if (error.code === ErrorCode.UrlElicitationRequired) {
throw error; // Return the error to the caller without wrapping in CallToolResult
}
if (error instanceof McpError && error.code === ErrorCode.UrlElicitationRequired) {
throw error; // Return the error to the caller without wrapping in CallToolResult
}
return this.createToolError(error instanceof Error ? error.message : String(error));
}
@ -414,16 +412,19 @@ export class McpServer {
this.server.setRequestHandler(CompleteRequestSchema, async (request): Promise<CompleteResult> => {
switch (request.params.ref.type) {
case 'ref/prompt':
case 'ref/prompt': {
assertCompleteRequestPrompt(request);
return this.handlePromptCompletion(request, request.params.ref);
}
case 'ref/resource':
case 'ref/resource': {
assertCompleteRequestResourceTemplate(request);
return this.handleResourceCompletion(request, request.params.ref);
}
default:
default: {
throw new McpError(ErrorCode.InvalidParams, `Invalid completion reference: ${request.params.ref}`);
}
}
});
@ -768,15 +769,15 @@ export class McpServer {
enable: () => registeredResource.update({ enabled: true }),
remove: () => registeredResource.update({ uri: null }),
update: updates => {
if (typeof updates.uri !== 'undefined' && updates.uri !== uri) {
if (updates.uri !== undefined && updates.uri !== uri) {
delete this._registeredResources[uri];
if (updates.uri) this._registeredResources[updates.uri] = registeredResource;
}
if (typeof updates.name !== 'undefined') registeredResource.name = updates.name;
if (typeof updates.title !== 'undefined') registeredResource.title = updates.title;
if (typeof updates.metadata !== 'undefined') registeredResource.metadata = updates.metadata;
if (typeof updates.callback !== 'undefined') registeredResource.readCallback = updates.callback;
if (typeof updates.enabled !== 'undefined') registeredResource.enabled = updates.enabled;
if (updates.name !== undefined) registeredResource.name = updates.name;
if (updates.title !== undefined) registeredResource.title = updates.title;
if (updates.metadata !== undefined) registeredResource.metadata = updates.metadata;
if (updates.callback !== undefined) registeredResource.readCallback = updates.callback;
if (updates.enabled !== undefined) registeredResource.enabled = updates.enabled;
this.sendResourceListChanged();
}
};
@ -801,15 +802,15 @@ export class McpServer {
enable: () => registeredResourceTemplate.update({ enabled: true }),
remove: () => registeredResourceTemplate.update({ name: null }),
update: updates => {
if (typeof updates.name !== 'undefined' && updates.name !== name) {
if (updates.name !== undefined && updates.name !== name) {
delete this._registeredResourceTemplates[name];
if (updates.name) this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
}
if (typeof updates.title !== 'undefined') registeredResourceTemplate.title = updates.title;
if (typeof updates.template !== 'undefined') registeredResourceTemplate.resourceTemplate = updates.template;
if (typeof updates.metadata !== 'undefined') registeredResourceTemplate.metadata = updates.metadata;
if (typeof updates.callback !== 'undefined') registeredResourceTemplate.readCallback = updates.callback;
if (typeof updates.enabled !== 'undefined') registeredResourceTemplate.enabled = updates.enabled;
if (updates.title !== undefined) registeredResourceTemplate.title = updates.title;
if (updates.template !== undefined) registeredResourceTemplate.resourceTemplate = updates.template;
if (updates.metadata !== undefined) registeredResourceTemplate.metadata = updates.metadata;
if (updates.callback !== undefined) registeredResourceTemplate.readCallback = updates.callback;
if (updates.enabled !== undefined) registeredResourceTemplate.enabled = updates.enabled;
this.sendResourceListChanged();
}
};
@ -842,15 +843,15 @@ export class McpServer {
enable: () => registeredPrompt.update({ enabled: true }),
remove: () => registeredPrompt.update({ name: null }),
update: updates => {
if (typeof updates.name !== 'undefined' && updates.name !== name) {
if (updates.name !== undefined && updates.name !== name) {
delete this._registeredPrompts[name];
if (updates.name) this._registeredPrompts[updates.name] = registeredPrompt;
}
if (typeof updates.title !== 'undefined') registeredPrompt.title = updates.title;
if (typeof updates.description !== 'undefined') registeredPrompt.description = updates.description;
if (typeof updates.argsSchema !== 'undefined') registeredPrompt.argsSchema = objectFromShape(updates.argsSchema);
if (typeof updates.callback !== 'undefined') registeredPrompt.callback = updates.callback;
if (typeof updates.enabled !== 'undefined') registeredPrompt.enabled = updates.enabled;
if (updates.title !== undefined) registeredPrompt.title = updates.title;
if (updates.description !== undefined) registeredPrompt.description = updates.description;
if (updates.argsSchema !== undefined) registeredPrompt.argsSchema = objectFromShape(updates.argsSchema);
if (updates.callback !== undefined) registeredPrompt.callback = updates.callback;
if (updates.enabled !== undefined) registeredPrompt.enabled = updates.enabled;
this.sendPromptListChanged();
}
};
@ -898,21 +899,21 @@ export class McpServer {
enable: () => registeredTool.update({ enabled: true }),
remove: () => registeredTool.update({ name: null }),
update: updates => {
if (typeof updates.name !== 'undefined' && updates.name !== name) {
if (updates.name !== undefined && updates.name !== name) {
if (typeof updates.name === 'string') {
validateAndWarnToolName(updates.name);
}
delete this._registeredTools[name];
if (updates.name) this._registeredTools[updates.name] = registeredTool;
}
if (typeof updates.title !== 'undefined') registeredTool.title = updates.title;
if (typeof updates.description !== 'undefined') registeredTool.description = updates.description;
if (typeof updates.paramsSchema !== 'undefined') registeredTool.inputSchema = objectFromShape(updates.paramsSchema);
if (typeof updates.outputSchema !== 'undefined') registeredTool.outputSchema = objectFromShape(updates.outputSchema);
if (typeof updates.callback !== 'undefined') registeredTool.handler = updates.callback;
if (typeof updates.annotations !== 'undefined') registeredTool.annotations = updates.annotations;
if (typeof updates._meta !== 'undefined') registeredTool._meta = updates._meta;
if (typeof updates.enabled !== 'undefined') registeredTool.enabled = updates.enabled;
if (updates.title !== undefined) registeredTool.title = updates.title;
if (updates.description !== undefined) registeredTool.description = updates.description;
if (updates.paramsSchema !== undefined) registeredTool.inputSchema = objectFromShape(updates.paramsSchema);
if (updates.outputSchema !== undefined) registeredTool.outputSchema = objectFromShape(updates.outputSchema);
if (updates.callback !== undefined) registeredTool.handler = updates.callback;
if (updates.annotations !== undefined) registeredTool.annotations = updates.annotations;
if (updates._meta !== undefined) registeredTool._meta = updates._meta;
if (updates.enabled !== undefined) registeredTool.enabled = updates.enabled;
this.sendToolListChanged();
}
};
@ -1383,7 +1384,7 @@ function isZodRawShapeCompat(obj: unknown): obj is ZodRawShapeCompat {
}
// A raw shape has at least one property that is a Zod schema
return Object.values(obj).some(isZodTypeLike);
return Object.values(obj).some(element => isZodTypeLike(element));
}
/**

View file

@ -50,15 +50,15 @@ export function hostHeaderValidationResponse(req: Request, allowedHostnames: str
const result = validateHostHeader(req.headers.get('host'), allowedHostnames);
if (result.ok) return undefined;
return new Response(
JSON.stringify({
return Response.json(
{
jsonrpc: '2.0',
error: {
code: -32000,
code: -32_000,
message: result.message
},
id: null
}),
},
{
status: 403,
headers: { 'Content-Type': 'application/json' }

View file

@ -248,7 +248,7 @@ export class Server<
}
if (typeof methodValue !== 'string') {
throw new Error('Schema method literal must be a string');
throw new TypeError('Schema method literal must be a string');
}
const method = methodValue;
@ -302,70 +302,81 @@ export class Server<
protected assertCapabilityForMethod(method: RequestT['method']): void {
switch (method as ServerRequest['method']) {
case 'sampling/createMessage':
case 'sampling/createMessage': {
if (!this._clientCapabilities?.sampling) {
throw new Error(`Client does not support sampling (required for ${method})`);
}
break;
}
case 'elicitation/create':
case 'elicitation/create': {
if (!this._clientCapabilities?.elicitation) {
throw new Error(`Client does not support elicitation (required for ${method})`);
}
break;
}
case 'roots/list':
case 'roots/list': {
if (!this._clientCapabilities?.roots) {
throw new Error(`Client does not support listing roots (required for ${method})`);
}
break;
}
case 'ping':
case 'ping': {
// No specific capability required for ping
break;
}
}
}
protected assertNotificationCapability(method: (ServerNotification | NotificationT)['method']): void {
switch (method as ServerNotification['method']) {
case 'notifications/message':
case 'notifications/message': {
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
}
case 'notifications/resources/updated':
case 'notifications/resources/list_changed':
case 'notifications/resources/list_changed': {
if (!this._capabilities.resources) {
throw new Error(`Server does not support notifying about resources (required for ${method})`);
}
break;
}
case 'notifications/tools/list_changed':
case 'notifications/tools/list_changed': {
if (!this._capabilities.tools) {
throw new Error(`Server does not support notifying of tool list changes (required for ${method})`);
}
break;
}
case 'notifications/prompts/list_changed':
case 'notifications/prompts/list_changed': {
if (!this._capabilities.prompts) {
throw new Error(`Server does not support notifying of prompt list changes (required for ${method})`);
}
break;
}
case 'notifications/elicitation/complete':
case 'notifications/elicitation/complete': {
if (!this._clientCapabilities?.elicitation?.url) {
throw new Error(`Client does not support URL elicitation (required for ${method})`);
}
break;
}
case 'notifications/cancelled':
case 'notifications/cancelled': {
// Cancellation notifications are always allowed
break;
}
case 'notifications/progress':
case 'notifications/progress': {
// Progress notifications are always allowed
break;
}
}
}
@ -377,53 +388,60 @@ export class Server<
}
switch (method) {
case 'completion/complete':
case 'completion/complete': {
if (!this._capabilities.completions) {
throw new Error(`Server does not support completions (required for ${method})`);
}
break;
}
case 'logging/setLevel':
case 'logging/setLevel': {
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
}
case 'prompts/get':
case 'prompts/list':
case 'prompts/list': {
if (!this._capabilities.prompts) {
throw new Error(`Server does not support prompts (required for ${method})`);
}
break;
}
case 'resources/list':
case 'resources/templates/list':
case 'resources/read':
case 'resources/read': {
if (!this._capabilities.resources) {
throw new Error(`Server does not support resources (required for ${method})`);
}
break;
}
case 'tools/call':
case 'tools/list':
case 'tools/list': {
if (!this._capabilities.tools) {
throw new Error(`Server does not support tools (required for ${method})`);
}
break;
}
case 'tasks/get':
case 'tasks/list':
case 'tasks/result':
case 'tasks/cancel':
case 'tasks/cancel': {
if (!this._capabilities.tasks) {
throw new Error(`Server does not support tasks capability (required for ${method})`);
}
break;
}
case 'ping':
case 'initialize':
case 'initialize': {
// No specific capability required for these methods
break;
}
}
}
@ -506,21 +524,19 @@ export class Server<
options?: RequestOptions
): Promise<CreateMessageResult | CreateMessageResultWithTools> {
// Capability check - only required when tools/toolChoice are provided
if (params.tools || params.toolChoice) {
if (!this._clientCapabilities?.sampling?.tools) {
throw new Error('Client does not support sampling tools capability.');
}
if ((params.tools || params.toolChoice) && !this._clientCapabilities?.sampling?.tools) {
throw new Error('Client does not support sampling tools capability.');
}
// Message structure validation - always validate tool_use/tool_result pairs.
// These may appear even without tools/toolChoice in the current request when
// a previous sampling request returned tool_use and this is a follow-up with results.
if (params.messages.length > 0) {
const lastMessage = params.messages[params.messages.length - 1]!;
const lastMessage = params.messages.at(-1)!;
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
const hasToolResults = lastContent.some(c => c.type === 'tool_result');
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
const previousMessage = params.messages.length > 1 ? params.messages.at(-2) : undefined;
const previousContent = previousMessage
? Array.isArray(previousMessage.content)
? previousMessage.content
@ -646,10 +662,8 @@ export class Server<
* @param sessionId optional for stateless and backward compatibility
*/
async sendLoggingMessage(params: LoggingMessageNotification['params'], sessionId?: string) {
if (this._capabilities.logging) {
if (!this.isMessageIgnored(params.level, sessionId)) {
return this.notification({ method: 'notifications/message', params });
}
if (this._capabilities.logging && !this.isMessageIgnored(params.level, sessionId)) {
return this.notification({ method: 'notifications/message', params });
}
}

View file

@ -262,12 +262,12 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (options?.data !== undefined) {
error.data = options.data;
}
return new Response(
JSON.stringify({
return Response.json(
{
jsonrpc: '2.0',
error,
id: null
}),
},
{
status,
headers: {
@ -294,7 +294,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
const error = `Invalid Host header: ${hostHeader}`;
this.onerror?.(new Error(error));
return this.createJsonErrorResponse(403, -32000, error);
return this.createJsonErrorResponse(403, -32_000, error);
}
}
@ -304,7 +304,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
const error = `Invalid Origin header: ${originHeader}`;
this.onerror?.(new Error(error));
return this.createJsonErrorResponse(403, -32000, error);
return this.createJsonErrorResponse(403, -32_000, error);
}
}
@ -323,14 +323,18 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
}
switch (req.method) {
case 'POST':
case 'POST': {
return this.handlePostRequest(req, options);
case 'GET':
}
case 'GET': {
return this.handleGetRequest(req);
case 'DELETE':
}
case 'DELETE': {
return this.handleDeleteRequest(req);
default:
}
default: {
return this.handleUnsupportedRequest();
}
}
}
@ -372,7 +376,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
// The client MUST include an Accept header, listing text/event-stream as a supported content type.
const acceptHeader = req.headers.get('accept');
if (!acceptHeader?.includes('text/event-stream')) {
return this.createJsonErrorResponse(406, -32000, 'Not Acceptable: Client must accept text/event-stream');
return this.createJsonErrorResponse(406, -32_000, 'Not Acceptable: Client must accept text/event-stream');
}
// If an Mcp-Session-Id is returned by the server during initialization,
@ -398,7 +402,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
// Check if there's already an active standalone SSE stream for this session
if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
// Only one GET SSE stream is allowed per session
return this.createJsonErrorResponse(409, -32000, 'Conflict: Only one SSE stream is allowed per session');
return this.createJsonErrorResponse(409, -32_000, 'Conflict: Only one SSE stream is allowed per session');
}
const encoder = new TextEncoder();
@ -449,7 +453,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
*/
private async replayEvents(lastEventId: string): Promise<Response> {
if (!this._eventStore) {
return this.createJsonErrorResponse(400, -32000, 'Event store not configured');
return this.createJsonErrorResponse(400, -32_000, 'Event store not configured');
}
try {
@ -459,12 +463,12 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
if (!streamId) {
return this.createJsonErrorResponse(400, -32000, 'Invalid event ID format');
return this.createJsonErrorResponse(400, -32_000, 'Invalid event ID format');
}
// Check conflict with the SAME streamId we'll use for mapping
if (this._streamMapping.get(streamId) !== undefined) {
return this.createJsonErrorResponse(409, -32000, 'Conflict: Stream already has an active connection');
return this.createJsonErrorResponse(409, -32_000, 'Conflict: Stream already has an active connection');
}
}
@ -523,7 +527,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
return new Response(readable, { headers });
} catch (error) {
this.onerror?.(error as Error);
return this.createJsonErrorResponse(500, -32000, 'Error replaying events');
return this.createJsonErrorResponse(500, -32_000, 'Error replaying events');
}
}
@ -554,15 +558,15 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
* Handles unsupported requests (PUT, PATCH, etc.)
*/
private handleUnsupportedRequest(): Response {
return new Response(
JSON.stringify({
return Response.json(
{
jsonrpc: '2.0',
error: {
code: -32000,
code: -32_000,
message: 'Method not allowed.'
},
id: null
}),
},
{
status: 405,
headers: {
@ -584,14 +588,14 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (!acceptHeader?.includes('application/json') || !acceptHeader.includes('text/event-stream')) {
return this.createJsonErrorResponse(
406,
-32000,
-32_000,
'Not Acceptable: Client must accept both application/json and text/event-stream'
);
}
const ct = req.headers.get('content-type');
if (!ct || !ct.includes('application/json')) {
return this.createJsonErrorResponse(415, -32000, 'Unsupported Media Type: Content-Type must be application/json');
return this.createJsonErrorResponse(415, -32_000, 'Unsupported Media Type: Content-Type must be application/json');
}
// Build request info from headers
@ -600,40 +604,38 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
};
let rawMessage;
if (options?.parsedBody !== undefined) {
rawMessage = options.parsedBody;
} else {
if (options?.parsedBody === undefined) {
try {
rawMessage = await req.json();
} catch {
return this.createJsonErrorResponse(400, -32700, 'Parse error: Invalid JSON');
return this.createJsonErrorResponse(400, -32_700, 'Parse error: Invalid JSON');
}
} else {
rawMessage = options.parsedBody;
}
let messages: JSONRPCMessage[];
// handle batch and single messages
try {
if (Array.isArray(rawMessage)) {
messages = rawMessage.map(msg => JSONRPCMessageSchema.parse(msg));
} else {
messages = [JSONRPCMessageSchema.parse(rawMessage)];
}
messages = Array.isArray(rawMessage)
? rawMessage.map(msg => JSONRPCMessageSchema.parse(msg))
: [JSONRPCMessageSchema.parse(rawMessage)];
} catch {
return this.createJsonErrorResponse(400, -32700, 'Parse error: Invalid JSON-RPC message');
return this.createJsonErrorResponse(400, -32_700, 'Parse error: Invalid JSON-RPC message');
}
// Check if this is an initialization request
// https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
const isInitializationRequest = messages.some(isInitializeRequest);
const isInitializationRequest = messages.some(element => isInitializeRequest(element));
if (isInitializationRequest) {
// If it's a server with session management and the session ID is already set we should reject the request
// to avoid re-initialization.
if (this._initialized && this.sessionId !== undefined) {
return this.createJsonErrorResponse(400, -32600, 'Invalid Request: Server already initialized');
return this.createJsonErrorResponse(400, -32_600, 'Invalid Request: Server already initialized');
}
if (messages.length > 1) {
return this.createJsonErrorResponse(400, -32600, 'Invalid Request: Only one initialization request is allowed');
return this.createJsonErrorResponse(400, -32_600, 'Invalid Request: Only one initialization request is allowed');
}
this.sessionId = this.sessionIdGenerator?.();
this._initialized = true;
@ -660,7 +662,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
}
// check if it contains requests
const hasRequests = messages.some(isJSONRPCRequest);
const hasRequests = messages.some(element => isJSONRPCRequest(element));
if (!hasRequests) {
// if it only contains notifications or responses, return 202
@ -778,7 +780,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
} catch (error) {
// return JSON-RPC formatted error
this.onerror?.(error as Error);
return this.createJsonErrorResponse(400, -32700, 'Parse error', { data: String(error) });
return this.createJsonErrorResponse(400, -32_700, 'Parse error', { data: String(error) });
}
}
@ -812,19 +814,19 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
}
if (!this._initialized) {
// If the server has not been initialized yet, reject all requests
return this.createJsonErrorResponse(400, -32000, 'Bad Request: Server not initialized');
return this.createJsonErrorResponse(400, -32_000, 'Bad Request: Server not initialized');
}
const sessionId = req.headers.get('mcp-session-id');
if (!sessionId) {
// Non-initialization requests without a session ID should return 400 Bad Request
return this.createJsonErrorResponse(400, -32000, 'Bad Request: Mcp-Session-Id header is required');
return this.createJsonErrorResponse(400, -32_000, 'Bad Request: Mcp-Session-Id header is required');
}
if (sessionId !== this.sessionId) {
// Reject requests with invalid session ID with 404 Not Found
return this.createJsonErrorResponse(404, -32001, 'Session not found');
return this.createJsonErrorResponse(404, -32_001, 'Session not found');
}
return undefined;
@ -849,7 +851,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
return this.createJsonErrorResponse(
400,
-32000,
-32_000,
`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
);
}
@ -858,9 +860,9 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
async close(): Promise<void> {
// Close all SSE connections
this._streamMapping.forEach(({ cleanup }) => {
for (const { cleanup } of this._streamMapping.values()) {
cleanup();
});
}
this._streamMapping.clear();
// Clear any pending responses
@ -952,9 +954,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
this._requestResponseMap.set(requestId, message);
const relatedIds = Array.from(this._requestToStreamMapping.entries())
.filter(([_, sid]) => sid === streamId)
.map(([id]) => id);
const relatedIds = [...this._requestToStreamMapping.entries()].filter(([_, sid]) => sid === streamId).map(([id]) => id);
// Check if we have responses for all requests using this connection
const allResponsesReady = relatedIds.every(id => this._requestResponseMap.has(id));
@ -975,9 +975,9 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
const responses = relatedIds.map(id => this._requestResponseMap.get(id)!);
if (responses.length === 1) {
stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
stream.resolveJson(Response.json(responses[0], { status: 200, headers }));
} else {
stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
stream.resolveJson(Response.json(responses, { status: 200, headers }));
}
} else {
// End the SSE stream