Mcp Factory

io.github.jaimenbell由 GitHub 账号 jaimenbell 发布v0.1.3更新于 Oct 24, 2026

Manifest-driven MCP server scaffolder and runtime hub for tool integrations.

概览

AI 生成的概览

一个本地 Python 工具,可从 YAML 清单生成 MCP 服务器骨架,并运行一个 hub,通过单一 MCP 端点暴露所有已注册机器人的工具。

可以做什么
根据单个 mcp.yaml 清单生成 MCP 服务器骨架和客户端配置条目,支持 Python(原生 SDK 或 FastMCP)与 Node.js 风格,可先试运行预览,并附带可设为严格模式的工具设计检查。扫描项目目录中的清单,与客户端配置做差异比较或写入应用,写入前会创建带时间戳的备份。作为运行时 hub,把每个机器人的工具注册到 bot.tool 命名空间下,并按需启动各机器人子进程服务器来代理调用。通过 hub 元工具列出已注册机器人及其子进程状态。发现、校验、运行并缓存 SKILL.md 研究工作流,并保持注册表文件同步。
适用场景
当你维护多个 MCP 服务器或机器人仓库,希望用清单驱动的方式统一生成骨架、注册到客户端并通过一个本地端点全部暴露时适用。也适合在发布服务器前对工具名称、描述和参数做一次检查。
运行要求
以 PyPI 包(jaimenbell-mcp-factory)通过 pip 安装,作为本地 stdio 进程运行,需要 Python;仅 Node 风格骨架需要 Node.js 和 MCP SDK。它会读写客户端配置文件(如 ~/.claude.json),并扫描项目目录中的 mcp.yaml 清单。未声明认证或环境变量。
安装前请注意
扫描模式的 --apply 会写入实际客户端配置文件,但会先备份;工厂输出默认写入单独的测试文件,条目需人工审阅后复制。生成的骨架含 TODO 占位,并非生产服务器,README 说明不包含按工具授权、限流和完整故障处理。清单会声明用于密钥的环境变量名,生成的 env 条目值留空,需自行填写。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Mcp Factory,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

MCP Factory

[CI] [tests] [python] [PyPI] [MCP Registry]

The test count is verifiable below (python -m pytest tests/ → 348 passed, 8 skipped) and enforced in CI by scripts/check_readme_counts.py, which fails the build if this README's counts drift from the live suite.

60-Second Quickstart

From PyPI (registry users):

bash
pip install jaimenbell-mcp-factorymcp-factory-hub --serve# equivalent: python -m mcp_factory --serve

From a git checkout (contributors): see the python hub_server.py ... examples throughout this README — hub_server.py at the repo root is a backward-compat wrapper around the same mcp_factory.cli module the console script runs, so behavior is identical either way.

The manifest-driven engine behind the MCP Integration Sprint. Write one mcp.yaml for a bot repo and the factory generates the server stub and the ~/.claude.json entry; run the hub and it serves every bot's tools through a single MCP endpoint.

The SDK wrapper is the easy part. What makes an MCP server safe to put in front of a real internal tool — scoped auth/env, fail-soft error handling, validated manifests, a collision-safe registry, and a real test suite — is the engineering this engine is built around. That same production layer is hand-built per engagement; the factory scaffolds it, it doesn't fake it.

Browse before you reply

This repo is public so you can verify the discipline instead of taking my word for it. Every claim below maps to a file you can open:

ClaimWhere it livesWhat to look for
Validated, env-scoped manifestsmcp_factory/manifest.pystrict from_dict validation (raises on missing/invalid fields); the env_required / env model that scopes which secrets a server may see
Fail-soft subprocess proxyingmcp_factory/runtime/subprocess_adapter.pytyped SubprocessError, lazy start, JSON-RPC error surfacing, timeout/OSError-guarded teardown + atexit cleanup — a dead bot returns a clean error, it doesn't crash the hub
Collision-safe, manifest-driven registrymcp_factory/runtime/registry.py · registry.jsonCollisionError on duplicate <bot>.<tool> names; the registry is built from manifests, not hand-maintained
Tested on a clean checkouttests/348 passed, 8 skipped, 0 failed (Python 3.12); the 8 skips are real integration tests or lint checks that no-op when the thing they need is absent

Honesty rails: 348 is the real, reproducible count on a clean checkout — the same number the public CI run produces and gates on. mcp-factory generates the scaffold and runs the hub — it does not "generate the production server" or carry any client/CI claims. The hardened production layer (per-tool auth boundaries, the full failure set, two-axis version-pinning) is built per engagement on top of this engine. That applies to both Python scaffold styles below — see "Two Python styles" for exactly what the fastmcp variant does and doesn't add on top of that baseline.

Quick Start

Factory mode (generate config from one manifest)

bash
# 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 scratchpython hub_server.py --manifest my_bot/mcp.yaml --output-dir my_bot/
# Dry run — preview without writingpython hub_server.py --manifest my_bot/mcp.yaml --dry-run
# Self-verify: compare factory output to live ~/.claude.json entrypython hub_server.py --manifest examples/fleet_health.yaml --verify ~/.claude.json

Output always goes to ~/.claude.json.factory-test by default — never to the live ~/.claude.json. Copy entries manually after review.

Scan mode (batch-register all bots)

bash
# Dry-run diff: show what would change in ~/.claude.jsonpython hub_server.py --scan C:\path\to\projects
# Apply: write ~/.claude.json after backing it uppython hub_server.py --scan C:\path\to\projects --apply
# Force-update entries already registeredpython hub_server.py --scan C:\path\to\projects --apply --force

--scan discovers all projects/*/mcp.yaml files, validates each, and diffs them against the current ~/.claude.json. Default root is C:\path\to\projects. With --apply, a timestamped backup is created at ~/.claude.json.scan-backup-<timestamp> before writing.

Skip logic: manifests whose name already exists in ~/.claude.json are skipped unless --force is passed. This prevents accidentally overwriting manually-crafted entries.

Serve mode (runtime hub — single MCP for all bots)

bash
# 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

The hub scans all mcp.yaml manifests under each --scan-root at startup, then exposes every bot's tools under the <bot>.<tool> namespace (e.g., fleet-health.fleet_status, my-bot.run_scan). Tools are proxied to per-bot subprocess MCP servers with lazy startup.

Hub meta-tool: _hub.list_bots returns the registered bots and their subprocess status.

Hub is pre-registered in ~/.claude.json as mcp-factory-hub (see scripts/register_hub.py).

Node.js template

Factory generates Node.js stubs when runtime.type: node is set in mcp.yaml:

yaml
runtime:  type: node  command: "node"  output: "path/to/server.js"

Generated stubs use @modelcontextprotocol/sdk with stdio transport and zod for argument validation. See examples/node_example.yaml for a working demo.

Two Python styles: raw SDK vs. FastMCP

For runtime.type: python, the factory can scaffold either of two styles from the exact same manifest:

yaml
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

Both styles read the same tools: / env_required: fields and produce a server that speaks the same stdio JSON-RPC wire protocol — the runtime hub's SubprocessAdapter proxies either one without any adapter changes (see tests/test_fastmcp_template.py::TestFastmcpServeSmoke for a live generate-and-call test).

style: raw (python_server.py.j2)style: fastmcp (python_fastmcp.j2)
SDKofficial mcp package, mcp.server.Serverfastmcp (pinned exact fastmcp==3.4.2 — 4.0.0b1 is a beta that breaks sampling/roots, do not float)
Tool registrationmanual @server.list_tools() / @server.call_tool() dispatchone @mcp.tool(...)-decorated function per tool
Arg schemahand-built JSON Schema dict per argAnnotated[type, Field(description=...)] on real Python parameters — FastMCP derives the JSON Schema, including required/optional, from the signature
Tool body# TODO: implement stubsame stub, wrapped in try/except Exception — a runtime error in a filled-in implementation returns a structured {"status": "error", ...} instead of crashing the process
env_requirednot enforced at scaffold levelrendered into a _check_required_env() startup check that warns to stderr if a declared var is missing — a presence check, not credential validation

Gaps, stated honestly: neither style implements per-tool authorization, rate limiting, or the "full failure set" the hub-level subprocess_adapter.py gives you for free (typed errors, lazy start, atexit cleanup) — that's still a per-engagement build on top of either scaffold. The fastmcp template's fail-soft wrapper and env-presence check are new, real code (read mcp_factory/templates/python_fastmcp.j2), not a marketing claim about auth — they were added because FastMCP's decorator model made them cheap to include cleanly; they have not (yet) been backported to the raw template, which is why the two styles differ slightly in what ships out of the box. If your engagement needs FastMCP-specific features beyond this (resources, prompts, HTTP/SSE transport, middleware-based auth), the generated file is a normal FastMCP app — extend it directly.

See examples/fastmcp_example.yaml for a working demo manifest.

mcp.yaml Schema

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 routingpriority: high                 # high | medium | low

Key rules

  • runtime.script + existing file → factory references it, skips scaffold
  • runtime.script + missing file → validation error (use runtime.output for new scaffolds)
  • runtime.output → explicit path for generated stub (absolute recommended)
  • Neither script nor output → error at config-write step

title and annotations

Both are optional and both are worth declaring.

title is the human display name, which lets name stay a programmatic identifier. Display precedence is title → annotations.title → name; the factory exposes only the top-level title so one manifest cannot declare two competing display names.

annotations matters more than it looks. The four hints have pessimistic defaults — destructiveHint and openWorldHint both default to true, readOnlyHint to false — so a tool that declares nothing is treated by a careful client as the most dangerous thing it could be. Declaring readOnlyHint: true on a read-only tool is how you opt out of that. Only the four hint names above are accepted, each must be a real boolean, and an unknown key is a hard error rather than a silent drop (the typo readonlyHint would otherwise leave the tool effectively unannotated on the wire).

Annotations are hints, not a security boundary — the spec is explicit that a client should never make tool-use decisions based on annotations from an untrusted server. They inform a client's UX; they do not enforce anything.

Both fields are emitted by the two Python templates. The Node template does not render them yet — the repo vendors no node_modules, so an altered call shape could not be executed in test, and shipping unverified generated code is worse than shipping the current shape. Declaring them in a node manifest is still valid and forward-compatible.

Generation-time tool-design lint

Manifest validation is a security gate: it fails closed on anything that could inject code into a generated server. It says nothing about whether the resulting tools are any good. A manifest can be perfectly valid and still produce a server that makes an agent measurably worse.

Every run of factory mode therefore also runs a tool-design lint (Step 2), covering 15 rules across three scopes:

ScopeChecks
manifesttool-count budget, names colliding once case/separators are ignored, bare generic names that collide across servers
toolempty / thin / filler descriptions, descriptions that only restate the name, non-snake_case and over-long names, missing annotations, unbounded listing tools, annotations that contradict the name
argundescribed parameters, unqualified parameter names, structured (object/array) parameters with no description and therefore no schema shape

It is advisory by default — findings print, generation proceeds, exit 0. --lint-strict makes error-severity findings fatal: it reports them, writes no scaffold, and exits 3.

bash
mcp-factory-hub --manifest examples/fastmcp_example.yaml --lint-strict

Three deliberate properties:

  • It reports INDETERMINATE, not a clean bill, when it has no discriminating power — a manifest whose runtime.script exists (the factory references that hand-written file rather than generating one, so the tool list may not match the real server), or a runtime with no template. --lint-strict never blocks on an indeterminate result: the absence of a verdict must not be converted into one.
  • There is no tool-name charset rule, even though SEP-986 defines one. The manifest's own identifier validation is already stricter and rejects violations at parse time, so such a rule could never fire on any input reaching the lint — and a check that cannot fail is not a check. Only the length half of SEP-986 is unenforced upstream, so only the length half is a rule.
  • Every rule ships a positive control: a test proving it fires on a known-bad tool definition and stays silent on a known-good one, plus a suite-level test asserting a well-formed manifest produces zero findings. The examples in examples/ are themselves held to the lint by tests/test_examples_lint_clean.py.

How to Add a New MCP

  1. Write mcp.yaml at your bot repo root (or in examples/)
  2. Run the factory:
    bash
    python hub_server.py --manifest path/to/mcp.yaml
  3. Review ~/.claude.json.factory-test — confirm the entry looks correct
  4. Copy the entry into ~/.claude.json under mcpServers
  5. Restart Claude Code

If the bot has no existing server, the factory generates a stub at generated/<name>_server.py. Fill in the # TODO: implement sections and set runtime.script to the stub path for future runs.

Runtime Hub Architecture

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

Subprocess lifecycle:

  • Adapters start lazily on first tool call (no upfront spawn)
  • Keep-alive for the hub session (one process per bot)
  • _hub.list_bots() reports status: idle (not yet started) or running
  • All adapters stopped via atexit on hub exit; stop() kills if needed after 5 s

Tool naming: <bot-name>.<tool-name> — hyphens preserved, dots as separator. Example: fleet-health.fleet_status, my-bot.get_alerts.

Day 4 — workflow_runner.py

Standalone CLI harness for research workflows, independent of hub_server.py.

bash
# Discover and list all SKILL.md workflowspython -m mcp_factory.workflow_runner --list
# Run a specific workflowpython -m mcp_factory.workflow_runner --run my-skill
# Validate all discovered SKILL.md filespython -m mcp_factory.workflow_runner --validate
# Write/update registry.json from discovered skillspython -m mcp_factory.workflow_runner --write-registry
# Check for drift between discovered skills and registry.jsonpython -m mcp_factory.workflow_runner --check
# Control cache behaviorpython -m mcp_factory.workflow_runner --run my-skill --cache-policy force-refreshpython -m mcp_factory.workflow_runner --run my-skill --cache-policy read-only

How it works

workflow_runner.py scans ~/research by default (override with --scan-root) for SKILL.md files containing YAML frontmatter. Each SKILL.md defines a named workflow with metadata:

yaml
---name: my-skilldescription: What this workflow doesoutput_path_template: "~/vault/output/{date}/{name}.md"---Prompt body passed to claude -p subprocess...
  • Discover: git ls-files to enumerate tracked SKILL.md files under each scan root
  • Validate: checks required frontmatter fields (name, description)
  • Cache: SHA-based cache keyed on prompt content; auto (default) skips re-run if output unchanged, force-refresh always re-runs, read-only never writes
  • Run: invokes claude -p <prompt> as a subprocess, streams output
  • Write output: expands output_path_template, writes result to vault
  • Registry: --write-registry persists discovered skills to registry.json; --check detects drift between filesystem and registry without writing

Directory Layout

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

Self-Verification

The examples/fleet_health.yaml manifest references an example server. Running:

bash
python hub_server.py --manifest examples/fleet_health.yaml --verify ~/.claude.json

confirms the factory produces a matching ~/.claude.json entry.

Running Tests

bash
python -m pytest tests/ -v

On a clean checkout (Python 3.12), with pip install -e .[dev]: 348 passed, 8 skipped, 0 failed — the same numbers the public CI run produces and gates on.

The 8 skipped tests skip automatically when the resource or condition they need is absent:

  • test_integration_fleet_health.py (5 tests) requires a fleet-health server.py on disk (FLEET_HEALTH_SERVER_PATH).
  • test_node_template.py (1 test) requires node and @modelcontextprotocol/sdk (node_modules/) to be present.
  • test_examples_lint_clean.py (2 tests) skips examples/fleet_health.yaml, which references an existing hand-written server — the tool-design lint correctly reports INDETERMINATE there rather than judging code it cannot see, and a skip is the honest way to record that.

test_smoke_hub.py (4 tests) no longer needs a live bot fleet to run for real: the hub's demo-manifest fallback (see below) gives it something to discover even against an empty scan root, so these run unconditionally on a clean checkout now.

(On the maintainer's fleet machine, where the fleet-health server and live bots exist, the remaining skipped integration tests run for real and the passed count is higher — but this README claims only what a clean checkout and public CI reproduce.)

The fastmcp-style template tests (test_fastmcp_template.py) are not in this skip list — fastmcp is installed as a [dev] extra, so they run for real on a standard dev setup.

Commercial support

Maintained by Jaimen Bell. For production MCP integrations, custom servers, or agent-reliability work, see jaimenbell.dev.

Building your own MCP server? The MCP Starter Kit has templates, a build playbook, and packaging war-stories from shipping this one.

mcp-name: io.github.jaimenbell/mcp-factory

来源:README.md,提交 65126fb

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v0.1.3最新Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分