Liking cljdoc? Tell your friends :D

im.ttalk.agent.mcp.client

MCP 客户端:双时代。

(def c (client {:url "https://example.com/mcp"}))     ;; 或 {:command ["npx" …]}
(connect! c)                                            ;; 幂等;自动判时代
(list-tools c)
(call-tool c "get_weather" {:city "北京"})
(close! c)

判时代的算法(照规范 PR #2844 与官方 SDK 的实现写)

先按 modern 发一条 server/discover,然后看回来的是什么:

回来的判定为什么
DiscoverResult,supportedVersions 含我们这版modern,结束对面是新 server
DiscoverResult,但不含我们这版挑一个双方都支持的:还有 modern 版就换版重试,只剩 legacy 就退回 initialize它懂 discover,只是版本对不上
-32022 UnsupportedProtocolVersion同上(按 data.supported 重挑)这是新 server 的信号,不是老 server
-32021 / -32020原样抛,不回退能力不够 / 信封头不匹配——退回 initialize 也治不好,只会把真正的错误藏起来
其他任何 JSON-RPC 错误回退 initialize-32601(没这方法)、-32602(被 _meta 搞晕)、-32700…… 都说明对面是老 server。判据不能只认一个码,这是规范明写的
HTTP 400 / 404 且响应体里没有可识别的 modern 错误回退老 server 在 HTTP 层就把无会话的 POST 拒了(404 = 它要 Mcp-Session-Id)
超时没回回退stdio 上老 server 见到陌生方法可能干脆不吭声

时代是 server 的属性,不是单条请求的:判一次,记在 client 上,后续请求 都按那个时代走。

一处没做、且要说清楚的

server → client 的请求(elicitation / MRTR 的补充输入)只在 stdio 上到得了: stdio 的读线程会把它交给 :on-notification;而 HTTP 那侧,这类请求是从 POST 响应的 SSE 流里回来的,本模块的 HTTP 传输只取流里第一条 data: (见 mcp.transport),所以 HTTP 上收不到。 input_required 的结果原样返回(protocol/input-required? 可判), 调用方要接就自己接——不替想象中的用户把 MRTR 的整套状态机写出来。

MCP 客户端:**双时代**。

```clojure
(def c (client {:url "https://example.com/mcp"}))     ;; 或 {:command ["npx" …]}
(connect! c)                                            ;; 幂等;自动判时代
(list-tools c)
(call-tool c "get_weather" {:city "北京"})
(close! c)
```

## 判时代的算法(照规范 PR #2844 与官方 SDK 的实现写)

先按 **modern** 发一条 `server/discover`,然后看回来的是什么:

| 回来的 | 判定 | 为什么 |
|---|---|---|
| `DiscoverResult`,`supportedVersions` 含我们这版 | **modern**,结束 | 对面是新 server |
| `DiscoverResult`,但不含我们这版 | 挑一个双方都支持的:还有 modern 版就换版重试,只剩 legacy 就退回 `initialize` | 它懂 discover,只是版本对不上 |
| `-32022` UnsupportedProtocolVersion | 同上(按 `data.supported` 重挑) | 这是**新 server 的信号**,不是老 server |
| `-32021` / `-32020` | **原样抛,不回退** | 能力不够 / 信封头不匹配——退回 `initialize` 也治不好,只会把真正的错误藏起来 |
| 其他任何 JSON-RPC 错误 | 回退 `initialize` | `-32601`(没这方法)、`-32602`(被 `_meta` 搞晕)、`-32700`…… 都说明对面是老 server。**判据不能只认一个码**,这是规范明写的 |
| HTTP 400 / 404 且响应体里没有可识别的 modern 错误 | 回退 | 老 server 在 HTTP 层就把无会话的 POST 拒了(404 = 它要 `Mcp-Session-Id`) |
| 超时没回 | 回退 | stdio 上老 server 见到陌生方法可能干脆不吭声 |

**时代是 server 的属性,不是单条请求的**:判一次,记在 client 上,后续请求
都按那个时代走。

## 一处**没做**、且要说清楚的

**server → client 的请求(elicitation / MRTR 的补充输入)只在 stdio 上到得了**:
stdio 的读线程会把它交给 `:on-notification`;而 HTTP 那侧,这类请求是从
POST 响应的 SSE 流里回来的,本模块的 HTTP 传输只取流里第一条 `data:`
(见 `mcp.transport`),所以 HTTP 上收不到。
`input_required` 的结果**原样返回**(`protocol/input-required?` 可判),
调用方要接就自己接——不替想象中的用户把 MRTR 的整套状态机写出来。
raw docstring

call-toolclj

(call-tool c tool-name args)

tools/call → 结果原样:{content, structuredContent?, isError?}。

不替调用方解释 isError:那是「工具自己失败了」,与「协议出错」是两回事 (后者会抛)。要文本用 mcp.tools/result-text。

`tools/call` → 结果原样:`{content, structuredContent?, isError?}`。

**不替调用方解释 `isError`**:那是「工具自己失败了」,与「协议出错」是两回事
(后者会抛)。要文本用 `mcp.tools/result-text`。
sourceraw docstring

clientclj

(client {:keys [transport url command] :as opts})

构造客户端(不连接——连接在 connect!,因为判时代要发请求)。

传输三选一:

  • :transport 直接给一个 Transport(或任意 (fn [msg] resp))
  • :url Streamable HTTP
  • :command stdio 子进程

其余:

  • :client-info 自报家门,缺省 clj-agent-mcp
  • :capabilities 声明的客户端能力,缺省 protocol/default-client-capabilities
  • :protocol-version 钉死版本(给了就不做时代探测,直接按它走)
  • :server-id 本地标识,日志与工具归属用
  • :headers / :timeout-ms / :env / :dir / :on-notification 透传给传输
构造客户端(**不连接**——连接在 `connect!`,因为判时代要发请求)。

传输三选一:
- `:transport`  直接给一个 `Transport`(或**任意 `(fn [msg] resp)`**)
- `:url`        Streamable HTTP
- `:command`    stdio 子进程

其余:
- `:client-info`      自报家门,缺省 `clj-agent-mcp`
- `:capabilities`     声明的客户端能力,缺省 `protocol/default-client-capabilities`
- `:protocol-version` **钉死**版本(给了就不做时代探测,直接按它走)
- `:server-id`        本地标识,日志与工具归属用
- `:headers` / `:timeout-ms` / `:env` / `:dir` / `:on-notification` 透传给传输
sourceraw docstring

close!clj

(close! c)
source

connect!clj

(connect! c)

连接并判时代。幂等——连过了直接返回状态。

返回 {:era :modern|:legacy :protocol-version :capabilities :server-info :instructions}。

连接并判时代。**幂等**——连过了直接返回状态。

返回 `{:era :modern|:legacy :protocol-version :capabilities :server-info :instructions}`。
sourceraw docstring

ensure-connected!clj

(ensure-connected! c)

没连就连。所有特性方法的第一句。

没连就连。所有特性方法的第一句。
sourceraw docstring

eraclj

(era c)
source

error-codeclj

(error-code t)

从异常里取 MCP 错误码(不是 MCP 错误就 nil)。

从异常里取 MCP 错误码(不是 MCP 错误就 nil)。
sourceraw docstring

get-promptclj

(get-prompt c prompt-name)
(get-prompt c prompt-name args)

prompts/get → {description?, messages [...]}。

`prompts/get` → `{description?, messages [...]}`。
sourceraw docstring

instructionsclj

(instructions c)

server 自述的用法说明——适合塞进 system prompt,规范就是这么建议的。

server 自述的用法说明——**适合塞进 system prompt**,规范就是这么建议的。
sourceraw docstring

list-promptsclj

(list-prompts c)
source

list-resource-templatesclj

(list-resource-templates c)
source

list-resourcesclj

(list-resources c)
source

list-toolsclj

(list-tools c)

tools/list → 工具数组(原样,含 _meta / outputSchema / annotations)。

`tools/list` → 工具数组(**原样**,含 `_meta` / `outputSchema` / `annotations`)。
sourceraw docstring

mcp-errorclj

(mcp-error {:keys [code message data]} method)

server 回了 JSON-RPC 错误。ex-data 带 :code / :message / :data。

server 回了 JSON-RPC 错误。`ex-data` 带 `:code` / `:message` / `:data`。
sourceraw docstring

mcp-error?clj

(mcp-error? t)
source

pingclj

(ping c)
source

protocol-versionclj

(protocol-version c)
source

read-resourceclj

(read-resource c uri)

resources/read → {contents [...]}。

`resources/read` → `{contents [...]}`。
sourceraw docstring

request!clj

(request! c method-key)
(request! c method-key params)

发一条请求(自动补时代与信封)。method-key 是 protocol/method-names 的键。

发一条请求(自动补时代与信封)。`method-key` 是 `protocol/method-names` 的键。
sourceraw docstring

server-capabilitiesclj

(server-capabilities c)
source

server-infoclj

(server-info c)
source

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