(answer->elicit-result answer)SPEC §10 — a resolved §10 Answer as an MCP ElicitResult.
ok ⇒ accept carrying data as content; otherwise reason:"declined" ⇒
decline and anything else (cancelled, expired, absent) ⇒ cancel.
SPEC §10 — a resolved §10 Answer as an MCP `ElicitResult`. `ok` ⇒ accept carrying `data` as content; otherwise `reason:"declined"` ⇒ decline and anything else (cancelled, expired, absent) ⇒ cancel.
(connect server)(connect server {:keys [wait-for] :as conn-opts})SPEC §2 — reach the server, initialize, notifications/initialized,
tools/list. Returns a CONNECTION:
{:name … :status "connected"|"disabled"|"failed" :tools [Tool…] :transport … :server-info … :error {…}}
Never throws. §0.3: a server that fails to connect is recorded failed and
the caller carries on with the servers that did work.
EXIT vs EOF — ANSWERED, as of koine 0.8.0. When a stdio peer stops talking,
read-line! returns nil, and that alone cannot say whether the child exited,
crashed, or merely closed stdout while still running. This port used to report
a bare peer-eof for all three, and distinguished a dead peer from a quiet
one with a kill! timeout — a guess dressed as a policy, wrong in both
directions: too short kills a slow peer, too long hangs on a dead one.
koine.process/exit-code replaces the guess with an observation: nil = has
not exited, a number = the status. The close reason is now peer-exited (status N) where the status is known, and :exit-code is on the connection
for a caller whose retry logic needs to tell a crash from a hang.
ONE TRAP, MEASURED RATHER THAN REASONED: exit-code must NOT be read the
instant EOF arrives. A child running sh -c 'echo …>&2; exit 3' reported nil
on 4 of 5 runs at that moment — closing stdout and the reaper recording the
status are different events, and EOF wins the race. Read naively, that turns
a do-not-know-yet into a confident still-running, which is a worse answer
than the peer-eof it replaced. This layer waits up to ~250 ms for the reaper,
and if the status is still unknown it SAYS unknown rather than guessing
alive.
Note that alive? is still not consulted for this. It answers a different
question, and koine reads its own reaper for exit rather than cljgo's native
:exit-code so there is ONE source of truth for has-it-exited — two would
drift. The child's stderr ring remains attached to the failure, because a
status code says THAT it died and the stderr says WHY.
SPEC §2 — reach the server, `initialize`, `notifications/initialized`,
`tools/list`. Returns a CONNECTION:
{:name … :status "connected"|"disabled"|"failed" :tools [Tool…]
:transport … :server-info … :error {…}}
Never throws. §0.3: a server that fails to connect is recorded `failed` and
the caller carries on with the servers that did work.
EXIT vs EOF — ANSWERED, as of koine 0.8.0. When a stdio peer stops talking,
`read-line!` returns nil, and that alone cannot say whether the child exited,
crashed, or merely closed stdout while still running. This port used to report
a bare `peer-eof` for all three, and distinguished a dead peer from a quiet
one with a `kill!` timeout — a guess dressed as a policy, wrong in both
directions: too short kills a slow peer, too long hangs on a dead one.
`koine.process/exit-code` replaces the guess with an observation: nil = has
not exited, a number = the status. The close reason is now `peer-exited
(status N)` where the status is known, and `:exit-code` is on the connection
for a caller whose retry logic needs to tell a crash from a hang.
ONE TRAP, MEASURED RATHER THAN REASONED: exit-code must NOT be read the
instant EOF arrives. A child running `sh -c 'echo …>&2; exit 3'` reported nil
on 4 of 5 runs at that moment — closing stdout and the reaper recording the
status are different events, and EOF wins the race. Read naively, that turns
a do-not-know-yet into a confident still-running, which is a worse answer
than the `peer-eof` it replaced. This layer waits up to ~250 ms for the reaper,
and if the status is still unknown it SAYS unknown rather than guessing
alive.
Note that `alive?` is still not consulted for this. It answers a different
question, and koine reads its own reaper for exit rather than cljgo's native
`:exit-code` so there is ONE source of truth for has-it-exited — two would
drift. The child's stderr ring remains attached to the failure, because a
status code says THAT it died and the stderr says WHY.SPEC §0.3 — the per-phase budget when a server does not state one.
SPEC §0.3 — the per-phase budget when a server does not state one.
(disconnect connection)Close a connection's transport. Idempotent, never throws, and BOUNDED — for stdio it kills rather than politely waits, because an unkillable disconnect is how one wedged server takes the host down with it.
Close a connection's transport. Idempotent, never throws, and BOUNDED — for stdio it kills rather than politely waits, because an unkillable disconnect is how one wedged server takes the host down with it.
(disconnect-all result)Close every connection a from-config opened.
Close every connection a `from-config` opened.
(elicitation->request params)SPEC §10 — an MCP elicitation/create params object as a §10 Request.
The id is opaque and per-process (js/src/mcp.ts builds it from a timestamp and a counter, and no port compares ids with another). Uniqueness is the only property anything depends on, so the counter — not the clock — is what guarantees it here.
SPEC §10 — an MCP `elicitation/create` params object as a §10 Request. The id is opaque and per-process (js/src/mcp.ts builds it from a timestamp and a counter, and no port compares ids with another). Uniqueness is the only property anything depends on, so the counter — not the clock — is what guarantees it here.
(expand-headers headers)SPEC §0.3 — remote headers VALUES expand ${ENV_VAR} from the environment.
The expanded value is used and NEVER logged. Callers may report the SHAPE
(header-shape) and nothing else.
SPEC §0.3 — remote `headers` VALUES expand ${ENV_VAR} from the environment.
The expanded value is used and NEVER logged. Callers may report the SHAPE
(`header-shape`) and nothing else.(from-config config)(from-config config conn-opts)SPEC §2 — the whole source in one call. config is a JSON string or a map.
Returns {:tools [Tool…] :statuses {name -> "connected"|"disabled"|"failed"} :connections [conn…] :errors {name -> message}}.
Servers are connected in name order, one at a time. Not because serial is better — it is slower — but because it is DETERMINISTIC, and §0's whole premise is that two runtimes given the same fixture produce the same output. A tool-name collision resolved by whichever server answered first is exactly the kind of drift this repo exists to prevent.
SPEC §2 — the whole source in one call. `config` is a JSON string or a map.
Returns `{:tools [Tool…] :statuses {name -> "connected"|"disabled"|"failed"}
:connections [conn…] :errors {name -> message}}`.
Servers are connected in name order, one at a time. Not because serial is
better — it is slower — but because it is DETERMINISTIC, and §0's whole
premise is that two runtimes given the same fixture produce the same output.
A tool-name collision resolved by whichever server answered first is exactly
the kind of drift this repo exists to prevent.(header-shape raw expanded)The only thing about headers that may leave this process: which keys exist, and whether ${ENV} expansion CHANGED anything. Never a value, and never a length — a length leaks a secret's size.
The only thing about headers that may leave this process: which keys exist,
and whether ${ENV} expansion CHANGED anything. Never a value, and never a
length — a length leaks a secret's size.(http-transport server)(http-transport {:keys [url headers]} {:keys [wait-for]})The remote leg. Identical shape to stdio-transport, which is the point:
every line above §2 is written once.
The remote leg. Identical shape to `stdio-transport`, which is the point: every line above §2 is written once.
(mcp-tool-name server-name remote-name)SPEC §0.2 — sanitize(server)_sanitize(tool).
SPEC §0.2 — sanitize(server)_sanitize(tool).
(parse-config config)SPEC §0.3 / §2 — a JSON string or an already-parsed map becomes a vector of server maps, sorted by name so two hosts cannot disagree about order.
Accepts mcpServers | servers | mcp; with no wrapper the object itself is
the server map, minus the reserved sibling keys.
SPEC §0.3 / §2 — a JSON string or an already-parsed map becomes a vector of server maps, sorted by name so two hosts cannot disagree about order. Accepts `mcpServers` | `servers` | `mcp`; with no wrapper the object itself is the server map, minus the reserved sibling keys.
(server-request-response wait-for msg)The response to ONE server-initiated JSON-RPC request, or nil when the message is not one.
Satisfied INLINE, as §10 requires: the in-flight tools/call resumes when
this returns, and nothing re-executes. With no wait-for the client never
advertised elicitation, so a request for it is simply an unknown method —
a clean refusal rather than a hang. A host callback that THROWS becomes a
cancel: §0.3's isolation rule does not stop at the reverse-request boundary.
The response to ONE server-initiated JSON-RPC request, or nil when the message is not one. Satisfied INLINE, as §10 requires: the in-flight `tools/call` resumes when this returns, and nothing re-executes. With no `wait-for` the client never advertised `elicitation`, so a request for it is simply an unknown method — a clean refusal rather than a hang. A host callback that THROWS becomes a cancel: §0.3's isolation rule does not stop at the reverse-request boundary.
(shape-result result)SPEC §0.4, all three branches: isError ⇒ error ToolResult carrying the joined text structuredContent ⇒ output is the JSON encoding of it (text ignored) else ⇒ the joined text parts
koine's write-str sorts keys, so the structured branch is byte-identical on
both hosts — which is what makes §0's cross-language byte comparison possible
at all.
SPEC §0.4, all three branches: isError ⇒ error ToolResult carrying the joined text structuredContent ⇒ output is the JSON encoding of it (text ignored) else ⇒ the joined text parts koine's `write-str` sorts keys, so the structured branch is byte-identical on both hosts — which is what makes §0's cross-language byte comparison possible at all.
(stderr connection)The connection's recent child stderr (stdio only; [] for remote). The reason koine drains it: it is the only thing that explains why a server died.
The connection's recent child stderr (stdio only; [] for remote). The reason koine drains it: it is the only thing that explains why a server died.
(stdio-transport server)(stdio-transport {:keys [command cwd environment]} {:keys [wait-for]})Spawn the child and hand back a transport. §2: merged env = process env +
environment, in cwd. koine's spawn drains stderr into a bounded ring
from the first instant, which is why a verbose server no longer deadlocks on a
full pipe, and why :stderr below can explain a crash.
Spawn the child and hand back a transport. §2: merged env = process env + `environment`, in `cwd`. koine's `spawn` drains stderr into a bounded ring from the first instant, which is why a verbose server no longer deadlocks on a full pipe, and why `:stderr` below can explain a crash.
(tools x)The Tools of a connection, or of a from-config result.
The Tools of a connection, or of a `from-config` result.
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 |