杠精派
派聪明
0/512

热门评论

是你啊 2026年08月30日 12:43
引用原文:手写 JSON-RPC 2.0 客户端,基于 CompletableFuture + ConcurrentHashMap 实现请求响应异步配对,支持超时调度和通知广播
@派聪明 解释
点赞
回复13 取消回复
派聪明 2026年08月30日 12:43
这句话描述的是 PaiCLI 中 MCP通信底层客户端的设计,逐点解释如下: **1. 手写 JSON-RPC2.0客户端** MCP协议的底层通信基于 JSON-RPC2.0(一种轻量级的远程过程调用协议,每条消息就是一个带 `method`、`params`、`id` 的 JSON)。作者没有引入现成的 SDK,而是自己写了约120 行代码的 `JsonRpcClient`,目的是理解协议原理、掌控实现细节。 **2.请求响应异步配对(CompletableFuture + ConcurrentHashMap)** JSON-RPC 是异步的:发出请求后,响应可能延迟到达,且多个请求可并发。如何知道哪个响应对应哪个请求?靠 `id` 字段: - 发请求时用 `AtomicLong`生成递增 ID,往 `ConcurrentHashMap<Long, CompletableFuture<JsonNode>>` 里塞一个 future- 收到响应时按 ID 从 map 中找到对应 future 并 complete(填充结果) -调用方通过 `future.get(timeout)`阻塞等待结果这样即使多个请求并发发出、响应乱序返回,也能准确配对,互不干扰。 **3.超时调度** 用 `ScheduledExecutorService`(daemon线程)设置定时任务,默认60秒超时。到时间还没收到响应,就把 pending 的 future 以 `TimeoutException` 异常结束,避免调用方永久挂起。调用方设置的超时会比内部多1秒缓冲,避免两处同时超时产生竞争。 **4.通知广播** JSON-RPC 中有一类“通知”(notification),没有 `id` 字段、不需要回复(如 `notifications/initialized`)。客户端收到这类消息时,不走配对逻辑,而是广播给注册的监听者处理。 **一句话总结**:这是用 Java 并发工具(Future + 并发 Map +调度线程池)实现的一个轻量、支持并发、带超时保护的 RPC通信客户端,是 MCP协议交互的底层基石。
点赞
回复 取消回复
展开剩余 12 条回复

80 条评论

是你啊 2026年09月06日 19:53
引用原文:Claude 官方提供的 filesystem
@派聪明 1.既然前面已经写好write,read文件的一些工具了,为啥还要引入这个filesystem的mcp 2.自己定义的工具和这个mcp是不是都能读当前电脑上所有文件夹的目录以及文件,以及所有类型的文件都能读吗
点赞
回复1 取消回复
派聪明 2026年09月06日 19:53
# 关于 filesystem MCP 的两个问题##1. 为什么还要引入 filesystem MCP? 核心原因是**解耦**,而不是功能重复: - **自己写的工具**:功能、边界、维护都是你自己的事。想加新能力(比如搜索文件、列目录树、批量重命名),每个都得自己写代码、自己测试、自己修 bug。 - **MCP 的价值**:Claude官方的 filesystem server 是别人**已经写好并持续维护**的成品(包含 read_file、write_file、edit_file、search_files、list_directory、move_file、get_file_info 等11 个工具)。PaiCLI **不用写一行代码**,在 `mcp.json` 里加一段配置就能直接用。 一句话:**不是“必须用 MCP才能读文件”,而是“有了 MCP,别人写好的工具可以直接拿来用”**。你今天需要文件操作,明天需要读 GitHub、读数据库、开浏览器,难道每个都自己写?MCP 让 Agent接入第三方工具变成“配置问题”而不是“开发问题”。这也正是文章要演示的重点——PaiCLI 作为 MCP客户端,能同时接入任意多个 MCP server。 ##2.权限范围和文件类型支持**两者读的范围是一样的,但都不是“整个电脑”,而是受限的:** - 自己写的工具:取决于你自己实现的路径校验逻辑。 - filesystem MCP:只能访问**启动时指定的目录**。看配置就明白: ```json"args": ["-y", "@modelcontextprotocol/server-filesystem", "${PROJECT_DIR}"] ``` `${PROJECT_DIR}` 就是允许访问的根目录,MCP server 会拒绝访问这个范围之外的路径。想让它读别的目录,改这个参数即可(也可以传多个目录)。 **文件类型方面**:都能读取任意路径下的文件,但要注意: - **文本类文件**(代码、Markdown、JSON 等)可以完整读取内容; - **二进制文件**(图片、视频、压缩包等)不是“读不了”,而是读出来对 LLM意义有限——文本内容会被当作字符串返回,二进制数据不适合直接塞给模型。文中也提到,非 text 类型(如图片)PaiCLI 只会给占位提示。 另外,MCP工具注册时会加 `mcp__filesystem__`前缀避免与内置 read_file 重名,且默认
点赞
回复 取消回复
是你啊 2026年08月30日 12:44
引用原文:设计 McpTransport 传输层抽象,实现 stdio(子进程管道通信)和 Streamable HTTP(OkHttp + SSE 流式解析)两种传输方式
@派聪明 为什么要设计两种传输方式
点赞
回复1 取消回复
派聪明 2026年08月30日 12:45
#为什么要设计 stdio 和 Streamable HTTP两种传输方式? MCP协议本身定义了这两种标准传输方式,它们各有适用场景,互为补充: ##1. stdio(子进程管道通信) **原理**:Agent 作为父进程启动 MCP server 子进程,通过 stdin/stdout管道交互,每条 JSON-RPC消息占一行(NDJSON 格式)。 **适用场景与优势**: - **本地工具**:如文件操作、Git、SQLite 等,server必须跑在本地- **简单直接**:无需联网,延迟极低- **安全性好**:server 是自己启动的子进程,权限和 Agent 进程相同,不存在网络暴露风险,不用担心被别人远程调用**缺点**:只能本地运行,无法远程调用。 ##2. Streamable HTTP(OkHttp + SSE 流式解析) **原理**:往一个 URL 发 POST请求,服务端用 SSE(Server-Sent Events)流式回复(或一次性返回 JSON),支持 Session ID 会话管理。 **适用场景与优势**: - **远程工具**:server 可部署在云端,如智谱的 zread读取 GitHub仓库- **资源共享**:多个客户端可共享同一个云端 server实例,便于团队/企业统一提供服务**缺点**:有网络延迟,且需要处理鉴权问题。 ## 总结两种方式覆盖了 MCP生态的两类典型工具:**stdio 对应本地高性能工具,Streamable HTTP 对应云端共享服务**。通过 `McpTransport`接口(`send` + `onReceive`)做统一抽象,上层代码无需关心底层是管道还是 HTTP,新增传输方式也不影响业务逻辑,这是典型的面向接口设计。
点赞
回复 取消回复
是你啊 2026年08月30日 12:43
引用原文:手写 JSON-RPC 2.0 客户端,基于 CompletableFuture + ConcurrentHashMap 实现请求响应异步配对,支持超时调度和通知广播
@派聪明 解释
点赞
回复13 取消回复
派聪明 2026年08月30日 12:43
这句话描述的是 PaiCLI 中 MCP通信底层客户端的设计,逐点解释如下: **1. 手写 JSON-RPC2.0客户端** MCP协议的底层通信基于 JSON-RPC2.0(一种轻量级的远程过程调用协议,每条消息就是一个带 `method`、`params`、`id` 的 JSON)。作者没有引入现成的 SDK,而是自己写了约120 行代码的 `JsonRpcClient`,目的是理解协议原理、掌控实现细节。 **2.请求响应异步配对(CompletableFuture + ConcurrentHashMap)** JSON-RPC 是异步的:发出请求后,响应可能延迟到达,且多个请求可并发。如何知道哪个响应对应哪个请求?靠 `id` 字段: - 发请求时用 `AtomicLong`生成递增 ID,往 `ConcurrentHashMap<Long, CompletableFuture<JsonNode>>` 里塞一个 future- 收到响应时按 ID 从 map 中找到对应 future 并 complete(填充结果) -调用方通过 `future.get(timeout)`阻塞等待结果这样即使多个请求并发发出、响应乱序返回,也能准确配对,互不干扰。 **3.超时调度** 用 `ScheduledExecutorService`(daemon线程)设置定时任务,默认60秒超时。到时间还没收到响应,就把 pending 的 future 以 `TimeoutException` 异常结束,避免调用方永久挂起。调用方设置的超时会比内部多1秒缓冲,避免两处同时超时产生竞争。 **4.通知广播** JSON-RPC 中有一类“通知”(notification),没有 `id` 字段、不需要回复(如 `notifications/initialized`)。客户端收到这类消息时,不走配对逻辑,而是广播给注册的监听者处理。 **一句话总结**:这是用 Java 并发工具(Future + 并发 Map +调度线程池)实现的一个轻量、支持并发、带超时保护的 RPC通信客户端,是 MCP协议交互的底层基石。
点赞
回复 取消回复
展开剩余 12 条回复
是你啊 2026年08月30日 12:12
引用原文:一种是 stdio 传输(filesystem),另一种是 Streamable HTTP 传输(zread)
@派聪明 这两个内网开发能用吗 仓库用的自己搭的,不是github
点赞
回复1 取消回复
派聪明 2026年08月30日 12:12
# 内网环境能用吗? **stdio传输 ——完全可以用✅** - stdio 是本地子进程通信(stdin/stdout管道),**完全不需要联网** -唯一需要注意的是:`npx`启动时要从 npm仓库下载依赖。内网没有外网的话,解决办法: -提前在有网机器上 `npm install -g @modelcontextprotocol/server-filesystem`,然后把整个 node_modules拷贝到内网,配置里 `command` 直接指向内网的 node 和脚本路径 - 或者在内网搭建 npm私服(如 Nexus/Verdaccio),配置 npm registry指向私服- 操作本地文件系统的能力(filesystem)本身就是本地操作,跟 GitHub 无关**Streamable HTTP ——也可以,但取决于你的内网部署🟡** - 它本质上就是往一个 URL 发 HTTP请求。只要 **MCP server部署在内网可达的位置**(比如内网服务器上),就能正常用- zread 这个具体 server 是智谱的公网服务,内网访问不了,但它是用来读 **GitHub仓库**的,你自己搭的仓库用不上-替代方案:Git也有官方 MCP server(stdio 方式),`@modelcontextprotocol/server-git`,直接对本地 clone下来的仓库做读操作、log查询、diff 分析等,完全离线可用**针对你的场景建议:** ```json{ "mcpServers": { "filesystem": { "command": "node", "args": ["/path/to/server-filesystem/dist/index.js", "${PROJECT_DIR}"] }, "git": { "command": "node", "args": ["/path/to/server-git/dist/index.js", "${PROJECT_DIR}"] } } } ``` 总结:**内网用 stdio 方式的 filesystem + git 这两个 server,配合本地 clone 的私有仓库,完全不依赖外网**,是最适合你的组合。
点赞
回复 取消回复
eating 2026年08月28日 17:20
引用原文:两个位置
@派聪明 ~/.paicli/mcp.json和.paicli/mcp.json有什么区别?
点赞
回复1 取消回复
派聪明 2026年08月28日 17:20
# `~/.paicli/mcp.json` 与 `.paicli/mcp.json` 的区别这两个是 PaiCLI 的 MCP 配置文件,区别在于**作用范围和优先级**: ##1. 用户级配置:`~/.paicli/mcp.json` - `~` 代表当前用户的**家目录**(如 `/home/yourname`) - **全局生效**,所有项目都能用这份配置里的 MCP server-适合放**个人偏好**,比如你自己的 API Key(zread 的 `Bearer ${GLM_API_KEY}`) ##2. 项目级配置:`.paicli/mcp.json` - 位于**当前项目根目录**下- **仅对该项目生效** - 可以提交到 git,团队成员 clone下来就能直接用-适合放**团队共用**的 server,比如公司内部的知识库 MCP## 加载规则- 加载顺序:**先读用户级,再读项目级** - 如果**同名 server** 在两个文件里都配置了,**项目级会覆盖用户级** -优先级:项目级 > 用户级##简单总结| | 用户级 | 项目级 | |---|---|---| |路径 | `~/.paicli/mcp.json` | `项目/.paicli/mcp.json` | | 生效范围 | 全局所有项目 | 仅当前项目 | | 用途 | 个人偏好、私人密钥 |团队共享配置 | | 是否入 git | 否 | 是 | 另外,两者配置格式完全相同,且与 Claude Code 的配置格式兼容。
点赞
回复 取消回复
eating 2026年08月28日 17:16
引用原文:工具参数 Schema
@派聪明 什么是返回的工具参数?请你举例子讲解一下
点赞
回复 取消回复
eating 2026年08月28日 17:14
引用原文:Content-Type 如果是 text/event-stream
@派聪明 这种响应是什么格式?为什么要用SSE格式解析
点赞
回复1 取消回复
派聪明 2026年08月28日 17:14
# SSE(Server-Sent Events)格式解析## 这是什么格式`text/event-stream` 是 **SSE(Server-Sent Events)** 的 MIME 类型,它是 HTTP协议中一种**服务器向客户端单向推送数据**的流式格式。 SSE 的数据格式非常简单,就是纯文本,按行组织: ``` data: 第一条消息的一部分data: {"jsonrpc": "2.0", "id":1, "result": {...}} data:消息结束``` 规则: -每条消息由若干 `data:` 行组成,多个 `data:` 行会被拼接- **空行**作为消息分隔符,表示一条完整消息结束- 除 `data:` 外还有 `event:`(事件类型)、`id:`(消息序号)、`retry:`(重连间隔)等字段,但 MCP场景主要用 `data:`##为什么要用 SSE 格式解析在 MCP 的 **Streamable HTTP**传输中,客户端向 server 的 URL 发一个 POST请求,服务端有两种可能的响应: 1. **普通 JSON**(`Content-Type: application/json`):一次性返回完整结果2. **SSE 流**(`Content-Type: text/event-stream`):流式返回用 SSE 的原因主要有: 1. **支持长耗时操作**:工具调用可能执行很久(如读大文件、跑复杂查询),SSE 让服务端可以先推送进度通知,最后才返回最终结果,而不是让客户端干等一个阻塞的 HTTP响应。 2. **一条连接传多条消息**:一次 POST 的响应流里可以依次推送多条 JSON-RPC消息(通知、日志、结果),SSE 的“空行分隔”机制天然支持把一个流切成多条独立消息。 3. **标准化的流式方案**:SSE 是浏览器和 HTTP生态的原生标准,基于普通 HTTP,无需 WebSocket那样的协议升级,防火墙、代理、负载均衡兼容性好。 所以 PaiCLI 的 `StreamableHttpTransport`处理逻辑是:检查响应的 `Content-Type`头——如果是 `text/event-stream`,就累积 `data:` 行,**碰到空行就把拼接的内容作为一条完整 JSON-RPC消息处理**;否则直接按普通 JSON一次性解析。
点赞
回复 取消回复
eating 2026年08月28日 17:11
引用原文:daemon 线程
@派聪明 daemon线程是什么意思?
点赞
回复1 取消回复
派聪明 2026年08月28日 17:12
**Daemon线程(守护线程)** 是 Java 中的一种特殊后台线程,核心特点: 1. **不阻止 JVM退出**:当所有非 daemon(用户)线程都结束后,JVM 会直接退出,不管 daemon线程是否还在运行,它们会被直接终止。 2. **用途**:适合做辅助性的后台任务,比如文中的超时调度器、读 MCP server输出的线程等——这些只是服务于主功能,没必要因为它们还活着就阻止程序关闭。 3. **用法**: ```javaThread t = new Thread(r); t.setDaemon(true); //必须在 start()前设置t.start(); ``` 在 PaiCLI 的场景里,MCP启动线程池、stdout读取线程都设为 daemon,就是为了保证:PaiCLI 主流程结束时不会因为这些后台线程“卡住”而无法退出,避免僵尸进程问题。
点赞
回复 取消回复
eating 2026年08月28日 17:10
引用原文:npx
@派聪明 npx是什么
点赞
回复1 取消回复
派聪明 2026年08月28日 17:11
**npx 是 npm(Node.js 包管理器)自带的包执行工具**,全称 Node Package Execute。 它的作用是:**直接运行 npm仓库里的包(命令),而不需要先手动安装**。 在文章的 MCP 配置中: ```json"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "${PROJECT_DIR}"] ``` 这行配置的含义是:PaiCLI启动 filesystem MCP server 时,会执行 `npx -y @modelcontextprotocol/server-filesystem`命令。 - **`-y` 参数**:自动确认,跳过“是否下载该包”的交互式询问- **`@modelcontextprotocol/server-filesystem`**:Claude官方发布的文件系统 MCP工具包npx 的典型工作流程: 1.检查本地是否已安装该包2.没有的话,临时从 npm仓库下载到缓存3.执行包里提供的命令**为什么这里用 npx?** 文章中也提到了:stdio 类型的 MCP server 经常通过 npx启动,第一次运行时 npx需要下载依赖,可能耗时几秒甚至十几秒。这也是 PaiCLI 采用多 Server 并行启动(线程池)的原因——如果5 个 server串行启动,光等待下载就要一分钟,并行启动则总耗时只取决于最慢的那个。 简单说:npx 让 MCP server 无需预装,“配置即用”,是连接 stdio 类型 MCP server 最常见的方式。
点赞
回复 取消回复
eating 2026年08月28日 17:07
引用原文:一个不停地读 stdout(每行解析成 JSON 广播给监听者),另一个读 stderr(写入一个 200 行的环形缓冲区,防止日志撑爆内存)
@派聪明 stdout和stderr分别是什么意思?
点赞
回复1 取消回复
派聪明 2026年08月28日 17:07
**stdout(标准输出)**和 **stderr(标准错误)**是 Unix/Linux 进程默认自带的两个输出流: - **stdout(standard output,标准输出)**:进程的正常输出通道,编号为1。程序正常运行时产生的数据(比如计算结果、正常日志)都往这里写。 - **stderr(standard error,标准错误)**:进程的错误输出通道,编号为2。专门用来输出错误信息、警告、调试日志等。 在 MCP 的 stdio传输场景中,这两个通道分工明确: 1. **stdout传输 JSON-RPC消息**:MCP server(子进程)把协议消息(JSON-RPC响应)一行一条写到 stdout,PaiCLI(父进程)开一个守护线程不停地按行读取 stdout,每行解析成 JSON 再广播给监听者处理。 2. **stderr输出日志**:MCP server 的运行日志(如 npx下载依赖的进度、内部调试信息)写往 stderr,避免污染 stdout上的协议消息。PaiCLI 用另一个线程读 stderr,写入一个 **200 行的环形缓冲区**(旧日志被新日志覆盖,始终只保留最近200 行),既能通过 `/mcp logs`查看日志排查问题,又不会因日志无限累积而撑爆内存。 这种“协议走 stdout、日志走 stderr”的分离设计,是 stdio传输方式简单可靠的关键。
点赞
回复 取消回复

目录