关于 mcp-factory
MCP Factory 根据经过验证的清单生成 MCP 服务框架,并通过运行时中心提供其工具,适合管理多项工具集成的开发者。它以清单驱动注册,并通过子进程代理将多个服务汇聚到一个 MCP 端点。
主要功能
- 01验证 mcp.yaml 清单
- 02生成 Python 和 Node.js 服务框架
- 03注册现有服务而不生成代码
- 04扫描清单并更新客户端配置
- 05通过单个 MCP 中心代理各 Bot 工具
- 06检查工具及参数设计
适用场景
- 01根据集成清单搭建服务框架
- 02批量注册项目目录中的服务
- 03通过一个端点提供多个 Bot 服务
- 04生成前检查工具描述和参数架构
MCP 相关
可生成 MCP 服务框架和配置项,也可作为 MCP 中心运行,将工具代理到各 Bot 的子进程服务。启动时扫描清单,并通过单个端点公开已注册工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
MCP Factory
下方的测试数量可验证(
python -m pytest tests/→ 348 passed, 8 skipped),并由scripts/check_readme_counts.py在 CI 中强制检查;如果本 README 中的数量与实际测试套件不符,就会导致构建失败。
60 秒快速入门
通过 PyPI 安装(注册表用户):
pip install jaimenbell-mcp-factory
mcp-factory-hub --serve
# equivalent: python -m mcp_factory --serve
**通过 git 检出代码(贡献者):**请参阅本 README 中的 python hub_server.py ... 示例——仓库根目录下的 hub_server.py 是一个向后兼容的包装器,调用的正是控制台脚本运行的同一 mcp_factory.cli 模块,因此两种方式的行为完全一致。
**MCP Integration Sprint 背后的清单驱动引擎。**为 bot 仓库编写一个 mcp.yaml,Factory 就会生成服务存根和 ~/.claude.json 条目;运行 hub 后,它会通过单个 MCP 端点提供每个 bot 的工具。
SDK 包装器只是简单部分。让 MCP 服务能够安全地连接到真实内部工具所依赖的工程实践——限定范围的身份验证和环境变量、宽容失败的错误处理、经过验证的清单、避免冲突的注册表,以及真实的测试套件——正是此引擎围绕其构建的核心。每个合作项目都需要手工构建同样的生产层;Factory 负责搭建脚手架,不会弄虚作假。
回复前先浏览代码
本仓库公开可见,**因此你可以亲自验证这些实践,而不是只听我一面之词。**以下每项声明都对应一个可打开查看的文件:
| 声明 | 文件位置 | 查看内容 |
|---|---|---|
| 经过验证、按环境限定的清单 | mcp_factory/manifest.py | 严格的 from_dict 验证(缺少字段或字段无效时会抛出异常);用于限定服务可访问哪些机密信息的 env_required / env 模型 |
| 宽容失败的子进程代理 | mcp_factory/runtime/subprocess_adapter.py | 类型化的 SubprocessError、延迟启动、JSON-RPC 错误传递、受 timeout / OSError 保护的拆卸,以及 atexit 清理——bot 失效时会返回清晰的错误,而不会导致 hub 崩溃 |
| 避免冲突、由清单驱动的注册表 | mcp_factory/runtime/registry.py · registry.json | 重复的 <bot>.<tool> 名称会触发 CollisionError;注册表由清单生成,而非手动维护 |
| 在干净检出环境中测试 | tests/ | 348 passed, 8 skipped, 0 failed(Python 3.12);8 个跳过项是真实的集成测试或 lint 检查;缺少所需条件时,这些测试或检查会直接跳过 |
诚实准则:
348是在干净检出环境中得到的真实、可复现的数量——与公开 CI 运行所产生并作为门槛的数量相同。mcp-factory 生成的是脚手架并运行 hub——它不会“生成生产服务”,也不声称具备客户端或 CI 相关能力。加固后的生产层(每个工具的身份验证边界、完整的故障处理集合、双轴版本固定)是在此引擎之上针对每个合作项目构建的。下面介绍的两种 Python 脚手架风格都适用这一点——请参阅“两种 Python 风格”,了解 fastmcp 变体在此基线之上具体增加了什么、没有增加什么。
快速开始
Factory 模式(通过一个清单生成配置)
# Reference an existing MCP server (no code generated — just the config entry)
python hub_server.py --manifest examples/fleet_health.yaml
# Scaffold a new MCP server from scratch
python hub_server.py --manifest my_bot/mcp.yaml --output-dir my_bot/
# Dry run — preview without writing
python hub_server.py --manifest my_bot/mcp.yaml --dry-run
# Self-verify: compare factory output to live ~/.claude.json entry
python hub_server.py --manifest examples/fleet_health.yaml --verify ~/.claude.json
默认情况下,输出始终写入 ~/.claude.json.factory-test——绝不会写入实际的 ~/.claude.json。审核后请手动复制条目。
扫描模式(批量注册所有机器人)
# Dry-run diff: show what would change in ~/.claude.json
python hub_server.py --scan C:\path\to\projects
# Apply: write ~/.claude.json after backing it up
python hub_server.py --scan C:\path\to\projects --apply
# Force-update entries already registered
python hub_server.py --scan C:\path\to\projects --apply --force
--scan 会发现所有 projects/*/mcp.yaml 文件,逐一验证,并与当前的 ~/.claude.json 进行差异比较。默认根目录为 C:\path\to\projects。使用 --apply 时,写入前会在 ~/.claude.json.scan-backup-<timestamp> 创建带时间戳的备份。
**跳过逻辑:**除非传入 --force,否则会跳过名称已存在于 ~/.claude.json 中的清单。这可以防止意外覆盖手动编写的条目。
服务模式(运行时中心——所有机器人的单一 MCP)
# Run the hub as a live MCP server (stdio transport)
python hub_server.py --serve
# Serve with multiple scan roots (repeatable)
python hub_server.py --serve \
--scan-root C:\path\to\projects \
--scan-root C:\path\to\Claude
# Register+serve in one step (scan --apply then start hub)
python hub_server.py --register \
--scan-root C:\path\to\projects \
--scan-root C:\path\to\Claude
中心会在启动时扫描每个 --scan-root 下的所有 mcp.yaml 清单,然后在 <bot>.<tool> 命名空间下公开每个机器人的工具(例如,fleet-health.fleet_status、my-bot.run_scan)。工具会通过延迟启动代理到各个机器人的子进程 MCP 服务。
中心元工具:_hub.list_bots 会返回已注册的机器人及其子进程状态。
中心已在 ~/.claude.json 中预先注册为 mcp-factory-hub(参见 scripts/register_hub.py)。
Node.js 模板
当 mcp.yaml 中设置 runtime.type: node 时,Factory 会生成 Node.js 桩代码:
runtime:
type: node
command: "node"
output: "path/to/server.js"
生成的桩代码使用 @modelcontextprotocol/sdk,采用 stdio 传输,并使用 zod 验证参数。可参阅 examples/node_example.yaml 查看可运行的演示。
两种 Python 风格:原始 SDK 与 FastMCP
对于 runtime.type: python,Factory 可以根据完全相同的清单搭建以下任一种风格:
runtime:
type: python
command: "python"
style: raw # default — official `mcp` SDK, hand-rolled list_tools/call_tool
# style: fastmcp # FastMCP v3 (PrefectHQ/fastmcp), decorator-based tool registration
两种风格都会读取相同的 tools: / env_required: 字段,并生成使用相同 stdio JSON-RPC 线路协议的服务端——运行时中心的 SubprocessAdapter 无需修改适配器即可代理其中任一种(可参阅 tests/test_fastmcp_template.py::TestFastmcpServeSmoke,了解实时生成并调用的测试)。
style: raw (python_server.py.j2) | style: fastmcp (python_fastmcp.j2) | |
|---|---|---|
| SDK | 官方 mcp 包,mcp.server.Server | fastmcp(固定为 fastmcp==3.4.2——4.0.0b1 是会破坏 sampling/roots 的测试版,不要使用浮动版本) |
| 工具注册 | 手动进行 @server.list_tools() / @server.call_tool() 分发 | 每个工具对应一个由 @mcp.tool(...) 装饰的函数 |
| 参数架构 | 为每个参数手动构建 JSON Schema 字典 | 在真实 Python 参数上使用 Annotated[type, Field(description=...)]——FastMCP 会根据函数签名推导 JSON Schema,包括必填与可选项 |
| 工具主体 | # TODO: implement 桩代码 | 相同的桩代码,外层包裹 try/except Exception——填充实现中的运行时错误会返回结构化的 {"status": "error", ...},而不是导致进程崩溃 |
env_required | 不在脚手架层面强制执行 | 会渲染为启动检查 _check_required_env(),如果声明的变量缺失,就会向 stderr 发出警告——这只检查变量是否存在,不验证凭据 |
**坦诚说明差距:**两种风格都未实现按工具授权、速率限制,也未实现中心级 subprocess_adapter.py 免费提供的“完整故障处理集”(类型化错误、延迟启动、atexit 清理)——这些仍需在任一脚手架基础上,针对每个具体项目单独构建。fastmcp 模板的容错包装器和环境变量存在性检查是新增的真实代码(阅读 mcp_factory/templates/python_fastmcp.j2),并非关于身份验证的营销说法——之所以加入,是因为 FastMCP 的装饰器模型让它们能够以较低成本清晰地实现;它们尚未(目前还没有)回移到原始模板中,因此两种风格开箱即用的功能略有不同。如果你的项目需要 FastMCP 在此之外的特定功能(资源、提示词、HTTP/SSE 传输、基于中间件的身份验证),生成的文件就是一个普通的 FastMCP 应用——可直接扩展。
可参阅 examples/fastmcp_example.yaml 查看可运行的演示清单。
mcp.yaml 架构
name: my-bot # REQUIRED — unique MCP server name (key in claude.json)
description: > # REQUIRED — shown in Claude's tool descriptions
What this bot does and when to use it.
runtime: # REQUIRED
type: python # python | node | binary
command: "C:\\Python314\\python.exe" # full path to interpreter
script: "path/to/server.py" # existing server (skips scaffold generation)
output: "path/to/out.py" # where to write generated scaffold (omit = auto)
style: raw # python only: raw (default) | fastmcp — see "Two Python styles"
tools: # REQUIRED — list of MCP tools to expose
- name: tool_name # REQUIRED — programmatic identifier
title: "Tool Name" # Optional — human display name (see below)
description: > # REQUIRED — used by Claude for routing
What this tool does.
annotations: # Optional — the four MCP behaviour hints
readOnlyHint: true # tool does not modify anything
destructiveHint: false # tool does not perform destructive updates
idempotentHint: true # repeat calls have no additional effect
openWorldHint: false # tool does not touch an open external world
args: # Optional list of arguments
- name: arg_name # REQUIRED
type: string # string | number | boolean | object | array
required: true # default: true
description: "..." # shown in Claude's tool schema
env_required: # env var names that must be set at runtime
- MY_API_KEY
env: # static env vars injected into claude.json entry
MY_API_KEY: "" # leave value empty — fill in ~/.claude.json manually
tags: [trading, health] # for documentation / future routing
priority: high # high | medium | low
关键规则
runtime.script+ 已有文件 → 工厂会引用该文件,跳过脚手架生成runtime.script+ 文件不存在 → 验证错误(新建脚手架时使用runtime.output)runtime.output→ 生成的存根文件的显式路径(建议使用绝对路径)- 既没有
script也没有output→ 在配置写入步骤报错
title 和 annotations
这两个字段都是可选的,也都值得声明。
title 是面向用户显示的名称,因此 name 可以保持为程序标识符。显示名称的优先级为 title → annotations.title → name;工厂只会暴露顶层的 title,因此一个清单不能声明两个相互竞争的显示名称。
annotations 的重要性超乎表面。四个提示都有保守的默认值——destructiveHint 和 openWorldHint 的默认值都是 true,readOnlyHint 的默认值是 false——因此,如果工具没有声明任何内容,谨慎的客户端会将其视为可能具有最高危险性的工具。只读工具通过声明 readOnlyHint: true 来避免这种情况。只接受上述四个提示名称,每个值都必须是实际的布尔值;未知键会导致硬错误,而不是被静默忽略(否则,拼写错误 readonlyHint 会导致工具在协议传输中实际上没有任何注解)。
注解只是提示,不是安全边界——规范明确指出,客户端绝不应基于来自不可信服务的注解来决定是否使用工具。注解会影响客户端的用户体验,但不会强制执行任何操作。
这两个字段都会由两个 Python 模板生成。Node 模板目前还不会渲染它们——仓库不会 vendoring node_modules,因此无法在测试中执行经过修改的调用形式;发布未经验证的生成代码,比维持当前形式更糟。即使在 Node 清单中声明这些字段仍然有效,并且具备向前兼容性。
生成时工具设计检查
清单验证是一道安全关卡:只要存在任何可能向生成的服务注入代码的情况,就会失败关闭。但它不会检查生成的工具质量如何。清单可能完全有效,但生成的服务仍可能让 Agent 的表现显著变差。
因此,工厂模式的每次运行都会执行工具设计检查(步骤 2),涵盖三个范围内的 15 条规则:
| 范围 | 检查项 |
|---|---|
| 清单 | 工具数量限额;忽略大小写和分隔符后发生冲突的名称;跨服务冲突的泛化名称 |
| 工具 | 描述为空/过于单薄/充斥填充内容;描述仅复述名称;名称不符合 snake_case 或过长;缺少注解;列表工具无上限;注解与名称相矛盾 |
| 参数 | 未描述的参数;未限定的参数名称;没有描述、因此也没有 schema 形状的结构化(object/array)参数 |
默认情况下,检查仅提供建议:打印发现项,继续生成,并以状态码 0 退出。
--lint-strict 会将错误级别的发现项视为致命问题:报告这些问题、不写入脚手架,并以状态码 3 退出。
mcp-factory-hub --manifest examples/fastmcp_example.yaml --lint-strict
这项检查有三个有意设计的特性:
- 无法判断时会报告
INDETERMINATE,而不是判定为合格:例如,清单中的runtime.script已存在(工厂会引用这个手写文件,而不会生成文件,因此工具列表可能与实际服务不符),或者运行时没有模板。--lint-strict绝不会因结果不确定而阻止执行:不能把没有判定结果转换成判定结果。 - 没有工具名称字符集规则,尽管 SEP-986 定义了相关规则。清单自身的标识符验证已经更严格,会在解析时拒绝违规项,因此该规则不可能对任何进入检查的输入生效;无法触发的检查不算检查。只有 SEP-986 中关于长度的部分未在上游强制执行,因此规则也只检查长度。
- 每条规则都附带正向对照测试:测试证明规则会针对已知有问题的工具定义触发,而对已知良好的定义保持静默;此外还有套件级测试,确保格式良好的清单不会产生任何发现项。
examples/中的示例本身也必须通过检查,相关测试为tests/test_examples_lint_clean.py。
如何添加新的 MCP
- 在你的机器人仓库根目录(或
examples/中)编写mcp.yaml - 运行工厂:
python hub_server.py --manifest path/to/mcp.yaml - 检查
~/.claude.json.factory-test,确认条目无误 - 将条目复制到
~/.claude.json的mcpServers下 - 重启 Claude Code
如果机器人没有现成的服务,工厂会在 generated/<name>_server.py 生成一个存根。填写其中的 # TODO: implement 部分,并在后续运行时将 runtime.script 设置为该存根路径。
运行时 Hub 架构
hub_server.py --serve
└── mcp_factory/runtime/
├── hub.py async MCP server (lists + routes all tools)
├── registry.py maps <bot>.<tool> → manifest + adapter
└── subprocess_adapter.py spawns per-bot MCP server, proxies JSON-RPC
子进程生命周期:
- 首次调用工具时才惰性启动适配器(不会预先启动进程)
- Hub 会话期间保持运行(每个机器人一个进程)
_hub.list_bots()报告状态:idle(尚未启动)或running- Hub 退出时通过
atexit停止所有适配器;如有需要,stop()会在 5 s 后强制结束进程
工具命名:<bot-name>.<tool-name> —— 保留连字符,以点号作为分隔符。
示例:fleet-health.fleet_status、my-bot.get_alerts。
第 4 天 —— workflow_runner.py
用于研究工作流的独立 CLI 测试工具,与 hub_server.py 相互独立。
# Discover and list all SKILL.md workflows
python -m mcp_factory.workflow_runner --list
# Run a specific workflow
python -m mcp_factory.workflow_runner --run my-skill
# Validate all discovered SKILL.md files
python -m mcp_factory.workflow_runner --validate
# Write/update registry.json from discovered skills
python -m mcp_factory.workflow_runner --write-registry
# Check for drift between discovered skills and registry.json
python -m mcp_factory.workflow_runner --check
# Control cache behavior
python -m mcp_factory.workflow_runner --run my-skill --cache-policy force-refresh
python -m mcp_factory.workflow_runner --run my-skill --cache-policy read-only
工作原理
workflow_runner.py 默认扫描 ~/research(使用 --scan-root 覆盖),查找包含 YAML 前置元数据的 SKILL.md 文件。每个 SKILL.md 都定义一个带有元数据的命名工作流:
---
name: my-skill
description: What this workflow does
output_path_template: "~/vault/output/{date}/{name}.md"
---
Prompt body passed to claude -p subprocess...
- 发现: 使用
git ls-files枚举每个扫描根目录下受跟踪的SKILL.md文件 - 验证: 检查必需的前置元数据字段(
name、description) - 缓存: 使用基于 SHA 的缓存,以提示词内容为键;
auto(默认)在输出未变化时跳过重跑,force-refresh始终重新运行,read-only从不写入 - 运行: 将
claude -p <prompt>作为子进程调用,并流式传输输出 - 写入输出: 展开
output_path_template,将结果写入知识库 - 注册表:
--write-registry将发现的技能持久化到registry.json;--check检测文件系统与注册表之间的差异,但不写入
目录结构
mcp-factory/
├── hub_server.py # CLI entry point (factory / scan / serve)
├── mcp_factory/
│ ├── manifest.py # Manifest dataclass + YAML loader + validation
│ ├── generator.py # Python MCP server stub scaffolder
│ ├── config.py # claude.json entry builder + comparator
│ ├── scan.py # --scan mode: manifest discovery + diff/apply
│ ├── workflow_runner.py # Day 4: standalone CLI harness for SKILL.md workflows
│ ├── templates/ # packaged as data so `pip install` ships them too
│ │ ├── python_server.py.j2 # Jinja2 template — raw mcp SDK stubs (style: raw, default)
│ │ ├── python_fastmcp.j2 # Jinja2 template — FastMCP v2 stubs (style: fastmcp)
│ │ └── node_server.js.j2 # Jinja2 template for generated Node.js stubs
│ └── runtime/
│ ├── subprocess_adapter.py # subprocess MCP client (JSON-RPC proxy)
│ ├── registry.py # tool registry with collision detection
│ └── hub.py # async hub MCP server
├── tests/
│ ├── fixtures/
│ │ ├── fleet_health.yaml # Day 1 self-verification fixture
│ │ ├── minimal.yaml # Minimal valid manifest
│ │ └── mock_mcp_server.py # Stdlib-only mock MCP server for adapter tests
│ ├── test_manifest.py
│ ├── test_generator.py
│ ├── test_subprocess_adapter.py
│ ├── test_registry.py
│ ├── test_scan.py
│ ├── test_hub_cli.py
│ ├── test_mcp_pkg.py
│ ├── test_node_template.py
│ ├── test_python_template.py
│ ├── test_fastmcp_template.py # style: fastmcp generation + import + serve-smoke tests
│ ├── test_register_flag.py
│ ├── test_registration.py
│ ├── test_smoke_hub.py
│ ├── test_watcher.py
│ ├── test_workflow_runner.py # Day 4: workflow_runner unit + integration tests
│ └── test_integration_fleet_health.py # live integration tests (skipped if server absent)
├── examples/
│ ├── fleet_health.yaml # Example manifest referencing an existing server
│ ├── node_example.yaml # Example manifest for the node template
│ └── fastmcp_example.yaml # Example manifest for the fastmcp template
└── pyproject.toml
自我验证
examples/fleet_health.yaml 清单引用了一个示例服务。运行:
python hub_server.py --manifest examples/fleet_health.yaml --verify ~/.claude.json
可确认工厂会生成匹配的 ~/.claude.json 条目。
运行测试
python -m pytest tests/ -v
在干净的检出副本上(Python 3.12),执行 pip install -e .[dev] 后:348 项通过,8 项跳过,0 项失败——这与公开 CI 运行得出的结果相同,也是其门槛。
当所需资源或条件不存在时,8 项跳过的测试会自动跳过:
test_integration_fleet_health.py(5 项测试)需要磁盘上存在 fleet-healthserver.py(FLEET_HEALTH_SERVER_PATH)。test_node_template.py(1 项测试)需要存在node和@modelcontextprotocol/sdk(node_modules/)。test_examples_lint_clean.py(2 项测试)会跳过examples/fleet_health.yaml。该文件引用了一个现有的手写服务,工具设计检查会正确地将其报告为INDETERMINATE,而不会评判它无法看到的代码;跳过测试是如实记录这一情况的方式。
test_smoke_hub.py(4 项测试)现在无需连接实际运行的机器人群即可真正运行:hub 的演示清单回退机制(见下文)即使在空扫描根目录下也能提供可发现的内容,因此这些测试现在会在干净的检出副本上无条件运行。
(在维护者的机器人群机器上,由于 fleet-health 服务和实际运行的机器人都存在,其余跳过的集成测试会真正运行,通过的测试数也会更高——但本 README 仅声明干净检出副本和公开 CI 都能复现的结果。)
fastmcp 风格的模板测试(test_fastmcp_template.py)不在此跳过列表中——fastmcp 已作为 [dev] 额外依赖安装,因此在标准开发环境中会真正运行。
商业支持
由 Jaimen Bell 维护。如需生产环境 MCP 集成、自定义服务或 Agent 可靠性方面的工作,请访问 jaimenbell.dev。
正在构建自己的 MCP 服务?MCP Starter Kit 提供模板、构建指南,以及发布这个服务时积累的打包经验。
mcp-name: io.github.jaimenbell/mcp-factory
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。