关于 mcp-loom
mcp-loom 是位于 MCP 客户端与多个下游 MCP 服务之间的 stdio 代理,可将大型表格结果存入嵌入式 DuckDB,并返回便于查询的摘要信封。它还提供只读 SQL 查询、跨服务连接和数据导出能力。
主要功能
- 01代理并聚合多个下游服务工具
- 02将大型表格结果存入 DuckDB
- 03以摘要信封替代大型结果
- 04只读 SQL 查询与跨服务连接
- 05查看数据集结构、统计和来源
- 06导出 CSV 或 JSON 文件
适用场景
- 01减少大型工具结果占用的上下文
- 02查询来自多个服务的数据
- 03检查数据集结构后编写查询
- 04导出会话中的数据集
MCP 相关
mcp-loom 本身是 stdio MCP 服务,可启动并管理配置的下游服务、聚合并转发其工具。它还向客户端提供数据集查询、查看、物化和导出工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
███╗ ███╗ ██████╗██████╗ ██╗ ██████╗ ██████╗ ███╗ ███╗
████╗ ████║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔═══██╗████╗ ████║
██╔████╔██║██║ ██████╔╝█████╗██║ ██║ ██║██║ ██║██╔████╔██║
██║╚██╔╝██║██║ ██╔═══╝ ╚════╝██║ ██║ ██║██║ ██║██║╚██╔╝██║
██║ ╚═╝ ██║╚██████╗██║ ███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
The context loom for MCP.
One proxy in front of N servers. Oversized tool results are woven
into queryable datasets — 48k tokens in, 2k envelope out.
warp: results stream in weft: the loom shuttles across
══════════════════════ ═════════════════════════════
│ │ │ │
│ │ │ │ 48,377 tokens ┄┄┄┄┄┄┄┄┄> ✂ intercept
●━━━━┿━━━━┿━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
│ ●━━━━┿━━━━┿━━━━━━━ ingest → DuckDB ━━━━━━┫
│ │ ●━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
│ │ │ ●━━━━ schema · stats · joins ━━┛
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[ raw floods, dropped ] [ 2,104-token envelope ]
ref · sample · SQL
它的作用
MCP 客户端向工具提问,得到的答案却是一份包含上万行的 JSON 转储——模型大多用不上这些内容,却不得不将其留在上下文中。Loom 就在中间发挥作用。
结果较小时,Loom 会原样传递。结果较大且呈表格形式时,Loom 会将其导入嵌入式 DuckDB,并交给模型一个精简的封套:数据集引用、架构、列统计信息、几行样本、联接提示和来源信息。随后,模型可以精确查询所需内容——包括联接来自不同服务的数据,而单个服务无法独自回答这类问题。
效益:48k 个 token 的工具结果会变成约 2k 个 token 的封套。模型可以将上下文用于获取答案,而不是处理原始转储。
Loom 何时有用——何时无用
在 MCP 实际使用中最常见的三种情况下,Loom 能发挥作用:
- 没有 shell 的客户端。 Claude Desktop、移动端和 Agent 平台——覆盖面最大的客户端——都没有可供回退使用的 CLI。MCP 工具是模型获取数据的唯一途径,因此必然会进行拦截。
- 没有模型可访问 CLI 的服务。 内部工具、SaaS 连接器,以及密钥只保存在服务
env块中的凭据数据库——这类连接器占了绝大多数。模型没有类似gh的工具可以绕开它们。 - 针对持久数据集的可审计分析。 Loom 将“模型粗略查看了一份转储”变成“模型对仍可供检查的数据运行了可验证的查询”——对带有来源标记的数据集执行精确 SQL,而这些数据集事后仍可重新打开和查询。这一点在任何场景中都适用,无论有没有 shell,也是对“为什么要通过 Loom 路由”的坦诚回答。
跨服务联接适用于以上所有情况:没有任何单个 CLI 能回答涉及两个服务数据的问题。
明确说明两个限制:
- 有 shell 的 Agent 会绕过 MCP 层,直接访问它能触达的服务。 如果给模型
bash,并且服务有已知 CLI 和环境凭据——GitHub 的gh是最典型的例子——模型就会直接使用 CLI,速度更快、成本更低,同时绕过 Loom 和 MCP。这是 Agent 更倾向使用 CLI 而非 MCP,并非 Loom 的缺陷;Loom 是 MCP 层产品,不会比gh更擅长充当gh。而且这种情况并不常见:大多数服务封装的是没有模型可访问 CLI 的工具,覆盖面最大的客户端也没有 shell。 - 封套的保真度受下游返回内容的限制。 Loom 会忠实展平服务发送的内容,但无法补上服务从未发送的字段。例如,GitHub MCP 服务返回的是经过精简、包含 17 个字段的 repo 对象(没有
stargazersCount、language或forks);Loom 忠实地捕获了全部 17 个字段,但仍无法根据这些数据回答按星标数排名的问题。限制来自下游,而不是 Loom。
工作原理
Loom 本身是一个 stdio MCP 服务。它位于一个上游 MCP 客户端(Claude Desktop、Claude Code、Cursor 等)与 N 个由你配置的下游 MCP 服务之间:
- 启动并监控每个已配置的下游服务(握手健康检查、退避重启;如果某个服务耗尽重启额度,则将其工具从列表中移除并发出
list_changed)。 - 将每个下游工具命名为
<server>_<tool>,并向上重新公开聚合后的工具列表,通过list_changed保持列表实时更新。 - 将
call_tool路由到正确的下游子进程。小型结果不作修改地返回——透传结果逐字节一致。大型表格结果会被拦截并存入 DuckDB,然后替换为loom_dataset_ref信封;任何拦截失败都会回退为原样返回。 - 通过缓存的数据集公开合成查询接口:
loom_query(受保护的只读 SQL,包括跨服务联接)、loom_list_datasets、loom_describe(查看数据集的完整架构、统计信息和最新联接提示)、loom_materialize(将派生查询结果固定为新数据集)和loom_export(生成封存的 csv/json 导出)。每次上游调用的拦截深度上限为 1;loom_materialize不消耗此额度。 - 在收到信号或上游 stdin EOF 时正常关闭,不遗留子进程,并移除临时溢出目录。
Loom 会拦截结构化结果和格式化文本:如果结果包含 structuredContent 通道,则从该通道摄取;否则解析文本中的 JSON;若失败,则在严格遵循绝不造假的原则下,从格式化文本中提取重复记录(它绝不会输出没有把握的表格——任何不确定性都会回退为原样透传)。完整规范请参阅 SPEC.md。
用法
npx mcp-loom --config loom.config.json
让任意 MCP 客户端指向此命令。也可以通过 LOOM_CONFIG 环境变量提供配置,而不是使用 --config:
LOOM_CONFIG=/path/to/loom.config.json npx mcp-loom
(如果两者都已设置,则优先使用 --config。)最小可运行示例请参阅 loom.config.example.json。
客户端配置示例
Loom 的启动方式与任何 stdio 服务相同,由 MCP 客户端启动——让客户端指向 npx -y mcp-loom --config <absolute path to loom.config.json>。
Claude Desktop(claude_desktop_config.json):
{
"mcpServers": {
"loom": {
"command": "npx",
"args": ["-y", "mcp-loom", "--config", "/absolute/path/to/loom.config.json"]
}
}
}
Cursor(.cursor/mcp.json):使用与上文相同的 mcpServers 配置块。
Claude Code——通过 CLI 添加:
claude mcp add loom -- npx -y mcp-loom --config /absolute/path/to/loom.config.json
请使用 loom.config.json 的绝对路径——MCP 客户端启动服务时的工作目录未指定。然后,Loom 会启动并为该配置文件中列出的下游服务添加命名空间。
配置参考
配置是一个 JSON 文件,启动时使用 zod 进行验证;无效配置会立即失败,并给出清晰易读的错误,列出所有未通过验证的字段。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
servers | 数组(至少 1 项) | —(必填) | 要代理的下游服务。见下文。 |
servers[].name | 字符串 | —(必填) | 必须匹配 ^[a-z][a-z0-9_]{0,31}$,且在所有服务中唯一。loom 是保留名称(仅用于合成工具),不允许使用。 |
servers[].command | 字符串 | —(必填) | 要启动的可执行文件(例如 npx)。不能为空。 |
servers[].args | 字符串数组 | [] | 传递给 command 的参数。 |
servers[].env | 对象(字符串→字符串) | {} | 仅为此服务的子进程设置额外环境变量。见下文“子进程环境”。 |
servers[].envPassthrough | 字符串数组 | [] | 除经过筛选的安全基础环境变量外,要从 Loom 自身环境转发给此服务子进程的额外非机密变量名称(默认拒绝——不会完整复制启动 shell 的环境变量)。见下文“子进程环境”。 |
servers[].provenanceDenylist | 字符串数组 | [] | 对于此服务中列出的工具,其 provenance.args 会从信封、loom_describe 和 loom_list_datasets 中完全省略(显示为 "[omitted]"),适用于参数全是凭据的工具。其他工具仍使用基于键名的启发式脱敏。 |
tokenThreshold | 整数 ≥ 100 | 2000 | 表格化文本结果超过此阈值时触发拦截;这是按字符数除以 4 粗略估算的数量级调节值,并不精确(会低估包含 CJK 字符的文本)。 |
memoryBudgetBytes | 整数 ≥ 1048576 | 268435456(256 MiB) | 对所跟踪数据集字节数设置的软淘汰预算;隐式查询结果数据集优先按 LRU 淘汰,其次是下游导入的数据。 |
duckdbMemoryLimit | 字符串,/^\d+(\.\d+)?(KB|MB|GB|TB)$/ | "512MB" | 引擎的硬性保护限制(SET memory_limit);在计算出的线程数限制下,操作会溢写到磁盘,而不是导致 OOM。 |
exportDir | 字符串 | "./exports" | loom_export 写入 csv 和 json 文件的目录。 |
queryTimeoutMs | 整数 ≥ 1 | 30000 | 限制 loom_query 的执行时间(使用 interrupt() 计时器)。 |
restart.maxAttempts | 整数 ≥ 0 | 5 | 每个下游服务在被移出列表前允许连续重启的最大次数(移除工具并发出 list_changed)。0 表示禁用重启。 |
restart.baseDelayMs | 整数 ≥ 1 | 1000 | 重启退避的基础延迟。 |
如果配置的所有服务都无法启动,Loom 自身也会启动失败(致命错误)。如果只有部分服务启动失败,Loom 会发出警告并继续启动,已成功启动的服务所提供的工具仍可用。
拦截与查询
大型表格化工具结果会被拦截,并导入嵌入式 DuckDB 存储,成为可查询的数据集。超过 tokenThreshold 且包含结构化数据的工具结果(优先从协议的 structuredContent 通道读取;若不存在,则从文本呈现内容解析)会转换为 loom_dataset_ref 信封,其中包含引用 ID、推断出的架构、列统计信息、示例行和联接提示,从而降低 token 开销。使用合成工具 loom_query 查询这些数据集:它接受 DuckDB 方言的只读 SQL(单条 SELECT 或 WITH 语句),并由引擎解析器判定为只读。如果数据集引用丢失,可通过 loom_list_datasets 找回,或列出当前会话中所有可用的数据集。
数据集引用仅限会话使用:它们只在对话期间有效,并会在内存压力下自动淘汰(优先淘汰隐式查询结果,以便显式导入的数据保留更长时间)。信封中的示例行是任意选取的——DuckDB 默认不保留插入顺序——因此,在行顺序很重要时,务必使用显式的 ORDER BY 子句。token 估算器只是粗略的数量级调节值,会低估包含 CJK 字符的文本,因此,如果你经常超过 tokenThreshold,请监控实际 token 用量。
使用 loom_describe 检查数据集
对数据集引用调用 loom_describe,详细检查其结构和内容:完整架构及各列统计信息、10 行样本、指向其他数据集的最新联接提示(根据值重叠得分排序,让真正的联接排在偶然的名称匹配之前)、来源和创建时间戳。当你需要在查询数据集之前了解其形态,或在执行许多中间操作后重新发现数据集的架构时,可以使用此工具。
使用 loom_materialize 物化查询结果
查询结果会隐式缓存,但如果你想让派生结果跨会话保留,或确保结果永不被逐出,请使用 loom_materialize:传入只读 SELECT 语句,即可获得新的固定 loom_dataset_ref 信封。物化的数据集在内存压力下永不被逐出。**重要提示:**物化的数据集以 JSON 文本形式存储,因此日期和十进制列会变成文本字符串。如果需要对物化结果执行日期运算或数值运算,请在 SELECT 中加入 CAST,例如,使用 SELECT CAST(created_at AS DATE) FROM ... 来物化可查询的日期列。
使用 loom_export 导出数据集(封闭边界)
使用 loom_export 将缓存的数据集导出到文件:它会将数据集写入配置的 exportDir 下的文件,格式为 csv 或 json(仅支持这两种格式),并返回所写入文件的绝对路径。若要将结果带出当前会话,请使用此工具。
**封闭式导出边界:**Loom 引擎绝不会接触文件系统。导出操作会在 Node 进程中将只读通道的 SELECT 语句序列化为文本,再由 MCP 服务写入文件,而不是由 DuckDB 执行。这是有意为之的安全设计:引擎解析器会强制执行语句类型分类(只读或写入),且引擎本身禁止文件系统操作(COPY TO 已禁用,并将继续保持禁用)。安全边界在于引擎不可变的语句分类,而非运行时过滤;这可确保任何查询都不会在运行时被错误分类,或被提升为可访问文件系统。
命名空间
每个下游工具都会在上游重新公开为 <server>_<tool>,其中 <server> 是该服务在配置中的名称。路由通过内部双向映射(公开名称 ⇄ 服务 + 原始工具名称)完成——绝不会通过按 _ 拆分公开名称来路由——因此包含下划线的服务名称也能正常工作。超过常见客户端限制(64 个字符)的名称会以确定性方式截断,并添加哈希后缀;清理后发生的名称冲突会添加数字后缀。loom 是保留的服务名称,因此以 loom_ 为前缀的合成工具绝不会与真实下游工具冲突。
子进程环境(默认拒绝——配置密钥前请阅读)
每个下游子进程都会获得一组经过筛选的非密钥基础环境变量(PATH、HOME、TMPDIR、区域设置,以及 Windows 对应项),再加上该服务在配置中自己的 env 块——而不是启动 Loom 的 shell 环境副本。因此,在启动 shell 中导出的密钥(GITHUB_TOKEN、AWS_SECRET_ACCESS_KEY 等)不会转发给下游服务,遭到入侵的 npx -y 软件包也无法读取原本供其他服务使用的密钥。
请将每个服务的密钥放入该服务自己的 env 块:
{ "name": "repo", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "ghp_..." } }
如果某个服务需要从 shell 中额外获取一个非密钥变量,请通过 envPassthrough 显式允许传入(默认值为 []):
{ "name": "svc", "command": "...", "envPassthrough": ["HTTPS_PROXY", "NODE_EXTRA_CA_CERTS"] }
边界——最小权限,并非零泄漏。这项机制封闭的是环境变量这一途径,而非所有途径。传入的变量本身可能包含密钥(例如 HTTPS_PROXY=http://user:pass@host 或 CA 路径),恶意子进程仍可自行访问文件系统和网络。操作系统级的逐子进程隔离(容器、命名空间)才是安全上限,但这超出了零配置 stdio 代理的范围——并且我们有意不提供“继承所有环境变量”选项。
注意事项
- 子进程环境默认拒绝,不会继承。 下游子进程会获得精心筛选的非机密基础环境,以及各自服务的
env——不会转发启动 Shell 的机密信息。可通过envPassthrough选择额外的非机密变量。此举遵循最小权限原则,但并非完全杜绝泄露(详见上文子进程环境)。 - 令牌阈值是粗略的调节旋钮。 拦截根据
chars/4估算值触发——这是数量级上的触发条件,而非精确的令牌计数,且会低估中日韩文本。请将tokenThreshold视为数量级调节项,而非边界值。估算涵盖整个结果——文本块,以及存在时序列化后的structuredContent。 - 导出功能受到限制,且会在 Node 中重新序列化。
loom_export通过在 Node 进程中将读取通道的查询结果序列化为文本来写入 csv/json——引擎不会写入文件(COPY TO仍处于禁用状态)。代价是在 JS 端对导出的行进行一次序列化处理:开销有界,对于会话大小的数据集远低于预算(测得导出 100k 行约需 0.42s),但确实存在——导出是一次复制,并非零成本句柄。 - 数据集引用仅限会话使用,且可能被逐出。 引用仅在当前对话中有效,并会在内存压力下被逐出——先逐出隐式查询结果,最后逐出显式导入的数据;物化引用会被固定,永不逐出。除非使用
ORDER BY,否则示例行是任意选取的。 - 下游
outputSchema不会重新公开。 Loom 可能会将任何大型结果替换为loom_dataset_ref信封,因此无法如实声明下游工具所定义的输出形状——重新公开的架构会导致严格校验规范的客户端拒绝每个被拦截的结果。代价是:对于通过 Loom 代理的工具,客户端不会对结构化输出进行架构校验;小型结果的structuredContent本身仍会原样透传。
开发
npm ci
npm run build # TypeScript compile (tsc)
stdout 是协议通道——所有日志都写入 stderr。完整规范(包括拦截规则和提取约定)见 SPEC.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。