Skip to content

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

@zhuyuanmin

Description

@zhuyuanmin

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions