@brooswit/thatch (英语)
中央HTTP MCP服务器,作为 爱莉西亚 插件 。 许多 Claude 代码会话通过 URL 连接到一个服务器;服务器按名称处理,可以将消息推入一个活的会话.
import { Elysia } from "elysia";
import { thatch, z } from "@brooswit/thatch";
const { plugin, mcp } = thatch({
tools: {
status: { description: "Fleet status", input: {}, handler: (_a, c) => `hello ${c.id}` },
},
});
const app = new Elysia().use(plugin).listen(3000);
// every client is accepted and gets a UUID; it holds all its request headers.
mcp.on("connect", (c) => console.log("connected", c.id, c.headers["x-workspace"]));
// address by a header predicate, then push into that session — from anywhere
const c = mcp.connections.find((c) => c.headers["x-workspace"] === "epic/KAN-39");
const d = await c?.send({ content: "PR #296 approved", meta: { key: "KAN-39" } });
// d: { claim: "C2" } — a connected session's stream took it
// { claim: "refused", reason: "not-connected" | "no-channel-stream" | "bad-meta" | "closed-mid-send" }
Claude代码连接到:
claude mcp add --transport http fleet http://localhost:3000/mcp --header "x-workspace: epic/KAN-39"
用 Claude 代码接收频道消息
推车 notifications/claude/channel。关于届会 提交 克劳德代码必须选择:
- 发射 互动 (无)
claude -p与--channels server:<name>,比如说.claude --mcp-config mcp.json --channels server:thatch。一个被推动的帧会获得许可 提示用户接受; 无头-p没有接受者并跳过频道. - 在克劳德.艾 团队/企业中,频道通知为 org(默认关闭)和 org 必须启用它们; 控制台账户默认打开.
test/live/channel-render.md 是一种旁证。 离服务器而去的是
包含在自动套房中;这最后的一首是交互式的.
为什么送货类型不是 void
推入会话可能会以 MCP SDK 隐藏的方式失败: 在其通知流没有被附加时可以注册连接,在这种情况下, SDK 悄悄地放下了帧. thatch 大声拒绝no-channel-stream)和仅索赔 C2 当一个溪流实际上在那里 承载帧. C3 (输入记录)和 C4 (模型读出) 是不可见的, 所以这里没有API 假装给他们.
API 密码
thatch({ tools?, auth?, path?, history?, serverInfo?, resurrectSessions? })→{ plugin, mcp }。每个客户端都被接受并指定一个UUID。 门连接到auth(req) => boolean(default accept all);它没有识别——一个被接受的客户端仍然获得UUID并持有其信头.instructions(可选字符串)作为 MCP 服务器指令初始化时返回到每个客户端. Claude Code将其添加到模型的上下文中,因此每个呼叫者应该遵循的行为(例如如何在聊天中回复)会在这里一次,而不是在每个代理的设定中.mcp.connections数字 :list(), (中文(简体) ),get(id), (中文(简体) ),has(id), (中文(简体) ),count(), (中文(简体) ),find(pred), (中文(简体) ),filter(pred)。 。 。 .mcp.send(id, frame), (中文(简体) ),mcp.sendMany(ids, frame), (中文(简体) ),mcp.sendAll(frame, { where? })。 。 。 .mcp.on/once/off页:1connect编号 :disconnect。断开的原因是closed, (中文(简体) ),error,或stale。 。 。 .- 收获 (单位:千美元)
reap),默认情况下:没有DELETE而死亡的客户端从不关闭其传输,因此切片会自己关闭,释放disconnect有理无据stale。一旦它的通知流下降detachGraceMs(默认为60s),此后没有提出请求,或者有一次从未打开过流的会话没有请求idleMs(默认为10分). 一个开通的流或任何请求都保持会话活性,一个返回的收割客户端得到404,这是MCP重新初始化的信号. 给它thatch({ reap: { detachGraceMs, idleMs, intervalMs } }),或将其关闭reap: false。 。 。 . - 会话复活 (单位:千美元)
resurrectSessions,默认关闭):见下文“重新启动服务器”. - A 类
Connection携带id, (中文(简体) ),headers(他们),connectedAt方法send(frame)编号 :close()没有内在的历史,lastSeenAt或准备旗——签署send事件和按键,无论你喜欢send结果告诉你,如果一个框架不能降落. import { FakeConnection } from "@brooswit/thatch/testing"测试.
幸存服务器重启( LIBS-7)
每一个会话id一个道克服务器 都只在这个过程中发出生命。 之后a
重新启动(或任何取代进程的重新部署),这些id的每一个都不得而知
和默认的新进程答案 404 {"error":"unknown session"}
包括客户自己的背景
长生不老的溪流 频道推力需要降落。 根据 MCP 传输规格,一个客户端
得到404是应该重新 -initialize忘了旧的身份证. 这是否真的
自动发生完全取决于客户端在流下降时所做的, (中文(简体) ),
这里有一个混合的画面:
- Claude代码的MCP客户端是SDK的
StreamableHTTPClientTransport当 独立通知流下降, 它重复相同的 GET, 使用相同的会话 ID 和页眉,使用指数回放——默认为两次,大约在1和1.5后, 然后,它静静地放弃,永远. 它在同一个地方打开了溪流: 对 之后notifications/initialized得到一个202。 后来成功的请求永远不会重开 这个. - 仅a 请求 (不是独立的 得到流 重新连接自己) 驱动一个满
客户端重新初始化——这就是打开新鲜流的原因. 所以没有
resurrectSessions,否则会话的频道在 打电话者碰巧用它做别的东西,此时404会强迫客户 通过完全重新启动 和新鲜的溪流.
thatch({ resurrectSessions: true }) 从服务器侧关闭部分空隙—— 页:1
独立流只能重新连接: 未识别 mcp-session-id 上场
以该精确的 id 而不是 404 键重新创建会话,但以 auth 钩子
接受新请求(拒绝仍然回答401),完全是一个新的连接
永不复活。 恢复的连接 headers 只来自要求
复活了它 旧的连接 不管它持有什么头条 都消失了 所以什么都没有
重复使用或猜测.
一个POST或DEELETE 在一个未识别的ID 仍然404s, 即使有这个. 这是
故意的,不是疏忽:因为SDK客户端在之后才重新打开它的流
其本身 initialize 握手,复活一个 POST 会使这个请求"成功"与
没有重新初始化——因此没有溪流重新开放。 会议将保持登记,但
永久耳聋,比起今天的404更糟糕. 只有GET是客户自己的流
从字面上说,试图回来,这就是一个案例 复活可以帮助而不削减
离开客户的自我康复路径。 所以,净效应是:
- 在客户端自身重试窗口内完成的重启( 几秒钟, by) 默认 : Get 重试后附 没有客户端的更改 没有 重新初始化, 一旦重接, 就不会丢出推力 .
- 一个需要更长时间的重启: 在新进程存在之前, Get 重试已经用尽, 因此,流在客户端下一个请求(POST/DELETE)之前会一直被死 驱动同样完全重新初始化 ~ 新鲜的溪道 今天存在,有或无此选项.
默认关闭,因为即使更窄的只有一条路通过标记
刚建成的SDK运输,因为已在A
调用者选择代号——使用运输内部(sessionId, (中文(简体) ), _initialized),而不是
运输的公共 API —— 以测试为主(test/unit/end-to-end.test.ts, (中文(简体) ),
describe("session resurrection (LIBS-7)")因此,SDK升级 改变这些内部
而不是默默地停止生产.
遗留物 stdio 发现回落
server/discover 在MCP2026-07-28中被标准化. 更新的stdio客户 在遗产之前调查它 initialize 当他们收到JSON-RPC时握手倒地 -32601. Thatch 使用了遗留的会话 HTTP 生命周期; 它并不要求现代的无国籍人协议支持.
对于stdio-to-HTTP中继器,请在转发请求前拨打共享帮助器:
import { legacyStdioRelayAction } from "@brooswit/thatch";
const action = legacyStdioRelayAction(message, !!http.sessionId);
if (action.type === "reply") await stdio.send(action.message);
else if (action.type === "forward") await http.send(message);
帮助者只回答有效的 server/discover 要求,保存他们的身份证。 它无视 notifications/roots/list_changed 在 HTTP 会话存在之前 : 新鲜的启动会话很早就发出, 而服务器会话状态不需要无效 。 一旦召开届会,它通常会转发该通知。 所有其他信件均已转发。 下级 legacyStdioDiscoveryResponse(message) 仅供发现(答复或 undefined) (中文(简体) ).
无论是帮助者创建会话,排队消息,还是广告现代能力. 在 stdio 边界上处理此信息: HTTP 初始化守护符可能在其到达Titch 之前拒绝无会话消息 。 HTTP插件及其初始化规则不变.
见 MCP 发现规格 财务报告和审定财务报表 stdio 后向兼容性。 。 。 .
层
protocol (框架、交付、方法——纯)- registry (名称连接+历史)- channel (终止,诚实的索赔) plugin (Elysia挂载,每次连接一个MCP服务器)- testing。 。 。 .
脚本
bun run check # generate load tests + typecheck + unit + load + coverage ≥90%
bun test test/unit
MCP_LIVE=1 bun test test/live # against a real Claude Code session (opt-in)