mcpField
N
n8n-subworkflows

n8n-subworkflows

github.com/czlonkowski开发23.2k41 次安装3 天前更新
代码仓库

关于 n8n-subworkflows

面向 n8n 工作流作者的子工作流设计指南,涵盖输入输出契约、复用和调用模式。它也说明如何把子工作流作为 Agent 工具,并强调先搜索现有实现。

主要功能

  1. 01定义带类型的输入契约
  2. 02设计稳定一致的输出形状
  3. 03按无状态或有状态逻辑拆分
  4. 04配置逐项或异步调用
  5. 05将子工作流用于 Agent 工具
  6. 06搜索并命名可复用工作流

适用场景

  1. 01抽取跨工作流复用的逻辑
  2. 02为工作流定义清晰的参数与返回值
  3. 03逐项运行或后台并发调用子工作流
  4. 04提供带结构化输入的 Agent 工具

MCP 相关

指南提及社区 MCP 用于列出和读取工作流,并介绍测试子工作流时所需的 MCP 可用设置。它没有描述该技能自身提供 MCP 服务。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/czlonkowski/n8n-mcp --skill n8n-subworkflows

安装后在 Claude Code 中输入/n8n-subworkflows确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

NAMING_AND_DISCOVERY.md
README.md
SUBWORKFLOW_PATTERNS.md

详细介绍

n8n 子工作流

子工作流是可复用的函数。Execute Workflow Trigger 声明类型化输入,主体负责执行操作,最后一个节点返回输出。调用方可以像调用其他步骤一样,通过 Execute Workflow 节点调用它。

这种设计带来了函数在任何场景下都能提供的好处:封装、复用、可测试、可替换。它是 n8n 中主要的复用机制,但远未得到充分利用。如果不使用它,同样的逻辑就会在工作流中被复制粘贴——接着,一个 bug 在两处得到修复,第三处副本却被遗漏,而那些“完全相同”的副本也会悄然变得不一致。

本技能介绍何时应使用子工作流、如何定义其输入/输出契约,让调用方(和 Agent)能够实际使用它、如何正确调用(all 与 each、阻塞与即发即弃),以及如何为它命名,让它能被找到而不是被重新构建。


两条不可妥协的原则

其他一切都可以酌情判断。唯独这两条不行。

1. 构建之前先搜索

在为通用问题编写逻辑之前,先检查是否已有子工作流可以解决。社区 MCP 无法按标签筛选工作流,因此名称就是发现入口:

n8n_list_workflows()                          # scan the library
n8n_get_workflow({ id: "<candidate>" })       # read its inputs/outputs + body

如果找到合适的工作流,就使用它并告知用户(“我找到了 Subworkflow: Parse RFC2822 date——将使用它”)。如果找不到,就用便于发现的名称来构建,以便下次搜索时能找到它。发现约定(动词优先的前缀)见 NAMING_AND_DISCOVERY.md。

2. Execute Workflow Trigger 使用带类型字段的“Define Below”,而非透传

触发器有两种输入模式。默认使用“Define Below”,并显式定义带类型的字段。“Define Below”是唯一能为调用方提供可填写架构的模式——它让 AI Agent 能通过 $fromAI 传递值,也让结构化调用方能够清晰地映射字段。透传没有架构,因此触发器无法配置为简洁的 Agent 工具,结构化调用方也无字段可绑定。

只有以下两种例外:

  • 二进制输入。 带类型字段仅支持 JSON。如果子工作流必须接收图像/文件/PDF,就需要使用透传,让 binary 槽位能够传递过去。
  • 无输入。 “Define Below” 至少要求一个字段。真正不需要参数的操作(“列出活动凭据”“当前数量”)没有位置可放置空架构,因此只能使用透传。

除这两种情况外,使用透传就是 bug。请参阅下文的“作为契约的输入和输出”。


这应该是子工作流吗?

你正准备编写一段逻辑。用下面的方法判断:

Could this plausibly be needed in another workflow?
  └─ Yes → extract.

Is it a generic concern (auth, retry, parsing, formatting, ID generation)?
  └─ Almost always → extract. These are the canonical reusable sub-workflows.

Is it >5 nodes and conceptually one thing?
  └─ Probably extract, even if reuse isn't certain. It's better isolated.

Is it one HTTP call with no logic around it?
  └─ Don't. A sub-workflow that's just trigger → HTTP → return adds a boundary
     for nothing.

Is it tightly coupled to this one caller's data shape?
  └─ Don't extract yet — fix the data shape first, or you just relocate the coupling.

提取逻辑的理由不止是复用:

  • 可读性。 调用方只显示一个节点(“解析日期”),而不是五个。
  • 可测试性。 使用固定输入单独运行子工作流:n8n_test_workflow({workflowId, method: "prepare"}) 会列出需要数据的节点,然后 method: "pinned" 会使用你构建的输入运行它(需要 N8N_MCP_ACCESS_TOKEN 和工作流的“Available in MCP”设置——请参阅 n8n-mcp-tools-expert)。子工作流没有 HTTP 触发器,因此默认的 method: "auto" 无法运行它。
  • 可替换性。 替换实现时不会波及调用方。

如果一个包含 20 个节点的工作流主要由一系列线性的 Execute Workflow 调用和决策组成,那完全没问题——每个节点都有一个用途;要检查某个部分时,打开它调用的子工作流即可。包含 20 个内联转换的工作流则不合适。如果你的工作流有 15 个以上节点,且并非主要由子工作流调用和分支组成,就应进一步提取逻辑。


无状态与有状态(有意为之)

两者都是一等选项。如何选择,取决于你的意图以及契约承诺了什么。

无状态 — 输入后得到输出,不进行除此之外的任何 I/O。纯逻辑的默认选择。需要再次使用时,直接调用即可,无需担心触发副作用。

  • Subworkflow: Parse RFC2822 date — 日期字符串 → ISO 日期或错误。
  • Subworkflow: Compute MRR from subscription — 订阅对象 → 数字。
  • Subworkflow: Format invoice as HTML — 发票数据 → HTML 字符串。

有状态(有意为之) — 在 清晰的契约 背后读取或写入外部状态。这就是仓储模式:子工作流将存储操作抽象出来,让调用方使用领域术语,而不是 SQL。

  • Customer: get by id — ID → 客户对象或 { ok: false, error: "not_found" }。读取数据库。
  • Customer: write billing record — 记录 → { ok: true, id }。写入数据库。
  • Notify: send to on-call — 频道、消息 → { ok: true, messageId }。调用 Slack/SMTP。

将这些构建为子工作流的原因:调用方只需使用 get customer by id,而不必编写查询;你可以替换存储方案(Postgres → Supabase、本机节点 → HTTP),而无需修改任何调用方;幂等性、重试和验证也能集中在一个位置处理。

需要避免的是 意外状态 — 子工作流的名称和描述都表明它是纯逻辑,却悄悄写入了日志表。这会让每个合理地认为可以安全重试或组合该工作流的调用方措手不及。要么将副作用纳入契约(重命名、写明文档、返回结果),要么将其移出去。


将输入和输出作为契约

触发器声明的字段和最后一个节点的输出形态 就是 子工作流的 API。应将它们视为一体。

声明有类型的输入(Define Below)

每个已声明的输入都是由调用方填写的有类型参数。应审慎选择类型(string、number、boolean、array、object)——Agent 会将这些类型用作填写工具参数时所需的类型,人类在连接调用方时也会依赖它们。触发器节点的参数如下所示:

{
  "type": "n8n-nodes-base.executeWorkflowTrigger",
  "parameters": {
    "workflowInputs": {
      "values": [
        { "name": "list_of_ids",        "type": "array" },
        { "name": "include_transcript", "type": "boolean" },
        { "name": "session_id",          "type": "string" }
      ]
    }
  }
}

在工作流主体中,可通过 $json.list_of_ids 读取这些参数;也可在下游任意位置通过 $('When Executed by Another Workflow').first().json.<field> 读取(参见 n8n-expression-syntax)。

契约规则
  • 在工作流的 description 中记录输入和输出。 包括字段名称、类型、用途,以及一些有代表性的关键词。调用方(人类和 Agent)会通过描述了解契约,n8n_list_workflows 也会根据描述进行匹配。
  • 返回一致、自然的形态,而不是存储形态。 如果子工作流负责管理 Data Table 或 S3 文件,就应向调用方隐藏其表示形式。数组以数组形式返回,对象以对象形式返回,日期以 ISO 字符串形式返回——无论底层存储是否为 JSON 字符串化文本。返回契约是 接口;存储布局则是 实现细节。常见失误:子工作流的「新鲜」路径(刚计算出的自然形态)和「缓存」路径(刚从字符串化的列中读取)。错误的做法:将新鲜路径字符串化,以匹配缓存路径。正确的做法:解析缓存路径,让两者都返回自然形态。
  • 返回错误,不要总是抛出错误。 对于 预期内 的失败(解析错误、未找到),返回 { ok: false, error: "..." },这样调用方无需连接错误输出即可进行分支处理。只有真正意外的失败才应抛出错误——参见 n8n-error-handling。
  • 一旦有调用方使用,契约就应冻结。 添加 可选 字段是安全的。重命名或移除字段则很危险:n8n 不会因无法识别输入字段而报错——主体只会看到 undefined,调用方毫不知情,于是契约便会悄然失效。要修改字段,需列出所有调用方(n8n_list_workflows + 检查每个调用方的 Execute Workflow 节点),在同一次变更中完成迁移,并在结束前通过 validate_workflow 和 n8n_get_workflow 验证。
最后的 Return 节点——Set 的合理例外

使用最后的 Set / Edit Fields 节点整理输出,并将其命名为 Return 或 Return <thing>。这是 Set 节点唯一值得保留的场景,也是 n8n-expression-syntax 中“不要添加尾随 Set 节点”这一建议的合理例外:子工作流最后一个节点的隐式调用方是所有调用者,因此,显式使用 Set 节点可以让返回契约一目了然——读者只需查看一个节点,就能了解完整 API;同时,你还能剔除最后一个计算节点携带的所有无关字段。


调用子工作流:mode 和 waitForSubWorkflow

调用方 Execute Workflow 节点上的两个设置决定子工作流的运行方式。

mode:all 与 each
mode子工作流运行次数每次运行的项目数
all(默认)一次全部 N 个项目(照常逐项流经各节点)
eachN 次每次运行恰好一个项目

对于只按常规方式处理项目的工作流,这两种模式是等效的——无论哪种模式,n8n 节点都会逐项迭代。只有当工作流主体假定自己恰好接收一个项目时,拆分才有意义:例如每次运行都要聚合数据、“当前处理对象就是那个客户”的逻辑,或每个输入只应触发一次的最终写入。使用 all 时,工作流主体会一次性收到全部 N 个项目,这一假设就不成立了(你会把所有人的数据聚合成一个结果,而不是为每个输入各生成一个结果)。使用 each 时,每次调用只会收到一个项目,因此这一假设成立。

因此,如果需要逐项迭代,优先选择 mode: each,而不是在子工作流内部添加 Loop Over Items 节点。该模式会替你完成迭代,工作流主体也能保持简单,并始终只处理单个项目。

waitForSubWorkflow:true 与 false

waitForSubWorkflow 默认值为 true——调用方会一直等待,直到子工作流返回,然后再使用其输出继续执行。设置 options.waitForSubWorkflow: false 即可采用即发即弃方式:发出调用后,调用方立即继续执行,子工作流则在后台运行,下游节点不会收到返回数据。

n8n 提供的唯一真正并行化方式

mode: each + waitForSubWorkflow: false 是实现子工作流真正并发执行的唯一方式:N 个项目会触发 N 次运行,并行执行(但仍受每个实例的并发限制约束)。调用方不知道这些运行何时结束,也无法确认它们是否结束,因此只有在配合独立的完成跟踪机制时才有用,通常由子工作流在运行过程中更新 Data Table。完整的“分阶段 → 分发 → 轮询”模式见 SUBWORKFLOW_PATTERNS.md(“Fire-and-forget parallelization”)。


按输入形状拆分(N+1 模式)

如果子工作流有多个输入路径,且其契约确实不同——例如 binary 与 JSON、同步与异步、不同的身份验证方案——就不要把它们塞进同一个触发器,再通过直通传递 + 内部 Switch 进行分流。确实存在必须拆分的原因:直通传递(用于 binary 或零输入)和 Define Below(用于类型化输入)无法在同一个触发器上同时使用。惯性地选择“限制最少的直通传递模式,然后在内部进行分支”,会让你失去类型化 schema(无法创建简洁的 Agent 工具)、增加分支形状相关的杂项,并使每种新增输入形状都需要添加更多分支。

解决方法:对于 N 种不同的输入契约,构建 N+1 个子工作流——每种契约各建一个外层子工作流,负责执行特定于该输入的准备工作(验证、获取、哈希、提取),然后调用一个共享的下游子工作流,并传入经过标准化的数据形状。共享核心只定义一种类型化输入契约,不需要知道是哪个外层工作流调用了它。完整示例(从外部 ID 或上传的 PDF 处理论文)见 SUBWORKFLOW_PATTERNS.md。


子工作流作为 Agent 工具

带有类型化 Define Below 触发器的子工作流也可用作 AI Agent 工具:Agent 通过 $fromAI 填写声明的字段,执行主体,结果作为工具观测值返回。这是默认使用 Define Below 的重要理由——直通触发器无法公开可填写的架构。

零输入场景仍可用作工具:Agent 唯一需要决定的是是否调用。二进制场景则无法顺利作为工具使用,因为 Agent 无法直接传递二进制数据。

有关工具命名、描述和二进制输入的变通方案,请参阅 n8n-agents;有关二进制数据处理本身,请参阅 n8n-binary-and-data。


反模式

反模式问题所在修复方法
在三个工作流中重复相同逻辑两处修复了 bug,第三处却逐渐偏离提取到一个命名明确的子工作流中
未搜索就新建子工作流库中出现重复项;以后搜索会找到多个结果先使用 n8n_list_workflows / n8n_get_workflow
不处理二进制数据且不是零输入场景,却将触发器设为 passthrough没有架构 → Agent 无法填写参数,结构化调用方无法绑定使用 Define Below,并设置类型化的 workflowInputs.values
零输入 passthrough 未清空并注明主体会悄悄读取调用方转发的杂散字段以 Set 开始(“Keep Only Set”,不设字段),并添加便笺注明“预期无输入”
子工作流命名或描述为纯操作,却悄悄写入状态调用方无法判断重试或幂等性;副作用让他们措手不及将副作用纳入契约,或将其移出
子工作流没有 description以后搜索时找不到;没人知道它的用途设置 description,包含输入/输出结构和关键词
名称类似 Helper 3 / 没有前缀未说明用途,也无法通过前缀搜索匹配使用动词优先的前缀(Subworkflow:、<Domain>:、Tool:)
在假定只有一个条目的主体上使用 mode: all将所有输入聚合为一个结果,而不是每个输入各生成一个结果使用 mode: each(并跳过内部的 Loop Over Items)
重命名正在使用的输入字段,却未迁移调用方调用方仍发送旧名称 → 主体看到 undefined,但不会有任何报错在同一变更中迁移所有调用方;使用 validate_workflow 验证
30 个节点的工作流没有提取逻辑难以阅读、测试和替换将逻辑部分提取为子工作流

社区 MCP 不支持的功能

想要执行的操作实际情况
按标签筛选/发现工作流MCP 无法读取或按标签筛选(仅限 UI)。发现工作流靠的是名称——使用动词优先的前缀和 n8n_list_workflows。
捕获无法识别的输入字段n8n 不会因此报错。主体看到 undefined,调用方却毫不知情——契约静默失效。请手动检查所有调用方中的字段重命名情况。
不使用类型化触发器就设置输入模式/字段触发器节点本身必须声明 workflowInputs.values。使用 n8n_update_partial_workflow(updateNode / patchNodeField)进行配置;使用 get_node / validate_node 进行验证。

MCP 可以执行的操作:构建子工作流及其调用方(使用 addNode / addConnection / updateNode / patchNodeField 的 n8n_update_partial_workflow)、发现现有工作流(n8n_list_workflows、n8n_get_workflow)、验证(validate_workflow、n8n_validate_workflow)、独立测试(n8n_test_workflow)、检查运行记录(n8n_executions)、使用 Data Table 支持有状态子工作流(n8n_manage_datatable),以及激活(activateWorkflow)。


参考文件

文件阅读时机
SUBWORKFLOW_PATTERNS.md深入了解 mode: all 与 each、按输入形状拆分(N+1 完整示例),以及通过 Data Table 轮询实现即发即忘并行处理
NAMING_AND_DISCOVERY.md为新的子工作流命名、了解动词前置的前缀约定、搜索现有子工作流,以及编写便于发现的描述

与其他技能集成

  • n8n-workflow-patterns — 用它确定编排工作流的整体结构;用本技能决定哪些部分应成为子工作流。
  • n8n-mcp-tools-expert — 了解 n8n_list_workflows、n8n_get_workflow、n8n_update_partial_workflow 和 n8n_manage_datatable 的参数格式(Data Table 是有状态子工作流和即发即忘轮询所依赖的表)。
  • n8n-node-configuration — workflowInputs 和 inputSource(Define Below 与 passthrough)切换项,是 Execute Workflow Trigger 上由 displayOptions 驱动的配置。
  • n8n-expression-syntax — 在此了解读取输入($json、$('When Executed by Another Workflow'))以及合法的最终 Set 例外情况。
  • n8n-error-handling — 预期失败返回 { ok: false, error };意外失败则抛出错误并通过错误输出路由。子工作流边界是定义这条界线的自然位置。
  • n8n-validation-expert — 验证子工作流及其调用方;无法在此发现未识别的输入字段,因此需手动核对字段变更。
  • n8n-code-javascript / n8n-code-python — 如果子工作流主体是单个 Code 节点,其契约仍是触发器的类型化输入和返回形状,而不是 Code 节点的内部实现。
  • n8n-code-tool — Custom Code Tool 是内联式 Agent 工具选项;子工作流工具则是可复用的多步骤选项。如果逻辑由多个 Agent 共享,或需要完整的 Code 节点沙箱,请选择子工作流。
  • n8n-agents — 了解如何将类型化子工作流接入为 Agent 工具,包括零输入和二进制数据的情况。
  • n8n-binary-and-data — 了解用于二进制输入的 passthrough 触发器,以及二进制数据为何无法直接通过 Agent 工具传递。
  • using-n8n-mcp-skills — 了解构建过程中何时查阅哪些技能。

快速参考清单

发布子工作流前:

  • 先搜索 n8n_list_workflows / n8n_get_workflow — 确认它尚不存在
  • 触发器使用 Define Below,并设置类型化的 workflowInputs.values(二进制输入或零输入的情况除外)
  • 零输入 passthrough(若使用)以“Keep Only Set” Set 节点开头,并添加便笺说明没有输入
  • 名称采用动词前置的前缀(Subworkflow:、<Domain>:、Tool:)
  • 描述说明输入/输出形状,并包含便于搜索的关键词
  • 返回自然且一致的形状,通过最终的 Return Set 节点实现,而非存储形状
  • 预期失败返回 { ok: false, error };只有意外失败才抛出错误
  • 调用方的 mode 在主体假定只有一个项目时设为 each(而非内部使用 Loop Over Items)
  • 明确设置 waitForSubWorkflow(只有在存在完成跟踪机制时才设为 false)
  • 有状态子工作流应在名称和描述中声明其副作用,避免意外产生状态
  • 使用 validate_workflow 完成验证;使用 n8n_test_workflow 单独测试

请记住:子工作流是一个函数。它的 API 是触发器的类型化输入和最后一个节点的输出形状——明确声明两者,使用便于搜索的名称,并根据主体的预期设置调用时的 mode。如果 passthrough 触发器并非用于二进制输入或零参数操作,或者名称无法通过搜索找到,可复用的函数就会悄无声息地变成下一个重复项。

评论

支持 Markdown