Skip to main content
Glama
README.md
# Todo App

pnpm monorepo:

| Package | Stack |
| --- | --- |
| [`apps/server`](apps/server) | Express 5, Drizzle ORM, PostgreSQL, JWT auth, MCP server |
| [`apps/web`](apps/web) | React 19, Zustand, Vite |
| [`packages/shared`](packages/shared) | zod schemas, API types and MCP tool metadata used by both apps |

## Getting started

Requires Node 22+, pnpm 10 and Docker.

```sh
pnpm install
cp apps/server/.env.example apps/server/.env   # then set JWT_SECRET
pnpm db:up          # start Postgres (host port 5433)
pnpm db:migrate     # apply migrations
pnpm dev            # API on :4000, web on :5173
```

Open http://localhost:5173. In dev, Vite proxies `/api` to the server.

## Scripts

| Command | What it does |
| --- | --- |
| `pnpm dev` | Run server and web in watch mode |
| `pnpm build` | Build every package |
| `pnpm typecheck` | Type-check every package |
| `pnpm db:up` | Start the Postgres container |
| `pnpm db:generate` | Create a migration after changing `apps/server/src/db/schema.ts` |
| `pnpm db:migrate` | Apply pending migrations |

## API

`/api/todos` and `/api/mcp` routes need an `Authorization: Bearer <token>` header.

| Method | Path | Body | Response |
| --- | --- | --- | --- |
| POST | `/api/auth/register` | `{ email, password }` | `201 { token, user }` |
| POST | `/api/auth/login` | `{ email, password }` | `200 { token, user }` |
| GET | `/api/auth/me` | | `200 user` |
| GET | `/api/todos` | | `200 todo[]` |
| GET | `/api/todos/:id` | | `200 todo` |
| POST | `/api/todos` | `{ title }` | `201 todo` |
| PATCH | `/api/todos/:id` | `{ title?, completed? }` | `200 todo` |
| DELETE | `/api/todos/:id` | | `204` |
| GET | `/api/mcp` | | `200 { token, url }`, creating the token on first call |
| POST | `/api/mcp/regenerate` | | `200 { token, url }` with a new token |

Errors come back as `{ error, details? }`. Status codes: `400` validation, `401` auth, `404` missing or not yours, `409` email already taken.

## MCP server

Each user has a personal MCP URL, shown in the **MCP** tab of the web app:

```
http://localhost:4000/mcp?token=mcp_...
```

The token identifies the user, and the tools only see that user's todos. Regenerating it in the web app revokes the old URL immediately. Set `PUBLIC_URL` in `apps/server/.env` to the address clients use to reach the server.

The endpoint speaks Streamable HTTP in stateless mode (`POST /mcp`). Tools: `list_todos`, `get_todo`, `create_todo`, `update_todo`, `delete_todo`.

Connect Claude Code:

```sh
claude mcp add --transport http todoapp "http://localhost:4000/mcp?token=mcp_..."
```