{"spec_version":"1.0","server":"aic-web","description":"AIC-Web MCP server with device-pairing authentication.","auth_endpoints":{"pair":{"method":"POST","path":"/mcp/pair","description":"Request a 6-digit pairing code. No auth required. The code is REUSABLE for its full TTL.","request":{"body":{"agentId":{"type":"string (UUID)","required":false,"description":"Pass the returned agentId on retry for idempotency. Omit on first call."},"label":{"type":"string","required":false,"maxLength":80,"description":"Human-readable agent label."}}},"response_200":{"agentId":"string (UUID) — server-generated, never reuse across agents","code":"string — 6-digit code (e.g. \"482-913\")","expires_in":"number — 120 (seconds); the code can be exchanged repeatedly within this window","instructions":"string — human-readable next steps"}},"cancel_pair":{"method":"DELETE","path":"/mcp/pair?agentId=<UUID>","description":"Cancel a pending pairing code."},"exchange":{"method":"POST","path":"/mcp/exchange","description":"Exchange a pairing code for access + refresh tokens. Poll until 200. The code is REUSABLE for 120s: call this endpoint again with the same { agentId, code } to re-mint tokens if the previous ones were lost or truncated. Response includes reissued:true on a re-mint.","request":{"body":{"agentId":"string (UUID) — required","code":"string — required, e.g. \"482-913\""}},"response_200":{"token":"string — Bearer access token for /mcp (64 hex chars; copy it WHOLE — never truncate)","refresh_token":"string — long-lived, rotated on each use (64 hex chars; copy it WHOLE)","token_type":"\"Bearer\"","label":"string — agent label","expires_at":"string (ISO) | null — access token expiry; null = never expires","refresh_expires_at":"string (ISO) | null — refresh token expiry","reissued":"boolean — true if this was a re-mint within the code's TTL (token pair replaced)","token_length":"number — exact length of `token`; verify your stored copy matches","refresh_token_length":"number — exact length of `refresh_token`","warning":"string — DO NOT TRUNCATE the tokens; send them verbatim as Authorization: Bearer <full token>"},"response_202":{"pending":true,"code":"\"pending_authorization\"","retry_after_seconds":2},"response_400":{"error":"string","code":"\"invalid_body\" | \"invalid_code_format\" | \"unknown_agent\""},"response_401":{"error":"string","code":"\"invalid_code\" | \"agent_revoked\""},"response_410":{"error":"string","code":"\"code_expired\" | \"already_exchanged\" (code past TTL)"}},"refresh":{"method":"POST","path":"/mcp/refresh","description":"Exchange a refresh token for a new access token. The refresh token is ROTATED.","request":{"body":{"refresh_token":"string — required"}},"response_200":{"token":"string","refresh_token":"string (new — old one invalidated)","expires_at":"string (ISO) | null"}},"mcp":{"method":"ANY","path":"/mcp","description":"The MCP endpoint. Requires Authorization: Bearer <token>.","auth":"Authorization: Bearer <access_token>","on_401":"See the 'pairing' and 'refresh' fields in the error body for recovery instructions."}},"token_lifecycle":{"access_token_ttl":"Configured via AICWEB_AGENT_TOKEN_TTL_MS (default 3600000 = 1h; 0 = never expires)","refresh_token_ttl":"Configured via AICWEB_AGENT_REFRESH_TTL_MS (default 2592000000 = 30d; 0 = never expires)","refresh_strategy":"Call POST /mcp/refresh before expires_at. The refresh token is rotated on each use."},"examples":{"full_flow":["POST /mcp/pair → { \"agentId\": \"abc-123\", \"code\": \"482-913\", \"expires_in\": 120 }","Student enters 482-913 in portal","POST /mcp/exchange { \"agentId\": \"abc-123\", \"code\": \"482-913\" } → { \"token\": \"<64 hex chars>\", \"refresh_token\": \"<64 hex chars>\", \"expires_at\": \"2026-01-01T12:00:00Z\" }  (copy the token and refresh_token WHOLE — never truncate)","POST /mcp Authorization: Bearer <full token> → tools/list response","If you truncated/lost the token and it's still within 120s of the code: POST /mcp/exchange again with the SAME { agentId, code } → re-minted token pair (reissued:true)."]}}