Project Planner MCP
README.md
# Project Planner MCP
A remote [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that lets AI assistants manage projects and their todos. It runs on [Cloudflare Workers](https://developers.cloudflare.com/workers/) and stores data in [Cloudflare D1](https://developers.cloudflare.com/d1/), a serverless SQLite database.
Connect it to an MCP client such as Claude or the Cloudflare AI Playground, and you can say things like *"Create a project called Website Redesign and add three high-priority todos"*. The assistant calls the right tools for you.
> [!WARNING]
> This server has **no authentication**. Anyone who knows the URL of a deployed instance can read, change and delete all of its data. Use it for learning and personal experiments, and don't store anything sensitive in it.
## Tools
### Projects
| Tool | Description | Parameters |
| --- | --- | --- |
| `create_project` | Create a new project. Returns its ID. | `name`, `description` |
| `list_projects` | List all projects. | none |
| `get_project` | Get one project together with all of its todos. | `project_id` |
| `update_project` | Change a project's name and description. | `project_id`, `name`, `description` |
| `delete_project` | Delete a project **and all of its todos**. | `project_id` |
### Todos
| Tool | Description | Parameters |
| --- | --- | --- |
| `create_todo` | Add a todo to a project. Returns its ID. | `project_id`, `title`, `description`?, `status`?, `priority`? |
| `list_todos` | List all todos in a project. | `project_id` |
| `get_todo` | Get one todo. | `todo_id` |
| `update_todo` | Change a todo's title, description, status and priority. | `todo_id`, `title`, `description`, `status`, `priority` |
| `delete_todo` | Delete a todo. | `todo_id` |
Parameters marked `?` are optional. `status` is one of `pending` (default), `in_progress` or `completed`. `priority` is one of `low`, `medium` (default) or `high`.
## Tech stack
- **Runtime:** Cloudflare Workers
- **Database:** Cloudflare D1 (SQLite)
- **MCP:** [`@modelcontextprotocol/server`](https://www.npmjs.com/package/@modelcontextprotocol/server) and the `createMcpHandler` from Cloudflare's [`agents`](https://www.npmjs.com/package/agents) SDK, serving MCP over Streamable HTTP at `/mcp`
- **Validation:** [Zod](https://zod.dev) schemas for every tool's input
- **Language:** TypeScript
- **Tooling:** Wrangler, oxlint, oxfmt
## Project structure
```text
.
├── migrations/
│ └── 0001_create_planner_tables.sql # D1 schema: projects and todos tables
├── src/
│ ├── index.ts # Worker entry point: creates the MCP server and registers tools
│ ├── db/
│ │ ├── projects.ts # SQL queries for projects
│ │ └── todos.ts # SQL queries for todos
│ ├── tools/
│ │ ├── projects.ts # MCP tool definitions for projects
│ │ └── todos.ts # MCP tool definitions for todos
│ └── utils/
│ └── timestamp.ts # ISO 8601 timestamp helper
├── wrangler.jsonc # Worker and D1 binding configuration
└── worker-configuration.d.ts # Generated binding types (npm run cf-typegen)
```
The code is split in two layers. `src/db/` holds plain functions that run SQL against D1 and know nothing about MCP. `src/tools/` registers the MCP tools, validates input with Zod, and calls the `db` functions. That keeps each layer small and lets you reuse the database code elsewhere.
## Database schema
```text
projects todos
──────────────────── ─────────────────────────────────────
id TEXT PK ◄──┐ id TEXT PK
name TEXT └──── project_id TEXT FK → projects.id ON DELETE CASCADE
description TEXT title TEXT
created_at TEXT description TEXT
updated_at TEXT status TEXT pending | in_progress | completed
priority TEXT low | medium | high
created_at TEXT
updated_at TEXT
```
- IDs are UUIDs generated with `crypto.randomUUID()`.
- Timestamps are ISO 8601 strings, for example `2026-11-03T13:07:00.000Z`.
- `ON DELETE CASCADE` means deleting a project automatically deletes its todos.
- `CHECK` constraints make the database itself reject invalid `status` and `priority` values.
- An index on `todos.project_id` keeps "all todos of a project" queries fast.
## Getting started
### Prerequisites
- [Node.js](https://nodejs.org) (a current LTS version)
- A free [Cloudflare account](https://dash.cloudflare.com/sign-up) (only needed for deploying, and for creating the D1 database)
### 1. Install dependencies
```bash
git clone https://github.com/<your-username>/project-planner-mcp.git
cd project-planner-mcp
npm install
```
### 2. Create your D1 database
The `database_id` in `wrangler.jsonc` points to my database, which your account can't access. Create your own:
```bash
npx wrangler login
npx wrangler d1 create project-planner
```
Copy the `database_id` from the output into the `d1_databases` section of `wrangler.jsonc`.
### 3. Apply the migrations locally
```bash
npx wrangler d1 migrations apply project-planner --local
```
### 4. Run the server
```bash
npm run dev
```
The MCP endpoint is now available at `http://localhost:8787/mcp`.
## Testing with MCP Inspector
[MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a browser UI for calling MCP tools by hand. It's the quickest way to check that everything works:
```bash
npx @modelcontextprotocol/inspector
```
In the Inspector, choose the **Streamable HTTP** transport, enter `http://localhost:8787/mcp` and click **Connect**. Open the **Tools** tab, then try `create_project` followed by `list_projects`.
## Deploying to Cloudflare
Apply the migrations to the remote database, then deploy the Worker:
```bash
npx wrangler d1 migrations apply project-planner --remote
npm run deploy
```
Your server will be live at:
```text
https://project2-planner-mcp.<your-subdomain>.workers.dev/mcp
```
The first part of the URL comes from the `name` field in `wrangler.jsonc`. Change it before deploying if you want a different URL.
## Connecting an MCP client
### Cloudflare AI Playground
1. Open [playground.ai.cloudflare.com](https://playground.ai.cloudflare.com/).
2. Enter your deployed `/mcp` URL and connect.
3. Ask the model to create a project.
### Claude Desktop (via `mcp-remote`)
Local clients that only speak stdio can reach a remote server through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) proxy. In Claude Desktop, open **Settings → Developer → Edit Config** and add:
```json
{
"mcpServers": {
"project-planner": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8787/mcp"]
}
}
}
```
Replace the URL with your deployed one to use the hosted version. Restart Claude Desktop and the tools appear.
## Scripts
| Command | What it does |
| --- | --- |
| `npm run dev` | Start a local dev server with a local D1 database |
| `npm run deploy` | Deploy the Worker to Cloudflare |
| `npm run type-check` | Type-check the project with TypeScript |
| `npm run lint:fix` | Lint with oxlint and fix what it can |
| `npm run format` | Format the code with oxfmt |
| `npm run cf-typegen` | Regenerate `worker-configuration.d.ts` after changing bindings |
## How this project evolved
I built this while learning MCP, TypeScript and Cloudflare Workers, starting from Cloudflare's [authless remote MCP template](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-authless).
The first version stored everything in **Workers KV**, a key-value store. Because KV can't query or join, it had to keep hand-maintained lists of IDs (`project:list`, `project:<id>:todos`) and read each record one by one. Those read-modify-write updates could lose data when two requests arrived at the same time.
The current version uses **D1**. SQL handles listing and filtering, the schema enforces valid values, and `ON DELETE CASCADE` removes a project's todos in one statement, inside the database.
## Possible improvements
- Add authentication, for example with Cloudflare's [OAuth provider for MCP](https://developers.cloudflare.com/agents/model-context-protocol/authorization/).
- Return `isError: true` from tools when a project or todo isn't found, so clients can tell failures from results.
- Make the `update_*` tools accept partial updates, so changing only a todo's status doesn't require resending every field.
- Add tests with [Vitest and the Workers pool](https://developers.cloudflare.com/workers/testing/vitest-integration/).
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues