Skip to main content
Glama
Peanuts1605

mcp-apps-workers-starter

by Peanuts1605
README.md
# mcp-apps-workers-starter

A small, tested starting point for an **MCP server with an MCP Apps view on Cloudflare Workers**, with one
**Durable Object per session**. It also includes a plain web page that acts as an **MCP Apps host**, so you can
see the view working in a browser without a chat client.

When I built [Open Tomorrow for Alexa+](https://github.com/Peanuts1605/open-tomorrow-alexa), I could not find one
example that put these pieces together. This repo is that missing example, in about 230 lines of source:

| Piece | File | What it shows |
|---|---|---|
| Stateless MCP over Streamable HTTP | `src/mcp.mjs` | `WebStandardStreamableHTTPServerTransport` with `sessionIdGenerator: undefined` and `enableJsonResponse: true`: one Request in, one JSON Response out. No SSE stream, so it fits a Worker. Protocol `2025-11-25`. |
| MCP Apps tools and view | `src/mcp.mjs`, `src/view.*` | `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps`. Both tools point to one `ui://` view, and the view calls tools back through the host. |
| Per-session state | `src/worker.mjs` | `?session=<id>` maps to a Durable Object. State is saved only when a tool changed it, and an alarm deletes it after a day without use. |
| Safe concurrent writes | `src/checklist.mjs` | Every write sends `expectedRevision`. If two writers race (an agent and the view, say), the second one gets `STALE_REVISION` and reads again. |
| Browser host | `src/host.*` | SDK `Client` + `AppBridge` + `PostMessageTransport`. The view runs in an `allow-scripts` iframe with no same-origin access. |
| Small bundles | `build.mjs` | esbuild inlines the view and host into ES modules. A small plugin keeps only zod's English locale, which roughly halves the view. |

## Run it

Needs Node 22+.

```sh
npm install
npm test          # 10 tests: the state rules, MCP over real Requests with the SDK client, and a check that generated files are current
npm run dev       # wrangler dev on http://localhost:8787, then open it in a browser
npm run smoke     # with dev running: persistence, session isolation, and 405/400/403 guards
```

Deploy with `npx wrangler deploy`. Durable Objects with SQLite storage are available on the Workers free plan.

To connect an MCP client, use `https://<your-worker>/mcp?session=<8-64 letters, digits or dashes>`.

## Make it yours

Replace `Checklist` with your own state. Keep `read()` and `apply()` pure and revision-checked. Then change the two
tool definitions in `src/mcp.mjs` and the rendering in `src/view.mjs`, and run `npm run build`. The generated files are
committed so Node tests can import them without a build step; `npm test` fails if they are stale.

## Notes and limits

- The server sends JSON only, so the SDK client's optional `GET /mcp` (for an SSE stream) gets `405`, which the spec
  allows. The browser host answers that probe itself (`fetchNoProbe` in `src/host.mjs`) so the console stays clean.
- Cross-origin `POST /mcp` with an `Origin` header is rejected. Clients that send no `Origin` (server-side agents) are allowed.
  The session id is a capability, so anyone who knows it can use that session. Add authentication before you store anything private.
- Pinned versions: `@modelcontextprotocol/sdk` 1.30.0 and `@modelcontextprotocol/ext-apps` 1.7.5. ext-apps 2.x expects SDK v2.

## License

MIT