Back to site

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 and database the admin and client portals use, but it has a separate authentication plane. MCP clients do not use your Clerk browser session: the ctsp_live_… Bearer key authenticates each request, and its scope and permissions limit what the caller can do. Every MCP tool call lands an audit-log row for operations review.

Endpoint/api/mcp
AuthBearer ctsp_live_…
TransportStreamable HTTP
Tools89
Money actions2-step confirm
Quick start — 3 steps
  1. Enable MCP. Open /admin/settings/mcp and flip Enable MCP Server on the Setup tab.
  2. Create a key. Same screen → Create API Key for MCP. Pick the write groups to grant. The full key shows once, so copy it now. (Key management lives in API keys.)
  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 89 tools automatically and the agent can start working. (Strictly, tools/list shows 88: get_openapi is HTTP-only, so it doesn't appear on the MCP surface.)

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. The assistant reads that preview and waits for your explicit approval. Only then may it repeat the exact params plus the token so 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. Receiving a token is not approval, and the assistant must never run both calls automatically.

send_client_message follows the same two-step confirm — it puts a real email in a client's inbox, so a human approval sits between the agent and the send.

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.
Revocation and expiryRevoke and reissue keys from Settings. The runtime also rejects a key with an expired expiresAt; the current creation screen does not yet offer an expiry control.
Org gateThe endpoint returns 403 for your org until mcp_enabled is on (and 401 for any request without a valid key).
AuditEvery tool call writes an audit-log row (key, tool, status, IP, duration). Initialization and tool discovery do not execute a tool.
Rate limit30 calls/min per admin key, 60/min per client key, counted across all tools. A few expensive tools have a tighter cap of their own (the AI quote drafter is 5/min, the draft editor 10/min), counted separately so ordinary calls never use it up. 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

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

Context & identityany5
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, portal theme, accent color (the effective one: custom or the selected theme's own), logo, default currency.
get_openapiThe full machine-readable OpenAPI 3 spec for every endpoint — params, schemas, and required flags. For codegen or deep introspection.
Admin overview — readadmin10
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.
get_advisor_briefingThe Advisor's cached daily briefing: AI headline, narrative, and open recommendations with their computed evidence. Never triggers an AI call.
codex_money_summaryRetired: always returns an error pointing at /admin/codex, since Codex bank-feed data stays in the authenticated pages. Use get_cash_projection for money expected in, or admin_get_insights for invoice revenue.
get_action_centerThe org's open Action Center items (failed/overdue invoices, new and stale leads, unviewed or expiring quotes, pending cancellations, countersigns, calendar sync errors), each with severity, title, and a link. Optional severity filter. Read-only.
get_cash_projectionWhen money you are already owed is expected to land, week by week, placed by each client's real payment habits. Money coming in only; never present it as cash flow or a net position.
Website leadsadmin3
ToolPurpose
list_website_leadsQuote requests submitted through your own website (the Leads inbox).
accept_website_leadTurn a lead into a client (links by email or creates a new record). Two-step confirm.
dismiss_website_leadDismiss a new or spam lead. Reversible from the UI.
Advisor actionsadmin1
ToolPurpose
set_advisor_item_stateCheck off an Advisor recommendation: done, dismissed, or snoozed for 7 days.
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. Optional customerId narrows any kind to one client. (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, and start/end dates.
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 — readadmin5
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.
get_email_statusWhether mail sends from the org's own verified domain or the shared address, whether the /admin/email inbox is unlocked, and where client replies land. Read-only.
list_importsThe 20 most recent CSV import batches from the import wizard: kind, file name, status, and per-row tallies. Optional status filter. Read-only; starting or committing an import stays in the wizard UI.
Catalogadmin7
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.
list_pricing_ratesThe pricing rate card — retainer tiers, hourly rates, block packages, and fixed-fee rows, with their codes and included hours.
create_service_planGuided. Build a reusable Service Plan — net days, deposit, after-hours/weekend multipliers, min increment, auto-approve ceiling, tax behavior, schedule template. Only name required; the rest inherit org defaults.
Service plans, scheduling, costs & configadmin8
ToolPurpose
attach_service_planGuided. Apply a Service Plan to a project — locks its billing terms and regenerates the client's Billing Terms document. Two-step confirm — the preview reads back the resolved terms about to lock.
add_project_discountGuided. Add a standing discount to a project (percentage / fixed / per-hour) that recurs into billing as a negative line item.
add_project_expenseGuided. Record a recurring product cost — a third-party cost the client pays the vendor directly (a disclosure, never invoiced; no email goes out). Two-step confirm.
get_calendar_eventsList events in a date window (defaults to the next 7 days in your timezone) — the same rows the calendar page shows, auto entries and Google imports included. Optional client/project filters. Read-only.
create_calendar_eventGuided. Add a calendar event (UTC times; optional client/project link and client visibility). Pushes to Google when the key creator's calendar is connected.
create_delivery_requestGuided. Open a work request on the Request-to-Delivery board for a client. Two-step confirm — the card shows on the client's portal immediately.
update_delivery_requestMove a delivery request across the board — status / priority / due date / project. Moves into client_review or delivered are a two-step confirm (those columns ask the client to act); other changes apply immediately.
update_settingChange one org setting from a curated safe set, including the portal theme (brand_theme) and accent color (brand_accent_color, empty string to match the theme); money/tax keys require a two-step confirmation.
Clients — writeadmin5
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.
remember_about_clientSave a durable observation to the client's private admin notes (communication preferences, site quirks). Appends, never overwrites; the client never sees these.
Projects — writeadmin6
ToolPurpose
create_projectLocks a pricing snapshot. Optional start and end dates (the end date drives the calendar milestone). Elicits clients + billing profiles if anything's missing.
update_projectPatch name, description, billing profile, budget, start and end dates. Changing the end date keeps the calendar milestone in step. Touching the billing profile or budget is a two-step confirm with a from-to preview; other fields apply immediately.
complete_projectMarks done + completion note. Refuses if a session is still running. Two-step confirm — the preview carries the full completion note the client sees on their dashboard.
reopen_projectClears the completion state so new work and billing can resume. Completed projects stay browsable without reopening; reopen only to log new work or billing.
add_project_surchargeMid-job surcharge. Auto-approved when pre-disclosed, otherwise emails the client for approval. Amount is in cents (amountCents), unlike the Surcharges form in the app, which takes dollars: a $25 surcharge is 2500 here and 25.00 there. Confirm.
withdraw_project_surchargeThe undo for add_project_surcharge: retracts a charge without emailing the client; the row is kept for the audit trail. Two-step 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, led by a count + summary line (and a note when the 100-row window is full).
post_project_updateAdmin status note — emails the client and shows on their dashboard. Two-step confirm with the full note in the preview. (admin)
reply_to_project_updateThreaded reply — shows on the client's dashboard thread; no email. Notifies admins for client replies. Two-step confirm for admin keys; a client key replying on its own thread posts immediately.
Quotes & contractsadmin7
ToolPurpose
draft_quote_from_descriptionPrice a whole job from plain English using the same AI drafter as the New Quote screen (catalog matches, price anchors, live material lookups). Returns a priced preview, a draft_token for create_quote, and a preview_pdf_url so the admin can see the exact client PDF before anything is saved.
modify_quote_draftEdit a drafted quote from a plain-English instruction ("make the water heater $700", "drop the cleanup line") without re-drafting. Untouched lines carry through unchanged; returns a fresh token and preview.
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. Also persists AI drafts via draftToken. Returns a preview_pdf_url for the saved draft.
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 & paymentsadmin7
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.
record_paymentBook money already received outside Stripe (cash, check, bank transfer). No card charged, no email sent; counts toward revenue like a paid invoice. Two-step confirm.
Client commsadmin5
ToolPurpose
send_client_messageFree-form email to a client. Two-step confirm (token) — the first call returns the message preview to read back. Refuses a repeat of the same subject to the same client within 10 minutes, naming when the earlier send went out. Elicits recipient + subject + body when missing. Your saved email signature is appended automatically. Replies come back to the portal (readable via list_client_replies) with a copy forwarded to your own mailbox; the /admin/email inbox shows them once your email domain is verified. Repeat sends to the same client continue the same thread.
list_client_repliesClient replies to mail sent on your behalf, unread first, with the reply text so the agent can read it out. Never marks anything read. The /admin/email hub itself unlocks with a verified email domain.
list_client_emailsEverything the platform has emailed a client (composed messages, invoices, notifications), newest first. Outbound only. Read-only.
list_client_contactsPeople saved for a client (or the whole directory): name, role, email, phone, primary flag. Read-only.
send_payment_reminderChase the customer's oldest past-due invoice (or a specific one). All dollar figures come from the server; refuses when nothing is past due. Two-step confirm.
Team managementadmin4
ToolPurpose
list_team_membersYour org's admin staff — roles, status, owner count. Read-only.
invite_team_memberOwner-only. Invite an admin (admin or viewer, never owner). Two-step confirmation with an email + role read-back before it sends.
list_client_teammatesA specific client's portal teammates. Read-only.
invite_client_teammateInvite a teammate to a client's portal (joins as a member). Two-step confirmation with an email read-back.
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

Kept out of the agent surface on purpose: removing or re-roling team members — inviting admins and client teammates is now invite_team_member / invite_client_teammate, but removals and role changes stay in the admin UI, since revoking the wrong person's access is the kind of mistake a confirmation prompt can't fully undo. Two more are intentionally not agent actions: approval decisions are client-side by design — the admin opens a request with request_approval, but accepting it is the client's consent to give, never the admin's (or an agent's) to make on their behalf; and the platform-fee / subscription / API-key / MCP-toggle settings are excluded from update_setting on purpose. Need a tool we don't have? 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 MCP tool call lands an AgentAuditLog row — tool name, status, IP + user agent, duration, and the linked key. These records are available for server-side operations and incident review. A per-key Activity screen is not yet exposed in Settings, and this data does not currently feed the daily briefing.