MCP integration

Errors & fixes

Sorted by what you actually see. Read the HTTP status first — it tells you which of the three layers failed.

Three layers, three places to look

  • HTTP layer — 401 / 400 / 404 / 405 / 415 / 403. The request never reached a tool. Wrong credential, or the wrong shape.
  • JSON-RPC layer — HTTP 200 but the body carries error. Mostly -32602: that tool name does not exist *for this credential*.
  • Business layer — HTTP 200, isError: true, machine code in _meta["com.popkit/error"].code. The call was understood and refused, or it failed downstream. This is the normal path for rate limits and permissions — branch on the code, don't treat it as an exception.
A business failure
{ "isError": true,
  "content": [{ "type": "text",
    "text": "rate_limited: rate limit for create_post: 10 calls per 60s; retry after 42s {\"limit\":10,\"windowSec\":60,\"retryAfterSec\":42}" }],
  "_meta": { "com.popkit/error": {
    "code": "rate_limited",
    "params": { "limit": 10, "windowSec": 60, "retryAfterSec": 42 } } } }

HTTP layer

You seeIt meansFix
401 + WWW-AuthenticateNo token, expired, or the credential was disabled / revoked. Auth runs before anything else, so a wrong token gives 401 even for a malformed requestClass A: run the authorization flow. Class B: check the key, ask an admin
400 -32020Mcp-Method / Mcp-Name disagree with the body, or the header is missingSend the header, make it match params.name (or params.uri)
400 -32602The _meta envelope is missing a required key or the io.modelcontextprotocol/ prefixAdd protocolVersion and clientCapabilities, both prefixed
404, empty bodyMCP is not enabled in this environment, or the URL is wrongCheck the URL / environment
404 -32601Method name misspelled (tools/lists…)Check the spelling
405You sent GET or DELETE. This endpoint is stateless — expected and harmless; the MCP SDKs treat it as normalNothing to fix if it came from an SDK's listener stream
415Content-Type is not application/jsonSet the header
403The Origin header is not allowed (only browsers send one)Call it server-side, or from the site's own origin

Business machine codes

codeMeaningWhat to do
unauthenticatedNo usable identity. Business tools are then absent from tools/list; calling one yields -32602Refresh / replace the credential; check authorized from ping
workspace_not_foundThe workspace is gone or dissolved, or the authorizing user is no longer a memberClass A: re-authorize. Class B: ask an admin to reissue
forbiddenThe role matrix, the credential's scopes or its publish tiers say noDrop a tier (action:"draft"), or ask for more scopes / tiers
not_foundThe object does not exist, or belongs to another workspace (not distinguished, to prevent enumeration)Check the id
invalid_inputBad input (length / enum / date / cursor / idempotency key); params carries the detailFix per params and retry
plan_limitPlan quota exhausted (currently: monthly scheduled posts; retries of failed posts count too)Wait for next month or upgrade — do not retry
no_accountNo usable account on the target platformConnect one in popkit first
needs_reauthThe account's platform authorization expiredA human must re-authorize in popkit; an external system cannot
rate_limitedA tier limit, the per-account interval, the daily publish cap, or one of the two comment-reply caps. params.reason names it: comment_reply_cap (too many replies on one comment within 24 h) or comment_reply_daily_cap (this credential's replies for the day are used up). The other sources carry no reasonWait params.retryAfterSec, then retry — except on comment_reply_cap, where retrying will not help; reply to a different comment
replayThe previous call with the same clientRequestId is still running (the placeholder lives ≤ 2 min)Wait a few seconds and retry with the same key — you get the original result
external_writes_offOutbound publishing is switched off in this environment (platform channels only)Drafts / schedules only, or switch environment
platform_errorThe platform rejected or failed (policy / duplicate / 5xx); params.reason has a machine codeNot fixable by input; a human checks the deepLink
internalUnclassified server error (logged)Retry later; contact us if it persists

By symptom

  • Everything is 401 — the credential is the problem, not the network. If the curl smoke test passes with the same token, it is your client's header config.
  • ping returns authorized: false — the credential is valid but resolves to no workspace: it was revoked, or the person who created it is no longer a member. Not a scope problem.
  • A tool is missing from tools/list — the credential lacks that scope. Class A: re-authorize and tick it. Class B: scopes are fixed at issue, ask an admin to reissue.
  • forbidden although the scopes are all there — either the role is too low, or that publish tier was not ticked. Check credential.allowedActions from get_workspace_context.
  • invalid_input only on a retry — the same clientRequestId was reused with different arguments. Retries must resend the *identical* payload; if the content changed, use a new key.
  • Published but nothing happened — publishing is asynchronous. Poll get_post and read targets[].status / errorCode per account.
  • A flood of GET requests from n8n — old n8n versions leaked MCP sessions after a run. Upgrade to 2.33 or later.

Still stuck? Send us the failing call — request headers, request body and the full response, with the credential masked — plus what ping returns for the same credential. Those two together identify the layer almost every time. Setup for your specific client: Client setup.