Skip to main content
Glama
IslamKhalil314

MCP Task Board Playground

README.md
# MCP Task Board Playground

A TypeScript playground for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io): a small **task-board MCP server** (stdio) plus a **terminal host** that talks to that server and to a local **Ollama** model.

Use it to learn how MCP **tools**, **resources**, and **prompts** work end-to-end — from decorator registration on the server, through a stdio client, into an LLM tool loop in the CLI.

## What you get

| Piece | Role |
| --- | --- |
| MCP server (`npm run mcp:stdio`) | Exposes task-board tools, one resource, one prompt over stdio |
| CLI host (`npm start`) | Spawns the server, connects to Ollama, chat + `/` prompts + `@` resources |
| Domain (`TasksService`) | In-memory board; knows nothing about MCP or LLMs |

```
you (terminal)
    │
    ▼
CLI host  ──HTTP──►  Ollama (/api/chat + tools)
    │
    │ stdio (StdioClientTransport)
    ▼
MCP server ──► TaskTools ──► TasksService
```

## Requirements

- **Node.js** ≥ 22
- **Ollama** running locally, with a **tool-capable** model (e.g. `llama3.1`)

```bash
ollama serve
ollama pull llama3.1
```

Phi-3 and similar models that do not support tools will return HTTP 400 when the CLI sends tool definitions.

## Quick start

```bash
npm install
npm start
```

On startup the CLI:

1. Spawns `src/stdio/main.stdio.ts` as an MCP child process
2. Lists tools / resources / prompts from the server
3. Pings Ollama (`OLLAMA_HOST`, default `http://127.0.0.1:11434`)
4. Opens a raw-mode REPL (`you>`)

### Environment

| Variable | Default | Meaning |
| --- | --- | --- |
| `OLLAMA_HOST` | `http://127.0.0.1:11434` | Ollama base URL |
| `OLLAMA_MODEL` | `llama3.1` | Chat model (must support tools for free-text chat) |

```powershell
$env:OLLAMA_MODEL="llama3.1"
npm start
```

## CLI usage

### Live `/` and `@` menus

At an empty `you>` prompt:

- Press **`/`** → inline menu of MCP **prompts** (filter as you type; arrows + Enter)
- Press **`@`** → inline menu of MCP **resources**

Mid-line incomplete `@…` still works after Enter (picker / expand). Escape dismisses the current menu token.

### Chat (Ollama + tools)

Type a normal sentence and press Enter. The host sends MCP tool schemas to Ollama, executes `tool_calls` via the MCP client (up to 8 rounds), and prints the final answer.

Examples:

```
list my todos
create a task titled Fix the retry loop for islam
```

### `@` resource mentions

`@name` or `@uri` in a message is resolved: the host `readResource`s and appends the body into the text sent to Ollama.

```
rewrite this using @board-conventions
```

Unknown `@foo` → error, that turn does not call Ollama.

### Slash commands

| Input | Behavior |
| --- | --- |
| `/` (keypress) | Prompt picker |
| `/standup-review assignee=islam` | Run that MCP prompt, then complete with Ollama |
| `/prompt <name> …` | Alias for the above |
| `@` (keypress) | Resource picker, then optional message |
| `/tools` | List tools |
| `/resource <uri>` | Print resource contents |
| `/help` | Help |
| `/quit` | Exit |

## MCP server surface

Registered from [`src/mcp/tools/task.tools.ts`](src/mcp/tools/task.tools.ts) via homemade decorators (`@McpTool` / `@McpResource` / `@McpPrompt`) and [`createMcpServer()`](src/mcp/mcp-server.factory.ts).

### Tools

| Name | Purpose |
| --- | --- |
| `list-tasks` | List tasks; optional `status` filter |
| `create-task` | Create a todo (`title`, optional `assignee`) |
| `set-task-status` | Move a task by UUID |
| `task-stats` | Counts per column (structured + text) |

### Resource

| Name | URI | Content |
| --- | --- | --- |
| `board-conventions` | `board://conventions` | Markdown house rules for the board |

### Prompt

| Name | Args | Behavior |
| --- | --- | --- |
| `standup-review` | `assignee` | Builds a user message from that person’s tasks for stand-up drafting |

Seed data (in-memory, per MCP process): two sample tasks on startup.

## Project layout

```
src/
  cli/                 # Terminal MCP host + Ollama
    main.ts            # REPL entry
    mcp-session.ts     # Stdio MCP client (spawns server)
    ollama.client.ts   # /api/chat + tool mapping
    chat-loop.ts       # Tool-calling loop
    slash.ts           # Slash parse + line classification
    mentions.ts        # @resource expand
    pickers.ts         # Inquirer search helpers (fallback flows)
    repl-input.ts      # Raw-mode editor + live / and @ menus
  mcp/                 # MCP server registration
    decorators/        # WeakMap @McpTool / @McpResource / @McpPrompt
    tools/task.tools.ts
    mcp-server.factory.ts
  stdio/main.stdio.ts  # serveStdio(() => createMcpServer())
  tasks/tasks.service.ts
test/                  # Vitest (slash, mentions, chat-loop, tools, repl-input)
```

The server wires `TasksService` → `TaskTools` by hand and discovers decorated methods with `collectDecorated()`.

## Scripts

| Script | Description |
| --- | --- |
| `npm start` | CLI host (spawns MCP over stdio) |
| `npm run mcp:stdio` | MCP server only (for Cursor / Claude Desktop / Inspector) |
| `npm test` | Vitest |

### Point another host at this server

```json
{
  "command": "node",
  "args": [
    "--import",
    "@swc-node/register/esm-register",
    "src/stdio/main.stdio.ts"
  ]
}
```

(Working directory = this repo.) Do not write logs to stdout in the stdio process — stdout is the MCP wire.

## How registration works

1. Methods on `TaskTools` are marked with `@McpTool` / `@McpResource` / `@McpPrompt`.
2. Decorators store metadata in a `WeakMap` keyed by the class constructor.
3. `createMcpServer(tasks?)` constructs `TaskTools`, calls `collectDecorated()`, and `registerTool` / `registerResource` / `registerPrompt` on a fresh `McpServer`.
4. Stdio uses a process-level `TasksService` singleton so board state survives across connection factory calls. Tests pass `new TasksService()` for isolation.

## Ollama tool loop (host)

1. `listTools` → map to Ollama `{ type: 'function', function: { name, description, parameters } }`
2. User message → `POST /api/chat` with `tools`
3. If `tool_calls` → `callTool` on MCP → append `role: 'tool'` results → chat again (max 8 rounds)
4. Domain tool errors (`isError`) are returned as text so the model can retry

## Tests

```bash
npm test
```

Coverage includes:

- Slash / line classification
- `@` mention expand (name + URI, unknown tokens)
- Chat-loop with mocked Ollama + MCP
- In-memory MCP protocol (tools + resource) via `InMemoryTransport`
- Live-menu helpers (`shouldOpenPromptMenu` / resource)

## License

MIT