Skip to content

sse_client blocks indefinitely when server has incorrect base URL #447

Description

@Kudryavkaz

Describe the bug
When using the sse_client function to connect to a remote MCP server, if the MCP server sets an incorrect base URL (like http://localhost:8080), the sse_client will raise an exception in the following code:

raise ValueError(error_msg)

The read_stream_writer then sends this exception to the read_stream:

await read_stream_writer.send(exc)

However, since the read_stream has not been yielded yet, the exception will never be received.
As a result, the sse_client will be blocked indefinitely.

To Reproduce

  1. Set up an MCP server with an incorrect base URL on a remote server, for example:
import express, { Request, Response } from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { z } from 'zod';

const server = new McpServer({
    name: 'example-server',
    version: '1.0.0',
});

server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

const app = express();

const transports: { [sessionId: string]: SSEServerTransport } = {};

app.get('/sse', async (_: Request, res: Response) => {
    // set baseUrl for the transport
    const baseUrl = 'http://localhost:8080';
    const transport = new SSEServerTransport(`${baseUrl}/messages`, res);
    transports[transport.sessionId] = transport;
    res.on('close', () => {
        delete transports[transport.sessionId];
    });
    await server.connect(transport);
});

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

app.listen(8080);
  1. Try to connect to the remote MCP server, for example:
import asyncio

from mcp.client.sse import sse_client


async def run():
    try:
        async with sse_client("http://{your_remote_server}:8080/sse"):
            print("Connected to SSE endpoint successfully.")
    except Exception as e:
        print(f"Error: {e}")


if __name__ == "__main__":
    asyncio.run(run())

Expected behavior
The sse_client should raise an exception and exit gracefully.

Logs
None

Additional context
None

Activity

  1. kavinkumar807 commented on Apr 10, 2025

    @kavinkumar807

    @Kudryavkaz I was able to replicate this issue

    Image

    The issue in the screenshot I've added must be fixed, as the exception is not handled properly as you've mentioned.

    @Kudryavkaz But the above client code, when incorrect URL is added, quits with unhandled error as mentioned in this comment. It is not blocked indefinitely. Can please provide more information about this indefinite block?

    Currently debugging this.
    Image

  2. Kudryavkaz commented on Apr 11, 2025

    @Kudryavkaz
    Author

    @kavinkumar807 Thank you for your reply.

    If you start the example MCP server locally and connect the MCP client to "localhost:xxxx", you will not be able to reproduce this issue.

    You should start the example MCP server on a remote server, and then use the MCP client to connect to that remote server.

    Like this:
    Image

    "zhangfish.top" is my remote server, and I ran this command on it:
    Image

  3. kavinkumar807 commented on Apr 11, 2025

    @kavinkumar807

    @Kudryavkaz Debugging the unhandled exception issue. Shall I use your endpoint to test?

  4. Kudryavkaz commented on Apr 11, 2025

    @Kudryavkaz
    Author
  5. kavinkumar807 commented on Apr 11, 2025

    @kavinkumar807
  6. kavinkumar807 commented on Apr 11, 2025

    @kavinkumar807

    @Kudryavkaz I could see there are two issue when I was debugging,

    1. The issue mentioned here, when I try to connect to the above mentioned url the deadlock situation happens and client is blocked instead of graceful shutdown -> has to be fixed :: I'm working on this
    2. The main reason for this exception is that with the above url client is able to make SSE connection but when checked with endpoint_url and url (as in the below screenshot) there is a mismatch
    Image

    Logs
    endpoint_url :: http://localhost:8080/messages?sessionId=18b0b6fb-b3e9-4faf-9350-e2b0d66d6549
    url :: http://zhangfish.top:8080/sse
    Endpoint origin does not match connection origin: http://localhost:8080/messages?sessionId=18b0b6fb-b3e9-4faf-9350-e2b0d66d6549
    Error in sse_reader: Endpoint origin does not match connection origin: http://localhost:8080/messages?sessionId=18b0b6fb-b3e9-4faf-9350-e2b0d66d6549

    @dsp-ant @jspahrsummers the endpoint_url has to be http://zhangfish.top:8080/messages?sessionId=18b0b6fb-b3e9-4faf-9350-e2b0d66d6549 right? is it an issue or am I missing something?

  7. kavinkumar807 commented on Apr 12, 2025

    @kavinkumar807

    #410 seems like this issue is related to this. Adding here for reference

  8. kavinkumar807 commented on Apr 13, 2025

    @kavinkumar807

    On further debugging as I've mentioned in the above comment the first point handling exception must be fixed, handling exception gracefully and quitting the client. But feel free to ignore the second point, that's my misunderstanding.

    app.get('/sse', async (_: Request, res: Response) => {
        // set baseUrl for the transport
        const baseUrl = 'http://localhost:8080';
        const transport = new SSEServerTransport(`${baseUrl}/messages`, res);
        transports[transport.sessionId] = transport;
        res.on('close', () => {
            delete transports[transport.sessionId];
        });
        await server.connect(transport);
    });
    

    @Kudryavkaz Here the baseUrl for the transport is added intentionally to demonstrate the indefinite blocking scenario right?

  9. Kudryavkaz commented on Apr 13, 2025

    @Kudryavkaz
    Author
  10. kavinkumar807 commented on Apr 14, 2025

    @kavinkumar807

    @Kudryavkaz Raised PR #500 for the above issue.

    Cc: @dsp-ant @jspahrsummers @nick-merrill @Kludex @jerome3o-anthropic

    Note: adding the above people in cc from top contributors as I'm not sure who is the maintainer.

  11. felixweinberger commented on Oct 3, 2025

    @felixweinberger
    Contributor

    This issue has been open for a while and the linked PR has been closed #500

    I'm curious if this is still a problem?

  12. added
    bugSomething isn't working
    needs confirmationNeeds confirmation that the PR is actually required or needed.
    on Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds confirmationNeeds confirmation that the PR is actually required or needed.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions