Skip to main content
Glama
lethal1147

office-mcp

by lethal1147

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_missionPOST /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_missionGET /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).

Related MCP server: project-manager

Setup

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:

{
  "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:

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:

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.

Related MCP Connectors

Related MCP Servers