modelcontextprotocol / modelcontextprotocol/typescript-sdk

【BUG】Use Koa server create StreamableHTTPServerTransport, when call tool, server crash!

Open
#912 5 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.