Skip to main content
Your users authorize your app with a standard OAuth 2.1 authorization code flow with PKCE. Agentcard hosts the whole authorization experience — email verification and the consent screen — so you build none of it. You end up with a per-user token for authenticated MCP requests. You’ll need an OAuth client first — created in Manual Implementation (or by the wizard for you).

The players

RoleWho
UserYour end user, who owns the Agentcard account
Your appThe OAuth client, identified by your client_id
Agentcard authorization servermcp.agentcard.sh — hosts /authorize and /token
Agentcard MCP servermcp.agentcard.sh/mcp — the resource your token is for
Your client is registered in sandbox (test cards) or production (live cards) mode — a production client requires an active subscription. See Production.

The flow

Connect with Agentcard — OAuth 2.1 + PKCE sequence diagram
  1. The user clicks Connect with Agentcard in your app.
  2. Your app generates a PKCE code_verifier, derives its code_challenge, and redirects the user to /authorize.
  3. Agentcard verifies the user by email and shows the consent screen with your app’s name.
  4. The user approves; Agentcard redirects back to your redirect_uri with an authorization code.
  5. Your app exchanges the code (plus the code_verifier and your client_secret) at /token for an access_token and refresh_token.
  6. Your app calls the MCP server with Authorization: Bearer <access_token>.
  7. When the token expires, a call returns 401 — refresh once at /token and retry. Each refresh rotates the refresh token.

The requests

Authorize — redirect the user to:
https://mcp.agentcard.sh/authorize
  ?response_type=code
  &client_id=<client_id>
  &redirect_uri=<your callback>
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
  &resource=https://mcp.agentcard.sh/mcp
  &state=<state bound to this user>
Exchange the code on your callback:
curl -X POST https://mcp.agentcard.sh/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d redirect_uri=<your callback> \
  -d client_id=<client_id> \
  -d client_secret=<client_secret> \
  -d code_verifier=<code_verifier> \
  -d resource=https://mcp.agentcard.sh/mcp
Response: { access_token, refresh_token, expires_in, token_type }. Store both tokens encrypted, keyed to the user. Refresh when a call returns 401:
curl -X POST https://mcp.agentcard.sh/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d grant_type=refresh_token \
  -d refresh_token=<refresh_token> \
  -d client_id=<client_id> \
  -d client_secret=<client_secret> \
  -d resource=https://mcp.agentcard.sh/mcp
Public (PKCE-only) clients omit client_secret on both requests; confidential clients must send it on both.

Tokens

Access tokenRead expires_in from the response — don’t hardcode a lifetime
Refresh tokenLong-lived; rotates on every refresh — always persist the new one
Refresh strategyLazy — refresh on 401, then retry. No polling needed
RevocationThe user disconnecting your app invalidates both tokens immediately

Keep it safe

  • PKCE is always enforced — for confidential clients too. The code_verifier proves the token request comes from whoever started the flow.
  • Always send resource=https://mcp.agentcard.sh/mcp on both /authorize and /token — tokens are bound to that audience and validated on every call.
  • Bind state to the initiating user and verify it on the callback to prevent CSRF.
  • Keep secrets server-side. Never expose client_secret or tokens to the browser, and never log them.
Using a spec-compliant MCP client (like the Vercel AI SDK’s)? Point it at Agentcard and it handles discovery, PKCE, the resource parameter, storage, and refresh-on-401 for you.

Discovery

Everything above is discoverable at runtime — endpoints can be confirmed programmatically:
curl https://mcp.agentcard.sh/.well-known/oauth-authorization-server
curl https://mcp.agentcard.sh/.well-known/oauth-protected-resource
Users stay in control: agent-cards connections lists the apps they’ve connected, and connections revoke <clientId> disconnects one instantly.