Connect over MCP
Configure is one MCP server. The same configure_* tools are at two endpoints. Which one you use depends on who the agent acts for. A connected agent can read approved context and save new context, and the connection continues across conversations.
Choose how your agent connects
| You want to | Endpoint | Credential |
|---|---|---|
| Let any AI assistant you message connect itself, from a URL you give it | connect.configure.dev | None up front. The assistant opens a session and sends you one link to approve. See Any assistant can connect itself. |
| Give your own Claude, ChatGPT, Cursor or Codex your Configure memory | mcp.configure.dev | OAuth. The host runs it. Nothing to hold. |
| Give your product's users their own memory inside your agent | api.configure.dev/mcp | A per-user session your server mints with mcpSession() |
| Run one agent for yourself, headless, with no browser | api.configure.dev/mcp | X-API-Key plus X-Agent, your own credentials, and X-User-Id naming the user |
| Connect a person's own headless agent (no browser, no API key) | mcp.configure.dev | The person approves one link. See Connect a headless agent. |
The first row is a person connecting an app they already use. The second and third rows are a developer writing code. Each endpoint rejects the other endpoint's credentials. This is the most common way setup goes wrong.
Any assistant can connect itself
An assistant that can send an HTTP POST request can connect to Configure. It does not need to be a listed app or have a connector settings screen. Give it the URL:
text
https://connect.configure.devconnect.configure.dev and mcp.configure.dev are the same server. Each answers on its own name, so either works anywhere on this page. The assistant connects in two JSON-RPC calls, POSTed with Content-Type: application/json and Accept: application/json, text/event-stream:
initializeopens a session. ItsclientInfonames the assistant on the approval screen: thetitleif one is sent, otherwise thename, with the favicon of itswebsiteUrlbeside it. TheMcp-Session-Idresponse header identifies the session on each later request.json{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"Your product name","websiteUrl":"https://your-product.example"}}}configure_connecton that session returns aconnect_url.json{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"configure_connect","arguments":{}}}
You open the connect_url, sign in and approve. Nothing in your profile is readable before that. After approval, the profile tools work on the same session. For a connection that outlasts the session, see Connect a headless agent.
Connect an app you already use
You are a Configure user and you want Claude or ChatGPT to know you. There is nothing to build.
text
https://mcp.configure.dev| Client | How |
|---|---|
| Claude Code | claude mcp add --transport http configure https://mcp.configure.dev |
| Codex | codex mcp add configure --url https://mcp.configure.dev, then codex mcp login configure |
| Claude, ChatGPT, Cursor, anything that does OAuth | Add https://mcp.configure.dev as a remote MCP server in its connector settings. |
npx -y @configure-ai/mcp login --personal --client codex is a second way into Codex. It skips Codex's own OAuth: you approve once in your browser, and it adds a local Configure server to ~/.codex/config.toml (Serve one person with no browser is what it runs).
The configure_* tools appear in the client. You sign in inside the conversation. When a tool needs you, its result carries a connect_url, which you open once. Repeat this for as many agents as you like. The same context follows you into each one.
Use a connection token when the client has no OAuth
Claude, ChatGPT, Codex, and any client registered through dynamic client registration run the OAuth flow themselves. They need nothing else. A host without its own OAuth client needs a durable credential instead. Configure offers it in two shapes, because it cannot detect which one your client's settings screen supports:
| Your client's settings screen has | Use |
|---|---|
| A field for request headers | https://mcp.configure.dev with Authorization: Bearer <token> |
| Only a server URL box | https://mcp.configure.dev/u/<token> |
The token is a long-lived, scoped cfg_agt_ credential. The user mints it after they approve, on the sign-in page that connect_url opens. In the URL form, the token is part of the URL. Treat the whole URL as a password. Anyone who has it has that user's grant.
Configure reads the clientInfo your host sends at initialize to tell which case applies. configure_connect includes this guidance in its result only for clients that need it.
Connect from your own server
Everything below uses api.configure.dev/mcp. The tools and the protocol are the same. Your developer credentials replace the browser sign-in. mcp.configure.dev cannot serve this case, because OAuth needs a person at a browser.
Serve one user: yourself
Examples are a personal assistant, a cron job, or a headless agent on someone's platform. There is one user, and it is you. Send your own credentials, plus X-User-Id with the id you give yourself, the way the Quickstart uses externalId:
bash
curl -X POST https://api.configure.dev/mcp \
-H "X-API-Key: $CONFIGURE_API_KEY" \
-H "X-Agent: $CONFIGURE_AGENT" \
-H "X-User-Id: me" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'200 means the key and the agent name match. 401 means they do not. A key is issued for one agent handle, so CONFIGURE_AGENT must be the handle the key was minted for.
X-User-Id decides whose context the tools use. Without it, tools/list still answers, but every profile tool fails with -32001 "provide an agent user token or X-User-Id", and configure_connect fails with -32000 connect_session_required. Once that user connects a real Configure account, use a per-user session instead.
Serve many users: one session each
Your product serves people other than you. Mint a short-lived session for each user on your server, and pass the result to the model. configure is the client from the Quickstart, and user.id is your own id for the user:
ts
// A session is minted for any user you name. Whether they have connected comes
// from read().profile.linked, and connect() mints the link.
const profile = configure.profile({ externalId: user.id }); // your own user id, as in the Quickstart
const session = await profile.mcpSession();
const response = await anthropic.beta.messages.create({
model: "claude-opus-5",
max_tokens: 16000,
betas: ["mcp-client-2025-11-20"],
system: systemPrompt,
messages: turnMessages,
mcp_servers: session.mcp_servers,
tools: session.mcp_servers.map((s) => ({ type: "mcp_toolset", mcp_server_name: s.name })),
});The session returns { ok, linked, continued, session, mcp_url, mcp_servers, message }, plus livemode: false on a sk_test_ key. It has no connect_url; mint one with profile.connect(). Do not treat the session's linked as proof the user connected. It is true for any user you name by externalId who never connected, in test and live mode alike, because Configure approves your agent for a user only you know. read() reports correctly.
Each entry in mcp_servers is { type: "url", url, name, authorization_token }. That is a Streamable HTTP endpoint and a bearer, which is all any MCP client needs. The same session works with:
- Anthropic's
mcp_servers - OpenAI's
mcptool on the Responses API - the Vercel AI SDK's
createMCPClient - the MCP TypeScript SDK's
StreamableHTTPClientTransport, with the bearer inrequestInit.headers
The Quickstart shows each one. Always use one session per user. The bearer is the identity.
Pass mcp_servers to the model. If you mint a session and do not pass it, the tools never appear in the model's list, and the model cannot use them.
Why one shared connection cannot work
An MCP connection carries exactly one identity. It comes from the credential, never from tool arguments. No Configure tool accepts a user id as input, and every write is attributed on the server.
If a tool took a user_id argument, the model would choose whose memory to read. One prompt injection in a retrieved email could then expose another user's memory. The credential keeps identity out of the model's reach. Do not share one session across users. The per-user session is what keeps each user's context separate.
Serve one person with no browser
This is for a cron job or scheduled agent that acts for one person other than you and cannot run OAuth. It takes three unauthenticated calls. npx -y @configure-ai/mcp login --personal runs them for you, and the local server it installs keeps the refresh token:
POST /v1/mcp/personal/sessionswith{ "client": "<your name>" }returns asessionIdand abrowserUrl. The person opens the URL once and approves.GET /v1/mcp/personal/sessions/<id>returnsinstallationIdandrefreshTokenafter they approve. Store both.POST /v1/mcp/personal/tokenwith those two values returns a 15-minute access token. CallPOST /v1/mcp/personalwith that token as a bearer, plusX-Configure-MCP-Installation: <installationId>.
Connect a headless agent is a different path to the same result. It runs on mcp.configure.dev, the person approves the link from configure_connect, and the agent keeps one Mcp-Session-Id instead of a refresh token. Use it for an agent that only speaks MCP. Use the calls above when you are building a client that stores an installation and refreshes its own token.
The card is an MCP app
configure_connect returns a result a host can render, as well as a link. The rendering is an MCP app, a page Configure serves that the host shows in an iframe. Hosts that support MCP apps show the card inline. It has the heading "Connect your accounts", the Claude, ChatGPT, Gemini, and Grok marks, and Connect and Not now. Other hosts fall back to the connect_url in the result. That is why the result always carries both.
You do not build any of this. Connect opens the same hosted flow as the Quickstart button. With the card, the user taps a button. Without it, the user follows a link.
In your own chat, your page is the host. When the model calls configure_connect, load Configure's host script and mount the tool result as the card. The code is on Components.
Match the credential to the host
Each host rejects the other host's credentials:
| Host | Accepts | Rejects |
|---|---|---|
mcp.configure.dev | OAuth grants, and cfg_agt_ connection tokens | API keys, minted session tokens |
api.configure.dev/mcp | X-API-Key plus X-Agent, minted session tokens | OAuth access tokens |
The failure comes late, which makes it confusing. mcp.configure.dev completes initialize and lists 9 tools for an API key, or for no credential at all. It refuses only when a tool is called. This has broken production once. Check the host before you check the credential.
Read the tool list correctly
The tool list depends on who is signed in, not on your deploy:
- Your API key, with or without
X-User-Id: 9 tools,configure_connectplus the eight profile tools. You get only what the credential was explicitly granted. - A per-user session: the whole surface, 17 tools:
configure_connect, eight profile tools (configure_profile_read,configure_profile_search,configure_profile_remember,configure_profile_remember_many,configure_profile_forget,configure_profile_commit,configure_profile_import,configure_project_share), six reads (configure_gmail_search,configure_calendar_get,configure_drive_search,configure_notion_search,configure_sheets_search,configure_sheets_read), and two actions (configure_email_send,configure_calendar_create_event). The session lists all of them whether or not that user has connected yet. Sheets writes are SDK and REST only. See Connected apps.
The seventeen definitions come to about 50,000 characters of JSON. They land in the model's context on every turn. To send fewer, use profile.tools({ connectors, actions }) on the SDK path. mcpSession() takes no such filter: a session always serves all seventeen.
Understand one consequence of the per-user session before you design around it. The session decides which tools are listed. What the user approved decides which calls run. A session lists the full catalog whether or not the user connected each app, or connected at all. A call the user has not authorized comes back as a refusal with the link that fixes it.
So configure_notion_search appears as soon as you mint a session, before the user connects Notion. Calling it before they connect returns tool_not_connected and a connect link. That is the intended path, not an error to avoid.
The full list is on purpose. A model cannot ask the user to connect an app whose tools it has never seen.
Two things follow for your code:
- A tool count is not an auth check. Tools being present says nothing about what will execute.
- Most clients cache the tool list at connect time. A user who links after connecting needs a fresh connection before the fuller list appears.
Ongoing connections and MCP events
Imports are one way to add context. After that, MCP tool calls and SDK conversation commits keep adding to it.
MCP Events adds subscription delivery: an MCP server sends matching updates to a subscribed ChatGPT chat through a webhook. Configure's endpoints expose tools only. They do not implement events/list, events/subscribe or events/unsubscribe, and answer them -32601.
Handle refusals
A Configure refusal gives the model the exact next step:
authorization_requiredarrives as a successful tool result with aconnect_urlandexpires_at. It means the user is not signed in. It never means "no data on this user". Show the link verbatim.- A connector call for an unconnected app fails with
tool_not_connected. The error payload carries the link that fixes it. Give the user that link. Never build one yourself. -32009commit_requiredmeans a read obligation is unpaid. Commit the turn, withprofile.commit({ messages })on your server or theconfigure_profile_committool, then retry.configure_connectdoes not fix this.
The full failure table is in the Quickstart.