plungeai-mcp-setup
Identity & errors — whoami, the auth model, outcome envelope, trust fences
---
plungeai_whoami
Purpose: show the authenticated identity — the ground truth for "who am
I acting as" and "how much budget is left". Read-only, no arguments beyond
user_request. Use it to verify a new connection, and whenever ownership
("Execution not found") or rate-limit errors appear.
Example: {"user_request": "verify my plungeai connection"}
Returns
# Who am I
- **User:** `<uuid>`
- **Auth:** api-key · tier **pro**
- **Key:** <label> (`<key id>`)
- **Rate window:** N/min used · M/day used
- **Server:** plungeai.com (mcp.plungeai.com)Failures & fixes: "Authentication required." → the key never reached the
server; fix the header (Authorization: Bearer ozk_YOUR_KEY or
X-API-Key: ozk_YOUR_KEY). The rate window line is best-effort and may be
absent — that alone is not an error.
The auth model
An API key IS a user identity. The key resolves to a user id, and every read/write on every tool is scoped to what that user owns — the same view that user has in Studio, never the whole system. Every id-based operation (executions, workflows, jobs, conversations, memory) is ownership-checked before any storage is read; a foreign id answers "not found", not "forbidden" — so a plausible-looking id that returns "Execution not found" usually belongs to someone else or is mistyped.
Auth order: an internal service path an API key can never reach →
X-API-Key → Authorization: Bearer (personal API keys, e.g.
ozk_YOUR_KEY; any other bearer value is rejected — logged as
unsupported_bearer_token — only ozk_ keys authenticate, there is no
OAuth token path today). Unauthenticated requests get 401 with a
WWW-Authenticate: Bearer challenge (error="invalid_token" when
credentials were supplied but invalid). Keys are self-service — mint your
own at Dashboard → One API → Keys (https://dashboard.plungeai.com); an
account owner can additionally fence (allowed_ips/allowed_tools) or
revoke any key on the account, including a shared team key.
Per-key fences (both optional, set at mint):
allowed_ips— the key only authenticates from listed addresses/CIDRs; anywhere else is a 401 (and counts against failed-auth metering).allowed_tools— other tools are HIDDEN fromtools/list, and calling one returns-32602 Tool not permitted for this key. If a documented tool is missing fromtools/list, the key is fenced — re-mint or edit the key's fence in the Dashboard; do not keep calling it.
Rate limits are per tier: free 30/min · 1k/day, pro 100/min · 10k/day,
enterprise 300/min · 100k/day (tool calls only). Over-limit → HTTP 429 with a
Retry-After header — wait that long, then resume; never tight-loop.
Activity trail: every request is recorded to a per-user daily log
(arguments in full, answers as receipts). A user reads their own with their
own key: https://mcp.plungeai.com/log?date=YYYY-MM-DD (Bearer header, or
&key=ozk_YOUR_KEY in a browser). This is also why user_request matters:
it is the one place the user's actual sentence survives into diagnostics.
Two error planes
1. Protocol errors (JSON-RPC, transport-level) — the request itself was rejected; no tool ran:
| Signal | Meaning | Your move |
|---|---|---|
401 + WWW-Authenticate | Missing/invalid/revoked key, or IP outside the key's fence | Fix the credential; don't retry blind. Repeated failures meter the IP (429 with Retry-After). |
429 + Retry-After | Rate limit (per-tier), or failed-auth metering | Wait the stated seconds, resume, consider batching. |
-32602 "Tool not found" / "Tool not permitted for this key" | Typo'd tool name, or a key fence | Re-check against tools/list; if fenced, edit the key's fence in the Dashboard. |
-32700 Parse error / 413 body too large (1 MiB) | Malformed or oversized request | Fix the payload; huge YAML belongs in a saved workflow, not inline. |
405 / 403 invalid Host / -32603 internal | Transport misuse or server fault | Use POST to the canonical endpoint; internal errors: retry once, then report. |
One deliberate crossover: arguments that fail a tool's own schema (e.g.
limit: 200) are pre-screened BEFORE the tool runs and still answer in the
envelope — needs_input, "<tool> was not called — its arguments do not
match the tool's schema", with provide_field actions per bad field. Nothing
ran; fix the named fields against the tool's inputSchema from tools/list.
2. Tool outcomes (the envelope) — the tool ran (or deliberately refused)
and answered with structured guidance. Execution tools NEVER return a raw
error: the answer is text plus structuredContent with
status: ok | needs_input | needs_connection | needs_api_key
| needs_approval | unavailable | error
summary, agent_id?, operation?, result?, execution_id?,
missing?[], warnings?[], contract?,
remediation: { message, actions: [...] }Typed remediation.actions tell you the exact next move:
connect_provider (user opens the named URL and connects — needs their
browser), provide_api_key (user saves a key in a PlungeAI app),
provide_field (fix that field, retry the same call), approve_via /
respond_via (the ⏸ loop → plungeai_continue), retry_with (retry with
the stated changes, e.g. {mode: "async"}), use_alternative (a real,
callable substitute agent id).
isError is true ONLY for terminal error outcomes. Every other non-ok
status is guidance the model acts on — the response even ends with "This is
structured guidance, not a crash — do not retry the identical call."
Believe it.
The trust fences (surface, never retry, never bypass)
The active-only fence. Only status:active agents are listed and
callable over MCP. plungeai_execute_agent, plungeai_execute_tool,
ad-hoc YAML in plungeai_execute_workflow (including agents nested in
parallel/sequential blocks), and plungeai_schedule create all refuse an
unknown or inactive id BEFORE dispatch, writing no execution row. Every
refusal's prose points you at plungeai_list_agents; plungeai_execute_agent
and plungeai_execute_tool additionally attach live use_alternative
suggestions to their unavailable envelope — plungeai_execute_workflow's
fence refusal carries no remediation actions, and plungeai_schedule create
answers with plain error text, not an envelope. A refusal is a policy answer,
not a transient failure: always re-discover with plungeai_list_agents
(semantic search), take a fresh id from the results, and never "retry"
the refused id or guess a variant spelling.
The approval fence. Gated operations and real-world side effects pause
as needs_approval / ⏸ AWAITING USER APPROVAL (a paused run also shows it
in plungeai_get_workflow_status → continuation). This is a deliberate
trust boundary between the platform and the human: relay it verbatim, let
the USER decide, and only then plungeai_continue {approve: true} (yes) or
{message: "<their words>"} (no / change). Approving on your own, retrying
around the pause, or resubmitting the original call are all violations —
the platform holds the pending action server-side until the user answers.
Connection fences. needs_connection / needs_api_key mean the USER
must act in their browser (connect an account or save a key in a PlungeAI
app, e.g. Studio → Connectors, per the remediation's URL). Relay the exact
instructions, wait for their confirmation, then retry the IDENTICAL call —
this is the one case where the same call is expected to be repeated, and
only after the user acted.
Ownership answers. "not found / not one of your executions" on a
plausible id is an ownership verdict, not a storage glitch — re-list
(plungeai_executions, plungeai_list_workflows, plungeai_schedule {action: "list"}) and use an id the account actually owns.
Failure classification you can rely on
Whatever the pre-flight didn't predict, the engine's raw failure is
classified into the same envelope — a named missing API key becomes
needs_api_key, an upstream 401 becomes needs_connection naming the
provider to connect, insufficient OAuth scopes become "reconnect to grant
additional scopes", missing/invalid inputs become needs_input WITH the
agent's contract attached, an undeployed binding becomes unavailable with
alternatives, and a timeout becomes error with retry_with {mode: "async"}. A failed run always carries a reason — plungeai_get_result on a
failed id answers "This run failed: <reason>" rather than a dead end. So:
read the status, follow the remediation, and reserve blind one-time retries
for the one class labeled transient ("This is a transient platform failure
... Retry once in a few seconds", stamped with a request id to report if it
persists).