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.

Connect

  1. 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 read token is refused every write tool before the input is even validated; a write token is still bounded by the role and the locations of the person who minted it.

  2. 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"

    Any client that speaks MCP Streamable HTTP with a bearer token works — the JSON shape above is the generic one.

  3. 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_guide and read the overview section, then get_capabilities to 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

ToolWhy it comes first
get_platecost_guideThe 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_capabilitiesWhat 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 omit vendorIds for 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:

ToolWhat it does
propose_changesStage one or more writes as a changeset nobody has applied yet. Returns the changeset id. Manager, admin or owner.
list_pending_changesetsWhat is staged and waiting. A pending changeset has written nothing — treat its contents as intent, never as fact.
apply_changesetRun every op in a changeset, each through its own capability with its own audit row.
cancel_changesetDiscard 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.

Results, errors and pagination

  • A write returns {"ok": true, …}, or an error result whose structured content is {"ok": false, "error": …} — with conflict attached when the write lost a race. Re-apply your change onto conflict.currentRow and retry with its revision.
  • A read returns its own object and fails only by throwing.
  • A list read returns {"total", "returned", "offset", "note", "<rows>"}. total counts the whole match set, so zero means zero — it never means "cut short". Walk the rest with offset.
  • A read over the size cap (96,000 characters) returns {"truncated": true, "originalChars", "note", "preview"}. preview is 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:

Over the limitJSON
{
  "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 read token 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.

REST mirror

Every MCP tool is also an HTTP endpoint with the same input and the same rules:

Shell
curl -H "Authorization: Bearer pc_YOUR_TOKEN" \
  https://app.platecost.io/api/v1/get_food_cost_report

Reads 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.