生成启动配置
填写服务名称、command 和 args,然后点击生成。输出的 mcpServers 对象以服务名称为键,记录启动命令和参数数组。你可以核对 JSON 后下载 mcp.json,再按目标客户端要求放入配置。
参数示例
服务名称填写 filesystem,command 填写 npx,args 填写 -y @modelcontextprotocol/server-filesystem /tmp,会得到三个独立参数。当前工具按空白拆分 args;带空格的路径或需要保留引号的参数,应在生成的数组里手动调整。客户端若使用其他配置格式,需要按其说明转换。
离线合并案例:新增一个服务,原来两个对象不能消失
生成器交付的是一份配置片段。实际接入时,已有文件通常已经包含其他服务,甚至还有客户端自己的顶层设置。如果用一次顶层 update 把新片段盖上去,整个 mcpServers 对象可能被替换:新增服务还在,旧服务却消失了。另一种更难发现的情况是原始文本写了两次同名键,普通 JSON 解析后只剩其中一个,审阅者已看不到丢失的那份定义。
下面提供一个独立的 Python 标准库审阅器。它读取两份脱敏文件,输出合并候选与审阅清单,不修改原件、不导入客户端、不启动配置里的命令。案例固定为向已有 calendar-demo 服务旁边加入 fixture-reader;editor 是合成的客户端自定义字段,只用于证明无关顶层内容被保留,并非 Claude Code 或其他客户端必需或接受的字段。
这是一项“配置文件合并”实验,不是 MCP 连通性实验。example.invalid 地址、目录和服务名全是合成输入;probe.py 只回显参数,不实现 MCP。不要把案例当成可直接接入的服务器配置。它补足生成后的离线校对步骤,不改变本页表单行为。
合同先写清:仅新增一个 command/args 服务,不偷偷覆盖
基础文件必须是含对象型 mcpServers 的 JSON 对象;其余字段按解析后的值保留。增量文件顶层只能有 mcpServers,里面恰好一个服务;新增服务只接受 command 与 args。这样,试图顺带改顶层设置或传入 env、HTTP URL、客户端专属选项会被拒绝。本例刻意只覆盖页面产生的本地启动子集,不是完整客户端配置 Schema。已有服务作为不透明 JSON 值保留,不意味着已通过有效性审查。
同名服务内容完全相同,返回 unchanged;同名而内容不同,返回 name-conflict,要求人做决定。比较是 JSON 解析后的值比较,不比较空格与键顺序。不提供自动覆盖开关,也不把同名冲突解释成重试即可成功。
两份原始 JSON 的任意对象出现重复键都拒绝;非 JSON 的 NaN/Infinity 常量也拒绝。args 必须是字符串数组,空字符串允许,NUL 字符拒绝;不做 shell 拆词、引号补全、通配符展开或变量替换。命令字段只是非空文本检查,不能证明可执行文件可信、存在或权限合适。
输出保留的是数据语义,不保留源文件排版。输入采用 UTF-8;本实现限定每份输入最多 65,536 字节、结构嵌套最多 32 层,超出分别拒绝为 input-too-large、input-too-deep;指数溢出为无穷的数值拒绝为 number-range。JSON 小数采用 Python float 精度,不适合承载必须逐字保持的高精度数值;原始数字写法与排版不会保留。candidate 会包含基础文件原有字段,因此请只用脱敏副本,勿把真实凭据或个人配置打印到共享日志。
完整合成输入:空格、空参数、通配符和中文都留在数组里
在新的练习目录保存下面两份 JSON,各自最后保留一个换行。基础文件有一个旧服务和一个无关设置。增量有六个 args 元素;其中 probe.py 是脚本路径,后面五项才是探针看到的业务参数。参数里的 ${DEMO_DIR} 在本实验中只是字面文本。
不要把这些参数重新填入本页按空白拆分的输入框来代替这份 JSON;本节测试的是离线数组合并。目标客户端有自己的变量展开规则,探针保住字面文本不等于所有客户端都会原样传递。
base.json
{"mcpServers":{"calendar-demo":{"type":"http","url":"https://example.invalid/mcp"}},"editor":{"theme":"dark"}}
patch.json
{"mcpServers":{"fixture-reader":{"command":"python3","args":["probe.py","/tmp/mcp demo","","*.txt","${DEMO_DIR}","中文"]}}}
可复用审阅器:只读输入,候选输出到标准输出
保存为 config_merge.py。review 接收原始 bytes,先完成解析与冲突检查,再复制基础对象并添加服务。SHA-256 绑定的是本次读取的原始字节,方便审阅人核对用了哪一版文件;它不是签名、授权记录或锁,也不能阻止检查之后文件又被其他进程修改。
退出码 0 表示生成候选;退出码 2 表示受控输入错误,错误 JSON 只写 stderr,不产生成功候选。程序从不写回 base 或 patch。执行时只指定本练习目录的文件,勿直接指向真实客户端配置。
config_merge.py
"""Offline JSON review only. Never starts a configured command or writes inputs."""
import argparse
import copy
import hashlib
import json
import math
import re
import sys
from pathlib import Path
class Rejected(ValueError):
pass
def unique(pairs):
out = {}
for key, value in pairs:
if key in out:
raise Rejected('duplicate-key')
out[key] = value
return out
def bad_constant(value):
raise Rejected('non-json-number')
MAX_BYTES = 65536
MAX_DEPTH = 32
def finite_float(token):
value = float(token)
if not math.isfinite(value):
raise Rejected('number-range')
return value
def parse(raw):
if len(raw) > MAX_BYTES:
raise Rejected('input-too-large')
try:
text = raw.decode('utf-8')
# Count structural depth outside strings before recursive JSON parsing.
depth = 0
quoted = escaped = False
for char in text:
if quoted:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char == '"':
quoted = False
elif char == '"':
quoted = True
elif char in '[{':
depth += 1
if depth > MAX_DEPTH:
raise Rejected('input-too-deep')
elif char in ']}':
depth -= 1
return json.loads(text, object_pairs_hook=unique,
parse_constant=bad_constant, parse_float=finite_float)
except (UnicodeError, json.JSONDecodeError, ValueError) as exc:
if isinstance(exc, Rejected):
raise
raise Rejected('invalid-json') from exc
def review(base_raw, patch_raw):
base, patch = parse(base_raw), parse(patch_raw)
if not isinstance(base, dict) or not isinstance(base.get('mcpServers'), dict):
raise Rejected('base-shape')
if not isinstance(patch, dict) or set(patch) != {'mcpServers'}:
raise Rejected('patch-shape')
additions = patch['mcpServers']
if not isinstance(additions, dict) or len(additions) != 1:
raise Rejected('one-server-required')
name, spec = next(iter(additions.items()))
if not re.fullmatch(r'[A-Za-z0-9_-]+', name):
raise Rejected('server-name')
if not isinstance(spec, dict) or set(spec) != {'command', 'args'}:
raise Rejected('local-stdio-subset-only')
if not isinstance(spec['command'], str) or not spec['command'].strip():
raise Rejected('command-string')
args = spec['args']
if not isinstance(args, list) or any(not isinstance(x, str) for x in args):
raise Rejected('args-string-array')
if any('\x00' in s for s in [spec['command'], *args]):
raise Rejected('nul-character')
existing = base['mcpServers']
if name in existing and existing[name] != spec:
raise Rejected('name-conflict')
action = 'unchanged' if name in existing else 'add'
candidate = copy.deepcopy(base)
candidate['mcpServers'][name] = copy.deepcopy(spec)
return {
'review': {
'action': action,
'server': name,
'before': sorted(existing),
'after': sorted(candidate['mcpServers']),
'args_count': len(args),
'base_sha256': hashlib.sha256(base_raw).hexdigest(),
'patch_sha256': hashlib.sha256(patch_raw).hexdigest(),
'client_validated': False,
'server_started': False,
},
'candidate': candidate,
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('base')
parser.add_argument('patch')
args = parser.parse_args()
try:
result = review(Path(args.base).read_bytes(), Path(args.patch).read_bytes())
except Rejected as exc:
print(json.dumps({'error': str(exc)}), file=sys.stderr)
return 2
except OSError:
print('{"error":"input-read-failed"}', file=sys.stderr)
return 2
print(json.dumps(result, ensure_ascii=True, indent=2, allow_nan=False))
return 0
if __name__ == '__main__':
raise SystemExit(main())
运行与完整输出:review 包装不能整份当成客户端配置
在装有 Python 3 的环境中运行下列命令。实际记录来自 2026-10-14 的 Linux、CPython 3.12.14,未安装第三方依赖。命令的重定向只保存一份新的 review.json;不要把输出文件名设成任一输入文件名,shell 可能在程序读取前就截断该文件。
下方是这两份完整输入对应的实测输出。人工核对 before 仍有 calendar-demo,after 多出 fixture-reader,editor 值未变,六个参数逐项未变。review 是本例自己的报告包装;只有 candidate 才是候选配置对象,不能把整个输出直接作为客户端配置。
如果更换换行格式或调整输入缩进,SHA-256 会变化,即使解析后的候选值相同。正式应用前仍需重新读取当前基础文件,比较本次记录的哈希;发生变化就重新生成并审阅候选。本文没有实现原子写入、备份恢复或客户端导入。
命令
python3 config_merge.py base.json patch.json > review.json
实测 stdout
{
"review": {
"action": "add",
"server": "fixture-reader",
"before": [
"calendar-demo"
],
"after": [
"calendar-demo",
"fixture-reader"
],
"args_count": 6,
"base_sha256": "842465074e34be4aa6a0b9ddf15c68edc5c06faa4de03c2b7526a6fb2f7dc9d1",
"patch_sha256": "fdce45c6266cff4ebfe198d579c45822d5090aba511053f273961bdcc3b1e0f3",
"client_validated": false,
"server_started": false
},
"candidate": {
"mcpServers": {
"calendar-demo": {
"type": "http",
"url": "https://example.invalid/mcp"
},
"fixture-reader": {
"command": "python3",
"args": [
"probe.py",
"/tmp/mcp demo",
"",
"*.txt",
"${DEMO_DIR}",
"\u4e2d\u6587"
]
}
},
"editor": {
"theme": "dark"
}
}
}
独立验收:50 项固定断言与两个故意写错的合并策略
保存下面的 probe.py 与 test_merge.py。expected.json 是人工固定的完整输出对象,包含报告与候选;EXPECTED 另对候选进行交叉核对。测试不会调用 review 来生成自己的标准答案。两个输入哈希也固定写入测试;正常候选、幂等合并、失败后的修复、CLI 成败输出与输入原件保留分别检查。
负对照一故意使用顶层 dict.update,确认它丢掉 calendar-demo,不能通过完整对象标准答案;负对照二故意把参数 join 再 split,确认它破坏空格路径与空参数。重复键、非 JSON 常量、错误形状、非字符串参数、NUL、无效 UTF-8、同名冲突另有明确错误码断言。
唯一的参数执行测试使用当前 Python 的 sys.executable 和本节固定 probe.py 的绝对路径,shell=False。它没有执行 JSON 中的 command,也没有把任意文件内容当成程序。返回值必须精确等于五个手写参数;不是只检查进程退出码。另用 mutation_test.py 在临时目录实际改坏实现:替换服务表、拆散参数、容许重复键、覆盖重名冲突。四种变异均分别在 normal 与 -O 下被断言拦住;先编译变异源码,避免把语法错误当成测试成功。测试使用显式抛错,不依赖会被 -O 移除的 assert 语句。Windows、macOS 和实际客户端的参数传递没有实测。
expected.json:固定完整输出标准答案
{
"review": {
"action": "add",
"server": "fixture-reader",
"before": [
"calendar-demo"
],
"after": [
"calendar-demo",
"fixture-reader"
],
"args_count": 6,
"base_sha256": "842465074e34be4aa6a0b9ddf15c68edc5c06faa4de03c2b7526a6fb2f7dc9d1",
"patch_sha256": "fdce45c6266cff4ebfe198d579c45822d5090aba511053f273961bdcc3b1e0f3",
"client_validated": false,
"server_started": false
},
"candidate": {
"mcpServers": {
"calendar-demo": {
"type": "http",
"url": "https://example.invalid/mcp"
},
"fixture-reader": {
"command": "python3",
"args": [
"probe.py",
"/tmp/mcp demo",
"",
"*.txt",
"${DEMO_DIR}",
"\u4e2d\u6587"
]
}
},
"editor": {
"theme": "dark"
}
}
}
probe.py
import json import sys print(json.dumps(sys.argv[1:], ensure_ascii=True))
test_merge.py
import copy
import json
import subprocess
import sys
from pathlib import Path
from config_merge import Rejected, review
HERE = Path(__file__).resolve().parent
BASE = (HERE / 'base.json').read_bytes()
PATCH = (HERE / 'patch.json').read_bytes()
EXPECTED = {
'mcpServers': {
'calendar-demo': {'type': 'http', 'url': 'https://example.invalid/mcp'},
'fixture-reader': {'command': 'python3', 'args': [
'probe.py', '/tmp/mcp demo', '', '*.txt', '${DEMO_DIR}', '中文']},
},
'editor': {'theme': 'dark'},
}
COUNT = 0
def check(condition):
global COUNT
if not condition:
raise AssertionError('fixed oracle mismatch')
COUNT += 1
def raw(value):
return json.dumps(value, ensure_ascii=True).encode()
def reject(base, patch, code):
try:
review(base, patch)
except Rejected as exc:
check(str(exc) == code)
else:
raise AssertionError('invalid input accepted')
result = review(BASE, PATCH)
check(result == json.loads((HERE / 'expected.json').read_text()))
check(result['candidate'] == EXPECTED)
check(result['review']['before'] == ['calendar-demo'])
check(result['review']['after'] == ['calendar-demo', 'fixture-reader'])
check(result['review']['action'] == 'add')
check(result['review']['args_count'] == 6)
check(result['review']['client_validated'] is False)
check(result['review']['server_started'] is False)
check((HERE / 'base.json').read_bytes() == BASE)
check((HERE / 'patch.json').read_bytes() == PATCH)
check(review(raw(EXPECTED), PATCH)['review']['action'] == 'unchanged')
check(review(raw(EXPECTED), PATCH)['candidate'] == EXPECTED)
reject(BASE, b'{"mcpServers":{},"mcpServers":{}}', 'duplicate-key')
reject(BASE, b'{"mcpServers":{"a":{"command":"x","command":"y","args":[]}}}', 'duplicate-key')
reject(b'{"mcpServers":{},"x":NaN}', PATCH, 'non-json-number')
reject(b'{"mcpServers":[]}', PATCH, 'base-shape')
reject(BASE, b'[]', 'patch-shape')
reject(BASE, b'{"mcpServers":{},"editor":{}}', 'patch-shape')
reject(BASE, b'{"mcpServers":{}}', 'one-server-required')
reject(BASE, b'{"mcpServers":{"a":{},"b":{}}}', 'one-server-required')
reject(BASE, b'{"mcpServers":{"bad name":{"command":"x","args":[]}}}', 'server-name')
reject(BASE, b'{"mcpServers":{"x":{"url":"https://example.invalid"}}}', 'local-stdio-subset-only')
reject(BASE, b'{"mcpServers":{"x":{"command":" ","args":[]}}}', 'command-string')
reject(BASE, b'{"mcpServers":{"x":{"command":"x","args":"one two"}}}', 'args-string-array')
reject(BASE, b'{"mcpServers":{"x":{"command":"x","args":[3]}}}', 'args-string-array')
reject(BASE, b'{"mcpServers":{"x":{"command":"x","args":["\\u0000"]}}}', 'nul-character')
reject(BASE, b'{bad', 'invalid-json')
reject(BASE, b'\xff', 'invalid-json')
conflict = copy.deepcopy(EXPECTED)
conflict['mcpServers']['fixture-reader']['args'] = ['old.py']
reject(raw(conflict), PATCH, 'name-conflict')
# Recovery: explicitly choose a distinct name; the older service is preserved.
renamed = {'mcpServers': {'fixture-reader-v2': EXPECTED['mcpServers']['fixture-reader']}}
recovered = review(raw(conflict), raw(renamed))
check(recovered['candidate']['mcpServers']['fixture-reader']['args'] == ['old.py'])
check(recovered['review']['after'] == ['calendar-demo', 'fixture-reader', 'fixture-reader-v2'])
# Two deliberately defective implementations must disagree with the fixed oracle.
shallow = json.loads(BASE)
shallow.update(json.loads(PATCH))
check(shallow != EXPECTED)
check('calendar-demo' not in shallow['mcpServers'])
broken_args = ' '.join(EXPECTED['mcpServers']['fixture-reader']['args']).split()
check(broken_args != EXPECTED['mcpServers']['fixture-reader']['args'])
# Run only OUR fixed probe with the CURRENT Python, never the config's command.
received = subprocess.run(
[sys.executable, str(HERE / 'probe.py'), *result['candidate']['mcpServers']['fixture-reader']['args'][1:]],
check=True, capture_output=True, text=True, shell=False, timeout=5)
check(json.loads(received.stdout) == ['/tmp/mcp demo', '', '*.txt', '${DEMO_DIR}', '中文'])
# CLI failure: no successful result on stdout and no source modification.
failed = subprocess.run([sys.executable, str(HERE / 'config_merge.py'),
str(HERE / 'base.json'), str(HERE / 'base.json')],
capture_output=True, text=True, timeout=5)
check(failed.returncode == 2)
check(failed.stdout == '')
check(json.loads(failed.stderr) == {'error': 'patch-shape'})
check((HERE / 'base.json').read_bytes() == BASE)
check(result['review']['base_sha256'] == '842465074e34be4aa6a0b9ddf15c68edc5c06faa4de03c2b7526a6fb2f7dc9d1')
check(result['review']['patch_sha256'] == 'fdce45c6266cff4ebfe198d579c45822d5090aba511053f273961bdcc3b1e0f3')
success = subprocess.run([sys.executable, str(HERE / 'config_merge.py'),
str(HERE / 'base.json'), str(HERE / 'patch.json')],
capture_output=True, text=True, timeout=5)
check(success.returncode == 0)
check(success.stderr == '')
check(json.loads(success.stdout)['candidate'] == EXPECTED)
# Additional boundaries and full-object recovery oracle.
reject(b'{"mcpServers":{},"n":1e999}', PATCH, 'number-range')
reject(b' ' * 65537, PATCH, 'input-too-large')
reject(b'[' * 33 + b']' * 33, PATCH, 'input-too-deep')
reject(b'{"mcpServers":{},"x":{"k":1,"k":2}}', PATCH, 'duplicate-key')
unknown = json.loads(BASE)
unknown['vendor'] = {'enabled': False, 'items': [None, 7, 'literal'], 'nested': {'brace': '{["'}}
unknown['mcpServers']['opaque'] = {'unknown': [1, 'x']}
unknown_expected = copy.deepcopy(EXPECTED)
unknown_expected['vendor'] = copy.deepcopy(unknown['vendor'])
unknown_expected['mcpServers']['opaque'] = {'unknown': [1, 'x']}
check(review(raw(unknown), PATCH)['candidate'] == unknown_expected)
recovery_expected = copy.deepcopy(conflict)
recovery_expected['mcpServers']['fixture-reader-v2'] = copy.deepcopy(EXPECTED['mcpServers']['fixture-reader'])
check(recovered['candidate'] == recovery_expected)
print(f'PASS {COUNT} fixed checks; 2 defective merge strategies rejected; argv probe matched')
mutation_test.py:实际改坏实现后重跑测试
"""Run deliberately broken copies in disposable folders; never edit the baseline."""
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
HERE = Path(__file__).resolve().parent
source = (HERE / 'config_merge.py').read_text()
mutations = {
'replace-service-map': ("candidate['mcpServers'][name] = copy.deepcopy(spec)", "candidate['mcpServers'] = {name: copy.deepcopy(spec)}"),
'split-argv': ("candidate['mcpServers'][name] = copy.deepcopy(spec)", "candidate['mcpServers'][name] = copy.deepcopy(spec)\n candidate['mcpServers'][name]['args'] = ' '.join(args).split()"),
'allow-duplicate-keys': ("if key in out:", "if False and key in out:"),
'overwrite-conflict': ("if name in existing and existing[name] != spec:", "if False and name in existing and existing[name] != spec:"),
}
results = []
for name, (old, new) in mutations.items():
if source.count(old) != 1:
raise RuntimeError('mutation anchor is not unique: ' + name)
mutated = source.replace(old, new)
compile(mutated, name, 'exec')
with tempfile.TemporaryDirectory(prefix='merge-negative-') as folder:
target = Path(folder)
for file in ('test_merge.py', 'base.json', 'patch.json', 'expected.json', 'probe.py'):
shutil.copyfile(HERE / file, target / file)
(target / 'config_merge.py').write_text(mutated)
for flags in ([], ['-O']):
run = subprocess.run([sys.executable, *flags, str(target / 'test_merge.py')], capture_output=True, text=True, timeout=20)
if run.returncode != 1 or 'AssertionError' not in run.stderr:
raise RuntimeError('mutant survived or failed unexpectedly: ' + name + '\n' + run.stderr)
results.append({'mutation': name, 'mode': '-O' if flags else 'normal', 'result': 'rejected by test assertion'})
print(json.dumps(results, indent=2))
运行与实测结果
python3 test_merge.py python3 -O test_merge.py PASS 50 fixed checks; 2 defective merge strategies rejected; argv probe matched PASS 50 fixed checks; 2 defective merge strategies rejected; argv probe matched
四种变异的实际运行记录
[
{
"mutation": "replace-service-map",
"mode": "normal",
"result": "rejected by test assertion"
},
{
"mutation": "replace-service-map",
"mode": "-O",
"result": "rejected by test assertion"
},
{
"mutation": "split-argv",
"mode": "normal",
"result": "rejected by test assertion"
},
{
"mutation": "split-argv",
"mode": "-O",
"result": "rejected by test assertion"
},
{
"mutation": "allow-duplicate-keys",
"mode": "normal",
"result": "rejected by test assertion"
},
{
"mutation": "allow-duplicate-keys",
"mode": "-O",
"result": "rejected by test assertion"
},
{
"mutation": "overwrite-conflict",
"mode": "normal",
"result": "rejected by test assertion"
},
{
"mutation": "overwrite-conflict",
"mode": "-O",
"result": "rejected by test assertion"
}
]
失败到恢复:同名冲突应由人命名,不能靠覆盖消失
测试中先构造一个已有 fixture-reader、args 为 ["old.py"] 的基础配置;再提交本文新片段,必须得到 name-conflict。修复由操作者明确选择新名字 fixture-reader-v2,保持旧服务原样。测试断言修复后的三个名字为 calendar-demo、fixture-reader、fixture-reader-v2,并且旧 fixture-reader 仍指向 old.py。这里的恢复是重新形成可审阅候选,未在任何客户端增删服务。
若 duplicate-key 失败,回到原始文本找到两处定义,人工决定保留哪份;不要先用普通 json.loads 再导出来“清理”,那会在你看见冲突之前丢掉一份内容。若 args-string-array 失败,恢复逐项数组;不要对整条字符串自动 split。若客户端实际连不上,回到程序路径、运行目录、客户端格式与初始化阶段分别取证,不能用本工具 PASS 替代连接证据。
真正需要替换旧服务时,本练习的新增合同不适用。先在目标客户端核对配置作用域和实际生效项,准备旧值备份与单项替换方案,由操作者审阅后处理。多个作用域可能出现相同服务名,本工具只看两份输入文件,不能判断哪一个定义最终生效。
conflict-base.json
{
"mcpServers": {
"calendar-demo": {
"type": "http",
"url": "https://example.invalid/mcp"
},
"fixture-reader": {
"command": "python3",
"args": [
"old.py"
]
}
},
"editor": {
"theme": "dark"
}
}
conflict-result.json
{
"exit_code": 2,
"stdout": "",
"stderr": "{\"error\": \"name-conflict\"}\n"
}
renamed-patch.json
{
"mcpServers": {
"fixture-reader-v2": {
"command": "python3",
"args": [
"probe.py",
"/tmp/mcp demo",
"",
"*.txt",
"${DEMO_DIR}",
"\u4e2d\u6587"
]
}
}
}
recovered.json
{
"review": {
"action": "add",
"server": "fixture-reader-v2",
"before": [
"calendar-demo",
"fixture-reader"
],
"after": [
"calendar-demo",
"fixture-reader",
"fixture-reader-v2"
],
"args_count": 6,
"base_sha256": "2c4ce1b241a56484ad9999b9068883536cb18d55b07d764ee3e63bf767478eb7",
"patch_sha256": "b20716b463dca2982171e002ff105883490dc29657b0a8e6396691189dbbe538",
"client_validated": false,
"server_started": false
},
"candidate": {
"mcpServers": {
"calendar-demo": {
"type": "http",
"url": "https://example.invalid/mcp"
},
"fixture-reader": {
"command": "python3",
"args": [
"old.py"
]
},
"fixture-reader-v2": {
"command": "python3",
"args": [
"probe.py",
"/tmp/mcp demo",
"",
"*.txt",
"${DEMO_DIR}",
"\u4e2d\u6587"
]
}
},
"editor": {
"theme": "dark"
}
}
}
资料依据与实测边界
Python JSON 文档:重复名称说明默认解析保留最后一个同名值,可用 object_pairs_hook 改变处理;本例据此实现重复键拒绝。Python subprocess 参数文档说明序列参数与平台差异;本例只对当前 Linux 上的固定 Python 探针给出实测结论。
Claude Code MCP 官方说明区分 mcpServers 包装与单服务 add-json 对象,另有配置作用域和状态检查。这说明“JSON 能解析”与“客户端接受且已连接”是不同验收步骤;本文不执行其中的导入命令。以上资料查阅日期为 2026-10-14,页面会更新。
实际完成:Python 离线合并、50 个固定检查、两种故障策略负对照、四种实际源码变异的双模式运行、一次固定本地参数探针、CLI 成功与失败输出。合成内容:全部配置、路径、服务名和输入。未完成或未声称:本页浏览器新测试、配置下载/导入、MCP 服务启动、协议初始化、工具发现、实际目录授权、OAuth、真实凭据处理、生产回滚及跨平台验证。
常见问题
生成后会启动 MCP 服务吗?
工具只生成配置文本。服务是否能启动、目录是否存在,以及客户端怎样加载配置,都需要在实际环境验证。