office-mcp
by lethal1147
README.md
# office-mcp
MCP server wrapping the internal "office" mission-tracker API (`office-api-staging.100s.dev`), reverse-engineered from the web app's network calls — there is no public API doc for this system.
## Tools
- `create_mission` — `POST /api/v1/missions`. **Two-step, confirm-gated:** called with `confirm` omitted/false, it validates the input and returns a `{ status: "preview", payload }` result **without making any HTTP request** — no code path to the real POST runs. Only a call with `confirm: true` performs the actual create and returns `{ status: "created", data }`. This is enforced in `createMission()` (`src/tools/missions.ts`), not just prompt guidance, so it holds regardless of what the calling LLM decides to do on its own. `description` is accepted as **Markdown** and converted to HTML via `marked` before sending (the office system's field renders HTML, not Markdown) — note this does not sanitize embedded raw HTML in the input, see Known unknowns.
- `get_mission` — `GET /api/v1/missions/:code`. `code` is restricted to `^[A-Za-z0-9]+$` (rejects path/query injection attempts before any request is built).
Edit is not yet implemented (endpoint not captured yet).
## Setup
```bash
npm install
npm run build
```
Environment variables (no `.env.example` is checked in — set these in your MCP client config or shell):
| Var | Required | Notes |
| --- | --- | --- |
| `OFFICE_API_TOKEN` | yes | Bearer token captured from a logged-in browser session. No refresh flow — re-capture when it expires. |
| `OFFICE_API_BASE_URL` | no | Defaults to `https://office-api-staging.100s.dev` |
| `OFFICE_COMPANY_ID` | no | Sent as `x-company` header. Example captured value: `314`. |
Example Claude Code / Claude Desktop MCP config entry:
```json
{
"mcpServers": {
"office": {
"command": "node",
"args": ["/Users/joakim/projects/tathep/office-mcp/dist/index.js"],
"env": {
"OFFICE_API_TOKEN": "Bearer ...",
"OFFICE_COMPANY_ID": "314"
}
}
}
}
```
## Two entrypoints
- **`src/index.ts`** — local stdio server (`npm run build && node dist/index.js`), for personal use registered via `claude mcp add` (see above). Reads config from `process.env`.
- **`src/worker.ts`** — Cloudflare Worker, HTTP/SSE transport via `agents/mcp/server`'s `createMcpHandler` (stateless — no Durable Objects needed). Reads config from the Worker's `env` bindings/secrets instead of `process.env`. Both entrypoints share the same tool logic (`src/client.ts`, `src/tools/`) via `registerMissionTools()` (`src/tools/register.ts`).
## Deploying the Worker
This project was previously connected to **Cloudflare Pages** (git auto-build), which fails — Pages expects a static-site build output and this is a Worker script. Deploy it as a **Workers** project via Wrangler instead, not through the Pages dashboard:
```bash
npx wrangler login # once
npm run deploy:worker
```
**Auth is per-caller, not a shared server secret.** The Worker holds no office-api token of its own — it forwards whatever `Authorization` header the connecting MCP client sends straight to office-api. Each person adds their own token on their own client config:
```bash
claude mcp add --transport http office https://<your-worker>.workers.dev/mcp \
-H "Authorization: Bearer <their own office-api token>"
```
This resolves the shared-token/no-attribution concern from the security review — each caller's requests hit office-api under their own credential, and revoking one person's token doesn't affect anyone else. `OFFICE_API_BASE_URL`/`OFFICE_COMPANY_ID` (non-sensitive, shared per deployment) can still be set as Worker vars in `wrangler.jsonc` or via `npx wrangler secret put` if they ever need to differ from the defaults.
Local dev: `npm run dev:worker` (serves on `http://localhost:8787/mcp`) — pass your own `Authorization` header on each request/client config the same way. Typecheck the Worker specifically with `npm run typecheck:worker` (it's excluded from the main `tsc`/`npm run build`, which only covers the stdio entrypoint).
## Known unknowns
- `priority` enum (`low`/`normal`/`high`/`urgent`) is guessed — only `"normal"` has been observed in a live payload. Confirm the full set before relying on it.
- `startAt`/`dueAt` format is assumed ISO 8601 — only `null` has been observed so far.
- No delete/cancel endpoint captured, so a mistaken `create_mission` call can't be cleaned up via this server yet.
- `marked` renders raw HTML embedded in the input Markdown as-is (unsanitized) — still the unresolved XSS-adjacent concern from the security review (#5), just moved from "raw HTML input" to "raw HTML embedded in Markdown input". Low risk while the only caller is the CLI-driven skill, worth revisiting before any less-trusted input source feeds this tool.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues