Authentication
Get Past the Front Door
Auth trips up more first calls than anything else, so it leads here. MCP clients use OAuth 2.1 with dynamic client registration and PKCE. Backends and scripts use an API key. The browser uses your portal session. Pick the path for the surface you call from.
Three Paths, One Authenticated Request
Every call resolves to the same authorized identity. The difference is how you present it.
| Calling from | Use | How it travels |
|---|---|---|
| Claude Code / Desktop / Cursor / any MCP client | OAuth 2.1 bearer | Authorization: Bearer … (the client handles it) |
| cURL / SDK / CLI / partner backend | API key (sk_*) | Authorization: Bearer sk_… |
| Browser inside the portal (playground) | Portal session cookie | Cookie, set on sign-in. No header. |
The most common path
OAuth 2.1 for MCP Clients
When a client first reaches the Archie MCP server, it has no token. The server answers 401 with a WWW-Authenticate header that points at the protected-resource metadata. The client reads that, discovers the authorization server, registers itself, runs a PKCE authorization, and exchanges the code for a token. Every step is standard OAuth 2.1, so a conformant client does it without you writing any auth code.
- 1POST /mcp
First call, no token yet.
- 2401 + WWW-Authenticate
Points at the protected-resource metadata (RFC 9728).
- 3GET /.well-known/oauth-protected-resource
Client reads the resource metadata.
- 4GET /.well-known/oauth-authorization-server
Discovers the auth server (RFC 8414).
- 5POST /oauth/register
Dynamic client registration (RFC 7591). No pre-shared client id.
- 6client_id
The client is now registered.
- 7GET /oauth/authorize (PKCE S256)
User picks Google or Microsoft and approves the scopes.
- 8302 → redirect_uri + code
Authorization code returned to the client.
- 9POST /oauth/token (code + verifier)
Exchanges the code for an access token.
- 10access_token
Bearer token, scoped to the approved permissions.
- 11POST /mcp (Authorization: Bearer …)
The original call, now authenticated. Archie runs it.
Discovery and registration endpoints
| Step | Endpoint |
|---|---|
| Protected-resource metadata (RFC 9728) | GET https://mcp.dev.arch.ie/.well-known/oauth-protected-resource |
| Authorization-server metadata (RFC 8414) | GET https://api.dev.arch.ie/.well-known/oauth-authorization-server |
| Dynamic client registration (RFC 7591) | POST https://api.dev.arch.ie/oauth/register |
| Authorization (PKCE S256) | GET https://api.dev.arch.ie/oauth/authorize |
| Token exchange | POST https://api.dev.arch.ie/oauth/token |
The canonical resource indicator is https://mcp.dev.arch.ie/mcp. The /mcp suffix matters, because the OAuth resource indicator is canonicalized against it. Use the full path everywhere you configure the server.
Why no client secret
Dynamic Client Registration
You never paste a client id or secret into your MCP client. Archie supports dynamic client registration (RFC 7591): on first connect the client registers itself with the authorization server and receives its own client_id on the spot. This is what makes a one-URL connector possible. There is no developer-portal step to create an OAuth app before you can connect.
- Public clients (desktop apps, editors) register without a secret and prove possession with PKCE, so there is no long-lived secret to leak.
- Registration is bound to the redirect URIs the client declares. Archie allowlists the Claude callback URIs and loopback for local clients; an unrecognized redirect URI is refused.
- Each connection gets its own registration, so revoking one client does not affect another.
https://claude.ai/api/mcp/auth_callback and https://claude.com/api/mcp/auth_callback) plus loopback are allowlisted on the authorization server. DCR is enabled. No per-developer OAuth-app creation is required.What the user approves
Consent and Scopes
On the authorization step the user signs in with WorkOS (Google or Microsoft) and approves a set of scopes. The token Archie issues carries exactly those scopes, and every call is gated against them server-side. A user who cannot see a client in the web app cannot reach that client through a token either.
The scope vocabulary is the eleven-permission taxonomy the skill registry enforces: every skill declares exactly one required permission, and the bearer must carry it for the dispatch to pass. API keys pick from the same catalog, so a scope you see here is also a permission you can put on a key.
| Scope | Grants | Example skills |
|---|---|---|
accounting:write | Bookkeeping writes — post journal entries, enrich trial-balance rows, flag duplicates. | record_journal_entry, enrich_tb_rows, detect_tb_duplicates |
ask:execute | Ask Archie a question — the conversational entry point plus the open-ended advisory and research skills built on it. | ask_archie, advise_tax, research_standards |
clients:read | Read client financial and payroll data and run analyses over it. | list_clients, get_client_financials, analyse_financials |
documents:analyse | Analyse uploaded documents and review them against compliance requirements. | analyse_document, review_compliance, audit_workpaper_review |
email:draft | Draft client communications for human approval. | prepare_communication |
reports:generate | Generate reports, memos, and packs — financial reports, CFO packs, accounting memos. | prepare_financial_report, prepare_cfo_pack, prepare_asc606_memo |
search:read | Knowledge-base search. | search_knowledge |
topics:read | List and read topics and their message history. | topic_list, topic_get, topic_messages |
topics:write | Create topics and link conversations into them. | topic_create, topic_link_conversation |
workstreams:read | List WorkStreams and inspect cycle status. | workstream_list, workstream_describe, workstream_status |
workstreams:write | Run, advance, approve, and cancel WorkStreams. | workstream_run, approve_or_reject, workstream_cancel |
messages:create, conversations:read, documents:read, drafts:write, journal:write, reports:read, standards:read, workstreams:execute — is retired. Retirement is enforced at mint time: /oauth/authorize rejects a retired scope with invalid_scope, and creating an API key with one fails with 400 unknown_permissions (the response lists the valid slugs). Existing keys needed no action: the 2026-06-10 consolidation rewrote the old registry slugs in place on every key (e.g. client:read → clients:read, workstream:execute → workstreams:write), and the retired strings above were never required by any skill, so keys still carrying them lose nothing — they keep authenticating and the inert strings simply match nothing. If you want such a key to hold meaningful scopes, create a replacement with the table above and revoke the old one — rotation copies the parent's permission list verbatim, so rotating alone does not change scopes. Rough intent translation: messages:create → ask:execute; conversations:read → topics:read; documents:read → documents:analyse; drafts:write → email:draft; journal:write → accounting:write; reports:read → reports:generate; standards:read → ask:execute; workstreams:execute → workstreams:write.A token minted with no scope parameter carries the legacy archie:full wildcard and keeps full access, so pre-narrowing OAuth connections behave as before. Scopes are enforced by WorkOS FGA on top of the workspace-membership gate. Read-only tools never write; write tools that cross a risk threshold defer to the confirmation contract.
How long it lasts
Token Lifetime and Refresh
- An access token lives as long as the WorkOS session that backs it (default eight hours). The client refreshes transparently; you do not handle the refresh yourself.
- Revoke a connection from Dashboard → Connections. The next call from that client returns
401. - A token that does not begin with
sk_short-circuits the API-key validation path, so JWT bearers stay on the fast path.
For backends and scripts
API Keys
Mint a key in Dashboard → Keys → Create key. Pick the permissions you need. The full sk_* value shows once, so copy it then. After that the portal stores only the prefix; you cannot retrieve the full value again.
curl https://api.dev.arch.ie/api/v1/skills/workstream.list/invoke \
-H "Authorization: Bearer $ARCHIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 5}'The backend validates the key against WorkOS and caches the result for five minutes per token, so the round-trip stays off the hot path. Revoking a key takes effect on the next call within that window; a rotated key keeps its parent alive for a grace period (default 24h) while you roll the new value into your secrets store.
In the browser
Portal Session
When you sign into the developers portal, WorkOS sets a session cookie. Any portal page (the playground included) calls the API with the cookie attached and the same authorization gate fires. No header needed. For programmatic or cross-origin calls, use an API key instead, since the cookie path is browser-only.
Resolution Order
Server-side, the router tries each path in order and stops at the first hit. Knowing the order helps when you are debugging why a call authenticated as a particular identity.
- Already-authenticated request state, set by middleware from the session cookie or E2E bypass.
- Bearer Archie session JWT (HS256, internal secret).
- Bearer Archie OAuth JWT (RS256, validated against the WorkOS JWKS).
- Bearer WorkOS API key (
sk_*, validated via the WorkOS SDK).
When Auth Fails
Authentication failures return 401 with the canonical error envelope. Branch on code, never on message.
{
"error": {
"type": "authentication_error",
"code": "auth_required",
"message": "Authorization: Bearer <token> required. Accepts an Archie session JWT, an Archie OAuth JWT, or a WorkOS-issued API key (sk_*)."
}
}Common codes: auth_required, invalid_token, insufficient_scope, workspace_not_found, rate_limited. The full dictionary lives in Error handling.