Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.foundation.mcp.client

Minimal Model Context Protocol (MCP) client. Speaks JSON-RPC 2.0 over two transports:

:stdio — spawn the server process and frame newline-delimited JSON-RPC on its stdin/stdout (the dominant local-server pattern). A daemon thread drains stderr into the vis log so a chatty server never deadlocks on a full pipe.

:http — Streamable HTTP: POST each JSON-RPC message to one endpoint; the reply is either application/json (one response) or text/event-stream (SSE) — both handled. The Mcp-Session-Id handed back by initialize rides on every later request, a DELETE frees it on shutdown, and an optional GET listen loop reacts to server-pushed notifications/tools/list_changed.

OAuth 2.1 (spec 2025-06-18) is supported for HTTP transports via oauth.clj: pass :bearer-fn (a 0/1-arg fn yielding the current Bearer token, called with the just-rejected token on 401). A 401 triggers a single-flight refresh and one automatic retry.

A conn is a plain map of closures + state; the extension treats it opaquely. Lifecycle: connect (which performs the initialize handshake) → list-tools / call-toolclose.

Minimal Model Context Protocol (MCP) client. Speaks JSON-RPC 2.0 over two
transports:

  :stdio  — spawn the server process and frame newline-delimited JSON-RPC on
            its stdin/stdout (the dominant local-server pattern). A daemon
            thread drains stderr into the vis log so a chatty server never
            deadlocks on a full pipe.

  :http   — Streamable HTTP: POST each JSON-RPC message to one endpoint; the
            reply is either `application/json` (one response) or
            `text/event-stream` (SSE) — both handled. The `Mcp-Session-Id`
            handed back by `initialize` rides on every later request, a
            `DELETE` frees it on shutdown, and an optional GET listen loop
            reacts to server-pushed `notifications/tools/list_changed`.

OAuth 2.1 (spec `2025-06-18`) is supported for HTTP transports via
`oauth.clj`: pass `:bearer-fn` (a 0/1-arg fn yielding the current Bearer
token, called with the just-rejected token on 401). A 401 triggers a
single-flight refresh and one automatic retry.

A `conn` is a plain map of closures + state; the extension treats it
opaquely. Lifecycle: `connect` (which performs the `initialize` handshake) →
`list-tools` / `call-tool` → `close`.
raw docstring

com.blockether.vis.internal.foundation.mcp.core

Built-in Model Context Protocol (MCP) surface. The gateway daemon owns ONE shared pool of MCP connections ({server {:conn spec}}); every session sees the same live tools. Always on: MCP is core infrastructure, not a droppable plug-in and not gated by any toggle. The pool is empty (and costs nothing) until at least one server is declared in config.

Servers are declared natively in ~/.vis/state.yml:

{:mcp {:servers {"filesystem" {:transport :stdio :command "npx" :args ["-y" "@modelcontextprotocol/server-filesystem" "/path"]} "remote" {:transport :http :url "https://..." :headers {"Authorization" "Bearer ${MY_TOKEN}"} :timeout_ms 60000 :listen true :auth {:client_id "..." :scope "..."}} "stale" {:enabled false :url "..."}}}}

Every string in :headers / :env / :args / :url / :command / :cwd supports ${ENV_VAR} interpolation from the host environment. :enabled false skips the server without deleting the entry. HTTP servers with no static bearer transparently negotiate OAuth 2.1 on first 401 (RFC 9728 discovery + RFC 7591 dynamic client registration + PKCE loopback).

Five model-facing verbs under alias mcp (flat sandbox renders alias_name): mcp__servers() — configured servers + status + tool counts mcp__tools(server) — a server's tools (auto-connects) mcp__call(server, tool, args) — call a tool (auto-connects) mcp__connect(server) / mcp__disconnect(server) — manage the connection

Live connections + tool counts also ride in ctx under env.mcp.

Built-in Model Context Protocol (MCP) surface. The gateway daemon owns ONE
shared pool of MCP connections (`{server {:conn spec}}`); every session sees
the same live tools. Always on: MCP is core infrastructure, not a droppable
plug-in and not gated by any toggle. The pool is empty (and costs nothing)
until at least one server is declared in config.

Servers are declared natively in `~/.vis/state.yml`:

  {:mcp {:servers {"filesystem" {:transport :stdio :command "npx"
                                 :args ["-y" "@modelcontextprotocol/server-filesystem" "/path"]}
                   "remote"     {:transport :http :url "https://..."
                                 :headers {"Authorization" "Bearer ${MY_TOKEN}"}
                                 :timeout_ms 60000
                                 :listen true
                                 :auth {:client_id "..." :scope "..."}}
                   "stale"      {:enabled false :url "..."}}}}

Every string in `:headers` / `:env` / `:args` / `:url` / `:command` / `:cwd`
supports `${ENV_VAR}` interpolation from the host environment. `:enabled
false` skips the server without deleting the entry. HTTP servers with no
static bearer transparently negotiate OAuth 2.1 on first 401 (RFC 9728
discovery + RFC 7591 dynamic client registration + PKCE loopback).

Five model-facing verbs under alias `mcp` (flat sandbox renders `alias_name`):
  mcp__servers()                — configured servers + status + tool counts
  mcp__tools(server)            — a server's tools (auto-connects)
  mcp__call(server, tool, args) — call a tool (auto-connects)
  mcp__connect(server) / mcp__disconnect(server) — manage the connection

Live connections + tool counts also ride in ctx under `env.mcp`.
raw docstring

com.blockether.vis.internal.foundation.mcp.http

One JDK java.net.http.HttpClient shared by every MCP subsystem (request/response, OAuth discovery + token exchange, SSE listen loop).

Rationale — perf + threading:

  • The JDK client owns a small internal selector pool AND an executor used to run response-body pipelines and sendAsync completions. Two client instances = two selector pools = wasted daemon threads for zero win. So we share ONE instance across client.clj + oauth.clj.
  • The executor is a newVirtualThreadPerTaskExecutor — on Java 25 virtual threads no longer pin on synchronized (JEP 491), so blocking-.send calls made from a virtual thread never park a carrier. Bounded footprint, no cached thread-pool growth under bursty MCP load.
  • defonce + delay = constructed lazily at runtime, never at namespace-load; safe for GraalVM native-image (the client owns non-heap state that can't land in the build image).
One JDK `java.net.http.HttpClient` shared by every MCP subsystem
(request/response, OAuth discovery + token exchange, SSE listen loop).

Rationale — perf + threading:
  * The JDK client owns a small internal selector pool AND an executor used
    to run response-body pipelines and `sendAsync` completions. Two client
    instances = two selector pools = wasted daemon threads for zero win.
    So we share ONE instance across `client.clj` + `oauth.clj`.
  * The executor is a `newVirtualThreadPerTaskExecutor` — on Java 25 virtual
    threads no longer pin on `synchronized` (JEP 491), so blocking-`.send`
    calls made from a virtual thread never park a carrier. Bounded footprint,
    no cached thread-pool growth under bursty MCP load.
  * `defonce` + `delay` = constructed lazily at runtime, never at
    namespace-load; safe for GraalVM native-image (the client owns non-heap
    state that can't land in the build image).
raw docstring

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) — 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.

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.
raw 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