Skip to main content
Glama
alexisinwork

Task Board MCP Server

by alexisinwork
README.md
# ModelContextProtocol

A task-board MCP server that exercises all three server primitives — **tools**,
**resources**, and **prompts** — plus a host that drives it with a cheap model,
and a protocol-level test suite.

Part of [ai_engineering](https://github.com/alexisinwork/ai_engineering).

`THEORY.md` covers the concepts: what MCP actually standardises and what it does
not, why the tool/resource/prompt split is about *who decides* rather than what
the data is, the stdio rule that breaks more servers than anything else, why
tool errors must not be thrown, annotations as host hints, and how to test a
server so its failures are visible.

## Getting started

```bash
npm install
npm run seed     # writes a 5-task starting board
npm test         # 27 protocol-level assertions, no API key needed
```

For the LLM host, copy `.env.example` to `.env` and add an OpenAI key:

```bash
npm run client -- "What is left to do?"
npm run client -- "Check whether anything about CI is tracked, and mark it done."
```

Defaults to `gpt-4o-mini`. Tool calling does not need a frontier model — set
`MODEL` in `.env` to change it.

## What's here

| File | Role |
| --- | --- |
| `server.js` | The MCP server — 3 tools, 2 resources, 2 prompts, over stdio |
| `store.js` | Board logic, pure and testable, knows nothing about MCP |
| `client.js` | A minimal **host**: MCP client + model + tool loop |
| `test.js` | Protocol test — spawns the real server over the real transport |
| `seed.js` | Writes a starting board |

## The three primitives, as implemented here

The split is about **who decides**, not what the data is:

| Primitive | Decided by | Here |
| --- | --- | --- |
| **Resources** | the application | `task://board`, `task://item/{id}` |
| **Tools** | the model | `search_tasks`, `create_task`, `set_status` |
| **Prompts** | the user | `/standup`, `/triage` |

`client.js` shows the distinction concretely: it reads `task://board` itself
before the model runs, and lets the model choose when to call `search_tasks`.
Nothing stops you exposing the board as a tool — but then the *model* decides
what context to load, which is the thing resources exist to avoid.

## Testing

### 1. MCP Inspector — for looking

The official interactive client. Nothing to install:

```bash
npm run inspect
```

That runs `npx @modelcontextprotocol/inspector node server.js`, which opens a
browser UI with a pre-filled session token. In it you can:

- **Tools** → list, inspect schemas and annotations, call with arbitrary args
- **Resources** → browse `task://board`, and see the template expand to real
  ids. Type `task://item/` and autocompletion offers ids from the live board.
- **Prompts** → render `standup` with a `focus` argument and read the message
  it produces
- **Errors / notifications** panes → everything the server writes to **stderr**

The Inspector also has a CLI mode, which is what to reach for in a script:

```bash
npx @modelcontextprotocol/inspector --cli node server.js --method tools/list
npx @modelcontextprotocol/inspector --cli node server.js \
  --method tools/call --tool-name search_tasks --tool-arg query=docs
```

### 2. `npm test` — for knowing

The Inspector shows you one call at a time and depends on you noticing. The test
suite asserts 27 properties in about a second, with no API key and no model:

```
DISCOVERY              every tool, resource, template and prompt is registered
SCHEMAS + ANNOTATIONS  read-only, destructive, idempotent flags are correct
HAPPY PATH             a write shows up in the resource
STRUCTURED OUTPUT      structuredContent validates against outputSchema
THE EMPTY CASE         a search with no results explains itself
ERROR HANDLING         bad input returns isError rather than throwing
DISCOVERABILITY        template completion and prompt arguments work
```

It spawns `server.js` as a subprocess over stdio using the SDK's own `Client`,
so it exercises the same transport and schemas the Inspector and Claude Code do.
It stashes any real `data/tasks.json` first and restores it after.

**Why assert annotations.** A tool marked `readOnlyHint` when it writes will
skip the confirmation prompt a host would otherwise show. Nothing errors. The
only place that mistake is visible is a test that reads the flag back.

## Using it from Claude Code

```bash
claude mcp add task-board -- node /absolute/path/to/ModelContextProtocol/server.js
```

Then `/mcp` lists the server, its tools appear to the model, and `/standup`
shows up as a slash command.

## The one rule that breaks servers

**On stdio, stdout is the protocol.** Every byte must be a JSON-RPC message. A
single `console.log` in a handler corrupts the stream, and the client
disconnects with a parse error that names no line of your code. All diagnostics
go to `console.error`. `THEORY.md` §6 has the full argument.

TDQS

A4.3/5.0

Scored across 3 tools

Disambiguation5/5

The three tools have clearly distinct purposes: searching, creating, and updating status. No overlap or ambiguity exists between them.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern with lower snake_case: search_tasks, create_task, set_status. This makes the API predictable and easy to navigate.

Tool Count5/5

With only three tools, the server is tightly scoped to its purpose as a minimal task board. Each tool earns its place and there is no bloat.

Completeness4/5

The core lifecycle of creating, searching, and updating task status is covered. Missing delete and full task editing are minor gaps, but the essential workflow is functional.

Maintenance

ActivitySlowing
ResponsivenessNo issues