Developers / MCP
PaloWorks for your AI assistant
PaloWorks runs a remote Model Context Protocol server at https://paloworks.com/api/mcp. Connect it and the assistant you already talk to — Claude, Cursor, anything that speaks MCP — can turn a client brief into a project, a scope, and a draft invoice in your workspace, then hand you the link. You read it, you click Send. The assistant never contacts your client and never moves money.
Before you start
- Create an API key in Settings → API keys. Pick only the scopes the assistant needs; you can add more later with a new key.
- Copy the key once — it starts with
crk_and is shown a single time. - Paste it into one of the configurations below, in place of the placeholder.
API keys are included on the plans that include the API; see pricing. A key is a workspace credential: whoever holds it can read every client name and invoice amount in the workspace, so treat it like a password and revoke it from Settings if it leaks.
Connect
Claude Code
One command, from any terminal.
claude mcp add --transport http paloworks https://paloworks.com/api/mcp \
--header "Authorization: Bearer crk_your_key_here"Claude Desktop
Claude Desktop reads servers from claude_desktop_config.json (Settings → Developer → Edit Config). If your version connects to remote servers directly with a header, point it at the URL above with the Authorization header; otherwise a stdio-to-HTTP bridge such as mcp-remote forwards to it:
{
"mcpServers": {
"paloworks": {
"command": "npx",
"args": [
"-y", "mcp-remote", "https://paloworks.com/api/mcp",
"--header", "Authorization: Bearer crk_your_key_here"
]
}
}
}Cursor
Add to .cursor/mcp.json in the project, or to the global one in your home directory.
{
"mcpServers": {
"paloworks": {
"url": "https://paloworks.com/api/mcp",
"headers": { "Authorization": "Bearer crk_your_key_here" }
}
}
}Any other MCP client
The server speaks MCP over Streamable HTTP with a Bearer header. Configure the client with the URL and header; this is what it does under the hood.
# Remote MCP over Streamable HTTP. One JSON-RPC request per HTTP POST.
curl -X POST https://paloworks.com/api/mcp \
-H "Authorization: Bearer crk_your_key_here" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'The server is stateless and does not stream: every request is independent, no session id is issued or required, and every response is a single JSON body rather than a server-sent event stream. A client that requires SSE will not connect; one that accepts plain JSON responses will.
Tools
Each tool is one endpoint of the REST API under a name an assistant reads well. The server holds no logic of its own: validation, plan limits, and tenant scoping happen once, in the route, whichever way it is called. A tool an assistant calls without the matching scope on the key fails with scope_required and does nothing.
- get_workspacescope
read· GET /api/v1/me - Confirm the key works and learn the workspace's currency, plan, and limits.
- list_clientsscope
read· GET /api/v1/clients - Find a client by name or company before creating a project for them. Email addresses are never returned.
- create_clientscope
write:projects· POST /api/v1/clients - Create a client record.
- list_projectsscope
read· GET /api/v1/projects - List projects with their client, scope price, and invoice summary.
- get_projectscope
read· GET /api/v1/projects/{id} - Read one project in full: scope, revisions, invoices, and time.
- create_projectscope
write:projects· POST /api/v1/projects - Create a project for an existing client, optionally with the brief as intake answers.
- draft_scopescope
write:projects· POST /api/v1/projects/{id}/scope - Draft the deliverables, revision rounds, timeline, and price for a project's scope, or update the draft; the first draft on a new project is version 1. Refused (409) once the project has an agreement: its scope then changes through a new agreement version in the app.
- log_revisionscope
write:projects· POST /api/v1/projects/{id}/revisions - Record a revision request and, optionally, classify it as in scope or new work.
- log_timescope
write:time· POST /api/v1/projects/{id}/time - Log minutes worked on a project.
- list_invoicesscope
read· GET /api/v1/invoices - List invoices and estimates with their computed totals; filter by status, kind, project, or overdue.
- draft_invoicescope
write:invoices· POST /api/v1/invoices - Create a DRAFT invoice or estimate with computed totals. Give amountCents (a flat subtotal) or lineItems, not both. A person reviews it in the app and clicks Send.
- add_notescope
write:projects· POST /api/v1/projects/{id}/notes - Leave an internal note on a project explaining what was done and why.
How a conversation looks
You paste a client’s email into your assistant and ask it to set the job up. It calls list_clients to see whether the client already exists and create_client if not, then create_project with the brief as intake answers. It reads the email again and calls draft_scope with the deliverables it found, two revision rounds, and the price the client mentioned, in integer cents. The tool returns the scope and a link: /projects/abc123?tab=scope.
The assistant hands you that link. You open it, see the draft marked as created by your assistant, fix the deliverable it misread, and decide whether to share the scope with the client. Nothing has left your workspace. Later, when the work is done, the same assistant can draft_invoice from the approved scope — it comes back as a DRAFT with the computed total and the line “Open the link and click Send when you are ready.” Sending is your click, in the app, every time. Later still, list_invoices tells it which of those drafts you have sent and which are paid, without it ever seeing the link the client got.
The rules the server enforces
- Drafts only. Every write returns a record and an in-app link. Invoices are always DRAFT. There is no tool that changes a status, and none that deletes.
- You send. Nothing the assistant does is seen by a client until you open the link and click Send yourself.
- Share links stay private. No tool returns a share-link URL (/reset-password, /file, /intake, /inquiry, /kickoff, /scope, /invoice, /contract, /agreement, /delivery, /status, /portal, /statement, /change-order, /invite, /unsubscribe, /year), and the server’s own instructions tell assistants never to fetch, quote, or cite one they come across — a share link is private to the one person it was sent to.
- Scopes bound the key. An assistant with a
write:projectskey cannot draft an invoice; one withwrite:invoicescannot touch a scope. Every key can read. - Everything is labelled. Records created this way are marked as assistant-created in the app and in the audit log, so you and anyone on your team can see what the assistant did.
- Money is integer minor units. Cents, pence — never floats, never summed across currencies. Totals are computed by the same code that produces the invoice PDF.
- Nothing is silent. A tool called with a field it does not accept fails with
validationnaming the field, and an id from outside your workspace isnot_found— never a quiet success. If a call times out, check the link before repeating a create rather than assuming nothing was made.
Scopes
- read
- Read clients, projects, invoices, and plan usage
- write:projects
- Create clients, projects, scope drafts, and revision rounds
- write:invoices
- Create draft invoices and estimates
- write:time
- Log time entries
What no tool does
- send an invoice, estimate, contract, or any email to a client
- sign or countersign a contract
- charge a card, record a payment, or mark an invoice paid
- delete or trash anything
- invite, remove, or change the role of a member
- change the plan, billing, or Stripe settings
- return a share-link URL, a client's email address, or a Stripe id
Prefer REST?
The tools are a mirror of the REST API. An agent framework that consumes OpenAPI can read /openapi.json instead; each operation there names its scope and its MCP tool.