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
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues