Skip to content

Headless and scheduled agents

Use this when your agent has no MCP OAuth client and no credential store of its own (a cron job, a queue worker, a background agent in someone else's platform) and it has to still be authorized the next time it runs.

If your agent runs inside a host that owns OAuth (Claude, ChatGPT, Cursor, the Configure CLI), you do not need this page. Paste https://mcp.configure.dev into the host's connector settings and it stores and refreshes tokens for you.

configure_connect mints a sign-in link, and approving it authorizes the MCP session that asked for it. An interactive host holds tokens across restarts, so a session is all it needs. A headless agent holds nothing: when the process exits, the authorization goes with it, and the next run is a stranger again.

The durable equivalent is a personal MCP installation. You exchange a one-time user approval for an installationId and a refreshToken, persist those two strings, and mint short-lived access tokens from them on every later run. The user signs in exactly once, ever.

Do not register your own OAuth client for this. Dynamic client registration with PKCE and a loopback redirect does work, but it makes the user paste an authorization code out of a failed browser page, and it is not the path Configure maintains.

Endpoints

The paths below are relative to the origin you are talking to.

OriginPrefix
https://mcp.configure.dev (hosted MCP)none: /sessions, /token. Install routes only, not the JSON-RPC endpoint
https://api.configure.dev (API)/v1/mcp/personal: /v1/mcp/personal/sessions, /v1/mcp/personal/token

Calling /v1/mcp/personal/token on mcp.configure.dev returns 404: the MCP origin proxies these endpoints with the prefix stripped. Pick one origin and use its column.

Those two are the only install origins. In particular a connect_url points at the dashboard on configure.dev, which serves none of these routes, so do not build install URLs from the host of a link you were handed.

The flow

1. Start a session

No credentials required.

bash
curl -X POST https://mcp.configure.dev/sessions \
  -H 'Content-Type: application/json' \
  -d '{"client": "my-agent"}'
json
{
  "sessionId": "e82bc674-...",
  "mode": "personal",
  "agent": "my-agent",
  "browserUrl": "https://configure.dev/login?mcp_session=e82bc674-...&mcp_mode=personal&mcp_client=my-agent",
  "expiresIn": 900
}

client is a lowercase handle for your agent (letters, numbers and hyphens, 2-63 characters). There is no prior registration and no approval queue: any agent that reaches this endpoint picks a handle and is provisioned on the spot. The handle becomes your write namespace (/agents/my-agent/), so keep it stable across runs: the same handle on a later install is the same namespace.

Handles are unique across Configure. A 409 already_exists means the one you asked for belongs to somebody else; pick a more specific one (acme-scheduler, not bot). Omitting client falls back to the shared configure-mcp handle, which works but pools your writes with every other agent that did the same, so name yourself.

2. Have the user approve browserUrl

Give the user the browserUrl exactly as returned. They sign in with their phone number and approve. expiresIn is 900 seconds. If they take longer than 15 minutes, start a new session at step 1.

3. Read the credentials once

Poll while the user is signing in. No credentials required.

bash
curl https://mcp.configure.dev/sessions/e82bc674-...

{"status": "pending"} while you wait, {"status": "expired"} if the window closed, and on approval:

json
{
  "status": "completed",
  "installationId": "...",
  "refreshToken": "...",
  "accessToken": "...",
  "expiresIn": 900,
  "agent": "my-agent",
  "scopes": ["..."]
}

This read is one-time: the session is consumed, and a second call returns expired. Persist installationId and refreshToken before you do anything else. They are long-lived and do not rotate. Losing them means asking the user to sign in again, which is the one thing this flow exists to avoid. Treat the refresh token like a password.

4. Mint an access token on every run

bash
curl -X POST https://mcp.configure.dev/token \
  -H 'Content-Type: application/json' \
  -d '{"installationId": "...", "refreshToken": "..."}'

Returns an accessToken with expiresIn of 900 seconds. Mint on demand, cache it in memory for the process, and re-mint on 401. There is no second user approval, ever.

Calling MCP with the token

The token you just minted is spent on the API origin, not the one you installed against:

bash
curl -X POST https://api.configure.dev/v1/mcp/personal \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <accessToken>' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

This is the one place the two origins are not interchangeable. Configure runs two token types with two validators: https://mcp.configure.dev/mcp is the OAuth endpoint that hosted clients use, and it rejects a personal access token with Invalid OAuth access token. Personal installations call https://api.configure.dev/v1/mcp/personal. The install routes (/sessions, /token) work on either origin, with the prefix rule above; the JSON-RPC endpoint does not.

The installed surface is the same profile and connector tool set documented in Configure MCP, including configure_connect, which you still use after installation to get the user a link when they want to connect Gmail, Calendar, Drive, or Notion.

Correlation headers are optional here. configure_profile_read and configure_profile_search are read-then-commit tools, so they need a session and a scope to attach the obligation to, and a personal installation supplies both from the access token you present. Each token you mint is treated as one run, so an obligation you leave pending never blocks a later run.

Within a run it does. After ten reads in one run, or ten minutes, the next read returns commit_required (-32009) until you call configure_profile_commit with evidence of what you did with the last one: memories, or messages, or toolResults. A commit carrying none of those writes nothing and clears nothing, and it still answers status: "completed", so a run that reads in a loop should commit as it goes rather than at the end. Send X-Configure-Session-Id and X-Configure-Runtime-Scope-Id only if you want reads correlated per conversation instead.

Failure modes

What you seeWhat it means
404 on /v1/mcp/personal/...You used the API-origin prefix against mcp.configure.dev. Drop the prefix.
{"status": "expired"} immediatelyThe 15-minute window closed, or you already consumed the credentials. Start a new session.
{"status": "pending"} foreverThe user has not finished approving browserUrl.
409 already_exists from /sessionsThat client handle is taken by another agent. Choose a different one; nothing was created.
invalid_format on clientHandles are lowercase letters, numbers and hyphens, 2-63 characters. Reserved words like api or admin come back as 409 instead.
mcp_commit_plumbing_required (-32010)You sent a profile read to a surface that has no correlation and no declared commit support, which is the developer MCP endpoint rather than /v1/mcp/personal. A personal token on the personal endpoint never sees this.
commit_required (-32009)This run has reads you have not accounted for. Call configure_profile_commit with memories, messages or toolResults; a commit with none of those clears nothing.
401 from /tokenThe refreshToken does not match the installationId.
404 from /tokenNo such installation, or the user revoked it. Start again at step 1.
401 mid-run on the JSON-RPC endpointThe 15-minute access token expired. Mint a new one from the stored pair and retry.
{"error": "invalid_token", "error_description": "Invalid OAuth access token."}You sent a personal access token to mcp.configure.dev/mcp, which is OAuth-only. Send it to https://api.configure.dev/v1/mcp/personal instead.

The CLI does this for you

If your agent can shell out, npx -y @configure-ai/mcp login --personal --client <handle> runs this exact exchange and stores the credentials, and configure-mcp handles the silent refresh. Implement the REST flow directly only when you cannot run the CLI.

Personalization infrastructure for agents