todo-mcp-demo
by thienbanho
README.md
# Todo MCP Demo
A demo project showing how an AI Agent (**MCP Host**) uses the **Model
Context Protocol (MCP)** to communicate with Tools, Resources, and Prompts
exposed by an MCP Server, over both **stdio** and **HTTP** transports.
**Live public MCP endpoint:** `https://todo-mcp-demo.onrender.com/mcp`
(Streamable HTTP — no API key required; connect with MCP Inspector by
choosing the "Streamable HTTP" transport. First request after idle may take
~30-60s to wake up the free Render instance.)
Full specification: [`docs/00_GLOBAL_RULES.md`](docs/00_GLOBAL_RULES.md),
[`docs/01_REQUIREMENTS.md`](docs/01_REQUIREMENTS.md),
[`docs/02_ARCHITECTURE.md`](docs/02_ARCHITECTURE.md),
[`docs/03_IMPLEMENTATION_PLAN.md`](docs/03_IMPLEMENTATION_PLAN.md).
## Overview
```
User → Agent (MCP Host) → MCP Tool → Todo Store → Result → Agent → User
```
- **Todo Store** (`src/server/todoStore.ts`) — in-memory business logic:
`addTask`, `listTasks`, `completeTask`. No database, no transport
dependency.
- **MCP Server** (`src/server/server.ts`) — exposes the Todo Store as:
- 3 Tools: `add_task`, `list_tasks`, `complete_task`
- 1 Resource: `todo://list`
- 1 Prompt: `plan_my_day`
- **Transports** (`src/transports/`) — `stdio.ts` and `http.ts` both boot the
*same* MCP Server Core; no business logic is duplicated between them.
- **Agent / MCP Host** (`src/agent/`) — connects to an MCP Server (stdio or
HTTP), discovers Tools dynamically, and dispatches Tool calls.
- **Skill** (`src/skills/plan-my-day/SKILL.md`) — a reusable workflow: read
the todo list via the `list_tasks` Tool, filter unfinished tasks, and
generate a prioritized plan for the day.
## Architecture
```
User
│
▼
┌──────────────┐
│ Agent │
│ (MCP Host) │
└──────┬───────┘
│
MCP Client Layer
│
┌──────────────────┴──────────────────┐
│ │
▼ ▼
stdio Transport HTTP Transport
│ │
└──────────────────┬──────────────────┘
▼
MCP Server Core
│
┌──────────────┬──────────────┬──────────────┐
▼ ▼ ▼
Tools Resources Prompts
│
▼
Todo Store
```
## Prerequisites
- Node.js >= 22.9
- npm
## Installation
```bash
npm install
npm run build
```
## Configuration (optional)
No environment variable is required — every value has a working default.
To override one locally, copy the example file:
```bash
cp .env.example .env
```
| Variable | Used by | Default |
| ---------------- | ----------------------- | -------------------------------- |
| `PORT` | `npm run server:http` / `npm start` | `3000` |
| `MCP_TRANSPORT` | `npm run agent` | `stdio` |
| `MCP_HTTP_URL` | `npm run agent` (when `MCP_TRANSPORT=http`) | `http://localhost:3000/mcp` |
All npm scripts load `.env` automatically via Node's built-in
`--env-file-if-exists` flag (no `dotenv` dependency needed). `.env` is
git-ignored; only `.env.example` is committed. On hosting platforms
(Render/Railway) `PORT` is injected by the platform itself, so `.env` is
not used in production.
## Available npm Scripts
| Script | Description |
| ---------------------- | --------------------------------------------------------- |
| `npm run build` | Compile TypeScript to `dist/` |
| `npm run clean` | Remove `dist/` |
| `npm run server:stdio` | Build, then start the MCP Server over stdio |
| `npm run server:http` | Build, then start the MCP Server over HTTP (port `3000`) |
| `npm run agent` | Build, then run the Agent CLI with a natural-language command |
| `npm start` | Start the HTTP MCP Server from an already-built `dist/` (used by hosting platforms) |
## Run the stdio Server
```bash
npm run server:stdio
```
This starts the MCP Server on stdio. Connect with the
[MCP Inspector](https://github.com/modelcontextprotocol/inspector):
```bash
npx @modelcontextprotocol/inspector node dist/transports/stdio.js
```
## Run the HTTP Server
```bash
npm run server:http
```
Starts an HTTP MCP endpoint at `http://localhost:3000/mcp` (override the
port with the `PORT` environment variable). Connect with MCP Inspector by
choosing the "Streamable HTTP" transport and pointing it at that URL.
## Run the Agent
The Agent connects to a running MCP Server, discovers its Tools, and
dispatches a single natural-language command.
Against the **stdio** server (default — the Agent spawns the server itself,
no need to start it separately):
```bash
npm run agent -- "add task Buy milk"
npm run agent -- "list tasks"
npm run agent -- "complete task <task-id>"
```
Against a running **HTTP** server (start it first with
`npm run server:http` in another terminal):
```bash
MCP_TRANSPORT=http npm run agent -- "add task Buy milk"
MCP_TRANSPORT=http MCP_HTTP_URL=http://localhost:3000/mcp npm run agent -- "list tasks"
```
## Run the Skill: plan-my-day
See [`src/skills/plan-my-day/SKILL.md`](src/skills/plan-my-day/SKILL.md) for
the full workflow definition. The Agent recognizes the trigger phrase and
runs the workflow using the existing `list_tasks` Tool (no direct access to
the Todo Store):
```bash
npm run agent -- "plan my day"
# or, against the HTTP server:
MCP_TRANSPORT=http npm run agent -- "plan my day"
```
Example output:
```
Today's plan:
1. Buy milk
2. Write report
```
## Demo Walkthrough
```bash
npm run agent -- "add task Buy milk"
npm run agent -- "add task Write report"
npm run agent -- "plan my day"
```
> Note: each `npm run agent` call against the **stdio** transport spawns a
> fresh server process, so tasks only persist for the lifetime of a single
> command. To see tasks persist across multiple Agent calls, start the HTTP
> server once (`npm run server:http`) and pass `MCP_TRANSPORT=http` to every
> Agent call, as shown above.
## Deployment
The HTTP MCP Server (`src/transports/http.ts`) is deployment-ready:
- It reads the port from the `PORT` environment variable.
- `npm start` runs the compiled server directly (`node dist/transports/http.js`).
Deployed on **Render** (Free instance type):
- Repository: https://github.com/thienbanho/todo-mcp-demo
- Build command: `npm install && npm run build`
- Start command: `npm start`
- No environment variables required — Render sets `PORT` automatically.
- Live endpoint: `https://todo-mcp-demo.onrender.com/mcp`
To deploy on **Railway** instead, the steps are equivalent: connect the
repository, set the same build/start commands, and Railway will assign a
public URL and `PORT` automatically.
Verified with a raw MCP `initialize` + `tools/list` handshake against the
live endpoint (see `mcp-session-id` flow in `src/transports/http.ts`); you
can also verify interactively with MCP Inspector, using the public `/mcp`
URL as a Streamable HTTP endpoint.
## Out of Scope
Per the project spec: no database, authentication, authorization, user
management, frontend, Docker, CI/CD, or multi-user support. All Todo data is
in-memory and is lost on server restart — this is intentional for a demo
project.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues