SSE + MCP Server + Durable Objects
by irvinebroque
This project implements an SSE transport layer and an MCP server as a Durable Object, designed to work with `@modelcontextprotocol/typescript-sdk`. It provides a way to run an MCP server using Durable Objects in a Workers environment.
Last updated: N/A
SSE + MCP Server + Durable Objects
- A SSE Transport layer that works with
@modelcontextprotocol/typescript-sdk
(/src/sse.ts
) - A MCP Server as a Durable Object (
/src/mcp-server-do.ts
) - Steps to run it end-to-end
Run it
- Clone this repo
npm install
npm start
to start the DO (athttp://localhost:8787
)npx @modelcontextprotocol/inspector
to run the MCP inspector- 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 from @modelcontextprotocol/typescript-sdk
:
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
, 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?