civic:rest-auth capability to intercept auth-required responses, handle user authentication out-of-band, and automatically resume the tool call — all before the auth message ever reaches the LLM.
Overview
Withoutcivic:rest-auth, auth-required responses are returned directly to the LLM as text content. The LLM sees the authorization URL, renders it for the user, and waits for the user to manually tell it to continue. This works but is clunky.
With civic:rest-auth, the client can:
- Declare the capability during MCP initialization
- Intercept auth-required tool responses before they reach the LLM
- Retrieve the authorization URL from the polling endpoint
- Open the authorization URL for the user (browser redirect, popup, etc.)
- Poll for completion
- Automatically call
continue_jobto resume the original tool call - Return the final result to the LLM as if auth was never needed
Step 1: Declare the capability
During MCP initialization, includecivic:rest-auth in your client’s experimental capabilities:
rest-auth strategy for this session.
Step 2: Detect the auth-required response
Whencivic:rest-auth is active, the auth-required tool response contains 3 content items — a text message, a continue_job_id, and an auth_polling_endpoint. Notably, it does not include the authorization_url — the client retrieves that from the polling endpoint instead.
auth_polling_endpoint (and absence of authorization_url) signals that the client should handle auth programmatically.
Detection and parsing
SDK integrations: When integrating with AI SDKs (e.g. Vercel AI SDK’s
ToolSet), the tool execute return type may be unknown rather than a typed CallToolResult. In that case, add defensive checks before accessing nested properties — guard against null, verify typeof for each level, and check that content is an array before calling .find().Step 3: Get the auth URL and redirect the user
Once you detect an auth-required response, intercept it before it reaches the LLM. Make an initialGET request to the polling endpoint to retrieve the authorization URL:
authUrl for the user (popup, redirect, embedded webview — whatever fits your client).
Already authorized? If the user has previously authorized (e.g. from a prior session), the job may already be
APPROVED by the time your first GET returns. Check data.status — if it’s already APPROVED, skip straight to step 5 (continue_job). There is no need to open a browser or poll.Step 4: Poll for completion
Polling endpoint
GET /user/jobs/:id/status returns the job status without ever exposing the token:
| Status | HTTP code | Response body |
|---|---|---|
PENDING | 202 | { "jobId": "...", "status": "PENDING" } |
AUTH_REQUIRED | 202 | { "jobId": "...", "status": "AUTH_REQUIRED", "authUrl": "..." } |
APPROVED | 200 | { "jobId": "...", "status": "APPROVED" } |
ERROR | 422 | { "jobId": "...", "status": "ERROR", "error": "..." } |
HEAD request to the same endpoint returns only status codes (no body), useful for bandwidth-efficient polling after you already have the authUrl. The status codes mirror the GET response (200 = approved, 202 = pending/auth-required, 422 = error).
The polling endpoint requires the same JWT or API key used for MCP calls. Pass it as a
Bearer token in the Authorization header.Polling implementation
Timeout guidance: The timeout controls how long the client silently intercepts auth before falling back to the LLM. A shorter value (e.g. 15s) keeps the UX responsive — if auth doesn’t complete quickly, the user sees the link in the chat and can act on it manually. A longer value (e.g. 120s) is appropriate when the client has its own UI for showing auth progress (loading spinner, status bar). Choose based on how visible the auth flow is to the user in your client.
Step 5: Resume or fall back
When polling succeeds (status isAPPROVED), call continue_job to resume the original tool call:
continue_job response is the output of the original tool call — return it to the LLM as if auth was never needed.
Important: The continue_job result may itself be another auth-required response (for multi-step auth flows). Check it with parseCivicAuthResponse again and repeat steps 3–5 if needed.
Timeout fallback
If polling times out, fall back to passing the auth message to the LLM so the user can click the link manually. The message should instruct the LLM to retry the action after the user completes authorization:Complete reference implementation
Full auth flow
Security notes
- The polling endpoint never returns the actual token. It only returns job status (
PENDING,AUTH_REQUIRED,APPROVED,ERROR) and theauthUrlwhen applicable. The token is only exchanged server-side within Civic Nexus whencontinue_jobis called. - The polling endpoint requires the same authentication (JWT/API key) used for MCP calls. Pass it as a
Bearertoken in theAuthorizationheader. - The
authUrlpoints to Civic Nexus, which handles the OAuth flow securely (consent screen, PKCE, token exchange). - The MCP auth response intentionally omits the
authUrl— it is only available via the polling endpoint, keeping the MCP message minimal and preventing the LLM from seeing it directly.

