Two independent axes
Every MCP client sits somewhere on two independent axes:- Who authenticates — server-level (
none,headers,oauth) vs. per-user (per_user_headers,per_user_oauth,token_exchange). Covered in MCP Authentication. - How the connection is held — sticky (one persistent upstream connection, reused for every tool call) vs. per-call (a fresh connection dialed per tool call, closed immediately after). This page’s focus.
So
needs_session_stickiness only ever has an effect on http connections using a server-level auth type. Everything else is fixed by construction.
Two entirely separate credential-resolution paths exist for per-user auth types, and they’re easy to conflate: the admin discovery credential (retained once, used only for periodically refreshing the server’s tool list) and the end-user’s own credential (resolved per request, used only for the actual tool call). See Per-user lifecycle below.
Connection states
pending_tools and a bare connected/disconnected naming existed in older versions of this doc set — the current state names are exactly the seven above. If you see connected, disconnected, connecting, or pending_tools referenced anywhere else in these docs, that’s stale and maps to healthy, unstable, (nothing — was never a real state), and (removed — an empty healthy tool map already communicates the same thing) respectively.Lifecycle: sticky server-level (needs_session_stickiness: true, or sse/stdio)
- Connect happens once at
AddClient(boot, or client creation) and again on every reconnect. Reconnects are make-before-break: the old connection keeps serving until the new one is ready, so token rotation never causes downtime. unstableis purely informational — a network blip doesn’t stop tool calls, and the client self-heals on the next successful check with no human involvement.needs_reauthis a hard gate specifically for server-level clients: the periodic checker stops retrying entirely (retrying against a known-dead credential is pointless), and tool calls are refused outright rather than attempted. Recovery isPOST /api/mcp/client/{id}/reauthorize(also coversoauth;headers/nonecredentials don’t expire the same way, so this state is effectively OAuth-only in practice for server-level clients).- Every discovered tool list — from the initial connect and from every periodic refresh — persists to the DB, gated on the tool list actually having changed (no write on an unchanged tick), so a restart doesn’t lose anything a running instance had already discovered.
Lifecycle: per-call server-level (needs_session_stickiness: false/omitted, http only)
- No persistent connection ever exists — every tool call dials fresh and closes immediately after. There’s nothing to reconnect (
POST /reconnectreturns400for this mode), andneeds_reauthdoesn’t apply here (there’s no single connection credential to die —headers/none/oauthper-call clients cycle through a fresh credential resolution on every call). AddClientruns a synchronous first discovery pass if no tools are already known (e.g. from a prior successful discovery persisted to the DB) — without this, a client would sit athealthy/0-tools until the periodic checker’s slow first tick, which can be minutes away for an already-healthyclient.- The periodic checker’s ongoing ticks are the only thing revisiting this client afterward — same content-hash gating as the sticky case, so an unchanged tool list doesn’t cause a write.
Lifecycle: per-user (per_user_oauth, per_user_headers, token_exchange)
Per-user clients have two entirely separate credential paths, which is the single most important thing to understand about them:
Admin discovery credential — exists purely to keep the advertised tool list current, independent of any individual user:
- Bootstrap. An admin submits a sample credential once (
POST /verify-headers,POST /verify-exchange, or thecomplete-oauthcallback forper_user_oauth). This is the one and only time a live admin session is required. As a side effect, that credential is retained server-side. - Ongoing refresh.
AddClient’s own synchronous first-discovery pass and the periodic checker’s ongoing ticks both reuse the retained credential (never a fresh admin login) to keep the tool list current — same content-hash-gated persistence as server-level clients. - Repair. If the retained credential itself dies (OAuth refresh fails, or a header schema change flips it to stale), it projects as
needs_reauthin the client list — but only when the underlying runtime reading ishealthyorunstable; a client sitting inpending_verificationordisabledkeeps that state instead. Repair is auth-type-specific:POST /verify-headers(headers),POST /verify-exchange(token exchange), orreauthorize→complete-oauth(per_user_oauth).
- A caller’s request carries an identity (VK, signed-in user, or session ID — see Identity modes).
- Bifrost looks up that identity’s own stored credential for this MCP client. Found and active → the call proceeds transparently. Missing or stale → an
mcp_auth_requiredpayload with an auth URL is returned instead of executing the tool. - The caller completes their own auth (OAuth consent, or submitting header values) once; every later call resolves transparently from then on.
Recovery endpoints by combination
A
reconnect/reauthorize/close call that doesn’t apply to a client’s current connection mode returns a consistent error (client uses per-call connections; there is no persistent connection to reconnect/close) rather than one that implies something is specifically wrong with per-user auth — the same message applies to any per-call client, shared or per-user.
Next Steps
- Session Stickiness — how to set
needs_session_stickiness - MCP Authentication — which auth type to pick
- MCP Gateway — reconnection behavior, dynamic tool discovery, health monitoring

