维护safari-mcp项目时,对多Agent共享Safari标签页问题的修复方案描述有误,用户在issue上自行发现并纠正。
上周我写了一篇关于拒绝功能请求的文章:一位用户为我的项目设计了一种 laneId 模式,历时四天,正确的答案最终是零行代码,因为他们最终自己在自己的层里交付了修复方案。
那篇文章是准确的。决策是对的。交付的代码是对的。
但我给他们解释为什么某个特定配置会起作用的说法——我在 Issues 讨论区公开地、满怀信心写下的那个解释——是错的。不是完全错。是那种更令人尴尬的错误:差了整整一层。
他们在我的回复关闭话题后不到一天就发现了。
快速背景:我维护着 safari-mcp,这是一个为 AI Agent 在 macOS 上提供真实 Safari 会话的 MCP 服务器。整个讨论串(#76)是关于让多个 Agent 共用一个 Safari 而不互相抢标签页。
我的服务器已经有了最难的部分:在 HTTP daemon 模式下,每个客户端都会获得自己的 Mcp-Session-Id,服务器会将所有标签页状态——当前标签、所有权标记、以及所有相关的东西——都绑定到这个会话上。两个客户端连到同一个 daemon 看不到彼此的标签页指针。几天前我刚刚为这个机制中的一个真实 bug 交付了真正的修复。测试、验证、发布说明,那一层是扎实的,我知道它是扎实的。
用户通过 mcporter 运行他们的 Agent,mcporter 是一个为多个 Agent 会话管理 MCP 服务器的运行器。他们找到了 mcporter 自己的隔离旋钮——每个 Agent 独立的 daemon 目录——然后问我这是否能满足他们的需求。
以下是我告诉他们的,几乎是原话:
每个 Agent 独立 daemon 目录 + stdio 传输 = 按进程边界隔离,但你回到了 N 个 Safari-controller 进程。每个 Agent 独立 daemon 目录 + HTTP 传输 = 黄金组合——每个 mcporter daemon 持有自己的 MCP 客户端,因此每个都获得自己的 Mcp-Session-Id,所以我的按会话隔离机制得以触发。一个进程,完全隔离。
清晰的分析。两种拓扑结构,一个建议,一句自信的加粗表述。我把他们的 lane 需求描述为只在 stdio 路径上才需要的东西。
他们构建并发布了自己的修复方案(mcporter-lanes),我为此致谢,更新了 README,写了博客文章,闭环了。然后,在我关闭话题的回复大约一小时后,他们再次发帖——礼貌而精确:
有了 mcporter 在中间,无论传输方式如何,lanes 都是必需的。因为 mcporter 做两件事之一:
缓存一个 MCP 客户端并将其重用于每个 Agent 会话。我的服务器只看到一个 Mcp-Session-Id。我那个漂亮的按会话隔离机制永远不会触发——不是因为它坏了,而是从我的服务器的角度来看,只有一个会话。每个 Agent 都戴着相同的徽章走进来。
每次调用创建一个新客户端。现在根本没有会话连续性——每次工具调用都是一个陌生人,按会话状态在另一个方向上也没有意义。
在这两者之间没有中间地带,除非有 lanes。崩溃不在于传输方式的选择。它存在于 mcporter 的客户端缓存中——在我分析的所有东西的上游一层。
我的"黄金组合"表述并不是黄金组合。传输方式从来不是那个关键的变量。
我想精确地描述这个失败,因为它是一种我怀疑大多数维护者都会有的失败模式。
我详尽地验证了我自己的层。我在回答之前读了会话映射代码。我在那周刚刚交付并测试了按会话隔离机制。我关于服务器所做的每一条声明都是真实的。
但"每个客户端获得自己的会话"承载了一个隐性假设:每个 Agent 是一个不同的客户端。这是否成立取决于 Agent 和我的服务器之间的中间件——一个我不拥有、不交付、也没有读过的层。我通过类比推理 mcporter 的拓扑结构("每个 Agent 一个 daemon 肯定意味着每个 Agent 一个客户端")而不是通过它实际的缓存行为。
我所宣扬的保证是一条链:Agent → runner → 客户端缓存 → 传输方式 → 我的服务器 → 浏览器扩展。我只审计了这链条的一个环节就宣称整条链是健全的。
修复只需要一句话
这里让我和上一篇文章属于同一类:修正同样不需要任何代码行。
我的服务器在两种拓扑结构下的行为都是正确的。有问题的是 README 中的一段落,它把 lanes 描述为一个只在 stdio 上才需要的东西。修复方案(commit f12e5e3)重新措辞:如果你在 mcporter 后面,无论传输方式如何,你都需要 lanes,因为客户端缓存在传输方式选择的上游。
就一句话。但这是一句负载很重的话——区别在于下一个遇到这种配置的用户是花一个下午调试一个"坏掉的"隔离功能,还是在两分钟内安装正确的包。
我从中得到的
你的隔离保证的强度,取决于用户和你之间最弱的那一层。而这些层大多不是你的。在文档中记录保证而不说明其假设("这在每个 Agent 呈现自己的会话时才会触发"),会导致正确的代码产生错误的承诺。
公开推理在还便宜的时候就会被纠正。我在 Issues 上发布了我的拓扑分析,而不是仅仅私信一个建议。这是错误的机制存活了大约一天而不是被传为佳话的唯一原因。用户可以把我原话引用回来并指出它在哪一层出了问题。
"我验证了我的层"感觉和"我验证了系统"完全一样。从内部看,两者无法区分——两者都带着同样温暖的信心。我所知道的唯一可靠的分水岭是实际运行其他层的人。他们读过 mcporter 的内部代码。我读过我的。合在一起我们读了整个系统;分开来看,我们谁都没有读完整。
我在上一篇文章中说用户在问题真正所在的层解决了他们的问题。事实证明我的解释和那个功能请求有同样的 bug:正确的想法,错误的层。
你是否曾经记录过一个你自己的层实际上无法真正承诺的保证——一个依赖于你不控制的中间件的保证?你是怎么发现的?