todo-mcp-server
by andrew-klear
README.md
# todo-mcp-server
The same todo app served four ways, to isolate what changes when you stop
hand-writing MCP tools and serve a tRPC router instead.
| | MCP tools by hand | tRPC router |
| ----------- | ----------------- | -------------------- |
| **no auth** | `1_simple` | `3_trpc2mcp` |
| **auth** | `2_authenticated` | `4_trpc2mcp_authed` |
Shared at the root, so each stage is only its routing layer: `services.ts` (the
todo domain), `auth.ts` (bearer middleware + tRPC context), `trpc-mcp.ts` (the
tRPC → MCP adapter, adapted from [Jacse/trpc-mcp](https://github.com/Jacse/trpc-mcp)).
TypeScript on Bun, Express, no build step.
## Run
```
bun install
bun run start:simple # stdio
bun run start:authenticated # :3000/mcp
bun run start:trpc # :3001/trpc
bun run start:trpc-mcp # stdio, same router as start:trpc
bun run start:trpc-authed # :3002 — /trpc and /mcp, one router, one gate
```
The bearer token *is* the user id; `alice` is the only one with the `premium` scope.
## The idea
Opt a procedure in with one `.meta()` key and `trpc-mcp.ts` walks the router and
registers it as a tool. No `meta.mcp` (see `stats`) means it stays tRPC-only.
```ts
addTodo: t.procedure
.meta({ mcp: { enabled: true, name: "add_todo", description: "Add a new todo item" } })
.input(z.object({ text: z.string().describe("Todo text") }))
.mutation(({ input }) => Todos.add(user, input.text)),
```
Auth then only gets written once. `2_authenticated/` reads `extra.authInfo` in
every handler and gates `export_todos` inline; `4_trpc2mcp_authed/` does both in
tRPC middleware, and the `/trpc` and `/mcp` mounts inherit it from the same
`req.auth` — see `examples/4_trpc2mcp_authed/server.ts`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing