n8n-mcp 实战指南:深入理解 n8n$binary插槽——从槽位结构到文件大小限制
【免费下载链接】n8n-mcpA MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you项目地址: https://gitcode.com/GitHub_Trending/n8/n8n-mcp
导读
在 n8n 中,"文件"(PDF、图片、压缩包)从不直接存放在$json里,而是由每个 item 上的$binary插槽承载——数据走$json,文件走$binary,二者并行且互不干扰。本文以 n8n-mcp 仓库的 BINARY_BASICS.md 为核心,系统讲解$binary插槽的完整形态、哪些节点产出/消费二进制、在 Code 节点中如何读写字节、mime 类型契约、文件大小上限与执行过程排查方法。读完你将掌握"文件在 n8n 里到底存在哪、如何确保它活着到达消费节点"的完整技术链路。
一、插槽形态:$json与$binary是互不相通的"双轨"
n8n 中每一个 item 都有两个顶层键:json存放结构化数据,binary存放文件字节。两者相互独立——一个只改写json的转换节点不会自动携带binary,反之亦然。这是 n8n 二进制处理的第一性原理,也是 90% 二进制 bug 的根源(该原则在 SKILL.md 中被总结为三条铁律之一:文件内容在$binary,不在$json)。
一个典型 item 的完整形状如下(来自原文档):
{ "json": { "customerId": 42, "status": "sent" }, "binary": { "invoice": { "data": "<base64-encoded bytes>", "mimeType": "application/pdf", "fileName": "invoice-42.pdf", "fileExtension": "pdf", "fileSize": "12 kB" } } }1.1 二进制属性名(binary property name)
binary内部的键——这里的invoice——就是二进制属性名(binary property name)。它可以是任意字符串;data是大多数节点的默认名。文件处理类节点会暴露一个binaryPropertyName参数来指向这个键:生产者命名插槽,消费者按这个名字引用它。如果消费者端的名字写错,它就会去找一个不存在的插槽——文件"悄悄消失"的经典原因。
1.2 四个关键字段
| 字段 | 含义 |
|---|---|
data | 字节内容,Base64 编码 |
mimeType | 消费者应如何解释这些字节(application/pdf、image/png……) |
fileName | 用于邮件附件、上传、下载到磁盘等场景 |
fileExtension | 通常由fileName推导;部分节点直接使用它 |
表达式视角:
$json和$binary是两个独立的命名空间,{{ $binary.invoice.fileName }}读文件元数据,{{ $json.customerId }}读数据,二者永不混用(详见 SKILL.md)。
二、哪些节点产出二进制(Producer)
几乎不需要手工拼装插槽——节点会帮你填充它:
| 节点 | 要设置什么 | 结果 |
|---|---|---|
| HTTP Request | responseFormat: "file" | 响应体进入$binary.data(或options中指定的名字) |
| Read/Write Files from Disk(读取) | 文件路径 | 文件内容进入$binary |
| S3 / Google Drive / Dropbox(下载) | 文件引用 | 下载的文件进入$binary.<key> |
| 邮件触发器(IMAP、Gmail trigger) | 开启附件处理 | 每个附件进入$binary |
| Provider AI 媒体节点(图像/音频生成) | options.binaryPropertyOutput | 生成字节落入指定名字的插槽 |
2.1 最常见的下载 bug:HTTP Request 忘了responseFormat: "file"
如果 HTTP Request 保持默认的响应格式,n8n 会尝试把响应体当作 JSON 或文本解析——最终你得到的是$json里一段损坏的乱码字符串,而不是$binary里干净的字节。仓库中 enhanced-config-validator.ts 也印证了这一点:校验器会主动建议 API 端点显式设置options.response.response.responseFormat,并给出示例补丁:
{ "options": { "response": { "response": { "responseFormat": "json" } } } }不同 n8n 版本的响应处理选项位于不同的结构之下,务必用
get_node查询nodes-base.httpRequest确认当前版本的真实字段名(这条建议同样适用于下文所有"字段名随版本漂移"的参数)。
2.2 Provider AI 节点的隐藏开关
图像生成、文本转语音等 Provider AI 节点是另一个反复出现的坑:很多节点不显式设置options.binaryPropertyOutput就不会产出二进制。不设这个开关,下一个节点就没有任何东西可以上传。
补充说明:仓库的 example-generator.ts 在生成 FTP 上传示例时也使用了同族参数
binaryData: true+binaryPropertyName: 'data'——"启用二进制 + 指定属性名"这一组合是 n8n 各节点上传文件的通用约定。
三、哪些节点消费二进制(Consumer)
消费者通过属性名引用插槽:
| 节点 | 如何引用二进制 |
|---|---|
| Email(Send) | 附件字段指向binaryPropertyName |
| Slack(发送文件) | 引用二进制属性 |
| HTTP Request(multipart/form-data) | 在 body 参数中引用二进制 |
| 存储上传(S3、R2、Drive) | 把二进制作为请求体引用 |
| Write Files to Disk | 把命名的二进制属性写入某个路径 |
模式永远一致:生产者命名属性,消费者指向这个名字。绝大多数"文件没附上"的 bug 都是两端属性名不匹配——请同时用get_node核对两端字段,并检查执行记录验证。
四、在 Code 节点中读取二进制
大多数工作流根本不需要读字节——直接把二进制透传给消费者即可。当确实需要字节(哈希、解析、文本提取)时,在 Code 节点中使用getBinaryDataBuffer,不要自己取$binary.<key>.data再做 base64 解码——这个辅助方法会替你处理 n8n 的存储模式(内存 vs 文件系统):
// Code 节点,执行模式 "Run Once for Each Item" const buffer = await this.helpers.getBinaryDataBuffer(0, 'data'); // (itemIndex, propertyName) const text = buffer.toString('utf-8'); // 仅适用于文本类文件 const length = buffer.length; return [{ json: { ...$json, length }, binary: $input.item.binary, // ← 透传文件,否则经过此节点后文件就丢了 }];getBinaryDataBuffer(itemIndex, propertyName)返回一个 NodeBuffer,可以像普通 Buffer 一样切片、哈希、解码。语言层面的细节(可用辅助方法、执行模式、$input与$json的区别)属于n8n-code-javascript技能;二进制相关的唯一铁律就是上面注释里的那句:如果返回对象里不带binary,文件就会在这个节点被丢弃。
仓库测试 node-specific-validators.test.ts 中也出现了this.helpers.getBinaryDataBuffer(0, "data")的用法,可作为该 API 在真实代码中签名(itemIndex, propertyName)的佐证。
PDF 文本提取警告:
buffer.toString('utf-8')对 PDF 无效——PDF 是二进制容器而非 UTF-8 文本。你需要在有解析库的环境里做真正的解析(OCR/提取节点或专用库)。Buffer 给你的是字节,把字节变成可读文本是另一个独立问题。
五、在 Code 节点中写入二进制
自己构建插槽:把字节做 base64,再补上 mime 类型和文件名,消费者才知道自己拿到的是什么:
const text = 'Hello, world!'; return [{ json: { ok: true }, binary: { report: { data: Buffer.from(text).toString('base64'), mimeType: 'text/plain', fileName: 'report.txt', fileExtension: 'txt', }, }, }];不要省略mimeType——否则下游消费者可能拒绝文件或渲染错误(邮件附件不干净,Slack 显示通用文件图标而不是内联图片)。务必总是设置它。
六、Mime 类型:生产者与消费者之间的契约
mimeType是生产者与消费者之间的契约。一个错误的值不会报错——它只会让消费者行为异常:拒绝附件、把内联渲染变成下载、或显示损坏的缩略图。
| 文件类型 | Mime 类型 |
|---|---|
application/pdf | |
| PNG | image/png |
| JPEG | image/jpeg |
| 纯文本 | text/plain |
| JSON | application/json |
| CSV | text/csv |
| XLSX | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| ZIP | application/zip |
当来源不告知类型时,可以从文件头字节嗅探:PDF 以%PDF-开头、PNG 以\x89PNG开头、JPEG 以\xFF\xD8\xFF开头。在 Code 节点里写几行"魔数"(magic bytes)检查,是无法信任上游元数据时的可靠兜底方案。
七、文件大小上限与外部存储卸载策略
执行数据存储在 n8n 的数据库中,巨大的 base64 块会撑大数据库并拖慢实例。粗略指导:
| 每个插槽的大小 | 结论 |
|---|---|
| 几 MB | 没问题 |
| 几十 MB | 可用但更慢,留意实例内存 |
| 100 MB+ | 卸载到外部存储,只传递 URL/ID |
对大文件的推荐模式是:字节一产生就上传到对象存储,把 URL 或 key 作为纯 JSON 在工作流中传递,只在真正需要字节的那个节点重新拉取。这样每个 item 的负载都很小,执行也快。(如果自托管实例使用文件系统二进制数据模式而非内存模式,数据库压力会小一些,但对真正的大文件,同样的卸载建议依然成立。)
这与 SKILL.md 中"避免在 Code 节点硬编码 base64"的反模式一脉相承:硬编码会造成巨大的工作流 JSON、运行缓慢且易泄露。
八、在执行记录中检查二进制是否存活
validate_workflow不会告诉你二进制是否在某个节点幸存——插槽被丢弃是一种静默失败。唯一可靠的检查方法是看执行记录本身:
- 运行工作流(
n8n_test_workflow,或真实触发)。 - 用
n8n_executions拉取执行记录,查看每个节点输出中的binary插槽。 - 即使 base64 太大无法完整渲染,插槽也会显示存在性与元数据(名称、mime 类型、大小)。你要检查的就是它在每个节点上的存在与否。
binary最后一次出现、紧接着在下一节点消失的位置,正是需要加透传或 Merge 的地方(具体模式见 MERGE_FOR_CONTEXT.md)。
九、当二进制是触发器输入时
对于接收文件的工作流——multipart webhook 上传、邮件附件、被监听的文件夹——二进制在触发器输出处到达:
- 从触发器开始,就按它的二进制属性名引用。
- 在每个需要它的下游节点透传(每个节点都可能成为"剥落点")。
如果二进制没有出现在触发器输出处,检查两点:
- Content-type 处理:接收
multipart/form-data的 Webhook 会把文件放进$binary、把表单字段放进$json.body;接收 JSON 的 Webhook 则完全没有二进制。$json.body的表达式细节属于n8n-expression-syntax。 - 触发器的二进制设置:有些触发器除非显式告知,否则会跳过附件下载。
十、把二进制带过 JSON 转换:Merge 兜底与透传
二进制最容易丢失的地方是JSON-only 节点(Edit Fields、Code、IF)——它们可能从输出中丢弃$binary插槽,而工作流校验照常通过、运行无报错,只是下游邮件节点要附件时文件已经不在了。两种保留方式(详见 MERGE_FOR_CONTEXT.md):
- 转换节点的透传选项:Edit Fields 开启
includeOtherFields;Code 节点显式返回binary: $input.item.binary。有现成选项时这是最便宜的修复。 - 扇出 + 按位置合并:把源同时路由进转换分支和旁路分支,再用
combineByPosition模式的 Merge 重新组合。JSON 来自转换侧,二进制在旁路侧原样幸存:
[Source with binary] ─┬─→ [Edit Fields: change JSON] ─┐ │ (binary stripped here) │ │ ├─→ [Merge: combineByPosition] ─→ [Email: attach] │ │ └──────────────────────────────────┘ (bypass — binary passes through untouched)用 n8n-mcp 的n8n_update_partial_workflow接线时,combineByPosition会按位置把输入 1 的第 N 个 item 与输入 2 的第 N 个 item 配对,因此两条分支的 item 顺序与数量必须对齐。注意两个容易踩的细节:Merge 默认只有2 个输入(接 3+ 分支必须调高输入数,否则多余分支被静默丢弃);连接输入索引是0 基的(旁路分支落在targetInput: 1)。Merge 的字段名(mode、combineBy、numberOfInputs)在不同版本间有变动,提交结构前用get_node核对nodes-base.merge。
如果剥落点太多,逐个 Merge 的成本会超过收益,此时更优解是尽早上传(字节一出现就上传对象存储,URL/key 作为纯 JSON 穿越所有转换,只在需要的节点重新拉取)或把二进制工作推入子工作流(注意 Execute Workflow Trigger 默认的 typed-input 模式只携带命名 JSON 字段、会丢弃$binary,子工作流需要直接收字节时要用 passthrough 输入模式)。
结语
从插槽形态到消费节点、从 Code 节点读写到 mime 契约、从大小限制到执行排查,$binary的完整链路其实只有一条主线:生产者命名、消费者引用、转换节点透传、执行记录验证。记住两句话即可覆盖绝大多数场景——文件内容永远在$binary而不在$json;任何只返回json的节点都在默默丢弃文件。更进一步的边界场景(Agent 工具只能走 JSON、聊天界面必须用 URL 渲染图片)可继续阅读同技能目录下的 AGENT_TOOL_BINARY.md 与 CDN_REQUIREMENT.md。
【免费下载链接】n8n-mcpA MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you项目地址: https://gitcode.com/GitHub_Trending/n8/n8n-mcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考