Skip to main content
Glama
README.md
# runn-mcp

A generic [MCP](https://modelcontextprotocol.io) server that wraps the
[Runn](https://runn.io) REST API v1 (read + write). It exposes ID-based, "dumb"
tools — list projects, create assignments, read actuals, bulk-write timesheets,
and so on — and knows **nothing** about any specific project, journey, course or
Gantt. That mapping logic lives on the orchestrator (Claude) side, in a
project-specific mapping file (e.g. `dlp2-runn-mapping.json`), never in this server.

## Requirements

- Node.js >= 18 (uses the built-in `fetch`).
- A Runn API token — generated in the Runn app under **Settings > API** (admins
  only). Tokens are scoped read or write and tied to an account.
- Strongly recommended: develop and verify against a **Test Account** (in Runn:
  profile menu → *Switch to test account*; it has its own API tokens).

## Install & build

```bash
cd runn-mcp
npm install
npm run build      # compiles TypeScript to dist/
npm test           # builds + runs the unit/integration test suite
```

## Configuration

All configuration is via environment variables (see `.env.example`):

| Variable            | Default               | Meaning                                                       |
| ------------------- | --------------------- | ------------------------------------------------------------- |
| `RUNN_API_TOKEN`    | _(required for calls)_ | Bearer token for the Runn API. Empty → tools error clearly.  |
| `RUNN_API_BASE_URL` | `https://api.runn.io` | API base URL (use `https://api.us.runn.io` for the US region).|
| `RUNN_API_VERSION`  | `1.0.0`               | Sent as the mandatory `Accept-Version` header.                |
| `RUNN_DRY_RUN`      | `true`                | **Write safety.** When true, write tools only preview.        |

### Write safety (dry-run)

By default (`RUNN_DRY_RUN=true`) every write tool returns a preview of the exact
request it *would* send (`{ dryRun: true, wouldCall: { method, path, body } }`)
and makes **no** API call. To apply writes for real, set `RUNN_DRY_RUN=false`.
Read tools are unaffected by this flag.

## Connecting it to the Claude app

The Claude app launches the server over stdio. Add an entry to your MCP servers
config (`mcpServers`), pointing `node` at the built entry file and supplying the
token in `env`:

```json
{
  "mcpServers": {
    "runn": {
      "command": "node",
      "args": ["/home/sly/Dokumentumok/runn-mcp/dist/index.js"],
      "env": {
        "RUNN_API_TOKEN": "PASTE_YOUR_TOKEN_HERE",
        "RUNN_DRY_RUN": "true"
      }
    }
  }
}
```

Notes:

- Use an absolute path to `dist/index.js`. Run `npm run build` first.
- Keep `RUNN_DRY_RUN` as `"true"` until you're ready to write; flip to `"false"`
  for a session where you intend to push changes.
- For the safest setup, use a **read-scoped** token for everyday querying and a
  separate **write-scoped** token only when you actually push.
- The token lives only in this config (and/or a gitignored `.env`); it is never
  committed.

### Quick check

`runn_server_info` (no token needed) reports the effective config. `runn_whoami`
(needs a token) confirms the credentials by calling `GET /me`.

## Tools

### Diagnostics
- `runn_server_info` — server version + effective config (no token, no API call).

### Read
- `runn_whoami` — `GET /me`; verify token/account.
- `runn_list_projects` — projects; filters: `name`, `includeArchived`, `externalId`, `modifiedAfter`.
- `runn_get_project` — one project by `projectId`.
- `runn_list_phases` — phases; with `projectId` → that project's phases, else all.
- `runn_list_milestones` — milestones; with `projectId` → that project's, else all.
- `runn_list_assignments` — assignments; filters: `projectId`, `personId`, `roleId`, `startDate`, `endDate`.
- `runn_list_people` — people; `includePlaceholders`, name/email/`externalId` filters.
- `runn_list_placeholders` — placeholders.
- `runn_list_roles` — roles (resolve `roleId`).
- `runn_list_workstreams` — workstreams.
- `runn_list_actuals` — actuals; filters: `projectId`, `personId`, `roleId`, `workstreamId`, `minDate`, `maxDate`.
- `runn_report_hours_project` — by-day plan-vs-actual for a project.
- `runn_report_hours_person` — by-day plan-vs-actual for a person.
- `runn_report_totals_project` — aggregated plan/actual totals for a project.

Listing tools accept `limit` and `cursor`, plus `fetchAll: true` to follow the
cursor and return every item (`{ count, values }`). Without `fetchAll` they
return a single page (`{ values, nextCursor }`).

### Write (respect `RUNN_DRY_RUN`)
- `runn_create_phase` / `runn_update_phase` / `runn_delete_phase`
- `runn_create_milestone` / `runn_update_milestone`
- `runn_create_assignment` / `runn_delete_assignment`
- `runn_create_project` / `runn_update_project`
- `runn_bulk_actuals` — create/update up to 100 actuals in one call.
- `runn_create_placeholder` — create a placeholder (auto-named after the role).
- `runn_update_person` — update a person/placeholder (rename, email, team, archive).
- `runn_create_workstream` / `runn_update_workstream` / `runn_delete_workstream` — manage account-level workstreams.
- `runn_add_project_workstream` / `runn_remove_project_workstream` — attach/detach a workstream to/from a project.

## Behaviour notes

- **Pagination**: cursor-based (`values` + `nextCursor`); `listAll` follows it
  with a safety cap.
- **Rate limits**: 120 req/min. The client retries on `429`/`5xx`/network errors
  with backoff, honouring `retry-after`.
- **Errors**: any non-2xx becomes a structured tool error (`{ error, status,
  message, request }`, `isError: true`) — the server never crashes on an API error.
- **Actuals**: each bulk entry overwrites the existing actual for the same
  project/person/role/date; if any entry is invalid, none are written.
- **Assignments**: creating one may return multiple segments if the period
  overlaps scheduled leave.

## Scope

This server is intentionally project-agnostic and reusable for any Runn account.
DLP2-specific decisions (monogram → person/placeholder IDs, phase mapping, the
"everyone is Content Creator except EduPMs = PM, all billable" rule) belong in a
separate mapping file that the orchestrator reads — not here. Out of scope:
background/scheduled sync, writing back into spreadsheets, and multi-user hosting.

## Project layout

```
runn-mcp/
  src/
    config.ts            env config (incl. dry-run default)
    index.ts             stdio server entry; wires up all tools
    runn/
      client.ts          generic Runn HTTP client (auth, pagination, retries)
      types.ts           shared API shapes
    tools/
      diagnostics.ts     runn_server_info
      read.ts            read tools
      write.ts           write tools (dry-run aware)
      helpers.ts         result formatting, dry-run, pagination helpers
      *.test.ts          unit/integration tests (node:test)
  scripts/
    smoke-read.mjs       read-only live smoke test (node --env-file=.env ...)
```

TDQS

B3.4/5.0

Scored across 32 tools

Disambiguation5/5

Each tool targets a distinct resource/action combination. Even closely related tools like runn_list_people vs runn_list_placeholders are clearly separated by entity, and runn_list_actuals vs runn_bulk_actuals are list vs create/update. No two tools appear to do the same thing.

Naming Consistency3/5

Most tools follow the `runn_<verb>_<noun>` pattern (e.g., runn_create_project, runn_list_phases). However, several deviate: runn_report_totals_project, runn_report_hours_project, runn_bulk_actuals, runn_server_info, and runn_whoami are not clearly verb_noun. This mixed convention slightly reduces predictability.

Tool Count2/5

32 tools is well above the recommended range for a coherent set. Even accounting for the broad domain (projects, phases, milestones, assignments, actuals, workstreams, reports, auth), the count feels heavy and could overwhelm agents. Some consolidation might be possible.

Completeness4/5

The set covers the core lifecycle for projects, phases, milestones, assignments, actuals, workstreams, and people. Minor gaps exist (e.g., no delete_project, no update_assignment, no delete_milestone), but these may reflect API constraints and are typically workable (e.g., delete and recreate assignment). Reports cover the key planning vs. actuals use cases.

Maintenance

ActivityInactive
ResponsivenessNo issues