Skip to main content

Approvals

When a governed tool call requires the end user's approval, the invoke result comes back parked: the per-call mcp_task carries an approval_id (and the presentation your agent passed to the call). Your agent is headless, so you own the approval UI. Show the action to your end user however your app does UI, and when they decide, relay it through the SDK with session.decide.

s = cx.session(agent="support-bot", end_user_subject="user_42")

# ... a call on s.mcp parks; read approval_id + presentation from the result ...

# Render presentation in your own UI, get the user's choice, then:
s.decide(approval_id, "approve", note="Approved by the account owner")

session.decide(approval_id, decision, note=None) records the decision as that end user's consent - no CortexHub account for the end user is required. It is authenticated by the same org key, agent, and end_user_subject this session runs as, and the decision is bound to the parked call's end user (you can only decide for the user the call acted for). decision is "approve" or "deny"; note is optional and stored with the decision. Idempotent: a repeat submit returns the terminal state.

The full loop​

  1. Call a governed tool through s.mcp (cortexhub_invoke_tools).
  2. If the result is parked (status: "awaiting_user_consent"), read mcp_task.approval_id and the presentation.
  3. Present it in your UI; collect approve/deny (and an optional note).
  4. s.decide(approval_id, decision, note).
  5. Poll cortexhub_get_task with the mcp_task.task_id until it is terminal. An approved call runs server-side and its result comes back; a denied call returns a denial.
task = mcp_task_from(result) # your helper over the invoke result
if task:
decision, note = ask_your_user(task["presentation"]) # your UI
s.decide(task["approval_id"], decision, note=note)
final = await poll_until_terminal(mcp, task["task_id"])

If you would rather not build any approval UI, the parked result also carries a signed consent_url. Hand it to your end user (a page, an email, a Slack DM); they approve in a browser, and your poll of cortexhub_get_task resumes the call. Use this for a fully autonomous agent with no place to render consent.

Interactive hosts​

A UI host that declares the MCP-UI extension (for example claude.ai) has consent rendered inline by the gateway and never reaches session.decide - that path is for MCP-Apps hosts, not a headless SDK agent.

REST equivalent​

The same decision is available on the Platform REST API as POST /v1/orgs/{org_id}/approvals/{approval_id}/decision with a { "decision": "approve" | "deny", "comment": "..." } body. session.decide is the SDK-native form, authenticated by your agent's org key.

See also​