Skip to main content
Glama
1253William

mcp-express-playground

by 1253William
README.md
# mcp-express-playground
A minimal, stateless [Model Context Protocol](https://modelcontextprotocol.io) server built with Express and TypeScript, tested with Postman.

## What this demonstrates

- How an MCP server describes tools and how a client discovers them (`tools/list`)
- How a tool call travels (`tools/call`) over Streamable HTTP
- Stateless design: a new server and transport per request, no session store
- In-band error handling: `isError: true` with a `code` and `next_action` the model can act on
- Bearer authentication in front of `/mcp`

## MCP vs a traditional API wrapper

| | REST wrapped for an LLM | MCP server |
|---|---|---|
| Discovery | Hardcoded in each app | `tools/list` at runtime |
| Glue code | Written per app and vendor | Handled by the client |
| Errors | HTTP status codes | Readable in-band errors |
| Contract | Custom | Standard JSON-RPC methods |

## Tools

| Tool | What it does |
|---|---|
| `find_trips` | Search mock shuttle trips by origin, destination and date |
| `hold_seat` | Hold a seat for 10 minutes. Fails with `SOLD_OUT` or `TRIP_NOT_FOUND` |

## Run it

```bash
cp .env.example .env
npm install
npm run dev
```

Health check: `GET http://localhost:3000/health`
MCP endpoint: `POST http://localhost:3000/mcp`

## Test with Postman

Headers on every `/mcp` request:

- `Authorization: Bearer <MCP_BEARER_TOKEN>`
- `Content-Type: application/json`
- `Accept: application/json, text/event-stream`

Then send `initialize`, `tools/list`, and `tools/call` (bodies in the walkthrough below).

## Design decisions

- **Stateless transport:** survives restarts and horizontal scaling.
- **JSON responses enabled:** simpler to debug than SSE for request/response tools.
- **Integer pesewas for money:** no floating point errors.
- **Empty results are not errors:** the agent should offer alternatives.
- **In-memory data:** deliberate for a playground. Production uses Postgres.

## Known limitations

- Holds are in memory and never expire. Expiry is enforced lazily in the real project.
- Single shared bearer token. No rotation or per-client auth.
- No tests yet.