Skip to content

Multiple clients to the same SSE connection? #204

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

Open
chazcb opened this issue Mar 17, 2025 · 10 comments
Open

Multiple clients to the same SSE connection? #204

chazcb opened this issue Mar 17, 2025 · 10 comments

Comments

@chazcb
Copy link

chazcb commented Mar 17, 2025

👋 Because server.connect(transport) replaces any existing transport for a Server, I've been struggling to understand the intended pattern for simultaneous SSE client connections. Is it intended that an SSE MCP server can only have one Client?

Example:

const sessions = new Map<string, SSEServerTransport>();
const server = new McpServer({ name: "test-server", version: "1.0.0" });

app.get("/sse", (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  sessions.set(transport.sessionId, transport);
  server.connect(transport);  // ❌ Each call to /sse overwrites previous transport for server!
});

// Setup message endpoint
app.post("/messages", async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const transport = sessions.get(sessionId);
  if (!transport) {
    return res.status(400).json({ error: "Invalid session ID" });
  }
  await transport.handlePostMessage(req, res);
});

// When multiple clients connect:
const client1 = await createClient("client-1");  // ✅ First client works
const client2 = await createClient("client-2");  // ❌ Second client connection breaks client1

// Now when client1 calls a tool:
await client1.callTool({ name: "echo", arguments: { message: "hello" } });
// ❌ Response goes to client2 instead of client1, even though we're tracking sessions

One workaround for this is to create a clone of the McpServer within the /sse handler, but that doesn't seem to match with the concept of a "sessionId".

Would appreciate any guidance here, thanks!

@chazcb
Copy link
Author

chazcb commented Mar 17, 2025

I think maybe the README is just a touch misleading as it includes:

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

@jerome3o-anthropic
Copy link
Contributor

I suspect you want an instance of McpServer per SSE connection

@shushui
Copy link

shushui commented Mar 18, 2025

I struggle with similar issues.
I have some feeling the sdk developers didn't really use SSE server hosted somewhere other than in their laptop.
If anyone has decent code example, it would be nice.

@chazcb
Copy link
Author

chazcb commented Mar 18, 2025

I've built a factory that takes tools, prompts, resources, etc. and returns new instances of the McpServer for each /sse connection. Seems to work!

@chazcb
Copy link
Author

chazcb commented Mar 18, 2025

Maybe should keep this issue open until we update the README with this nuance, though?

@langscot
Copy link

As I understood the specification, clients and servers have a 1:1 relationship. Each connecting client requires an instance of McpServer for itself.

A host application creates and manages multiple clients, with each client having a 1:1 relationship with a particular server.
https://spec.modelcontextprotocol.io/specification/2024-11-05/architecture/#clients

Rather than the Typescript SDK implementing a way to manage client/server mappings, I think the README/docs could instead give a basic example for people to get started.

@QuantGeekDev
Copy link

Someone in another issue was mentioning implementing a websocket transport. It's theoretically not that complicated and I've seen several examples pop up. But I wonder if there's some underground mines or fundamental issues that the MCP Core team saw that made them chose the SSE approach. They aren't using standard SSE - SSE is meant to be unidirectional. But we are doing bi-directional communication with SSE where we can call tools and get the tool call events in the same SSE connection. Would love to hear some rationale on this, or if anyone has more thoughts

@the-vampiire
Copy link

Someone in another issue was mentioning implementing a websocket transport. It's theoretically not that complicated and I've seen several examples pop up. But I wonder if there's some underground mines or fundamental issues that the MCP Core team saw that made them chose the SSE approach. They aren't using standard SSE - SSE is meant to be unidirectional. But we are doing bi-directional communication with SSE where we can call tools and get the tool call events in the same SSE connection. Would love to hear some rationale on this, or if anyone has more thoughts

there was a lengthy back and forth in the GH discussion around stateful/less transports and web sockets came up several times. i'm on mobile and can't link it but it should be easy to find. ultimately it led to the current PR spec draft / RFC. although the focus on this one was primarily to offer a stateless HTTP variant of the spec.

it's still not entirely clear to me why this pseudo-bi-directional POST + SSE was chosen either. to the best of my knowledge from the thread it comes down to simplicity and using native HTTP features. of course there are benefits of using HTTP over websockets in certain, arguably most, cases and SSE has some well established uses. but i don't know that it was intended to be used this way specifically when there is an existing protocol that aligns with the desired behavior of bi-directionality.

one thing to consider is a core philosophy of MCP is to keep server complexity low and shift responsibility to the client. it's a pragmatic philosophy as it encourages development on the server side which in the long run will always be of higher demand and novelty relative to a smaller pool of clients. from that i can at least appreciate the intention behind the choice since HTTP is simpler and familiar to a broader audience.

on the other hand, i believe that well implemented SDKs (and abstractions over them) can reduce the perceived complexity of websockets. and i feel the current POST + SSE approach while feeling simple on the surface, grounded in that HTTP familiarity, in practice leads to some confusing mental models and implementations. i left a comment, that was genuinely well intentioned but i don't think was well received, that this current design is like trying to invent web sockets before web sockets.

i think the WS discussion will come up again. this is an important spec update to further encourage server development with the stateless and more traditional use of HTTP. i just hope it doesn't get held up by the language around the now optional POST + SSE streaming portion.

@csmoakpax8
Copy link

A couple examples in the readme would go a long way!!!

@cuijiudai
Copy link

cuijiudai commented Mar 28, 2025

That makes SSE connection pushed to newest user if there are more than one user ,because serve only save the newest transport

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants