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
- Call a governed tool through
s.mcp(cortexhub_invoke_tools). - If the result is parked (
status: "awaiting_user_consent"), readmcp_task.approval_idand thepresentation. - Present it in your UI; collect approve/deny (and an optional note).
s.decide(approval_id, decision, note).- Poll
cortexhub_get_taskwith themcp_task.task_iduntil 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"])
Zero-UI fallback: the consent link
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
- Meta-tools - the invoke -> park -> poll loop
- Outcomes - invoke statuses
- API reference -
session.decidesignature