developer
Intended Documentation
MCP Server and MCP Gateway
The Intended MCP server gives an AI assistant tools for asking Intended for decisions and, when you sign in as yourself, for reviewing requests that wait for your approval. The MCP gateway checks every tool call an assistant makes to your other MCP servers. What each tool does, how access is controlled, how failures are handled, and where the data goes.
MCP Server and MCP Gateway#
Intended works with the Model Context Protocol (MCP) in two ways:
| MCP server | MCP gateway | |
|---|---|---|
| What it is | Gives an AI assistant tools for asking Intended for decisions and reading them back | A proxy between your AI assistant and your other MCP servers. It checks every tool call with Intended before passing it on |
| Where it runs | Hosted at https://mcp.intended.so/mcp, or locally from the npm package @intended-inc/mcp-server | On your machine or servers, from the npm package @intended-inc/mcp-gateway |
Neither uses AI. Every decision comes from the same POST /v1/agent-intent path as the SDK: the deterministic classifier and your policies. The AI-assisted classification setting has no effect here. See How Intended Uses AI.
MCP server#
Tools#
| Tool | What it does | What it returns to the assistant | Permission needed |
|---|---|---|---|
submit_intent | Asks for a decision on a proposed action. Intended never runs the action; your assistant does, after an ALLOW | The decision (ALLOW, ESCALATE or DENY), a risk score, the rationale, the intent id, an escalation id when escalated, on ALLOW a signed single-use authority token, and inputQuality when the action text named no action | intent:create or authority:evaluate (the Write key preset) |
check_decision | Waits up to waitSeconds (default 25, max 50) for the final decision on an escalated intent | ALLOW with the single-use authority token minted at approval, DENY with the reason, EXPIRED (treat as a denial) or PENDING with an instruction to call it again | intent:read (in the Read and Write key presets) |
verify_token | Checks an authority token's signature, expiry and binding against your workspace's public key, then asks Intended whether the token has been revoked. Does not use up the token | Valid or not, the key id, expiry, single-use status, and revocation (not_revoked or revoked). If Intended can't be reached, the token is reported as not valid with revocation: "unknown" (signature valid, revocation unknown) | Any key for the workspace |
get_receipt | Fetches the receipt for a past decision, which you can verify offline | The decision receipt and its hash-linked audit entries | intent:read (in the Read and Write key presets) |
explain_decision | Reads back a past decision | The recorded intent (action text, target system, risk flags and metadata), the decision, and its rationale. Never tool-call argument values | intent:read (in the Read and Write key presets) |
list_policies | Lists the policy packs installed in your workspace. Read-only | Your policy packs | authority:policy:read (the Policy read key preset; older keys with authority:read also work) |
submit_intent takes action (the action to take, as a verb phrase such as "Send renewal reminder email"), optional args (the action's details, stored only as a digest) and optional targetSystem (the system acted on; recorded as unspecified when omitted). If the action reads like a status message or an alert, the result includes inputQuality with a hint on how to rephrase it. The decision is still made, fail-closed. See Agent Intent API.
The server also exposes your installed policy packs as a resource, intended://policy/packs, with the same permission as list_policies.
When submit_intent returns ESCALATE, the result tells the assistant not to perform the action yet and to call check_decision with the intent id. See What your agent gets back.
A key without the permission a tool needs gets an error that names the missing scope, for example INTENT_READ_SCOPE_REQUIRED for get_receipt and explain_decision.
Reviewing requests from your assistant#
When you connect with Sign in (below), you can ask your assistant "what needs my approval?" and approve or deny requests. Intended asks you to confirm each one, in a prompt your assistant cannot answer for you.
| Tool | What it does | What it returns to the assistant |
|---|---|---|
list_pending_approvals | Lists the requests waiting for an approver in your workspace. Read-only | For each request: a short summary, the target system, the risk score and level, which agent asked and how it connected, why it was held, a recommendation, whether it can be decided from this connection, and a link to it in the console. Requests Intended verified are the same (same action, target, parameters, risk, rules and reason) are grouped, with a count, the highest risk and each request listed |
approve_request | Asks you to confirm, then approves one request, by its intent id, as you | Whether it was approved, and the review flag recorded on the decision |
deny_request | Asks you to confirm, then denies one request, by its intent id, as you, with an optional reason | Whether it was denied |
approve_requests / deny_requests | Asks you to confirm once, listing every request, then approves or denies them all (up to 25), as you | What was approved or denied, and anything left out and why |
The recommendation is a fixed rule, not AI: high risk, or no recorded risk score, says deny unless you expected exactly this; low or moderate risk says approve if you recognise it. It is advice. You decide.
You confirm every approval and denial yourself#
When your assistant calls approve_request or deny_request, Intended sends a confirmation prompt straight to you through your app, for example: Approve "deploy-bot wants to: deploy the new release to production" on acme/api from deploy-bot? risk 55 (moderate). This uses the MCP elicitation feature.
- Nothing is approved or denied unless you accept the prompt with the confirmation box ticked.
- If you decline or cancel, or no answer comes back within 30 seconds, nothing changes. The assistant tells you the confirmation wasn't answered in your app and gives you the console link.
- The assistant cannot answer the prompt. No tool input can stand in for your confirmation. After you accept, Intended's MCP server signs a short-lived proof for that one request and that one decision. The proof is never shown to the assistant. The Intended API refuses every review over MCP without it (
403 MCP_REVIEW_CONFIRMATION_REQUIRED). - If your app cannot show confirmation prompts, the tools approve and deny nothing. They return a link straight away: to the request in the console (
https://app.intended.so/decisions?intentId=…), or for several requests, to the review inbox showing just those, already selected. Claude (claude.ai and the Claude desktop app) is one such app today.
approve_request and deny_request are also marked as destructive tools. Their descriptions tell the assistant to act only when you have told it to approve or deny that specific request, never on its own initiative, and never for a request it submitted itself.
Rules Intended enforces#
These apply whatever the assistant does:
- You must sign in as yourself. An API key can never list, approve or deny requests (
403 AGENT_CANNOT_REVIEW). - You must be able to approve. You need the approver, admin or owner role and an active membership in the workspace. Other roles get
403 MCP_REVIEW_NOT_APPROVER. - Not from the connection that asked. A request submitted through the same connection cannot be approved or denied from it (
403 MCP_REVIEW_OWN_CONNECTION).- Intended treats a connection as you plus the app you signed in through (its OAuth client).
- If either request carries no client, every MCP connection of yours counts as the same one.
- Review those requests in the console, or from a different app you have connected.
- Floor requests stay in the console. A request that tripped the catastrophic floor cannot be approved or denied over MCP (
403 MCP_REVIEW_FLOOR_CONSOLE_ONLY). An owner or admin reviews it in the console. - Unverified requests stay in the console. A request whose submitter Intended could not verify is reviewed in the console only (
403 MCP_REVIEW_UNVERIFIED_SUBMITTER). This includes requests recorded before submitters were tracked.
Every refusal includes the console link for that request.
Everything else follows the console's review rules. If the request came from an agent using an API key or connection you created, the approval is marked approved_by_key_owner.
What is recorded#
Every review made this way is recorded as made by you through MCP. The decision's rationale says so. The audit event and the sealed receipt carry:
via_mcp: true;- the app's OAuth client id (
mcpClientId); confirmed_via: "elicitation";- the confirmation's id.
A note you give with an approval is stored on the audit event.
Other review actions (cancelling an escalation, overriding a decision) stay in the console.
Connecting#
- Sign in. In Claude, add
https://mcp.intended.so/mcpas a custom connector and sign in with your Intended account. The tools then act as you, in your workspace, and your role decides what they can do: operators, approvers and admins can submit actions; approvers, admins and owners can also review requests; viewers can read decisions and receipts. Steps are in the console under Settings → Developer. - API key. For clients that can't sign in, send a workspace API key as
Authorization: Bearer intended_live_…. The key identifies the workspace, not a person, so it cannot review requests. Create keys under Settings → API keys.
The hosted server rate-limits by IP address and never puts your API key in a tool result.
Fail-closed#
submit_intent treats anything other than a clear decision as DENY: an unreachable API, an unexpected response, or a verdict it doesn't recognise. An assistant never gets an ALLOW by accident.
MCP gateway#
The gateway sits between your AI assistant (for example Claude Code or Cursor) and another MCP server (for example a GitHub MCP server). For every tool call:
| Intended's answer | What the gateway does |
|---|---|
ALLOW | Forwards the call to the upstream server with the authority token attached, and returns the result with the receipt id |
ESCALATE | Holds the call while an approver decides, up to INTENDED_GATEWAY_ESCALATION_WAIT_SECONDS (default 50). Approved: forwards the call with the token minted at approval. Rejected or expired: returns the reason as an error. Still waiting: returns a "pending" result with the intent id; the same call made again after approval is forwarded |
DENY | Returns the reason as an error. The upstream server is never called |
| No clear answer (unreachable API, unexpected HTTP status, unknown verdict) | Treated as DENY |
Two optional lists change this:
INTENDED_GATEWAY_DENYLIST: tools that are always refused, without asking Intended.INTENDED_GATEWAY_ALLOWLIST: tools that are always forwarded without asking Intended. These calls get no decision, token or audit record, so use this list sparingly.
Recognised read-only tools still go to Intended, but with a low base risk score.
The gateway needs a workspace API key with the Write preset (INTENDED_API_KEY) and your workspace id (INTENDED_TENANT_ID). Setup and examples are in the package README.
Where the data goes#
Tool results go into your AI conversation
Intended sends nothing to Anthropic, OpenAI or any other AI provider for MCP traffic. But whatever a tool returns becomes part of your AI assistant's conversation, and from then on it is handled under your agreement with that AI provider, not Intended's. That includes decisions and their rationale, the recorded action text returned by explain_decision, the request summaries returned by list_pending_approvals, receipts, and your policies if you use list_policies.
What Intended receives and stores. For each submit_intent call or gateway tool call, Intended receives the action description, the tool-call arguments and the actor id. The gateway also sends where the call came from (mcp, the harness and the tool name). The arguments field is evaluated in memory and stored only as a SHA-256 digest and its length.
The gateway describes a call by the tool name and the argument names only, for example Call MCP tool "create_issue" with args: owner, repo, title. The values go only in arguments. Any argument value that still appears in an action description is replaced with [arg] before it is stored, so explain_decision never returns argument values. Text you write yourself in a submit_intent action is stored as written. See Data Handling and Retention.
Policies for gateway calls. Because the action text carries argument names only, a policy that matched an argument value inside the action text (for example ... with arguments repo=staging) does not match gateway calls. Match the names-only action and put the value in an args.<path> condition:
See Author a Policy for a before/after example.
What the gateway does not send to Intended. The upstream server's results go straight back to your assistant. They are not sent to Intended.
Keeping it tight#
- Keep secrets and personal data out of the action descriptions you write yourself.
- Give an MCP key only what it needs. Write lets the assistant submit actions. Add Policy read only if you want the assistant to see your policies.
- Reading past decisions and receipts needs
intent:read. The Read and Write presets include it; a Policy read-only key cannot read decision history. - Revoke keys you no longer use under Settings → API keys.