@triplebooks/mcp
v0.1.0
Published
TripleBooks MCP server — connect Claude Desktop, Cursor, and other MCP clients to your books via the workspace API.
Downloads
101
Maintainers
Readme
@triplebooks/mcp
Stdio Model Context Protocol server that proxies tool calls to the workspace JSON API with Authorization: Bearer ak_… and x-entity-id. No tenant DB or Cortex logic lives here—only HTTP.
Quick start (users)
After this package is published to npm, use Settings → MCP or:
{
"mcpServers": {
"triplebooks": {
"command": "npx",
"args": ["-y", "@triplebooks/mcp@latest"],
"env": {
"AXIOMATIC_BASE_URL": "https://app.triplebooks.com",
"AXIOMATIC_API_KEY": "ak_…",
"AXIOMATIC_ENTITY_ID": "…"
}
}
}
}Quick start (developers)
Copy
.env.exampleto.env.localand setAXIOMATIC_BASE_URL,AXIOMATIC_API_KEY,AXIOMATIC_ENTITY_ID.Run the workspace (local dev, or
pnpm dev:workspace:prod-envfrom repo root for production-backed env +http://localhost:…).- Cursor auto-start: repo
.cursor/hooks.jsonruns onsessionStartand callsplatform/scripts/ensure-workspace-dev-server.sh— starts prod-env on port 3010 when down, kills stale listeners when the port is wedged. Manual:pnpm ensure:dev-serverfromplatform/.
- Cursor auto-start: repo
From this directory:
pnpm mcp-doctor pnpm buildPoint Cursor / Claude Desktop at
dist/cli.js(local mode) or use npx after publish. In-app: Settings → MCP.
Tool UX: installToolUx adds human titles (e.g. Ledger: List journal) and MCP annotations (readOnlyHint, destructiveHint, openWorldHint) on every tool. After pulling, pnpm build here and fully quit Claude so stdio reloads.
Publish to npm
cd apps/mcp
pnpm pack:npm
npm publish --access publicRequires npm auth for the @triplebooks scope. build:cli bundles workspace code (including @axiomatic/crypto sanitization) into dist/cli.js so the published tarball only depends on @modelcontextprotocol/sdk and zod.
Production usage (HTTP tool path)
What “production” means for MCP: the workspace process behind AXIOMATIC_BASE_URL resolves real control-plane and tenant data, and your key is a real ak_… with correct RBAC scopes. That is true when:
AXIOMATIC_BASE_URLis your deployed app origin, orAXIOMATIC_BASE_URLis localhost while the workspace runs with prod-backed env (pnpm dev:workspace:prod-env).
Both are valid; only the network hop to Next changes.
Entity alignment: API keys are pinned to one entity. AXIOMATIC_ENTITY_ID must match that key; withTenant rejects mismatches.
Scopes: Create keys from the workspace (Ledger API keys UI or /api/ledger/api-keys). Use least privilege—e.g. read:ledger for ledger/cap-table/entity_get; read:settings for entity_list_addresses; write:settings for entity_upsert_address and entity_update_profile (NAICS/tax ID); read:crm for CRM reads; write:crm for crm_update_company, crm_create_*, crm_advance_lead_stage, etc.; read:sales / write:sales for quotes/orders/deal pricing and fundraising_create_pipeline_entry; read:admin for parties_list / parties_get; write:admin for parties_create; read:sales for fundraising_get_investor; add read:invoicing / write:invoicing for invoice automation (including projects_invoice_milestone); read:inventory / write:inventory for catalog SKUs; read:projects / write:projects for client projects and internal Work. The API key owner’s RBAC still applies on every route.
Common URL / env mistakes
AXIOMATIC_BASE_URLdoesn’t match where Next is listening (wrong host/port after a port change).- Production
ak_key against a dev workspace that points at a different tenant DB than you expect—or the reverse—so entity UUIDs don’t exist. AXIOMATIC_ENTITY_IDdoesn’t match the entity the key was issued for (middleware returns 403).- 402 = tenant credits exhausted, not a “wrong URL” (but looks like random API failure if you don’t check body).
Credits: API-key requests debit api_calls per hit. Tenant balance 0 returns 402 (credits_exhausted). Top up tenant credits in-app if tools suddenly fail.
Secrets: Never commit .env.local. Prefer separate keys for read-only vs automation that can write.
Directory (entity, CRM, parties)
| Tool | Purpose |
|------|---------|
| entity_get | Active entity profile (read:ledger) |
| entity_update_profile | Set NAICS / tax ID (EIN, encrypted) / industry / jurisdiction (write:settings, OWNER/ADMIN) |
| entity_list_addresses | Entity HQ / registered / mailing addresses (read:settings) |
| entity_upsert_address | Create/update entity self-address (write:settings, OWNER/ADMIN) |
| crm_list_companies | Paginated companies (read:crm) |
| crm_get_company | Company + counterparty detail |
| crm_create_company | Create company (write:crm) |
| crm_update_company | Patch company; metadata.billingTenantId for credits grant mapping (write:crm) |
| crm_list_company_addresses | Addresses for a company |
| crm_upsert_company_address | Create/update company address (write:crm) |
| crm_list_contacts | People; optional companyId filter |
| crm_get_contact | Contact detail |
| crm_create_contact | Create contact (write:crm) |
| crm_update_contact | Patch contact (write:crm) |
| crm_list_leads | Leads/opportunities |
| crm_get_lead | Single lead detail |
| crm_list_pipeline_stages | Pipeline stages + lead counts |
| crm_create_pipeline_stage | Add open stage (write:crm, crm.pipeline.manage) |
| crm_update_pipeline_stage | Rename/restyle stage (write:crm, crm.pipeline.manage) |
| crm_delete_pipeline_stage | Delete open stage; optional lead reassignment (write:crm) |
| crm_reorder_pipeline_stages | Reorder stage columns (write:crm) |
| crm_create_lead | Create lead (write:crm) |
| crm_update_lead | Patch lead fields (write:crm) |
| crm_advance_lead_stage | Move lead stage; won automation (write:crm) |
| crm_batch_update_companies | Uniform patch many companies (write:crm) |
| crm_archive_company | Set company status inactive (write:crm) |
| crm_delete_company | Permanent company delete (write:crm) |
| crm_search_prospecting_places | Google Maps Discover search (read:crm) |
| crm_get_prospecting_place | Place details by id (read:crm) |
| crm_import_prospect_from_place | Import place → prospect company (write:crm) |
| crm_discover_company_contacts | Website contact/email discovery (write:crm) |
| crm_enrich_company_from_website | Website crawl + apply proposals (write:crm) |
| crm_start_succession_scan | Queue batch succession scan (write:admin + crm.leads.edit) |
| crm_get_succession_scan_run | Scan run progress + proposals (read:admin) |
| crm_list_outbound_email_connections | Gmail/Resend connections for CRM drafts (read:crm) |
| crm_create_email_draft | Create CRM email draft only (Gmail Drafts + outbound_messages); optional attachmentDocumentIds (write:crm) |
| crm_send_email_draft | Send an approved draft by outboundMessageId (write:crm) |
| parties_list | Legal parties search (read:admin, governance.view) |
| parties_get | Party detail (seats, holders, links) |
| parties_create | Create or dedupe legal party (write:admin, governance.manage) |
| fundraising_get_investor | Investor profile + pipeline (read:sales, fundraising.view) |
| fundraising_create_pipeline_entry | Add prospect to a round pipeline (write:sales, fundraising.pipeline.manage) |
| fundraising_list_pipeline_stages | Round pipeline stages + entry counts (read:sales) |
| fundraising_create_pipeline_stage | Add open stage; optional insertAfterStageId (write:sales, fundraising.pipeline.manage) |
| fundraising_update_pipeline_stage | Rename/restyle stage (write:sales) |
| fundraising_delete_pipeline_stage | Delete open stage; optional entry reassignment (write:sales) |
| fundraising_reorder_pipeline_stages | Reorder all stage columns for a round (write:sales) |
Documents & RAG
| Tool | Purpose |
|------|---------|
| documents_list_folders | Folder tree (read:documents) |
| documents_list | List docs; filter folderId or linkableType+linkableId (e.g. counterparty) |
| documents_get_text | Full text / PDF extract / OCR for one document |
| documents_list_revisions | Revision history (number, source, label, author) for editable docs |
| documents_get_revision | One revision: metadata, text body, derived PDFs linked to that revision |
| documents_get_derivation | PDF lineage → source document + revision (pass child PDF id) |
| documents_create_revision_checkpoint | Named version checkpoint without changing live file (write:documents) |
| documents_update_revision_label | Rename/clear revision label (write:documents) |
| documents_restore_revision | Restore prior revision as live doc (write:documents) |
| documents_update_content | Replace text body; prior version auto-snapshotted (write:documents) |
| documents_update_metadata | Rename or retag any file — PDF, PNG, MP4, etc. (write:documents) |
| documents_replace_file | Replace binary bytes from a local path on the MCP host (write:documents) |
| documents_save_html_pdf | Server-side HTML → PDF save/replace in folder (write:documents) |
| documents_list_signature_requests | List e-sign envelopes; optional status filter (read:documents) |
| documents_get_signature_request | Detail + signers + placed field overlays / fieldCount (read:documents) |
| documents_create_signature_request | Create draft (or send: true); reuses fielded draft for same PDF (write:documents, documents.sign) |
| documents_send_signature_request | Send draft invitations (write:documents, documents.sign) |
| documents_cancel_signature_request | Cancel envelope (write:documents, documents.sign) |
| documents_remind_signature_request | Reminder emails to pending signers (write:documents, documents.sign) |
| data_room_sync | Local export-pdfs.sh + data room upload scripts (requires pandoc + Chrome) |
| kb_retrieve_context | Semantic RAG (documents + optional kb_page scope); read:documents |
| kb_list_spaces | List KB spaces (read:kb) |
| kb_list_pages | Pages in a space (read:kb) |
| kb_create_space | Create KB space (write:kb) |
| kb_create_page | Create KB page with markdown (write:kb) |
| kb_update_page | Patch page body / publish (write:kb) |
| kb_search_lexical | Full-text search KB titles (read:kb) |
Add read:documents to your API key. After upload, OCR + embedding index runs via the document pipeline/cron — then RAG returns chunks.
Invoicing & printable HTML
MCP tools (requires scopes on the key + RBAC on the key owner):
| Tool | Purpose |
|------|---------|
| invoicing_list_invoices | Paginated invoice list (GET /api/invoicing/invoices — invoicing.view or ledger.view on owner) |
| invoicing_get_invoice | Full invoice JSON (GET /api/invoicing/invoices/:id — invoicing.view on key owner) |
| invoicing_create_invoice | Create DRAFT invoice (POST /api/invoicing/invoices — write:invoicing, ledger.post) |
| invoicing_update_invoice | Update DRAFT invoice lines/metadata (PATCH without action) |
| invoicing_invoice_action | send / void / unvoid / record_payment / mark_paid / void_payment |
| invoicing_void_payment | Void one payment (reverse payment/deposit JEs; reopen invoice to SENT when unpaid) |
| invoicing_backfill_line_catalog | Attach catalog SKU snapshots to existing lines (PAID ok) without changing amounts |
| invoicing_invoice_print_meta | Print / template metadata without full HTML: printPath, default vs invoicing.print template, MIME OK for merge, totals, line sample |
| invoicing_invoice_send_readiness | Send pre-flight: customer billing email, Gmail/Resend connections, HTML_TO_PDF_ENABLED, blockers/warnings, suggested billing period |
| invoicing_send_invoice | Email invoice PDF (same as Invoicing → Send): posts AR, marks SENT. Optional to / deliveryMemo; defaults connection + billing period from readiness. dryRun: true previews without emailing |
| invoicing_list_outbound_email_connections | Active outbound email connections (connectionId + channel for send) |
| invoicing_list_print_templates | Rows with category invoicing.print (linked document for HTML merge) — needs read:documents |
| invoicing_ar_subledger_grid | AR subledger vs GL (moduleKey=ar slice of subledger grid); optional onlyDrift |
Add read:invoicing / write:invoicing to your API key for invoice automation.
Sales — quotes, orders, deal pricing
| Tool | Purpose |
|------|---------|
| sales_list_quotes | Paginated quotes — read:sales |
| sales_get_quote | Quote + lines + linked Order Form hint |
| sales_update_quote | Patch quote; lines on draft with milestone_service — write:sales |
| sales_propose_quote_milestones_from_document | Propose milestone rows from OF/SOW (no write) — write:sales |
| sales_convert_quote_to_order | Quote → draft order (full line copy) — write:sales |
| sales_list_orders / sales_get_order | Order list + detail — read:sales |
| sales_update_order | Confirm order (spawn project), finalize one-time invoice — write:sales |
Catalog / inventory
| Tool | Purpose |
|------|---------|
| catalog_search | Line-picker search (GET /api/inventory/catalog) — read:inventory |
| inventory_list_items | Paginated item list (GET /api/inventory?view=items) |
| inventory_get_item | Single item detail |
| inventory_create_item | Create SKU (POST /api/inventory create-item) — write:inventory |
| inventory_update_item | Update SKU (PUT /api/inventory) |
Client projects / service jobs
| Tool | Purpose |
|------|---------|
| projects_list | List client jobs/projects (GET /api/projects/services) — read:projects |
| projects_get | Job detail with lines/milestones |
| projects_get_finance | WIP, recognized revenue, billable milestones + invoiceId |
| projects_invoice_milestone | DRAFT AR invoice for one milestone (idempotent) — write:invoicing |
| projects_create | Create client project — write:projects |
| projects_update | Patch job status, notes, dates |
Internal Work backlog uses work_* tools (same projects API scope).
Period close (checklists + soft/hard close)
| Tool | Purpose |
|------|---------|
| ledger_close_calendar | Templates / checklists / checklist-items (+ system checks) — read:ledger |
| ledger_close_dashboard | Focus period, checklist progress, blockers — read:ledger |
| ledger_generate_close_checklist | Spawn checklist for a period — write:ledger + ledger.close |
| ledger_update_close_checklist_item | Status / assignee / notes on one item — write:ledger + ledger.close |
| ledger_update_close_checklist | Checklist IN_PROGRESS / COMPLETED — write:ledger + ledger.close |
| ledger_close_period | SOFT_CLOSE / HARD_CLOSE — write:ledger + ledger.close |
| ledger_reopen_period | Reopen soft/hard-closed period — write:ledger + ledger.close |
Shipped on main in eebd227d. Cursor loads tsx src/index.ts; after a pull that adds tools, run Developer: Reload Window or the IDE keeps a stale tool list.
Audit chain (tamper-evident log)
| Tool | Purpose |
|------|---------|
| ledger_audit_chain_summary | Entry counts, head/genesis hashes, needsBackfill |
| ledger_audit_chain_verify | Full SHA-256 chain recompute — valid, brokenAt |
| ledger_audit_chain_inspect | Diagnose break: stored vs recomputed hashes, breakReason, duplicate sequences |
| ledger_query_audit_trail | Paginated human-readable rows (no hash fields) |
Requires read:ledger scope and key owner admin.view RBAC (same as /accounting/audit-verify UI).
Verification
pnpm mcp-doctorLoads .env.local, validates env, GETs /api/ledger/subledgers?action=specs, lists period-close tool names, and checks GET /api/ledger/close-dashboard.
Extra smoke checks (cap-table summary; 403 is OK if the key lacks cap_table.view):
pnpm mcp-doctor -- --smokeSpot-check tools you rely on (same URL + headers as MCP): cap table summary, trial balance, etc.
Cursor
Repo .cursor/mcp.json runs tsx src/index.ts with envFile → platform/apps/mcp/.env.local.
Composer agent server id is often project-0-axiomatic-axiomatic (not axiomatic); see workspace Cursor rules.
Missing tools after git pull? Doctor passing only proves the API is reachable. Cursor caches the MCP tool list until Command Palette → Developer: Reload Window (or toggle the server off/on). Claude Desktop users who run node dist/index.js must also pnpm build in apps/mcp after tool registration changes.
Second MCP server (another entity)
Copy .env.example to platform/apps/mcp/.env.harvest-blade.local (gitignored) with a separate ak_… key and that entity's AXIOMATIC_ENTITY_ID. The repo registers server axiomatic-hb in .cursor/mcp.json (short id — Cursor limits combined server+tool name length). Reload Cursor after editing; agent server id becomes project-0-axiomatic-axiomatic-hb.
CapSign Inc. (separate tenant): .env.capsign.local + server axiomatic-capsign. Verify: pnpm mcp-doctor:capsign. Agent id: project-0-axiomatic-axiomatic-capsign.
Import / QuickBooks inspection
| Tool | Purpose |
|------|---------|
| ledger_list_import_batches | Batch list with counts and match status |
| ledger_get_import_batch | Rows in one batch |
| ledger_staged_import_breakdown | Group by source_format + status; pass importMethod: QUICKBOOKS |
| ledger_save_match_pattern | Save bank-feed match_patterns rule from a NEEDS_REVIEW row; optional reapply to siblings |
| ledger_list_match_patterns | List all Rules (match_patterns) for the entity |
| ledger_upsert_match_pattern | Create or update a rule by counterpartyPattern |
| ledger_update_match_pattern | Patch one rule by id |
| ledger_delete_match_pattern | Delete one rule by id |
| ledger_get_settings | Read Autopilot mode, confidence threshold, require AI approval (read:ledger) |
| ledger_update_settings | Patch Autopilot / controls (write:ledger, ledger.edit) |
| integrations_list_connections | QBO/Plaid connection + last sync error |
| integrations_get_sync_logs | Per-connection sync history |
| ledger_migration_sources | Migration wizard connected sources |
Requires read:ledger on the API key.
Claude Desktop
Recommended (after npm publish):
{
"mcpServers": {
"triplebooks": {
"command": "npx",
"args": ["-y", "@triplebooks/mcp@latest"],
"env": {
"AXIOMATIC_BASE_URL": "https://app.triplebooks.com",
"AXIOMATIC_API_KEY": "ak_…",
"AXIOMATIC_ENTITY_ID": "…"
}
}
}
}Local checkout (after pnpm build):
{
"mcpServers": {
"triplebooks": {
"command": "node",
"args": ["/ABS/PATH/platform/apps/mcp/dist/cli.js"],
"env": {
"AXIOMATIC_BASE_URL": "http://localhost:3010",
"AXIOMATIC_API_KEY": "ak_…",
"AXIOMATIC_ENTITY_ID": "…"
}
}
}
}During development you can use pnpm exec tsx src/index.ts with the same env map.
CI
pnpm build at the repo root runs Turbo build, which includes @triplebooks/mcp (tsc + bundled dist/cli.js). The GitHub Actions TypeScript workflow runs that full build so MCP must compile on every PR.
Remote / hosted MCP (next stage)
Claude Desktop and Cursor default to local stdio (spawn process). Hosting MCP over HTTP/SSE for claude.ai requires remote MCP + OAuth — Stage 3.
Scripts
| Script | Purpose |
|--------|---------|
| pnpm build | tsc + bundled dist/cli.js |
| pnpm pack:npm | Build and create an npm tarball for inspection |
| pnpm mcp-doctor | Env + subledger specs + close-dashboard API + period-close tool name list |
| pnpm mcp-doctor -- --smoke | Above + optional cap-table summary + audit chain verify |
| pnpm start | node dist/cli.js (stdio server) |
See src/index.ts header comment for full context.
