Skip to content

Cursor: SSE error: TypeError: terminated: Body Timeout Error #270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Hanxmai opened this issue Apr 7, 2025 · 4 comments
Closed

Cursor: SSE error: TypeError: terminated: Body Timeout Error #270

Hanxmai opened this issue Apr 7, 2025 · 4 comments
Labels
bug Something isn't working

Comments

@Hanxmai
Copy link

Hanxmai commented Apr 7, 2025

Describe the bug
I have the same questory when i used @modelcontextprotocol/sdk in npm package to create mcp sse server:
like this: modelcontextprotocol/rust-sdk#71

To Reproduce
Steps to reproduce the behavior:

  1. Refer to https://github.com/modelcontextprotocol/typescript-sdk/blob/main/README.md#http-with-sse to create sse server
  2. Deploy sse server
  3. Config mcp server in cursor
  4. 300s will timeout with error: SSE error: TypeError: terminated: Body Timeout

Expected behavior
A clear and concise description of what you expected to happen.

Logs
If applicable, add logs to help explain your problem.

2025-04-07 15:29:54.553 [info] x-ui: Handling ListOfferings action
2025-04-07 15:29:54.553 [info] x-ui: Listing offerings
2025-04-07 15:29:54.553 [info] x-ui: getOrCreateClient for sse server.  process.platform: darwin isElectron: true
2025-04-07 15:29:54.554 [info] x-ui: Reusing existing sse client
2025-04-07 15:29:54.554 [info] x-ui: Connected to sse server, fetching offerings
2025-04-07 15:29:54.597 [info] listOfferings: Found 2 tools
2025-04-07 15:29:54.597 [info] x-ui: Found 2 tools, 0 resources, and 0 resource templates
2025-04-07 15:34:56.798 [error] x-ui: Client error for command SSE error: TypeError: terminated: Body Timeout Error
2025-04-07 15:34:56.799 [error] x-ui: Error in MCP: SSE error: TypeError: terminated: Body Timeout Error
2025-04-07 15:40:02.110 [error] x-ui: Client error for command SSE error: TypeError: terminated: Body Timeout Error
2025-04-07 15:40:02.111 [error] x-ui: Error in MCP: SSE error: TypeError: terminated: Body Timeout Error

Additional context
Add any other context about the problem here.

@Hanxmai Hanxmai added the bug Something isn't working label Apr 7, 2025
@Siim
Copy link

Siim commented Apr 9, 2025

I experienced the same issue — it looks like the connection drops rather quickly. I was able to keep it alive by periodically writing a keep-alive comment to the response:

res.write(': keepalive\n\n');

Here is my setup:

import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createMCPServer } from "./services/mcp-service.js";
import { importGtfs } from 'gtfs';
import { gtfsConfig } from './config/gtfs-config.js';


process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

const server = createMCPServer();
const PORT = process.env.PORT || 9999;
const KEEP_ALIVE_INTERVAL_MS = 25000; // Send keep-alive every 25 seconds

const app = express()

const transports: {[sessionId: string]: SSEServerTransport} = {};
const sseConnections = new Map<string, { res: Response, intervalId: NodeJS.Timeout }>();

app.get("/health", (_: Request, res: Response) => {
  res.status(200).json({ status: 'healthy' });
});

app.get("/sse", async (_: Request, res: Response) => {
  const transport = new SSEServerTransport('/messages', res)
  const sessionId = transport.sessionId; // Get session ID from transport
  transports[sessionId] = transport;

  // Start keep-alive ping
  const intervalId = setInterval(() => {
    if (sseConnections.has(sessionId) && !res.writableEnded) {
      res.write(': keepalive\n\n');
    } else {
      // Should not happen if close handler is working, but clear just in case
      clearInterval(intervalId);
      sseConnections.delete(sessionId);
    }
  }, KEEP_ALIVE_INTERVAL_MS);

  // Store connection details
  sseConnections.set(sessionId, { res, intervalId });
  console.log(`[SSE Connection] Client connected: ${sessionId}, starting keep-alive.`);

  res.on("close", () => {
    console.log(`[SSE Connection] Client disconnected: ${sessionId}, stopping keep-alive.`);
    // Clean up transport
    delete transports[sessionId];
    // Clean up keep-alive interval
    const connection = sseConnections.get(sessionId);
    if (connection) {
      clearInterval(connection.intervalId);
      sseConnections.delete(sessionId);
    }
  });

  // Connect server to transport *after* setting up handlers
  try {
    await server.connect(transport)
  } catch (error) {
    console.error(`[SSE Connection] Error connecting server to transport for ${sessionId}:`, error);
    // Ensure cleanup happens even if connect fails
    clearInterval(intervalId);
    sseConnections.delete(sessionId);
    delete transports[sessionId];
    if (!res.writableEnded) {
      res.status(500).end('Failed to connect MCP server to transport');
    }
  }
});

app.post("/messages", async (req: Request, res: Response) => {
  const sessionId = req.query.sessionId as string;
  const transport = transports[sessionId];
  if (transport) {
    console.log('Transport found:', transport.sessionId);
    await transport.handlePostMessage(req, res);
  } else {
    res.status(400).send('No transport found for sessionId');
  }
});

console.log(`⭐️ Server is running on port ${PORT}`)
app.listen(PORT)

@cliffhall
Copy link
Contributor

cliffhall commented Apr 9, 2025

Hi @Hanxmai!

Possibly a better approach long term is to set the timeout on the request.

In the meantime, try this:

  • Connect to your MCP server with the Inspector
    • Set the timeout to something longer
    • Call the same tool
    • See if you can get it to complete without timing out.
    • If this works, you likely just need to use the keepalive workaround until Cursor implements request timeout management.

The Inspector's timeout config

Image

@duguanfeng
Copy link

I experienced the same issue — it looks like the connection drops rather quickly. I was able to keep it alive by periodically writing a keep-alive comment to the response:我遇到了同样的问题 — 看起来连接断开得相当快。我能够通过定期向响应编写一条 keep-alive 评论来保持活动状态:

res.write(': keepalive\n\n');``res.write(': 保持活动\n\n');

Here is my setup:  这是我的设置:

import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createMCPServer } from "./services/mcp-service.js";
import { importGtfs } from 'gtfs';
import { gtfsConfig } from './config/gtfs-config.js';

process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

const server = createMCPServer();
const PORT = process.env.PORT || 9999;
const KEEP_ALIVE_INTERVAL_MS = 25000; // Send keep-alive every 25 seconds

const app = express()

const transports: {[sessionId: string]: SSEServerTransport} = {};
const sseConnections = new Map<string, { res: Response, intervalId: NodeJS.Timeout }>();

app.get("/health", (_: Request, res: Response) => {
res.status(200).json({ status: 'healthy' });
});

app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res)
const sessionId = transport.sessionId; // Get session ID from transport
transports[sessionId] = transport;

// Start keep-alive ping
const intervalId = setInterval(() => {
if (sseConnections.has(sessionId) && !res.writableEnded) {
res.write(': keepalive\n\n');
} else {
// Should not happen if close handler is working, but clear just in case
clearInterval(intervalId);
sseConnections.delete(sessionId);
}
}, KEEP_ALIVE_INTERVAL_MS);

// Store connection details
sseConnections.set(sessionId, { res, intervalId });
console.log([SSE Connection] Client connected: ${sessionId}, starting keep-alive.);

res.on("close", () => {
console.log([SSE Connection] Client disconnected: ${sessionId}, stopping keep-alive.);
// Clean up transport
delete transports[sessionId];
// Clean up keep-alive interval
const connection = sseConnections.get(sessionId);
if (connection) {
clearInterval(connection.intervalId);
sseConnections.delete(sessionId);
}
});

// Connect server to transport after setting up handlers
try {
await server.connect(transport)
} catch (error) {
console.error([SSE Connection] Error connecting server to transport for ${sessionId}:, error);
// Ensure cleanup happens even if connect fails
clearInterval(intervalId);
sseConnections.delete(sessionId);
delete transports[sessionId];
if (!res.writableEnded) {
res.status(500).end('Failed to connect MCP server to transport');
}
}
});

app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
console.log('Transport found:', transport.sessionId);
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('No transport found for sessionId');
}
});

console.log(⭐️ Server is running on port ${PORT})
app.listen(PORT)

I experienced the same issue — it looks like the connection drops rather quickly. I was able to keep it alive by periodically writing a keep-alive comment to the response:我遇到了同样的问题 — 看起来连接断开得相当快。我能够通过定期向响应编写一条 keep-alive 评论来保持活动状态:

res.write(': keepalive\n\n');``res.write(': 保持活动\n\n');

Here is my setup:  这是我的设置:

import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createMCPServer } from "./services/mcp-service.js";
import { importGtfs } from 'gtfs';
import { gtfsConfig } from './config/gtfs-config.js';

process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

const server = createMCPServer();
const PORT = process.env.PORT || 9999;
const KEEP_ALIVE_INTERVAL_MS = 25000; // Send keep-alive every 25 seconds

const app = express()

const transports: {[sessionId: string]: SSEServerTransport} = {};
const sseConnections = new Map<string, { res: Response, intervalId: NodeJS.Timeout }>();

app.get("/health", (_: Request, res: Response) => {
res.status(200).json({ status: 'healthy' });
});

app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res)
const sessionId = transport.sessionId; // Get session ID from transport
transports[sessionId] = transport;

// Start keep-alive ping
const intervalId = setInterval(() => {
if (sseConnections.has(sessionId) && !res.writableEnded) {
res.write(': keepalive\n\n');
} else {
// Should not happen if close handler is working, but clear just in case
clearInterval(intervalId);
sseConnections.delete(sessionId);
}
}, KEEP_ALIVE_INTERVAL_MS);

// Store connection details
sseConnections.set(sessionId, { res, intervalId });
console.log([SSE Connection] Client connected: ${sessionId}, starting keep-alive.);

res.on("close", () => {
console.log([SSE Connection] Client disconnected: ${sessionId}, stopping keep-alive.);
// Clean up transport
delete transports[sessionId];
// Clean up keep-alive interval
const connection = sseConnections.get(sessionId);
if (connection) {
clearInterval(connection.intervalId);
sseConnections.delete(sessionId);
}
});

// Connect server to transport after setting up handlers
try {
await server.connect(transport)
} catch (error) {
console.error([SSE Connection] Error connecting server to transport for ${sessionId}:, error);
// Ensure cleanup happens even if connect fails
clearInterval(intervalId);
sseConnections.delete(sessionId);
delete transports[sessionId];
if (!res.writableEnded) {
res.status(500).end('Failed to connect MCP server to transport');
}
}
});

app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
console.log('Transport found:', transport.sessionId);
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('No transport found for sessionId');
}
});

console.log(⭐️ Server is running on port ${PORT})
app.listen(PORT)

Big help! Thx!!!

@Meandmybadself
Copy link

Am trying this method as well with Streamable HTTP & it appears that the response is no longer writeable by the time the interval is called. Will continue to explore solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants