MCP server (AI agents)

The Contractor Codex ships its own Model Context Protocol server. Point Claude Desktop, Claude Code, or any MCP-compatible client at it, hand it an API key, and the model can run your portal — clock in and out, send invoices, draft quotes and contracts, mark payments paid, refund mistakes, post updates to your clients — without you opening the admin UI.

It sits on the same backend the admin and client portals use. No separate database, no separate auth. Every agent call lands an audit-log row, so you can see exactly what the model did and when.

Endpoint/api/mcp
AuthBearer ctsp_live_…
TransportStreamable HTTP
Tools54
Money actions2-step confirm
Quick start — 3 steps
  1. Enable MCP. Settings → AI & Agents → MCP Server (inbound) → toggle on.
  2. Create a key. Same screen → Manage API Keys → New key. Pick the write groups to grant. The full key shows once — copy it now.
  3. Wire it into your client. Drop this into your MCP client config:
claude_desktop_config.json
{
"mcpServers": {
  "contractor-codex": {
    "url": "https://contractorcodex.com/api/mcp",
    "headers": { "Authorization": "Bearer YOUR_API_KEY" }
  }
}
}

Using the Claude Code CLI instead?

terminal
claude mcp add contractor-codex --transport http \
--url https://contractorcodex.com/api/mcp \
--header "Authorization: Bearer YOUR_API_KEY"

Once connected, the client lists all 54 tools automatically and the agent can start working.

The endpoint is locked until you do both

Until MCP is enabled and a valid key authenticates, every request is rejected — 401 without a valid key, 403 for your org until the toggle is on. A leaked key alone can't do anything if MCP is off.

How it works

Claude (or any MCP client)
  ↓  JSON-RPC over HTTPS, Bearer-token auth
https://contractorcodex.com/api/mcp
  ↓  validates token, checks your org's mcp_enabled flag
runAgentTool() dispatcher
  ↓  same handler as the /api/agent/<tool> HTTP route
your data

The MCP transport and the HTTP /api/agent/* routes call the same handler functions. Adding a tool exposes it on both surfaces at once — they never drift apart.

The mental model — three layers

Reading top to bottom, you get safer:

LayerWhat it doesExamples
ReadSurface data; no side effects.get_context, get_projects, find_client, daily_briefing
Write (no comms)Mutate state; no email, no money.start_work_session, add_session_note, create_project, update_client
Write (comms or money)Sends email, moves money, or destroys state. Two-step confirm required.send_invoice, mark_paid, send_quote, refund_invoice, archive_client

How the agent asks the right questions

Every write tool that takes a "which one?" parameter returns a structured needs_input response when called without it — instead of erroring. The response carries questions (what to ask), inventory (the real options — recent clients, draft invoices, available templates…), and next_step. The agent grounds in your actual data instead of inventing an ID.

Example — “mark the Henderson invoice paid”
AGENT → mark_paid()                              (no args)
      ← needs_input · inventory.open_invoices: [Henderson $2,400, Mosi LLC $800, …]
AGENT  "I see two open invoices for Henderson — $2,400 and $1,100. Which one?"
USER   "The 2400."
AGENT → mark_paid(snapshotId="…", method="check")
      ← confirmation_required · preview + confirmation_token
AGENT  "Mark the $2,400 Henderson invoice paid via check?"
USER   "Yes."
AGENT → mark_paid(snapshotId="…", method="check", confirmation_token="ct_…")
      ← marked_paid

Two structured round-trips before anything changes. No hallucinated IDs, no guessed amounts.

The two-step confirmation rule

Every tool that sends an email, moves money, or destructively changes state goes through a mandatory two-step confirm, regardless of dollar amount:

  1. First call returns confirmation_required + a confirmation_token + a preview the agent reads aloud.
  2. Second call repeats the params plus the token. The tool executes.

Tokens expire after 60 seconds and are bound to (keyId, action, paramsHash) — they can't be replayed or reused for a different action.

send_client_message is intentionally single-call — voice agents read the body aloud before sending, and the round-trip would slow down high-frequency client comms.

Auth model

ConceptDetail
Scopeadmin (any client in your org) or client (bound to one client). Keys minted in this screen are admin-scoped.
StorageStored as SHA-256 hashes. The full key shows once at creation.
ExpirySet per key via expiresAt. No auto-rotation — revoke and reissue.
Org gateThe endpoint returns 403 for your org until mcp_enabled is on (and 401 for any request without a valid key).
AuditEvery call writes an audit-log row (key, tool, status, IP, duration).
Rate limit30 calls/min per admin key, 60/min per client key (per-tool overrides available). String numeric params are accepted everywhere.

For a read-only key, untick every write group at creation — read tools require no write permission.

The tool catalog

54 tools, grouped by what they touch. Expand a group to see its tools.

Context & identityany4
ToolPurpose
get_contextThe boot call. Org name, your scope, system instructions, workflows, an inventory snapshot, and the endpoint catalog. Call this first.
whoamiThe key's scope + bound client, if any. Lightweight identity check.
get_helpKeyword search across this docs site.
get_brandingBusiness name, support email, portal greeting, accent color, logo, default currency.
Admin overview — readadmin6
ToolPurpose
admin_get_clientsAll active clients with health scores and project counts.
admin_get_insightsRevenue (net of refunds), overdue counts, dunning stats, health distribution.
admin_get_activityOrg-wide activity log.
daily_briefing"What needs my attention today?" — pending approvals, drafts, overdue invoices, payments collected (net of refunds).
overdue_arOverdue invoices with aging buckets.
quote_status_checkQuote pipeline: open, signed, expiring.
Search & lookup4
ToolPurpose
find_clientResolve a natural-language name into a customerId. (admin)
find_projectResolve a project name into a projectId.
find_recentLast N quotes / contracts / invoices / clients / projects, newest first. (admin)
find_active_sessionThe work session currently running, if any.
Client data — readclient4
ToolPurpose
get_projectsA client's projects with session status, hours, billing profile.
get_billingA client's invoices plus a canonical summary (owed, net paid, invoiced, refunded) and retainer usage.
get_notificationsA client's recent notifications.
get_activityA client's own activity log.
Settings & status — readadmin3
ToolPurpose
get_settingsFull admin-settings map (timezone, invoice defaults, payment methods, dunning, late fees, quote defaults).
get_billing_profileLegal name, DBA, entity type, EIN last-4, address, tax status, plus a complete boolean.
get_stripe_connect_statusDB flags plus a live Stripe diagnostics call (charges / details enabled). Handles webhook lag.
Catalogadmin5
ToolPurpose
list_quote_templatesSaved whole-quote templates.
list_line_item_templatesThe line-item library (built-in starter items + your own).
list_surchargesSurcharge templates.
list_contract_templatesThe contract-template library. Built-in starter templates auto-seed the first time it's called.
save_to_catalogPromote freeform line items / surcharges from a quote into reusable templates.
Clients — writeadmin4
ToolPurpose
create_clientFind-or-create by (org, email). Readies a portal-signup invite.
update_clientPatch name, phone, company, subscription, industry, timezone. Elicits a recent-clients list if customerId is missing.
archive_clientSoft-archive. Two-step confirm. Elicits if customerId missing.
restore_clientClears archive/delete flags. Lists archived clients if customerId missing.
Projects — writeadmin4
ToolPurpose
create_projectLocks a pricing snapshot. Elicits clients + billing profiles if anything's missing.
update_projectPatch name, description, billing profile, budget.
complete_projectMarks done + completion note. Refuses if a session is still running.
add_project_surchargeMid-job surcharge. Auto-approved when pre-disclosed, otherwise emails the client for approval. Confirm.
Sessionsadmin5
ToolPurpose
start_work_sessionClock in. Elicits active projects if projectId missing; returns a conflict if already running elsewhere.
stop_work_sessionClock out. Auto-resolves the active session if sessionId omitted.
add_session_noteAppend a note to a session.
set_session_billableFlip a session's billable flag. Refuses if the session is already on a sent invoice — void and re-issue first.
correct_sessionAdjust a session's start / end times. Same invoiced-session guard.
Project updates & notes3
ToolPurpose
list_project_updatesNewest-first thread with replies.
post_project_updateAdmin status note — emails the client and shows on their dashboard. Two-step confirm. (admin)
reply_to_project_updateThreaded reply. Notifies admins for client replies; no email.
Quotes & contractsadmin5
ToolPurpose
create_quoteThe marquee tool. Cold-call mode: tell it the client wants a quote and it asks every question (recipient, line items, billing mode, deposit %, expiry) with tailored catalog suggestions.
send_quoteEmail the signing link, pre-signed with the key-owner's saved signature. Two-step confirm. Elicits draft quotes.
create_contractDraft an agreement or change order from a template. Elicits a template picker (built-ins included) when templateId is missing; find-or-creates the client.
send_contractSame as send_quote for agreements / change orders. Two-step confirm.
request_approvalClient approval flow for budget overruns or undisclosed surcharges. Two-step confirm.
Invoices & paymentsadmin6
ToolPurpose
send_invoiceFinalize + email a draft. Two-step confirm. Elicits drafts. Supports dry_run.
mark_paidOut-of-band payment (check, wire, cash). Supports partial amounts (amount_paid_cents) — adds to prior payments and flips to paid only when fully covered. Two-step confirm.
list_invoice_draftsSnapshots with no Stripe invoice or draft status. Filterable by client.
void_invoiceVoid an open invoice in Stripe. Two-step confirm. (Refuses a paid invoice — refund instead.)
refund_invoiceRefund a paid invoice; partials supported; platform fee reverses proportionally. Two-step confirm.
mark_invoice_uncollectibleWrite off an open invoice as bad debt. Two-step confirm.
Client commsadmin1
ToolPurpose
send_client_messageFree-form email to a client. Single-call (no token) — the agent reads the body aloud first. Elicits recipient + subject + body when missing.
Auto pre-sign on send_quote / send_contract

Both look up the saved signature of the admin who minted the key and stamp it before the client email goes out — matching the human composer flow. The signer of record is whoever owns the key, so multi-admin teams get correct attribution. No saved signature → the send still goes through unsigned and the response returns a warning the agent should read back. Save one at Settings → Signatures and every future send is pre-signed. (Change orders skip pre-sign — they amend an already-signed parent.)

Not on the agent surface yet

Visible to humans in /admin but no MCP tool yet: calendar events, accepting the inbound approval queue, project-level discounts, recurring product costs, delivery requests, settings writes, and org-team CRUD. Need one end-to-end? Contact support — we ship new tools fast.

Troubleshooting

Stale tool list after a deploy

If your client cached the tool list before a platform update, restart the MCP connection — the next initialize refreshes everything.

  • Confirmation token expired — tokens TTL at 60s. Re-call the same tool without the token; the response carries a fresh preview.
  • Insufficient permissions — the key lacks the right write group. Edit it under Settings → API Keys.
  • Customer not found in this organization — an admin key passed a customerId from a different org. Keys never see across orgs.
  • Stripe Connect is not configured — the org hasn't finished Connect onboarding. Call get_stripe_connect_status to see where they are; you can't send or collect invoices without it.

Best practices for client developers

  1. Call get_context first. It returns the workflows map, inventory snapshot, system instructions, and endpoint catalog — the whole session leans on it.
  2. Never pre-fill IDs from training data. When a tool returns needs_input with an inventory, use those IDs verbatim. Hallucinated IDs are the #1 failure mode.
  3. Read previews back to the user word-for-word before the second (committing) call.
  4. On second thoughts, start fresh. If the user changes their mind between calls, re-ask the params and issue a new token.
  5. Use dry_run="true" on send_invoice / mark_paid to preview the dollar amount without locking a confirmation token.

Audit & observability

Every call lands an AgentAuditLog row — tool name, status, IP + user agent, duration, and the linked key. View recent calls under Settings → API Keys → Activity. The same data feeds daily_briefing's activity rollup (calls per day, error rate, top tools).