fix(mcp-demux): handle Streamable-HTTP handshake correctly
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 17s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 17s
LiteLLM opens a fresh MCP session per tools/call: initialize →
notifications/initialized → tools/call. Demux forwarded everything
to the WS unchanged, so:
- notifications/initialized (no id) hung forwardMcpCall waiting on
a response id, and the 200 JSON-RPC body confused the SDK reader
into cancelling the subsequent tools/call ("duplicate response
suppressed").
- initialize added an unnecessary WS round-trip coupled to
extension availability.
Answer initialize locally, return 202 empty for notifications/*,
set explicit Content-Type + Mcp-Session-Id on the handshake.
SB-48
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,11 +25,39 @@ export async function mcpDemuxRoutes(app: FastifyInstance) {
|
||||
|
||||
const { customer_id } = req.params;
|
||||
|
||||
const body = req.body as {
|
||||
method: string;
|
||||
params?: unknown;
|
||||
id?: string | number;
|
||||
};
|
||||
|
||||
// MCP Streamable-HTTP: notifications must return 202 empty, never forward.
|
||||
// notifications have no id; forwardMcpCall would hang waiting for a response.
|
||||
if (body.method?.startsWith("notifications/")) {
|
||||
return reply.code(202).send();
|
||||
}
|
||||
|
||||
// Answer initialize locally — avoids a WS round-trip per LiteLLM tool call
|
||||
// and makes the handshake robust to transient extension disconnects.
|
||||
if (body.method === "initialize") {
|
||||
reply.header("Mcp-Session-Id", randomUUID());
|
||||
reply.header("Content-Type", "application/json");
|
||||
return reply.send({
|
||||
jsonrpc: "2.0",
|
||||
id: body.id ?? null,
|
||||
result: {
|
||||
protocolVersion: "2024-11-05",
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: "sitebridge-demux", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!isCustomerOnline(customer_id)) {
|
||||
// Return a JSON-RPC error in MCP format
|
||||
return reply.code(200).send({
|
||||
jsonrpc: "2.0",
|
||||
id: (req.body as { id?: unknown })?.id ?? null,
|
||||
id: body.id ?? null,
|
||||
error: {
|
||||
code: -32001,
|
||||
message: "tool_unavailable: customer not online",
|
||||
@@ -37,12 +65,6 @@ export async function mcpDemuxRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const body = req.body as {
|
||||
method: string;
|
||||
params?: unknown;
|
||||
id?: string | number;
|
||||
};
|
||||
|
||||
const callId = body.id ?? randomUUID();
|
||||
|
||||
try {
|
||||
@@ -52,6 +74,7 @@ export async function mcpDemuxRoutes(app: FastifyInstance) {
|
||||
config.mcpBridgeTimeoutMs
|
||||
);
|
||||
|
||||
reply.header("Content-Type", "application/json");
|
||||
return reply.send({
|
||||
jsonrpc: "2.0",
|
||||
id: callId,
|
||||
|
||||
Reference in New Issue
Block a user