Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.foundation.mcp.oauth

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) — HEADLESS: start-authorization! binds the one-shot listener and RETURNS the URL for whoever is authorizing (often on a different device than the daemon), and finish-authorization! lands the code either from that listener or from a redirect URL pasted back. Nothing blocks waiting for a human;
  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; 1-arg (with the token the server just rejected) forces a refresh. It never opens a browser and never waits — with nothing to refresh it throws :mcp/oauth-required, which callers turn into sign in.

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`) — HEADLESS: `start-authorization!`
     binds the one-shot listener and RETURNS the URL for whoever is
     authorizing (often on a different device than the daemon), and
     `finish-authorization!` lands the code either from that listener or from
     a redirect URL pasted back. Nothing blocks waiting for a human;
  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; 1-arg (with the token the server just rejected) forces a
refresh. It never opens a browser and never waits — with nothing to refresh
it throws `:mcp/oauth-required`, which callers turn into `sign in`.
raw docstring

cancel-authorization!clj

(cancel-authorization! flow-id)

Forget an abandoned flow and release its loopback listener now.

Forget an abandoned flow and release its loopback listener now.
sourceraw docstring

complete-authorization!clj

(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.
sourceraw docstring

forget!clj

(forget! server-name)

Drop persisted tokens for server-name (e.g. on a gateway sign-out, or when a 401 recurs after refresh).

Drop persisted tokens for `server-name` (e.g. on a gateway sign-out, or when a
401 recurs after refresh).
sourceraw docstring

make-bearer-fnclj

(make-bearer-fn server-name server-url www-auth-atom)

Build a 0/1-arg fn returning the current Bearer token string for server-name. 0-arg yields the persisted token; 1-arg (the token the server just rejected) forces a single-flight refresh. server-url is the MCP endpoint and www-auth-atom is the atom the HTTP transport keeps pointed at the latest WWW-Authenticate header, so a refusal can carry the LIVE 401 details.

It never runs an interactive authorization. Every caller sits on a path a human is waiting on - a tool call, a reconcile tick, the health loop - and the browser dance blocked each of them for up to five minutes on a server nobody had signed into yet, which looked exactly like a hung daemon. With nothing to refresh it throws :mcp/oauth-required immediately; authorizing is the explicit headless flow below, which hands back a URL instead of waiting for one.

Build a 0/1-arg fn returning the current Bearer token string for `server-name`.
0-arg yields the persisted token; 1-arg (the token the server just rejected)
forces a single-flight refresh. `server-url` is the MCP endpoint and
`www-auth-atom` is the atom the HTTP transport keeps pointed at the latest
`WWW-Authenticate` header, so a refusal can carry the LIVE 401 details.

It never runs an interactive authorization. Every caller sits on a path a
human is waiting on - a tool call, a reconcile tick, the health loop - and the
browser dance blocked each of them for up to five minutes on a server nobody
had signed into yet, which looked exactly like a hung daemon. With nothing to
refresh it throws `:mcp/oauth-required` immediately; authorizing is the
explicit headless flow below, which hands back a URL instead of waiting for
one.
sourceraw docstring

poll-authorization!clj

(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.
sourceraw docstring

start-authorization!clj

(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} — string-keyed, like every MCP surface.

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}` — string-keyed, like every MCP surface.

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!`.
sourceraw docstring

token-statusclj

(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.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close