For AI agents: a documentation index is available at https://www.plungeai.com/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://www.plungeai.com/docs/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

Source: orchestration/mcp-gateway/tool-outcome.ts, server.ts (wrap, validateToolArgs), index.ts, shared.ts, auth-middleware.ts, rate-limiter.ts, session-do.ts, resources.ts, prompts.ts, tools.ts, tool-exec.ts, extras.ts, activity-log.ts, log-view.ts, execution-do.ts, leg-state.ts, core/core-base/content-guard.ts, orchestration/api-gateway/lib/auth.ts, MCP SDK 1.29.0, live registry cards (2026-09-24).

When something cannot run, the platform does not just fail. It answers with what is missing and how to fix it. Your client reads that answer and usually corrects itself. This chapter is so that you know what is happening: the answer shapes, every error code, the limits, when to retry, and how to read your own activity log. It applies to https://mcp.plungeai.com/v1. The same server at https://api.plungeai.com/v1/mcp differs only at the edge (4.11).

13.1 Three kinds of "no"

Your request
 ├─ stopped at the edge: auth, size, rate limit, parse,          → JSON-RPC error + HTTP status (13.6)
 │   unknown or fenced tool, unknown method/resource/prompt          nothing ran
 ├─ reached a tool that answered with guidance                   → a result with an outcome (13.2)
 │   needs_input / needs_connection / needs_api_key /                usually nothing ran
 │   needs_approval / unavailable / error
 └─ reached a tool that answered with a plain failure            → a result, isError: true, text only (13.5)

A client should branch in this order: first the HTTP status and the JSON-RPC error, then result.isError, then result.structuredContent.status.

13.2 The structured-feedback contract

The six tools whose outputSchema is the outcome envelope never answer with a bare error: plungeai_execute_workflow, plungeai_execute_agent, plungeai_execute_tool, plungeai_get_tool_contract, plungeai_run_mission and plungeai_learn (4.9). Every answer carries an outcome: a status plus a summary, and usually a remediation saying exactly what to do. plungeai_get_result uses the same envelope for its guidance answers (a missing or unknown id). A paused run is not an envelope there: its result text ends with a ⏸ block, and format:"json" gives {"content": …} with no status. For machine-readable pause state, read structuredContent.continuation from plungeai_get_workflow_status (9.3). Two paths hold the promise for every tool: argument validation (13.3) and unexpected exceptions (13.4).

statusText starts withWhat it meansWhat to doisError
okthe result itselfIt ran. This is your result.Nothing.false
needs_input📝 NEEDS INPUTA required field is missing or malformed. The answer names the exact field and, where the agent has one, includes its full form (the contract) with a worked example. A run that paused with a question also uses this status (⏸ AWAITING USER: …).Say "do what it suggests". Your client fills the form and retries. For a question, answer it (plungeai_continue).false
needs_connection🔌 NEEDS CONNECTIONThe agent acts as you (Google, Microsoft, …) and your connected account is missing, expired, short of scopes, or lacks a licence. Nothing was run: the check comes first.Connect the service once in Studio → Connectors, then ask again.false
needs_api_key🔑 NEEDS API KEYA bring-your-own-key agent (wallets, private APIs) has no key saved for you.Save your key once in Studio. Every surface can then use it.false
needs_approval⏸ NEEDS APPROVALNothing has executed. Either an agent staged an action and paused (the summary starts ⏸ AWAITING USER APPROVAL: …, with an approve_via action), or the trust fence refused an outward, irreversible operation (send, pay, transfer, buy, withdraw) before it ran (no actions, no paused session).Staged action: reply "yes"/"approve" to proceed (plungeai_continue), or anything else to change course. Trust-fence refusal: nothing to continue over MCP; the operation runs only from Ocean Studio, where a person approves it (6.6).false
unavailable🚫 UNAVAILABLEThe agent cannot run here. Its id does not exist (No such agent: …), it is not status:active, it is registered but not deployed, or it reported itself temporarily unable (for example its data provider's quota ran out). Apart from a made-up id, this is a platform-side gap, not your mistake.Pick one of the live alternatives listed in the answer, or retry later if it gave a reason.false
error❌ ERRORA genuine failure, with the real reason stated (for example an upstream API limit, or a timeout).Fix the input and rerun, or follow the retry hint. Don't keep polling.true

Every non-ok answer ends with the line _This is structured guidance, not a crash — do not retry the identical call. Follow the remediation, or relay it to the user._

The envelope is the structuredContent object: status, summary, agent_id, operation, result, execution_id, missing[], warnings[], remediation { message, actions[] } and contract (field reference in 4.9). The Markdown text shows the same information as a status line, the summary, Missing required: …, **To proceed:** <message> and one bullet per action. Each warning becomes a ⚠️ line.

Remediation actions (RemediationAction), and how each is rendered for your client:

actionFieldsRendered as
provide_fieldfield, description?Provide <field> — <description> and retry the same call.
connect_providerprovider, url, scopes?Connect provider: open <url> → Connectors and connect the account. Relay this to the user — it needs their browser.
provide_api_keyprovider, howProvide a provider API key: <how>
approve_viatool: plungeai_continue, execution_id, note?Ask the user to approve, then call plungeai_continue {execution_id, approve: true}. NEVER approve on your own.
respond_viatool: plungeai_continue, execution_id, question?Relay the question to the user, then call plungeai_continue {execution_id, message: "<their answer>"}.
retry_withchanges, note?Retry with {…} (for example {"mode":"async"} after a timeout).
use_alternativeagent_id, note?Alternative: <agent_id> — <note>

Connection and key remediations point at Ocean Studio, https://studio.plungeai.com → Connectors.

A refusal is not a run. If the platform stops a call before dispatch (a missing field, a missing connection, an unknown or inactive agent, an unknown model), no execution is created. Nothing half-happens, and your history stays clean. Looking up a result by an id that does not exist gets a needs_input answer naming the id (plungeai_get_result), never a bare "not found".

Limits are told to you, never silently applied. Search engines declare their maximum query length on their card (live-verified 2026-09-24): serper-agent 2,048 characters, brave-agent 400, tavily-agent 400. Send more and you get needs_input with the limit. Your input is never truncated behind your back.

The real reason comes back. When a run fails, the classifier (classifyExecutionError) passes the upstream reason through: Execution failed: <reason>, The run timed out: <reason>, or the agent's own message on unavailable. You do not get a generic "something went wrong".

Visible mapping, never guessing. Send only prose to plungeai_execute_tool for an agent whose form has exactly one required free-text field (a report generator's content, say), and the platform fills that field with your prose and says so: ⚠️ prose mapped: your prompt was passed as `content` (this card's only required parameter)…. Details are in Tools.

13.3 Argument validation

Every tools/call is checked against the tool's input schema before the tool runs (server.ts validateToolArgs, called from index.ts). A failure is not a protocol error. It is a needs_input outcome, returned as a normal JSON-RPC result (HTTP 200, plain JSON):

  • summary: `plungeai_list_agents` was not called — its arguments do not match the tool's schema. followed by one `field`: message per problem;
  • missing[]: required fields that were absent;
  • remediation.actions: one provide_field per missing or bad field.

Nothing runs, but the call still counts toward your rate limit. Free to try:

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_list_agents","arguments":{"user_request":"list agents","limit":500}}}'

The bounds every tool shares (shared.ts, tools.ts). Per-tool parameters are in the Tool reference.

Kind of argumentBound
ids (agent_id, execution_id, workflow_id, task_id, model, …)string, at most 256 characters
short text (search, folder, description, query, …)at most 2,000 characters
prompts and messages (prompt, input, goal, message, content, source, …)at most 65,536 characters
workflow_yaml, yamlat most 262,144 characters (256 KiB)
user_requestat most 4,000 characters
limitinteger 1–100
inputs (plungeai_execute_workflow)keys up to 128 characters, values up to 8,192
max_iterations (plungeai_run_mission)integer 1–50
maxTokens / max_tokens (plungeai_execute_agent)integer 1–64,000

This pre-screen covers single-message requests. Inside a JSON-RPC batch, bad arguments come back as the SDK's own isError text (Input validation error: …) instead (4.4).

13.4 Transient failures

If a tool throws unexpectedly (a database hiccup, a service-binding blip), the server still answers with an outcome (server.ts wrap):

  • status: "error", isError: true;
  • summary: the error message and (request <request id>). Messages that would expose internals (database or parser errors) are replaced with An internal error occurred while running this tool.;
  • remediation.message: This is a transient platform failure, not a bad request. Retry once in a few seconds; if it persists, report the request id above.

format: "json" is honoured on this path too. The full error goes to the server logs under the same request id.

13.5 Plain tool errors

Tools without an outputSchema answer their own failures as a plain text result with isError: true and no structuredContent. Examples from the code:

ToolText
any tool without a user (internal calls only)Authentication required.
plungeai_get_workflow_statusExecution not found. (the id is unknown or not yours)
plungeai_get_result, plungeai_get_workflow_statusThat is a scheduler run id, which this tool cannot resolve. Use plungeai_schedule {action: "runs"} … (ids starting exec_)
plungeai_executionsexecution_id is required for this action. · Execution not found. · Execution is <status>, not running — nothing to cancel.
plungeai_list_agentsRegistry unavailable — try again shortly. · Registry search failed (<status>).

They are short and say what to do. Treat them like a needs_input or error outcome.

13.6 JSON-RPC and HTTP errors

Protocol errors mean the request never reached a tool. The body is always {"jsonrpc":"2.0","error":{"code":…,"message":"…"},"id":…}. id is null when the server had not parsed one.

HTTPcodemessageWhen
400-32700Parse errorthe body is not valid JSON
400-32700Parse error: Invalid JSON-RPC messagevalid JSON, but not a JSON-RPC message (SDK)
400-32600Invalid Request: Only one initialization request is alloweda batch holding initialize plus other messages
400-32600Invalid Request: Server already initializeda second initialize on an opt-in session
400-32000Bad Request: Unsupported protocol version: <v> (supported versions: …)an unsupported MCP-Protocol-Version header on a request after initialize (not checked on initialize, nor on a tools/list served from cache, 4.3)
401-32001No valid authentication provided. Use an X-API-Key header or "Authorization: Bearer <api key>".no key, or the key is wrong, revoked or expired, or a Bearer token that is not ozk_. WWW-Authenticate: Bearer when you sent no credentials, Bearer error="invalid_token" when you did.
401-32001API key not permitted from <ip>the key is fenced to certain IP addresses and this is not one
403-32000Invalid Host header: <host>the request reached the server under a hostname it does not serve
404-32001Session not foundan Mcp-Session-Id that is unknown, expired, belongs to another key, or is over 128 characters (4.5)
405-32000Method not allowed.GET or DELETE without a session. Header Allow: POST, OPTIONS.
413-32600Request body too large (1 MiB max)over 1,048,576 bytes (13.8)
415-32000Unsupported Media Type: Content-Type must be application/jsona complete Accept header with a non-JSON Content-Type
429-32000Rate limit exceeded: Rate limit: <n> requests/minute (or /day)tier limit reached (13.7). Retry-After header plus error.data.retryAfter (seconds).
429-32000Too many failed authentication attemptstoo many failed logins from your IP. Retry-After header.
200-32602Tool not found: <name>the tool name does not exist (single-message tools/call)
200-32602Tool not permitted for this key: <name>the tool exists, but your key is fenced to other tools
200-32602Invalid params: <fields>malformed initialize params
200-32602MCP error -32602: Prompt not found: <name>prompts/get with an unknown name
200-32002MCP error -32002: Resource not found: <uri> (also Workflow not found / Execution not found)resources/read (12.11)
200-32601Method not founda method the server does not implement (4.4)
200-32603Registry error <status>, or the SDK's validation texta registry failure behind a resource, or request params that fail the SDK schema
500-32603Internal erroran unhandled server error
404—{"error":"Not found. MCP endpoint: POST /v1 (Streamable HTTP)."}any path other than /v1, /v1/, /mcp and the public routes (not JSON-RPC)

Unauthenticated requests, live-verified 2026-09-24:

curl -s -i -X POST https://mcp.plungeai.com/v1 -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
curl -s -i -X POST https://mcp.plungeai.com/v1 -H 'Authorization: Bearer not-a-key' \
  -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

An unknown tool is a protocol error, so it cannot be mistaken for "the tool ran and failed":

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"plungeai_nope","arguments":{}}}'

On https://api.plungeai.com/v1/mcp, the router answers its own edge errors in the One API envelope: 401 unauthorized (Bearer ozk_ key required / Invalid or revoked API key), 429 rate_limited, 413 payload_too_large, each with X-Error-Code (One API errors (opens in a new tab)). Everything the MCP server answers passes through unchanged.

13.7 Rate limits

Your key's tier sets two fixed windows, per minute and per day (rate-limiter.ts):

TierPer minutePer day
free301,000
pro10010,000
enterprise300100,000
  • Only tool calls count. initialize, tools/list, resources/*, prompts/* and ping are free on https://mcp.plungeai.com/v1 and on https://api.plungeai.com/v1/mcp, which forwards your key to the same gateway (4.11).
  • The counter is per account (your user id), not per key. Two keys on one account share it.
  • Windows are fixed, not calendar-aligned. A window starts at the first counted call after the previous one ended and lasts 60 seconds (or 24 hours).
  • A key with a missing tier is treated as free. An operator can give a key its own per-minute limit, which replaces the tier's per-minute number (the daily limit stays). Self-service keys use the tier limits.
  • Failed logins are counted per client IP: 20 per minute and 500 per day. After that the server answers 429 Too many failed authentication attempts until the window resets.

On a limit hit you get 429 with Retry-After (seconds until the window resets, at least 1) and error.data.retryAfter, and the call does not run. Ask "how much of my rate limit have I used?": plungeai_whoami shows minute_used and day_used for the current windows. Its own call counts, so the number includes it.

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"plungeai_whoami","arguments":{"user_request":"how much of my rate limit have I used?"}}}' \
  | sed -n 's/^data: //p'

These limits bound request rates. Spending (model tokens, paid APIs) is metered separately on your account, and money-moving actions stop at the trust fence (Tools).

13.8 Body caps and timeouts

LimitValueWhat happens
Request body1 MiB (1,048,576 bytes)413 -32600. Checked against Content-Length before auth, then enforced on the stream itself (chunked bodies included).
Argument sizes13.3needs_input, nothing runs
Keepalivea progress line every 15 s while a tool runskeeps the SSE stream from going idle (4.6)
Registry-backed reads10 sRegistry error / Registry unavailable
Background runslegs of 10 min; re-entered after 12 min; failed after 3 kills in a row without a checkpoint; Interrupted (run did not complete) after 16 min with no heartbeat4.7
Opt-in sessions10 min idle404 Session not found; re-initialize

The server itself sets no request timeout on a tools/call. Your client may: Claude Desktop cuts a single tool call at about 4 minutes (server.ts). Claude Code lets you set a per-server timeout (milliseconds) in .mcp.json, or MCP_TOOL_TIMEOUT (Claude Code docs (opens in a new tab), read 2026-09-24). For anything long, use mode: "async" (4.7).

13.9 Retry guidance

You gotDo
429 rate limitedWait Retry-After seconds, then retry. Don't hammer: every retry inside the window is refused again.
401Don't retry. Fix or replace the key (Authentication). From a new location, check whether the key is IP-fenced.
-32602 Tool not foundRefresh the tool list: reconnect your client, or call tools/list again.
-32602 Tool not permitted for this keyDon't retry. Use a key that allows the tool.
404 Session not foundDrop the Mcp-Session-Id and re-initialize.
413Shrink the request. Move long text into a saved workflow, or split the work.
outcome needs_input, needs_connection, needs_api_keyFollow the remediation, then retry. Never retry the identical call blind.
outcome needs_approval with an approve_via action (an agent staged the action and paused)Ask the user. Only after an explicit yes, call plungeai_continue with approve: true.
outcome needs_approval with no actions (trust-fence refusal: send / pay / buy / transfer / withdraw …)Nothing to continue: relay it to the user. The operation runs only from Ocean Studio, where a person approves it (6.6). plungeai_continue answers No conversation session….
outcome unavailableUse an alternative from the answer. If the agent gave a temporary reason, retry later.
outcome error "transient platform failure"Retry once after a few seconds. If it repeats, report the request id.
outcome error "The run timed out"From plungeai_execute_workflow, plungeai_execute_tool or plungeai_run_mission: retry with mode: "async" and poll plungeai_get_workflow_status. plungeai_execute_agent has no mode and drops it, so the same retry runs synchronously again: run the agent as a one-task workflow through plungeai_execute_workflow with mode: "async" instead.
outcome error "Execution failed: …"Read the reason and fix the input. Don't keep polling a failed run.
-32603 / 500Retry with backoff. If it persists, report it with the request id from your activity log.

13.10 The activity log

The server keeps a readable trail of everything your account did over MCP: one Markdown entry per request, grouped per account per UTC day (activity-log.ts, log-view.ts). Use it to see exactly what your client sent, which is often not what you typed, and what came back.

Read it with your own key. It is assembled live on every request, newest first:

curl -s "https://mcp.plungeai.com/log?date=$(date -u +%F)" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
QueryMeaning
date=YYYY-MM-DDthe UTC day. Defaults to today. Anything else gets 400 {"error":"date must be YYYY-MM-DD"}.
format=md / format=htmlforce Markdown or HTML. Otherwise a browser (Accept: text/html) gets a rendered page, and curl gets text/markdown.
refresh=1also rewrite the stored per-day document now (normally a cron does it every 15 minutes)
key=<your key>for opening the log in a browser, where you cannot set a header. The key then sits in the URL and your browser history, so prefer the header.

The same handler also answers at /admin/log. Responses are Cache-Control: no-store. A key reads only its own account's trail. A missing or wrong key gets 401 {"error":"Provide your API key: Authorization: Bearer <key>, X-API-Key, or ?key="}. The log is served on https://mcp.plungeai.com only.

What an entry looks like (the shape activity-log.ts renders):

### 14:03:12 · `plungeai_whoami` · ✅ · 212ms
- **User:** you@example.com · <your user id> · <client IP> · `<request id>`
- **Key:** <key label>

> **Asked:** how much of my rate limit have I used?

**Q:**
- **format:** markdown

- **A:** <the answer, whole if 300 characters or fewer, else "<n> chars · <first 240 characters>…">
PartWhat it holds
headingUTC time, the event (a tool name, or initialize, tools/list, resources/read, …, or rate_limited, tool_denied), ✅/❌ for tool calls, duration
User / Keythe account, email, client IP and request id, and which key was used (its label). One account can hold several keys. Calls through https://api.plungeai.com/v1/mcp show your key label like any other call.
Askedyour user_request, in your own words, when the client sent one. This is why the server asks clients to always pass it.
Qthe tool arguments as fields, never truncated, including defaults the server filled in (such as format: markdown). YAML and multi-line text stay fenced. For protocol traffic, the params (e.g. the plungeai:// URI read).
Client contextanything else your client put in params._meta besides the progress token
Athe answer as a receipt: whole when 300 characters or fewer, otherwise its length plus the first 240 characters. The full output stays in your execution history (plungeai_get_result).
Errorthe full error text, when there was one. For invalid arguments: invalid arguments: limit Too big: ….

What is recorded. Every authenticated request: tool calls, argument rejections, rate-limit hits, fenced-tool denials and protocol methods. Failed logins are recorded without an account, so they do not appear in your log. Secrets are redacted before anything is written: the value of any argument whose name looks like a secret (api_key, api_secret, token, secret, password, passwd, credential, authorization, cookie) is stored as •••redacted•••, including everything nested under it (core/core-base/content-guard.ts). A secret pasted into free text, such as a prompt, is not detected. Don't paste keys into prompts.

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.