MCP Server
Connect Claude, Cursor or any MCP client to your PlateCost data over Streamable HTTP
PlateCost ships a Model Context Protocol (opens in a new tab) server at
https://app.platecost.io/api/mcp. It is a remote server: there is nothing to install,
nothing to build, and no local process. Point your client at that URL with an API token and
your assistant can read your invoices, catalog, recipes and reports — and, if the token is
scoped to write, change them.
Every tool the server exposes is the same code path the app itself uses. What you can do on a screen, an agent can do through a tool — and it is refused by the same role, scope and subscription rules.
Connect
Mint an API token
In the app go to Settings → API tokens and mint one. Tokens start with
pc_, and the plaintext is shown once — it is stored as a hash and cannot be recovered afterwards.Choose the scope deliberately. A
readtoken is refused every write tool before the input is even validated; awritetoken is still bounded by the role and the locations of the person who minted it.Point your client at the server
The Agent setup panel next to your tokens emits exactly these snippets, with the plaintext already filled in at the moment you mint.
claude mcp add --transport http platecost https://app.platecost.io/api/mcp \ --header "Authorization: Bearer pc_YOUR_TOKEN"{ "mcpServers": { "platecost": { "type": "http", "url": "https://app.platecost.io/api/mcp", "headers": { "Authorization": "Bearer pc_YOUR_TOKEN" } } } }{ "mcpServers": { "platecost": { "type": "http", "url": "https://app.platecost.io/api/mcp", "headers": { "Authorization": "Bearer pc_YOUR_TOKEN" } } } }curl -H "Authorization: Bearer pc_YOUR_TOKEN" \ https://app.platecost.io/api/v1/get_capabilitiesAny client that speaks MCP Streamable HTTP with a bearer token works — the JSON shape above is the generic one.
Tell the assistant to read the manual first
Paste this once at the start of a session:
You are connected to PlateCost. Before anything else, call
get_platecost_guideand read the overview section, thenget_capabilitiesto see what this token may do. Ask me before any destructive change.Those two calls are free and they are the difference between an assistant that plans a chain of writes a read token cannot make and one that asks first.
The first two calls
| Tool | Why it comes first |
|---|---|
get_platecost_guide | The agent manual, served in sections (overview, invoices, catalog, recipes, insights, gotchas, connect). Pass section='list' for the index. It explains the data model and the traps before an agent finds them by failing. |
get_capabilities | What this credential may do: plan and billing status, the organization's limits, the features present, and this token's scope, role and location grants — plus the live tool counts. |
What the server exposes
The server advertises 158 tools to MCP clients, out of
204 in the registry. The
difference is deliberate: token administration, member administration, billing and the two
irreversible organization operations (delete_organization, reset_all_data) stay reachable
over REST with a write token but are hidden from the list an agent browses.
get_capabilities returns the live numbers under tools, which is the figure to trust — the
registry grows.
Broadly, the surface covers:
- Invoices and documents — upload, watch the pipeline, read the OCR text, review and
approve, fix or delete a line, re-label a misclassified document (
reclassify_document). - Catalog — vendors, items (one vendor's SKU), products (the same ingredient across vendors), unit conversions, merges, price history.
- Recipes — menu and prep recipes, ingredients, plate cost, the food cost report.
- Insights and reporting — findings with the numbers behind them, spending analysis,
compare_vendor_prices(name two to eight vendors, or omitvendorIdsfor all of them). - Inventory, order guides, jobs, notes, tenancy and the assistant's own changeset tools.
Call tools/list for the authoritative set. Each tool carries its own description, and every
one advertises a complete input schema.
Writes apply immediately — unless you stage them
This is the part worth reading twice.
A write tool writes. create_recipe, update_recipe, update_item, merge_items,
merge_vendors, approve_invoice and the rest take effect the moment they are called. There
is no hidden confirmation step and nothing to apply afterwards.
Staging is opt-in, and it is a separate tool. When a change should be reviewed by a person first — a batch of price updates, anything an agent is not certain about — propose it instead:
| Tool | What it does |
|---|---|
propose_changes | Stage one or more writes as a changeset nobody has applied yet. Returns the changeset id. Manager, admin or owner. |
list_pending_changesets | What is staged and waiting. A pending changeset has written nothing — treat its contents as intent, never as fact. |
apply_changeset | Run every op in a changeset, each through its own capability with its own audit row. |
cancel_changeset | Discard a pending changeset without applying any of it. |
A staged changeset is visible inside the PlateCost app too, so a change proposed from Claude Code can be reviewed and applied by a teammate on a screen.
There is no confirm_changeset and no get_pending_changeset. If your assistant calls those,
it is working from an older document — the tools are apply_changeset and
list_pending_changesets.
Results, errors and pagination
- A write returns
{"ok": true, …}, or an error result whose structured content is{"ok": false, "error": …}— withconflictattached when the write lost a race. Re-apply your change ontoconflict.currentRowand retry with itsrevision. - A read returns its own object and fails only by throwing.
- A list read returns
{"total", "returned", "offset", "note", "<rows>"}.totalcounts the whole match set, so zero means zero — it never means "cut short". Walk the rest withoffset. - A read over the size cap (96,000 characters) returns
{"truncated": true, "originalChars", "note", "preview"}.previewis a JSON prefix, not the tool's fields. Never conclude something is absent from a truncated result; re-call with a narrower filter.
Every write takes the revision you read. That is not ceremony: an assistant editing an
invoice while someone has the same screen open is the ordinary case, and a lost update there
is a wrong price nobody notices.
Rate limits
600 requests per minute per token, counted in a fixed one-minute window and shared with the REST mirror — the two doors are one counter, so speaking MCP is not a way around the REST limit.
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Past the limit the
server answers HTTP 429 with Retry-After and a JSON-RPC error:
{
"jsonrpc": "2.0",
"id": 12,
"error": {
"code": -32000,
"message": "Rate limit exceeded: 600 requests per minute per token. Retry in 24s.",
"data": { "retryAfterSeconds": 24, "limit": 600 }
}
}get_capabilities reports the same number under limits.mcpRequestsPerMinutePerToken.
Authentication and scope
Tokens are bearer credentials: Authorization: Bearer pc_….
- Stateless. Every request is authenticated on its own — there is no session to outlive a change. Revoke a token and the very next call fails; change someone's role and the next call is judged by the new one.
- Scope. A
readtoken is refused every write tool, before the input is validated, so it learns nothing from the shape of a write it cannot make. - Role and locations. A token inherits the role and location grants of the person who minted it. Anything outside those locations does not exist as far as this door is concerned.
- One organization. A token is bound to the organization it was minted in. Naming another
organization's row returns
found: false, never a hint that it exists. - Revocation. Settings → API tokens, effective immediately.
Treat a token like a password. It is shown once, it is stored as a hash, and it is not recoverable — if one leaks, revoke it rather than trying to rotate around it.
REST mirror
Every MCP tool is also an HTTP endpoint with the same input and the same rules:
curl -H "Authorization: Bearer pc_YOUR_TOKEN" \
https://app.platecost.io/api/v1/get_food_cost_reportReads accept GET with query parameters; writes are POST with a JSON body and honour an
Idempotency-Key header. See the API reference.
Troubleshooting
401 on every call
The token must start with pc_ and must not be revoked or expired. Every authentication
failure answers the same way on purpose — a door that distinguishes "revoked" from "unknown"
tells an attacker which tokens exist. Mint a fresh one in Settings → API tokens.
429, immediately
Something is calling in a loop. The limit is per token, so mint a second token for a second
workload rather than sharing one. Honour Retry-After.
A tool is missing from the list
Start a new session — clients load tools at initialization. If a write tool is missing,
the token's scope may be read, the role may be viewer, or the subscription may be inactive.
If it is one of the administrative tools, it is REST-only by design: token and member
administration, billing, delete_organization and reset_all_data are not offered to a
browsing agent.
"I proposed a change and nothing happened"
propose_changes stages; it does not write. Call list_pending_changesets to see it, then
apply_changeset — or apply it from inside the app.
The result looks cut off
Check for "truncated": true. That is a prefix, not the whole answer. Re-call with a filter,
a smaller limit, or one page at a time.