modelcontextprotocol / modelcontextprotocol/typescript-sdk
【BUG】Use Koa server create StreamableHTTPServerTransport, when call tool, server crash!
Open
Nobody has claimed this yet.
bug
needs confirmation
potentially close
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
code:
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import Koa from 'koa';
import { koaBody } from 'koa-body';
import KoaRouter from 'koa-router';
import { randomUUID } from 'node:crypto';
import { mcpServer } from './server.js';
// 初始化应用
const app = new Koa();
app.use(koaBody());
const router = new KoaRouter();
// 存储会话与传输实例的映射
const transportMap = {};
// MCP核心处理端点
router.post('/mcp', async (ctx) => {
const sessionId = ctx.headers['mcp-session-id'];
let transport;
if (sessionId && transportMap[sessionId]) {
transport = transportMap[sessionId];
} else if (!sessionId && isInitializeRequest(ctx.request.body)) {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
transportMap[sessionId] = transport;
},
});
ctx.req.on('close', () => {
console.log(`Connection closed for session: ${transport.sessionId}`);
delete transportMap[transport.sessionId];
});
await mcpServer.connect(transport);
} else {
ctx.status = 400;
ctx.body = { error: 'Invalid session ID' };
return;
}
await transport.handleRequest(ctx.req, ctx.res, ctx.request.body);
});
const handleSessionRequest = async (ctx) => {
const sessionId = ctx.headers['mcp-session-id'];
if (!sessionId || !transportMap[sessionId]) {
ctx.status = 400;
ctx.body = { error: 'Invalid or missing session ID' };
return;
}
await transportMap[sessionId].handleRequest(ctx.req, ctx.res);
};
router.get('/mcp', handleSessionRequest);
router.delete('/mcp', handleSessionRequest);
// 注册路由
app.use(router.routes()).use(router.allowedMethods());
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`MCP服务器启动在 http://localhost:${PORT}`);
console.log(`- MCP端点: http://localhost:${PORT}/mcp`);
});
// server.js
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { writeFileSync } from "node:fs";
// Create an MCP server
export const mcpServer = new McpServer({
name: "MCP 测试服务器",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Add an addition tool
mcpServer.registerTool(
"add",
{
title: "加法工具",
description: "两个数字相加",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
XrEZE MINGW64 /d/workspace/IoT_UI/mcp-server
$ node mcp-http2
Debugger attached.
MCP服务器启动在 http://localhost:3000
- MCP端点: http://localhost:3000/mcp
Waiting for the debugger to disconnect...
node:events:485
throw er; // Unhandled 'error' event
^
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at write_ (node:_http_outgoing:951:11)
at ServerResponse.write (node:_http_outgoing:904:15)
at StreamableHTTPServerTransport.writeSSEEvent (file:///D:/workspace/IoT_UI/mcp-server/node_modules/@modelcontextprotocol/sdk/dist/e
sm/server/streamableHttp.js:238:20)
at StreamableHTTPServerTransport.send (file:///D:/workspace/IoT_UI/mcp-server/node_modules/@modelcontextprotocol/sdk/dist/esm/server
/streamableHttp.js:565:22)
at Promise.resolve.then.then.capturedTransport.send.jsonrpc (file:///D:/workspace/IoT_UI/mcp-server/node_modules/@modelcontextprotoc
ol/sdk/dist/esm/shared/protocol.js:162:108)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
Emitted 'error' event on ServerResponse instance at:
at emitErrorNt (node:_http_outgoing:923:9)
at process.processTicksAndRejections (node:internal/process/task_queues:91:21) {
code: 'ERR_STREAM_WRITE_AFTER_END'
}
Node.js v23.1.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the failure with the Koa /mcp endpoint and the StreamableHTTPServerTransport example in the issue. Start by tracing server/streamableHttp.js around writeSSEEvent and send, then inspect how the request and response are closed after a tool call. Done means the add tool completes without ERR_STREAM_WRITE_AFTER_END or an unhandled ServerResponse error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100