-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Summary
The TypeScript SDK violates the MCP specification by allowing cancellation of initialize
requests, which is explicitly forbidden.
Specification Violation
According to the MCP specification in src/types.ts
line 183:
/**
* A client MUST NOT attempt to cancel its `initialize` request.
*/
Current Implementation Issue
Location
File: src/shared/protocol.ts
Function: Protocol.request()
method
Lines: 582-601 (cancel function)
Architecture Context
Protocol
is a base class inherited by bothClient
andServer
- The
cancel
function exists in the base class, so both Client and Server have this functionality - The specification constraint applies specifically to Client behavior
Problem
The cancel
function unconditionally sends notifications/cancelled
for all requests, including initialize
requests, regardless of whether it's called from Client or Server:
const cancel = (reason: unknown) => {
this._responseHandlers.delete(messageId);
this._progressHandlers.delete(messageId);
this._cleanupTimeout(messageId);
this._transport
?.send({
jsonrpc: "2.0",
method: "notifications/cancelled",
params: {
requestId: messageId,
reason: String(reason),
},
}, { relatedRequestId, resumptionToken, onresumptiontoken })
.catch((error) =>
this._onerror(new Error(`Failed to send cancellation: ${error}`)),
);
reject(reason);
};
Trigger Points
The cancel
function is called in two scenarios:
-
Manual cancellation via AbortSignal (lines 620-622):
options?.signal?.addEventListener("abort", () => { cancel(options?.signal?.reason); });
-
Automatic timeout cancellation (lines 625-629):
const timeoutHandler = () => cancel(new McpError( ErrorCode.RequestTimeout, "Request timed out", { timeout } ));
Request Flow Analysis
- Client sends initialize request to Server via
Protocol.request()
- If Client cancels (via AbortSignal or timeout), the
cancel
function is called - cancel function sends
notifications/cancelled
to Server with the Client's messageId - This violates the specification that Client must not attempt to cancel initialize requests
Impact
- Specification Compliance: Violates explicit MCP requirement that Client must not cancel initialize requests
- Protocol Correctness: Client sends
notifications/cancelled
to Server for initialize requests, which should not happen - Server Behavior: Servers may receive unexpected cancellation notifications for initialize requests from clients
- Architecture Issue: The problem exists in the shared
Protocol
base class, but the constraint only applies to Client behavior
Proposed Fix
Add a check in the cancel
function to prevent Client from sending cancellation notifications for initialize
requests:
const cancel = (reason: unknown) => {
this._responseHandlers.delete(messageId);
this._progressHandlers.delete(messageId);
this._cleanupTimeout(messageId);
// Don't send cancellation notification if this is a Client trying to cancel initialize
const isClientCancellingInitialize =
this instanceof Client && request.method === "initialize";
if (!isClientCancellingInitialize) {
this._transport
?.send({
jsonrpc: "2.0",
method: "notifications/cancelled",
params: {
requestId: messageId,
reason: String(reason),
},
}, { relatedRequestId, resumptionToken, onresumptiontoken })
.catch((error) =>
this._onerror(new Error(`Failed to send cancellation: ${error}`)),
);
}
reject(reason);
};
Alternative Approach
Alternatively, prevent AbortSignal
from being passed to initialize
requests in the Client.connect()
method:
// In Client.connect(), strip signal from options for initialize
const initializeOptions = options ? { ...options, signal: undefined } : undefined;
const result = await this.request(
{
method: "initialize",
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: this._capabilities,
clientInfo: this._clientInfo,
},
},
InitializeResultSchema,
initializeOptions
);
Testing
The fix should be tested with:
- Client cancelling initialize requests (should not send
notifications/cancelled
to Server) - Client timeout of initialize requests (should not send
notifications/cancelled
to Server) - Server cancelling any requests (should continue to send
notifications/cancelled
normally) - Client cancelling non-initialize requests (should continue to send
notifications/cancelled
normally)