# Cortex Planner MCP Server

A stdio [MCP](https://modelcontextprotocol.io) server that lets AI agents
(Claude Code, Claude Desktop, …) read and write your Cortex Planner data:
time entries, timers, projects, tasks, clients and users.

Each user runs the server locally with their own **personal access token**. It
calls the token-authenticated REST API (`/api/v1`) as that user, so all
role-based permissions (developer vs admin/manager) apply exactly as in the web
app.

The same tools are served from two other places: a native Go build of this
server, [`apps/mcp-go`](../mcp-go/README.md), for anyone who would rather not
run a Node process at all; and the API itself over Streamable HTTP at `/mcp`
(`apps/api/src/mcp/mcp-tools.ts`), for a client that would rather connect to a
URL than run anything. This server stays the reference for behavior, but **a new
or changed tool has to land in all three**. The tool list lives in
[`tools.manifest.json`](./tools.manifest.json) and all three test suites assert
their registered tools against it, so a one-sided change fails; see the parity
checklist in the Go README.

## Setup (users)

Nothing to clone or build — `npx` fetches the package on first run.

1. Mint a personal API token in the Cortex web app: **Profile → API tokens**.
   It is shown **once**, so copy it before closing the dialog.

2. Register the server with your client, replacing `ctx_…` with your token and
   the URL with your Cortex API:

   **Claude Code**

   ```bash
   claude mcp add cortex-planner \
     --env MCP_API_URL=https://api.cortex.mountainlift.cz \
     --env MCP_API_TOKEN=ctx_… \
     -- npx -y @mountainlift/cortex-mcp
   ```

   **Claude Desktop** — Settings → Developer → Edit config:

   ```json
   {
     "mcpServers": {
       "cortex-planner": {
         "command": "npx",
         "args": ["-y", "@mountainlift/cortex-mcp"],
         "env": {
           "MCP_API_URL": "https://api.cortex.mountainlift.cz",
           "MCP_API_TOKEN": "ctx_…"
         }
       }
     }
   }
   ```

3. Ask the assistant "who am I in Cortex?" — it should answer with your name
   and role.

The **API access** page in the web app carries the same instructions with the
URL of the instance you are signed in to already filled in.

## Setup (working in this repo)

The repo ships a `.mcp.json` that runs the locally built output, so a checkout
plus `pnpm install && pnpm build:mcp` is enough — approve the `cortex-planner`
server when Claude Code prompts, then check with `/mcp`. Configuration comes
from the **repo root `.env`** (gitignored):

```dotenv
MCP_API_TOKEN=ctx_xxxxxxxx
# Optional: target a deployed API instead of localhost:4000
MCP_API_URL=
```

Never put the token into `.mcp.json` or any committed file. Lost it? Rotate or
create a new one in the web app — tokens can't be read back.

## Tools

| Tool | Purpose |
| --- | --- |
| `cortex_whoami` | Current user, role, and the organisation the token reaches |
| `cortex_time_entry_list` | Entries with durations, Jira keys, project/client; filters by week or date range |
| `cortex_time_entry_create` / `_update` / `_delete` | Log and edit entries |
| `cortex_timer_start` | Start a running timer (never stops others — concurrent timers are supported) |
| `cortex_timer_stop` | Stop one timer; asks which one when several run |
| `cortex_timer_current` | Running timers with elapsed time |
| `cortex_project_list` / `cortex_client_list` / `cortex_task_list` / `cortex_user_list` | Catalog lookups (`cortex_client_list` is admin/manager only) |
| `cortex_opportunity_list` / `cortex_opportunity_forecast` / `cortex_opportunity_estimate_set` | The Notion-mirrored sales pipeline, its monthly forecast, and the capacity estimate — the one part of it Cortex owns (admin/manager only) |
| `cortex_ticket_list` / `cortex_ticket_get` / `cortex_ticket_create` / `cortex_ticket_update` | Cortex's own backlog, the `COR-N` series — list, read, file and update tickets |

Example prompts:

- "List my hours this week."
- "Start a timer on the EPC-123 task."
- "Read my Cortex hours for last week and create matching Jira worklogs using
  each entry's `jiraKey` and `durationMinutes`." (with a Jira MCP connected)

## Publishing

`npx @mountainlift/cortex-mcp` only resolves once a version is on the public
npm registry. Releasing is a **manual** GitLab CI job (`publish:mcp`, `main`
only) — a published version can never be replaced, so bump the version in
`package.json` first, then trigger the job. It needs an `NPM_TOKEN` CI variable
with publish rights to the `@mountainlift` scope.

## Troubleshooting

- **Tools fail with a token message** — the server started without
  `MCP_API_TOKEN`. Set it in the client's `env` block (or the root `.env` when
  working in the repo) and restart. A `401` means the token is wrong, revoked
  or expired — rotate it in the web app.
- **"Cannot reach the API"** — `MCP_API_URL` is missing or wrong. It defaults
  to `http://localhost:4000`, which only exists if you run the stack yourself
  (`docker-compose up -d` and `pnpm dev:api`).
- **Stale behavior after `git pull`** — the repo setup runs the built output;
  rebuild with `pnpm build:mcp`. The `npx` setup fetches the published version,
  so it updates when a new one is released.
- Logs go to stderr (visible in the MCP client's server logs); stdout is
  reserved for the protocol.
