Browse docs
previewThese docs describe OpenIntents as it is being built. Endpoints and payloads may change before launch.

MCP server

OpenIntents is a remote Model Context Protocol server. Any MCP client can use it: sign in from the client or add the server with your API key, and your agent gets tools to create and track intents. It speaks streamable HTTP, and the protocol revisions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.

text
https://api.openintents.io/mcp

There are two ways to sign in:

  • An API key, sent as a header, Authorization: Bearer oi_…. Start with a test key (oi_test_…): test intents run end to end but never charge. The commands below read it from OPENINTENTS_API_KEY.
  • OAuth, for clients that support it (Claude Code, Claude Desktop and others): add the server with just its URL. The client finds the sign-in on its own, opens your browser, and you pick the account and test or live mode on a consent page. The token it gets works on /mcp only, never on the REST API, and you can revoke it under Connectors in the dashboard. Without a token the server answers 401 with a WWW-Authenticate header that points the client to the sign-in.

Either way the tools work on one account, in the mode of the key or of the approval. /mcp and /v1 share one rate limit per key or connector.

Add it to your client

Claude Code

With OAuth, no key is needed. Add the server, then run /mcp in Claude Code and choose Authenticate:

bash
claude mcp add --transport http openintents https://api.openintents.io/mcp

With a key:

bash
claude mcp add --transport http openintents https://api.openintents.io/mcp \
  --header "Authorization: Bearer $OPENINTENTS_API_KEY"

Codex

bash
codex mcp add openintents --url https://api.openintents.io/mcp \
  --bearer-token-env-var OPENINTENTS_API_KEY

Claude Desktop, Cursor and other clients

Add it to the client's MCP config (for Cursor, ~/.cursor/mcp.json):

json
{
  "mcpServers": {
    "openintents": {
      "url": "https://api.openintents.io/mcp",
      "headers": { "Authorization": "Bearer oi_test_..." }
    }
  }
}

Tools

ToolWhat it does
create_intentStart an intent. Arguments mirror POST /v1/intents, plus an optional idempotency_key and wait_seconds.
wait_for_intentWait until the intent is final or pauses, up to 50 seconds, then return it. Call it again while it is still queued or running.
get_intentFetch an intent's status and result.
list_intentsRecent intents, filterable by status and customer.
get_intent_eventsAn intent's public timeline: what the agent did, step by step.
answer_inputAnswer what an intent asked (requires_input): a value, a form's values, a decline, or done. Takes wait_seconds too.
cancel_intentCancel an intent that hasn't finished.
create_secretStore a payer's secret (an address, contact details, a shop password). It is sealed and never returned.
list_secretsA payer's secrets as labels, masks and public fields, never their values.
delete_secretDelete a secret and its values for good.

Every tool works on the key's own account only. Each has an input schema and an output schema made from the REST API's own request and response types, so a field added to POST /v1/intents shows up in create_intent too. Tools that only read carry readOnlyHint, and cancel_intent and delete_secret carry destructiveHint. A failed call returns an error result (isError: true) whose text is the same error body the REST API sends.

Customers, sessions and profiles are not MCP tools yet: manage them through the REST API.

Waiting for an intent

An intent takes a few seconds to minutes. There are three ways to follow one, and a call that stops waiting loses nothing: the intent runs on.

  • Wait in the call. Pass wait_seconds (0 to 50, default 0) to create_intent or answer_input, or call wait_for_intent. It returns the intent as it stands when it is final, pauses for somebody, or the time is up; call it again to keep waiting. Fifty seconds is the longest because the connection would otherwise sit silent for longer than the load balancer allows.
  • Progress and sessions. initialize answers with an Mcp-Session-Id; send it back on later requests (an idle session ends after 24 hours, and DELETE /mcp with the ID ends it at once). A tools/call whose Accept header names text/event-stream is answered as a stream, so that a call which holds on an intent can report progress first. Ask for it by putting a progressToken in the request's _meta: the call then sends notifications/progress (the status and the newest event) before the result. Without a token the stream carries only keep-alive comments until the result.
  • Tasks. A client on 2025-11-25 that supports tasks can call create_intent as a task and carry on: tasks/get reads the intent (working, input_required, completed, failed, cancelled), tasks/result holds for up to 50 seconds and returns the finished intent, and tasks/cancel cancels it. The task ID is the intent ID.

Or skip MCP for the waiting and use a webhook.

When an intent pauses

An intent sometimes needs the user before it can go on. The tool result says which, and what to give them:

  • requires_input: the shop asked for something only the user knows: the phone number or email they sign in with, a one-time code, an approval, or a detail of the order (a size, a seat, a form of several fields). Ask the user input_request.prompt and send their answer with answer_input. Never answer for them. A question about an identity document (kind: sensitive) is never answered through a tool: give the user input_request.url, where they type it themselves. A payment question (pay_at_shop) shows the shop's UPI QR code at input_request.image_url: show it to the user, and answer done once they say they paid.
  • requires_login: the shop wants the user signed in. Give them sign_in.url, a link for them alone, and wait.
  • requires_payment: the user has a step to take in the wallet their purchases are paid from: connect it, add money, raise a limit or confirm who they are (payment.reason says which). Give them payment.payment_url rather than trying to pay; the intent carries on by itself once they're done. See Wallets & payments.

The server tells connecting clients this too, so a model handles it without being asked.

If your client supports elicitation and holds the call open (a wait_seconds, or wait_for_intent, on a stream), a plain question about the order is asked in the client itself, as a form, and your user's answer goes straight to the intent. Questions about the user's sign-in are never asked this way, since they are about the user's account and not your agent's: the user answers them at input_request.url.

Agents do best when told the budget. "Book an Uber to SFO at 7am, max $60" becomes create_intent with max_amount: 6000. Give each purchase its own idempotency_key, and send it again on a retry: a retried call never buys twice.