Skip to main content
Glama
vorortai

MCP Real-World Tasks Reference

by vorortai
README.md
# MCP reference server for real-world tasks

A minimal, dependency-free MCP server written against protocol revision
**2026-07-28** — the one that removed sessions and the `initialize` handshake.
It models a service that sends a person to check something in the physical
world and returns structured evidence.

The domain is a toy. The **shape** is not: it is extracted from a production
server that answers agents today, with the business logic replaced by a
readable example so the protocol layer is the thing you actually read.

```bash
npm start   # http://localhost:8787
npm test    # 20 tests, no dependencies
```

```bash
curl -s -X POST http://localhost:8787/ \
  -H 'content-type: application/json' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H 'mcp-method: tools/list' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'
```

## What changed in 2026-07-28, and why it makes this simpler

If you last read the MCP spec before this revision, most of what you remember
about transports is gone:

| Removed | Replaced by |
| --- | --- |
| Protocol-level sessions, `Mcp-Session-Id` | Nothing. Every request stands alone. |
| The `initialize` / `notifications/initialized` handshake | Version and capabilities in each request's `_meta` |
| The standalone `GET` SSE stream | `subscriptions/listen`, opt-in, not needed by most servers |
| `ping`, `logging/setLevel` | Removed outright |
| Resumable streams (`Last-Event-ID`) | Removed; a broken stream is re-issued as a new request |

The practical consequence is the whole point of this repo: **a stateless
request handler is now the shape the spec expects.** There is no session to
store, nothing to expire, and no reason to reach for a durable object, a Redis,
or a stateful process. `src/protocol.js` is a pure function from `Request` to
`Response`.

Two things were *added* that are easy to miss and that clients silently rely on:

- Every result carries `resultType: "complete"`.
- List results (`tools/list`, `server/discover`) carry `ttlMs` and `cacheScope`.
- `server/discover` is **mandatory** — clients may call it before anything else.

## Layout

| File | What it is |
| --- | --- |
| `src/protocol.js` | The protocol layer. Knows nothing about the domain. This is the part worth copying. |
| `src/tools.js` | The example domain: a catalogue, a deterministic quote engine, and one write tool. |
| `src/server.js` | Wires the two together; exposes a fetch handler and a Node listener. |
| `test/protocol.test.mjs` | 20 tests, each pinning one rule that is easy to get subtly wrong. |

`src/server.js` exports `fetchHandler(request)` — a plain `Request` →
`Response` function, so it drops unchanged into Cloudflare Workers, Deno
Deploy, Bun, or a Next.js route handler. The Node listener exists so `npm
start` works without a platform account.

## The five decisions worth stealing

Everything below is a choice that cost real debugging somewhere else.

**1. The header/body mirror is a real requirement, not decoration.**
`Mcp-Method` and `Mcp-Name` duplicate values from the body so intermediaries
can route without parsing. If they disagree, reject with `-32020`. Skipping
this creates a split-brain where a proxy routes on the header while your
handler executes the body — which is exactly the bug the rule exists to
prevent.

**2. Unknown method is `404`, not `200` with an error body.**
The spec uses the HTTP status so a client can tell "this endpoint does not
implement that method" from "this URL is not an MCP endpoint at all". Return
`200` and you break client fallback logic.

**3. Read-only tools need no authentication.**
Quoting creates nothing, reserves nothing, costs nothing. There is nothing to
gate. Gating it only teaches agents that exploring your service is expensive.
Authenticate the tools that *commit* something.

**4. Exactly one tool has a side effect, and its annotations say so.**
`readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint` are what
clients and directory reviewers branch on. An unannotated write tool will be
treated as dangerous or rejected.

**5. One pricing function serves every surface.**
In the production system this is extracted from, the same `quote()` answers the
web form, the REST endpoint and the MCP tool. A human and an agent therefore
cannot be shown different prices for identical work. If you take one idea from
this repository, take this one — it is a correctness property, not an
optimisation.

## The safety shape

The example write tool (`submit_request`) is deliberately **not** a booking:

- It requires `consent: true`, set only after a human has agreed.
- It returns `status: "pending_human_review"` and `dispatch: "never_automatic"`.
- Nothing in the response can be mistaken for a confirmation.

An agent should never be able to send a person into the physical world without
someone reading the request first. In the system this comes from, that is a
fixed product guardrail rather than a limitation to be lifted later — and the
tool description says so, because the description is what the model reads when
it decides whether to call it.

## Compatibility with older clients

`src/protocol.js` answers `initialize`, `notifications/initialized` and `ping`
for the `2025-*` revisions, because deployed clients lag published specs by
months. It is a shim, not a second protocol: both paths serve the same tools
from the same definitions. Header mirroring is enforced only on the modern
revisions, since requiring it on legacy clients would reject exactly the
clients the shim exists for.

Delete `LEGACY_PROTOCOL_VERSIONS` once nothing in the wild still opens with a
handshake.

## Connecting it

Once deployed at a public HTTPS URL, add it as a custom connector in Claude
(Settings → Connectors) or in ChatGPT (Settings → Apps → Advanced → Developer
mode). Both accept a no-auth Streamable HTTP endpoint.

## License

MIT. Built as a by-product of [VorOrtAI](https://vorortai.de), which runs the
non-toy version of this.