MCP server exposing the Martus budgeting/reporting API to Claude Code. Read-only wrapper — every tool is a GET. One codebase, two registrations (one per Martus tenant CPP operates).
CPP runs two Martus tenants, each fed from a different upstream accounting system:
| Tenant | Registration | Upstream | Sync mode |
|---|---|---|---|
| CPP (shared expenses) | martus-cpp |
QuickBooks Online | Modern API sync |
| ANN (Annunciation + multi-org) | martus-ann |
QuickBooks Desktop (delivered over Citrix) | CSV export → manipulation → upload |
Same code, same server URL (prod5.martus.app/api), different Token header. Following the mp / mp-sandbox precedent, this MCP is registered twice in each vault's .mcp.json with a different token per registration. Tool names land as mcp__martus-cpp__list-accounts vs mcp__martus-ann__list-accounts, so which tenant is being queried is always visible in the call.
See CPP RunBook/3-Resources/martus/curated-use/martus-data-flow.md for the full pipeline story.
Requires Node 20+.
cd %USERPROFILE%\code
git clone git@github.com:norm613/cpp-martus-mcp.git
cd cpp-martus-mcp
npm install
npm run buildCopy .env.example to .env and set your tokens. Each tenant needs its own value — use .env.cpp / .env.ann convention (both gitignored), or keep the tokens out of any file entirely and inject them via the MCP registration's env block (preferred — see below).
Note — shared-token model at CPP. A single pair of Martus tokens (one CPP tenant, one ANN tenant) is shared across every CPP user who deploys this MCP (Fr. Norman, Steph, Ann, etc.). Unlike cpp-espace-mcp (per-user keys; eSpace audits by key) and MS365 (per-user OAuth), Martus uses a shared-token model at CPP. New users receive the existing tokens from Fr. Norman directly — do not generate per-user tokens.
# .env (gitignored)
MARTUS_API_TOKEN=<shared token — from Fr. Norman>
MARTUS_TENANT_LABEL=martus-cppAdd two entries to your vault's .mcp.json:
{
"mcpServers": {
"martus-cpp": {
"command": "node",
"args": [
"C:\\Users\\jmnorman\\code\\cpp-martus-mcp\\dist\\index.js"
],
"env": {
"MARTUS_API_TOKEN": "<CPP token>",
"MARTUS_TENANT_LABEL": "martus-cpp"
}
},
"martus-ann": {
"command": "node",
"args": [
"C:\\Users\\jmnorman\\code\\cpp-martus-mcp\\dist\\index.js"
],
"env": {
"MARTUS_API_TOKEN": "<ANN token>",
"MARTUS_TENANT_LABEL": "martus-ann"
}
}
}
}Restart Claude Code. You should now see martus-cpp and martus-ann both listed as Connected in claude mcp list.
All tools are list-* or get-*. Every endpoint uses paginated responses (1000 rows/page); call with Page: 1 first, then walk forward using the TotalPages field in the response.
| Tool | Martus endpoint | Purpose |
|---|---|---|
list-accounts |
GET /Accounts.List |
Paged chart of accounts |
list-account-categories |
GET /Accounts.Categories |
Account category definitions |
list-account-reporting-groups |
GET /Accounts.ReportingGroups |
Account reporting groups |
list-budgets |
GET /Budgets.List |
All budgets (filterable by fiscal Year) |
get-budget-data |
GET /Budgets.Data |
Actuals/planned numbers for a budget |
list-budget-worksheets |
GET /Budgets.Worksheets |
Worksheets within a budget |
list-budget-worksheet-lines |
GET /Budgets.WorksheetLines |
Per-line account + dimension values |
list-budget-spws |
GET /Budgets.Spws |
Static Period Worksheets |
list-budget-spw-lines |
GET /Budgets.SpwLines |
Per-period SPW line items |
list-dimensions |
GET /Dimensions.List |
Dimensions defined in the tenant |
list-dimension-items |
GET /Dimensions.Items |
Values within a single dimension |
src/index.ts MCP tool registrations
src/provider.ts Singleton — composes client + services
src/client/
http-client.ts Generic HTTP w/ Token header
martus-client.ts Auth-aware wrapper
src/services/
accounts.service.ts
budgets.service.ts
dimensions.service.ts
src/models/ AUTO-GENERATED from swagger.json — regenerate with
`npm run generate:types`
src/scripts/
generate-types.ts Codegen reading ../swagger.json
swagger.json Archived OpenAPI 3.1 spec (refreshed alongside the
RunBook KB pack's quarterly schema refresh)
npm install
npm run generate:types # Regenerate src/models/ from swagger.json
npm run build # tsc → dist/
npm run dev # tsx src/index.ts (stdio loop; useful for smoke)
npm run typecheck # tsc --noEmitWhen Martus ships new endpoints (watch release notes in the Freshdesk help mirror at CPP RunBook/3-Resources/martus/raw/):
cd %USERPROFILE%\code\cpp-martus-mcp
curl -o swagger.json https://prod5.martus.app/swagger/v1/swagger.json
npm run generate:types
npm run buildRead-only by design. If this repo ever grows write tools:
- Stop before executing.
- Show the user exactly what will be created/modified (tenant, budget, account, amount).
- Wait for explicit confirmation.
A mistake in Martus can propagate back into finance committee pre-reads / the annual report. Treat writes with the same care as direct QuickBooks writes.
MIT — see LICENSE.