Skip to main content
Glama
irvinebroque

mcp-server-do

by irvinebroque
README.md
# SSE + MCP Server + Durable Objects

1. A SSE Transport layer that works with `@modelcontextprotocol/typescript-sdk` (`/src/sse.ts`)
2. A MCP Server as a Durable Object (`/src/mcp-server-do.ts`)
3. Steps to run it end-to-end

## Run it

1. Clone this repo
2. `npm install`
3. `npm start` to start the DO (at `http://localhost:8787`)
4. `npx @modelcontextprotocol/inspector` to run the MCP inspector
5. Open the inspector, enter `http://localhost:8787/sse`

You should see:

<img width="853" alt="Screenshot 2025-03-09 at 5 21 24 PM" src="https://github.com/user-attachments/assets/04e0436c-a621-41a5-9809-4a3bd637a9f2" />


## Details

I took [this example](https://github.com/modelcontextprotocol/typescript-sdk/tree/main?tab=readme-ov-file#http-with-sse) from `@modelcontextprotocol/typescript-sdk`:

```ts
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

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

// ... set up server resources, tools, and prompts ...

const app = express();

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

app.post("/messages", async (req, res) => {
  // Note: to support multiple simultaneous connections, these messages will
  // need to be routed to a specific matching transport. (This logic isn't
  // implemented here, for simplicity.)
  await transport.handlePostMessage(req, res);
});

app.listen(3001);
```

...and implemented the same thing in Durable Objects. But first needed a transport layer that worked on Workers.

Following [`sse.ts` from `@modelcontextprotocol/typescript-sdk`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/sse.ts), I made one, trying to mirror the shape of the existing one. Some pretty fundamental assumptions baked into the SDK around its use of `node:http` that leak out beyond the SSE transport through its input types — but really the only meaningful interface change is that `handlePostMessage` takes a request and returns a response. Seems like there's probably a clever way somehow to upstream?