Skip to main content
Glama
README.md
# ops-platform-mcp

A miniature rehearsal of a real consulting-stack architecture: a mock internal ops
platform (FastAPI + SQLite, standing in for BambooHR / Deltek Vantagepoint / ClickUp /
an internal Django app) wrapped by an **MCP server** so that Claude Code can read and
write platform data in natural language. The MCP layer talks to the platform only over
HTTP — exactly how a connector wraps a vendor API in real life. See `CLAUDE.md` for the
full PRD.

```
FastAPI "platform" (SQLite + seed)  ◄─HTTP─►  MCP server (stdio)  ◄─►  Claude Code
```

## Quickstart

Requires [uv](https://docs.astral.sh/uv/) (it will fetch Python 3.12 automatically).

```bash
uv sync                                # install dependencies
uv run python -m platform_api.seed    # create + seed ops_platform.db (re-run anytime to reset)
uv run uvicorn platform_api.main:app  # serve the platform API on http://127.0.0.1:8000
```

Interactive API docs: <http://127.0.0.1:8000/docs>

The seed is deterministic (no randomness) with **relative dates**: time entries are
fixed offsets from the current week's Monday and task due dates are offsets from today,
so "this week's utilization" always has data no matter when you run the demo. All
people and clients are fictional.

## Connect the MCP server to Claude Code

The platform API must be running first (see Quickstart). Then either register the
server with the CLI:

```bash
claude mcp add ops-platform -- uv run --directory /absolute/path/to/ops-platform-mcp python -m mcp_server.server
```

…or commit/drop a `.mcp.json` next to wherever you run `claude` (if that's this repo
root, the relative directory works):

```json
{
  "mcpServers": {
    "ops-platform": {
      "command": "uv",
      "args": ["run", "--directory", ".", "python", "-m", "mcp_server.server"]
    }
  }
}
```

Then ask Claude Code things like:

> list the employees · create a task on Atlas to "refresh the KPI deck" for Tessa ·
> mark task 21 in progress · log 3 hours for Marcus on Orion today · pull this week's
> utilization report

A scripted version of this loop — including the error-path curveballs and a
troubleshooting section — is in [DEMO.md](DEMO.md).

### Tools

| Tool | Kind | Notes |
|---|---|---|
| `list_employees` | read | full roster with capacity |
| `list_projects` | read | id, client, status, budget |
| `list_tasks(project?, assignee?, status?)` | read | names or ids accepted; results include names |
| `get_project_hours(project)` | read | logged vs budget |
| `utilization_report(week?)` | read | ISO week e.g. `2026-W24`, defaults to current week |
| `create_task(project, title, assignee?, due_date?)` | write | returns created task |
| `update_task_status(task_id, status)` | write | todo / in_progress / done |
| `log_time(employee, project, date, hours, note?)` | write | returns created entry |

Tools accept human-friendly names where reasonable and resolve them to ids internally;
ambiguous or unknown names return errors that list the candidates so the model can
self-correct.

### Task backends (Phase 3: the adapter pattern)

The three task tools (`list_tasks`, `create_task`, `update_task_status`) are
backend-pluggable — same tool surface, different system of record:

```bash
OPS_TASK_BACKEND=platform   # default: the mock platform above
OPS_TASK_BACKEND=clickup    # real ClickUp API (set CLICKUP_API_TOKEN)
```

In ClickUp mode a "project" is a ClickUp **list** (found by name across all spaces
and folders), an assignee is a workspace member, statuses map todo / in_progress /
done ↔ "to do" / "in progress" / "complete", and task ids are ClickUp's alphanumeric
strings. Get a personal token from ClickUp → Settings → Apps and see `.env.example`.
`CLICKUP_TEAM_ID` pins a workspace when the token can see several. The other five
tools (employees, hours, time, utilization) always use the platform.

This is the point of the exercise: the MCP tool layer didn't change when the backend
became a real vendor API — only the adapter behind it did.

## Development

```bash
uv run pytest              # 40 tests: API happy paths + error cases, MCP tool handlers
uv run ruff check .        # lint
uv run ruff format --check .
```

Tool-handler tests run against the real FastAPI app in-process (httpx ASGI transport +
seeded in-memory SQLite) — no server or network needed. CI runs the same three commands.

## What this rehearses

Directing an AI agent to build and operate the connector layer between LLM tooling and
internal business systems: a typed CRUD API over a real data model, an MCP server whose
tool descriptions a model can act on reliably, and the review/CI loop around both.

**Next steps (not in v1):** auth/OAuth on the platform API, a second adapter backed by
the real ClickUp API (same tool surface, different backend), deployment.

TDQS

A4.4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool has a distinct purpose covering tasks, projects, employees, and time tracking. There is no overlap; for example, create_task and update_task_status are clearly separate, and list_tasks is different from list_projects or list_employees.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., create_task, get_project_hours, list_employees). No mixing of conventions or inconsistent verb styles.

Tool Count5/5

With 8 tools, the count is well-scoped for an operations platform covering task management, project tracking, employee lookup, and time logging. Each tool serves a necessary function without bloat.

Completeness3/5

The set covers core operations (create, read, update status, list, log time, report), but lacks an update tool for task fields other than status (e.g., assignee, due date) and no delete tool. This creates a notable gap in task lifecycle management.

Maintenance

ActivityMaintained
ResponsivenessSyncing