OAuth 2.1 client for Model Context Protocol servers (spec 2025-06-18).
Flow — on HTTP 401 from an MCP server, we:
WWW-Authenticate: Bearer resource_metadata="..." (RFC 9728),
fall back to ${origin}/.well-known/oauth-protected-resource;authorization_servers[0];.well-known/oauth-authorization-server (RFC 8414) or
.well-known/openid-configuration for endpoints + capabilities;client_id;http://127.0.0.1:<ephemeral>/mcp-callback) — spawn a one-shot
com.sun.net.httpserver.HttpServer, print the URL, best-effort open
it in the user's browser, wait for ?code=;~/.vis/mcp-tokens/<server>.edn;com.blockether.vis.internal.oauth/make-file-refresher.The returned bearer-fn is a 0/1-arg function: 0-arg yields the current
bearer token (running the auth flow on first use); 1-arg (with the token the
server just rejected) forces a refresh.
OAuth 2.1 client for Model Context Protocol servers (spec `2025-06-18`).
Flow — on HTTP 401 from an MCP server, we:
1. read `WWW-Authenticate: Bearer resource_metadata="..."` (RFC 9728),
fall back to `${origin}/.well-known/oauth-protected-resource`;
2. GET the resource-metadata JSON → pick an `authorization_servers[0]`;
3. GET its `.well-known/oauth-authorization-server` (RFC 8414) or
`.well-known/openid-configuration` for endpoints + capabilities;
4. dynamic-client-register (RFC 7591) if the AS supports it, or use
the caller-supplied `client_id`;
5. run PKCE S256 authorization-code with a loopback redirect
(`http://127.0.0.1:<ephemeral>/mcp-callback`) — spawn a one-shot
`com.sun.net.httpserver.HttpServer`, print the URL, best-effort open
it in the user's browser, wait for `?code=`;
6. exchange code → access + refresh tokens; persist to
`~/.vis/mcp-tokens/<server>.edn`;
7. on later expiry / 401, refresh the token single-flight through
`com.blockether.vis.internal.oauth/make-file-refresher`.
The returned `bearer-fn` is a 0/1-arg function: 0-arg yields the current
bearer token (running the auth flow on first use); 1-arg (with the token the
server just rejected) forces a refresh.(cancel-authorization! flow-id)Forget an abandoned flow and release its loopback listener now.
Forget an abandoned flow and release its loopback listener now.
(complete-authorization! flow-id input)Finish flow flow-id with what the user pasted back: the redirect URL their
browser landed on, or a bare authorization code.
Finish flow `flow-id` with what the user pasted back: the redirect URL their browser landed on, or a bare authorization code.
(forget! server-name)Drop persisted tokens for server-name (e.g. on mcp__disconnect, or when a
401 recurs after refresh).
Drop persisted tokens for `server-name` (e.g. on `mcp__disconnect`, or when a 401 recurs after refresh).
(make-bearer-fn server-name server-url www-auth-atom auth-hint)Build a 0/1-arg fn returning the current Bearer token string for server-name.
On first use (no cached tokens) OR when called with the just-rejected token,
it runs the OAuth flow / refresh under a single-flight lock and persists the
new tokens. server-url is the MCP endpoint. www-auth-atom is an atom the
HTTP transport keeps updated with the latest WWW-Authenticate header (so we
discover from the LIVE 401). auth-hint is user config from :mcp :servers <name> :auth.
Build a 0/1-arg fn returning the current Bearer token string for `server-name`. On first use (no cached tokens) OR when called with the just-rejected token, it runs the OAuth flow / refresh under a single-flight lock and persists the new tokens. `server-url` is the MCP endpoint. `www-auth-atom` is an atom the HTTP transport keeps updated with the latest `WWW-Authenticate` header (so we discover from the LIVE 401). `auth-hint` is user config from `:mcp :servers <name> :auth`.
(poll-authorization! flow-id)Read a flow's verdict without blocking: pending, ok, or error. This is how
a client learns that the loopback listener already finished the flow for it.
Read a flow's verdict without blocking: `pending`, `ok`, or `error`. This is how a client learns that the loopback listener already finished the flow for it.
(start-authorization! server-name server-url {:keys [www-auth auth-hint]})Begin a HEADLESS OAuth flow for server-name at server-url and return its
public view {:flow-id :server :kind :url :redirect-uri :expires-at-ms :status}.
The caller shows :url; the user authorizes in their own browser. NO browser
is opened here — the user may be nowhere near this machine. The flow completes
by itself when that browser can reach the loopback listener on this host (the
local TUI case), otherwise the client posts the redirect URL back through
complete-authorization!.
Begin a HEADLESS OAuth flow for `server-name` at `server-url` and return its
public view `{:flow-id :server :kind :url :redirect-uri :expires-at-ms :status}`.
The caller shows `:url`; the user authorizes in their own browser. NO browser
is opened here — the user may be nowhere near this machine. The flow completes
by itself when that browser can reach the loopback listener on this host (the
local TUI case), otherwise the client posts the redirect URL back through
`complete-authorization!`.(token-status server-name)Non-secret view of the persisted OAuth tokens for server-name. Never returns
the token itself: a client may render it, and a client is never trusted with
credentials.
Non-secret view of the persisted OAuth tokens for `server-name`. Never returns the token itself: a client may render it, and a client is never trusted with credentials.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |