pipsocketDOCS
Guides / MCP

MCP retries and errors

When to send a trade again over MCP, and every refusal the server answers.

A trading tool's answer is the REST answer, so its retry rules are the REST API's. Here they are in MCP's terms, with the refusals that come from the MCP server itself.

Retrying a trade

place_order, modify_order, cancel_order, set_position_levels, close_position and close_by need an idempotency_key you choose: a new one for each trade you mean to make. Sent again with the same key, a trade returns the first request instead of trading twice. A key protects a request for 7 days after its outcome is known.

AnswerDo
200MT5 took it: the body's state is completed, placed or partial. partial is final for that request. Or executed, for the same key sent again: an owner or admin settled it after checking the broker (a person's finding, not MT5's answer).
202pending or unknown: never send it again. Follow it with get_request.
422 trade_rejectedMT5 rejected it: result.retcode says why. Change it, then send it with a new key.
503 not_sentThe terminal didn't send it. Send it again with a new key.
409 symbol_busy, retry_after a numberNothing was sent. Send it again with the same key after that many seconds.
409 symbol_busy, retry_after nullNothing was sent, and an earlier request is unknown. Don't retry: look up the request in blocking with get_request.
409 not_executedThe same key, for a request later settled as never executed. Check list_positions and the history before anything else, above all when settlement is operator.
Any other 503 or 504Send it again with the same key: if the first was recorded you get it back, and nothing is sent twice.

get_request answers 200 whatever the request's state, pending, unknown and rejected included: the 200 says the lookup worked, and state says how the trade went.

The others:

  • check_order sends nothing to the trade server: safe to repeat.
  • engage_kill_switch and lift_kill_switch take no key and are safe to repeat: engaging again retries whatever is left of the sweep. 503 account_not_connected from engage_kill_switch still means engaged: read get_kill_switch, the positions and the pending orders, then engage again.
  • resend_alert needs an idempotency_key, a key of the hook's: the same key with the same alert and accounts answers the one resend it made, for 7 days, and never makes another, even when two calls race. The same key for a different alert or accounts is refused 409 with reason key_reused. A new resend needs a new key.

Retrying account and hook tools

These answer the dashboard's status and body, and their retry_after is always null.

  • start_account, stop_account, restart_account are asked once. A 503 means the outcome is unknown: read get_account_status rather than asking again. 409 means the account is busy with another change: wait, then read it.
  • A 503 from a change (creating, changing, pausing or deleting a hook, a target or a symbol mapping) means it may or may not have happened: read it back (get_hook, list_hooks, list_symbol_maps) before trying again. A lost create_hook or rotate_hook_url is recovered with get_new_hook_url, never by doing it again (see hook URLs).
  • A 503 from a read can be retried shortly.

Refusals

Some refusals are answered by the server as HTTP errors, before any tool runs. The rest are tool errors: a tool result with its status and a body, marked isError.

HTTP errors

StatusWhenDo
401No access token. WWW-Authenticate names the metadata and the scopes to ask for.Connect: your app starts the sign-in.
401 invalid_tokenThe token has expired, isn't valid, isn't a Pipsocket MCP token, isn't sent as Authorization: Bearer <token>, or is DPoP-bound.Refresh it, or connect again. DPoP-bound tokens are never accepted: connect without DPoP.
403 insufficient_scopeThe connection lacks the scope in the workspace the call acts in, where your role would allow it. Nothing ran.Connect again from the app and tick that scope for that workspace.
403, no bodyThe request had an Origin header: the server doesn't answer web pages.Call it from an app or a server.
503, Retry-After: 5Pipsocket couldn't fetch the keys to check the token. The token may well be good.Try again shortly; don't sign in again.

Their bodies are JSON-RPC errors, with a message that says what to do. The insufficient_scope challenge names the scope and the workspace, so the app can tell you which box to tick:

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="read trade offline_access", resource_metadata="https://mcp.pipsocket.com/.well-known/oauth-protected-resource/mcp", error_description="needs trade in Acme Trading"

{ "jsonrpc": "2.0", "error": { "code": -32000, "message": "This connection needs trade in Acme Trading. Reconnect and tick trade for Acme Trading." }, "id": 7 }

scope is everything the connection was approved for, plus the scope it needs. Scopes can't say which workspace, so the approval page shows your current connection ticked, and you tick the box the message names.

Tool errors

StatuserrorWhenDo
404no_such_account, or the dashboard's NoSuchAccount, NoSuchHook, NoSuchWorkspaceNo such account, hook or workspace, or one in a workspace you're not in: the two look the same.Check the id with list_accounts or list_hooks.
403forbidden_roleYour role in the workspace doesn't allow the scope: only its owners and admins can trade or manage. The message names the workspace.Ask an owner. Connecting again can't fix it.
503accounts_unavailable, access_unavailablePipsocket couldn't read the account's entry, or check what the connection may do there. Nothing was sent.Try again.
503control_unavailable, unavailable, or the dashboard's AuthenticationUnavailablePipsocket couldn't answer, run or check an account or hook tool.A read: try again shortly. A change: it may or may not have happened, read it back first.

Everything else is the REST API's own answer, with its error codes, or the dashboard's for account and hook tools. Their bodies name the error as the dashboard does, in _tag ({ "_tag": "HookUrlUnavailable", "reason": "expired", "message": "..." }).

Arguments a tool can't use

Missing or wrong arguments are refused before anything is sent, as a tool error whose text says what's wrong (it has no status), so the model can correct the call: a missing idempotency_key, an account that isn't an account id, a missing path value. Account and hook tools also refuse arguments they don't know, so a misspelt setting never passes as a change that wasn't asked for. Trading tools pass the body's fields on as they are, so the engine refuses one it doesn't know, as over REST.