Reference
MCP Error Codes: Causes and Fixes
When a Rindler MCP tool call fails, the server returns a stable machine-readable error code beside a human-readable message telling you what to do next. Branch on the code, never on the message text. Many errors also attach a structured payload (error_data or data) with fields like retry_after_seconds, transient, valid_values, or param; read it before retrying. Every code the server can return is below, with its cause and the fix.
Map-acceptance verifier errors
This internal lane evaluates frozen mapping candidates and never expands their capabilities.
| Code | Cause | What to do |
|---|---|---|
capability_not_accepted | The verifier rejected a capability outside the frozen safe public-read cell set; no browser action ran | Fix or remap the candidate, freeze a new candidate, and verify again |
invalid_params | The server could not bind the action's effective parameters to immutable execution evidence; no browser action ran | Correct the candidate's parameter shape, freeze it again, and re-run verification |
map_acceptance_budget_exhausted | The claimed run reached its hard session, dispatch, or per-cell attempt limit; no additional browser action ran | Fix runaway chain planning or the stuck cell before claiming a new run |
How Rindler reports errors
The session tools (start_session, dispatch_action, close_session, await_auth) return a structured error object (stable code plus message) flagged as a tool error. Two exceptions mean the error flag alone is not a reliable branch point:
- A
dispatch_actionfailure that carries a diagnostic tree or safety-net suggestion but no structured error data returns as a normal, non-flagged result; act on the tree. memoryandmanage_cookiesnever flag errors. They return an ordinarymemoryorcookie_managerscreen carrying anerrorobject with the code and message.
extract_document returns a compact JSON body:
{"error_code": "not_a_pdf", "error": "document: malformed or unreadable PDF"}
Codes are stable, messages are not ("Prose changes; codes do not."). One convention runs through the catalog: a code that says a session, sign-in, or config is gone means start over with start_session; a code that says the page is loading or blocked means stay on the same session and fix the immediate obstacle. unsupported_site is a screen value on a successful start_session, not an error code (see the last section).
Session and capacity errors
From start_session, close_session, await_auth, and the session checks inside dispatch_action.
| Code | Cause | What to do |
|---|---|---|
invalid_url | url is missing, does not parse, or has no extractable domain | Pass a full absolute URL |
rate_limited_burst | Too many start_session calls for the same site in a short window; error_data.retry_after_seconds is set | Reuse the session you already have; a working session needs no restart |
session_limit_reached | Server is at concurrent-session capacity | Back off for retry_after_seconds and retry; close_session on sessions you are done with |
local_provider_unavailable | Browser pool is recovering (transient) | Retry after the suggested delay; do not retry-storm |
tier3_tunnel_unavailable | Site needs a connected tunnel daemon and none is attached | Surface it to the user; retrying will not help until a tunnel connects |
site_config_lookup_failed | Config DB lookup failed transiently during start_session, returned instead of unsupported_site so a timeout is not read as permanent | Retry start_session |
session_error | Session creation or resolution failed; data.cause classifies it (timeout, browser_provisioning_failed, page_init_failed, unknown), all transient | Wait and call start_session again |
navigation_error | Browser could not navigate to the URL | Retry once; confirm the URL loads for a signed-out visitor |
session_expired | This browser session is no longer active | Do not retry this session_id; start_session for a fresh one, then re-issue the action with the new id |
missing_session_id | Call omitted session_id | Pass the id from start_session |
unknown_session | await_auth got a session id that is not active for this user | Start a new session |
no_session_manager | No active session manager on the server | Treat as a transient server condition and retry |
site_unresolved | await_auth could not resolve which site the session belongs to | Start a new session on the site URL |
seal_read_failed | Internal read failure inside await_auth | Retry once, then re-run the sign-in handoff |
encode_failed | Internal encode failure inside await_auth | Retry once, then re-run the sign-in handoff |
internal_error | Unexpected internal error | Retry; if it repeats, open a fresh session |
Screen and action validation errors
dispatch_action validates before it touches the browser, so these mean nothing ran on the page.
| Code | Cause | What to do |
|---|---|---|
missing_action | Call omitted the action parameter | Pass action |
invalid_action | Action is not valid on the current screen; the message lists the screen plus the Available and Global actions | Pick from that list; the latest response is authoritative |
unknown_action | Action name is not in the site config at all, usually invented or stale | Only dispatch names from the most recent response |
unsupported_action | Known placeholder that is not supported on this site | Treat the capability as absent and tell the user |
action_disabled | Site owner turned this action off | Do not retry; report it |
action_not_applicable | The action's declared field is absent on this page variant, common on multi-tenant Lever or Workday forms | Treat it as a no-op, not a failure, and continue |
action_chain_unavailable | A saved Action Chain alias could not resolve, or its parameters were wrong (chains take exactly one string parameter, no attachments) | Dispatch the underlying actions directly |
no_site_config | Session has no site domain or no loaded config | Call start_session with the site URL first |
already_authenticated | Credential login dispatched on a session already signed in | Skip the login and proceed with the task |
action_policy_unavailable | Site owner's action policy could not be read (transient; nothing ran) | Retry |
invalid_filter | A job-board filter value was rejected; error_data may carry the accepted values | Re-read the valid values and re-dispatch |
adapter_error | A site adapter could not build structured screen data | Retry; if it persists, read the screen with extract_content |
Parameter errors
Most are your agent's inputs, fixable by retrying with corrected parameters. record_too_large and window_error are server-side exceptions.
| Code | Cause | What to do |
|---|---|---|
unsupported_param | A parameter is not in the action's supported list | Drop it |
invalid_param_value | An enum-constrained value is outside the accepted set; error_data.valid_values lists all. Also fires when dismiss_dialog gets an unusable button_text | Pick from valid_values and re-dispatch |
missing_required_param | A required parameter the action interpolates is absent; error_data.param names it | Re-dispatch with the parameter set |
invalid_param_format | A value fails the action's declared format; the message includes an example when one exists (dates are normalized first) | Reformat like the example and re-dispatch |
invalid_offset | A list-window offset is negative or past the current extracted total | Start at 0, then pass next_window.next_offset exactly; if the page changed, restart from 0 |
record_too_large | One extracted listing or product exceeds the record-window byte budget by itself | Narrow the page or report the oversized record; increasing limit will not help |
window_error | Server could not apply or encode a list window | Retry once; if it repeats, report the failure |
device_relay_param_invalid | A device-relay login accepts only declared, non-sensitive parameters; credentials are entered on the paired device, never passed through the tool call | Pass only the declared parameters |
Authentication errors
| Code | Cause | What to do |
|---|---|---|
auth_required | The action needs sign-in and the session is unauthenticated, the stored sign-in was forgotten, or a one-time code must be entered in the hosted Rindler form | Read auth_context and follow its lane. credential_prompt: collect the listed credential parameters and dispatch the credential action. remote_browser: share capture_url with the user, then call start_session again with no session_id once they finish (each capture_url expires in 15 minutes; use the newest). On a checkout or payment auth_required, reuse context.cart_items in the same response instead of re-navigating |
auth_state_unavailable | Server could not verify the stored sign-in state; no browser action ran | Retry; if it persists, re-authenticate through a fresh start_session |
device_relay_action_invalid | Session selected device relay and cannot dispatch a non-relay credential login | Use the sign-in lane the session advertised |
device_relay_not_admitted | A device-relay action was dispatched on a session not admitted for relay | Start a new session and use the advertised sign-in lane |
device_relay_required | capture_auth was dispatched on a session whose login comes from the paired device, so there is no hosted browser to open; the message lists the relay login actions | Dispatch one of those relay login actions instead |
device_relay_selection_failed | Device relay could not bind while the session started; the session was closed (transient) | Call start_session again |
Execution and page-state errors
These fire after the browser ran the action; the server classifies the failure so you get a diagnosis.
| Code | Cause | What to do |
|---|---|---|
execution_failed | Default when every transport failed with no more specific classification | Retry once or read the screen with extract_content; a transient failure is not a reason to abandon the site |
execution_error | Workflow failed with an internal error after all transports were exhausted | Retry; if it repeats, open a fresh session |
build_error | Server could not build the workflow steps for this action (config-side defect) | Report it; this is not an input problem |
captcha_required | A CAPTCHA blocks the action and the worker could not clear it | Share capture_url so the user solves it in a hosted browser; the captured session is reused on the next action |
bot_wall_compat | Site served a browser-compatibility wall; bot evasion is managed for you | Retry later or report the block; nothing to tune on your side |
blocked_by_dialog | A modal dialog covers the page; error_data carries dialog_title, dialog_buttons, and dismiss_candidates | Call dismiss_dialog with button_text from dismiss_candidates, then retry the same action on the same session; never abandon the session to escape a dialog |
page_loading | Page has not finished rendering | Wait a few seconds and retry the same action; do not start a new session |
post_action_check_failed | Action ran but its declared success condition did not hold, so the screen did not advance | Read the current screen, adjust, and retry |
mutation_precondition_failed | The action's write precondition could not be verified immediately before input, so the mutation was NOT attempted (error_data carries mutation_replayed: false, input_attempted: false, and a reason) | Nothing was written, so this is safe to retry: re-read the screen to confirm the current state, then re-dispatch |
write_transport_blocked | The site refused the write after the control was actuated and Rindler did not replay it | Do not repeat the mutating action; first verify current site state with a read action, and if the write is absent, recover the browser/egress or CAPTCHA lane, then retry only as a new explicit dispatch |
Site configs can also define their own patterns that set a config-specific code with a reset screen, so you may occasionally see a code specific to one site; treat its message as authoritative for the next step.
Dialog dismissal errors
dismiss_dialog is deliberately conservative: it only clicks controls that get past a dialog.
| Code | Cause | What to do |
|---|---|---|
no_dialog_open | No blocking dialog to dismiss | Re-read the screen; the obstacle is something else |
dialog_button_not_found | The requested button_text is not on the open dialog | Use one of the dismiss_candidates from the blocked_by_dialog error |
unsafe_dialog_button | Tool refused the button; it will not click controls that spend money or remove items | Pick a safe dismiss control; route purchases or deletions through the site's real actions |
Download errors
Some actions capture a file server-side (a CSV or PDF export). On success the response's data.download object carries the link, which expires in about 30 minutes.
| Code | Cause | What to do |
|---|---|---|
download_capture_failed | The action triggered the download in the remote browser but the file could not be retrieved or delivered | Do not claim success or tell the user to check their downloads folder; report the failure with the error and offer to retry |
download_capture_unsupported | File capture is not supported on the lane this action executed through | Never claim success |
Document errors (extract_document)
extract_document reads linked PDFs anonymously and sends no cookies, so it reads only what a signed-out visitor could.
| Code | Cause | What to do |
|---|---|---|
not_a_pdf | URL served HTML or a corrupt file, often a 404 page | Verify the link points directly at the PDF |
pdf_no_text_layer | Scanned-image PDF with no selectable text (needs OCR) | Say so rather than retrying |
document_too_large | File exceeds the fetch size limit (25 MiB by default) | Cannot be read through this tool; find a smaller source |
fetch_failed | Non-2xx response, commonly a login-gated or moved PDF (the fetch is anonymous) | Confirm the URL is publicly reachable |
invalid_page_selector | Requested pages do not exist or the selector does not parse | Retry with a valid selector; use the reported page_count to bound the range |
unsupported_scheme | Only http and https URLs are supported | Use an http or https URL |
blocked_host | Refuses to fetch private or reserved addresses | Use a public URL |
document_read_failed | Any other failure, including reader initialization | Retry once, then report |
invalid_argument | extract_document was called without a url | Pass url |
Scope, config, and approval errors
These enforce whose config, whose site, and whose approval govern a call.
| Code | Cause | What to do |
|---|---|---|
site_scope_denied | A site-scoped credential acted, navigated, or fetched outside its authorized site, or the site redirected off the authorized domains; the session is closed mid-action. Also fires on extract_document for an unbound URL | Stay within the authorized site; cross-site work needs a normal user token |
config_not_tracked | Shared site config is not in your tracked runtime set | Add it in the dashboard, or switch catalog access to All shared configs |
config_policy_unavailable | Runtime config policy could not be read (transient; no session created or session closed) | Retry shortly |
config_access_changed | Shared-config access changed while the session was starting or running; the session was closed | Review the dashboard selection and start a new session |
config_acceptance_invalid | The site config resolved, but its map-acceptance proof is missing or was invalidated by a config change | Not retryable: the config must be re-accepted before sessions can start |
workspace_deactivated | The workspace has been deactivated by a Rindler admin, so no sessions can start and no config resolves | Not retryable; contact Rindler support to reactivate the workspace |
connect_admission_blocked | On a Connect-enforced site, a mutation or download has no governed integration (reads always admit) | Report the refusal reason from the message |
escalation_required | A fleet Cell tried a state-changing action, or any synthesized dynamic tool, and it routed to one-way human approval (marked an error so it is not retried) | Wait for the human decision; do not re-dispatch |
capability_not_permitted | The workspace's permission rules set this capability to Never allow, so the action was refused before anything ran; error_data carries the capability, a plain-language capability_reason, and capability_certain: false when the action could not be classified and fell back to the strictest matching rule | Do not retry; report the reason. A workspace owner or admin changes it under Permissions |
approval_denied | Merchant approval gate paused a flagged mutating action and the approver denied it; error_data.approval_request_id identifies it | Do not retry; report the denial |
approval_timeout | The approval request expired unanswered | Ask the approver to act, then re-dispatch to open a new request |
approval_check_failed | Gate could not read policy or record the request (transient) | Retry |
device_policy_unavailable | Workspace custody-device policy could not be read before a mutation, so nothing ran | Retry; if it repeats, report the policy-read failure |
device_actor_unavailable | A custody-device rule applies, but the request has no trusted action actor, so nothing ran | Re-authenticate through a caller that stamps the real workspace actor; do not retry unchanged |
device_required | Workspace policy requires the action actor's custody device for this action class, but no device is connected, so nothing ran | Connect that actor's custody device, then retry |
mutation_precondition_failed | A mutating action's write precondition did not hold, so the mutation was never attempted; error_data carries mutation_replayed: false, input_attempted: false, and a reason | Read reason, fix the precondition, then re-dispatch; the write did not happen, so this is safe to retry once corrected |
Things that look like error codes but are not
A few names show up near errors without being machine-readable error_code values. Do not branch on them as codes.
unsupported_siteis a screen. With no config,start_sessionreturnsscreen: "unsupported_site",supported: false, and empty actions. Withintent: "login"the same response can still include acapture_urlfor a hosted remote-browser sign-in, so only anunsupported_siteresponse with nocapture_urlmeans the site is genuinely unavailable.auth_status: "expired"is a response field: a saved login expired. Do not retry the same session; re-authenticate through the laneauth_contextindicates, then callstart_sessionagain.page_stuckis a boolean response field (and a safety-net trigger) set when the URL has not changed across several consecutive actions; it arrives on otherwise-successful responses.error_codefields in server logs and telemetry duplicate codes already returned to the agent; they are not separate codes.- The dashboard HTTP API returns the string
unsupported_siteas an HTTP 404 body on one route; that is not an MCP error code. extract_contentreturns structured list-window codes, but its auth failures still carry anauth_required:orauth_state_unavailable:prose prefix.get_auth_url,synthesize_schema, andfleet_statusalso return prose errors without machine codes.