在 CLI Agent 里挂 MCP server
在终端 Agent 里挂 MCP server 的两种方式 —— claude mcp add 命令 vs settings.json 配置文件,含 stdio 与远程 server 的踩坑要点
理解 Claude Code 的 settings 三层模型(用户级 / 项目级 / local)的合并规则、覆盖优先级与最常踩的坑
内容摘要
Claude Code 的所有配置——能允许哪些工具、敏感目录要不要拦、用哪个模型、要不要挂钩子、要不要自动加 `Co-Authored-By`——最终都汇聚到一个名字叫 `settings.json` 的 JSON 文件里。但这个文件不止一份。Claude Code 把它**沿用户、项目、个人覆盖三层叠起来**,叫法不严格统一,本卡用三个固定的中文称呼:
Claude Code 的所有配置——能允许哪些工具、敏感目录要不要拦、用哪个模型、要不要挂钩子、要不要自动加 Co-Authored-By——最终都汇聚到一个名字叫 settings.json 的 JSON 文件里。但这个文件不止一份。Claude Code 把它沿用户、项目、个人覆盖三层叠起来,叫法不严格统一,本卡用三个固定的中文称呼:
~/.claude/settings.json。<repo>/.claude/settings.json。<repo>/.claude/settings.local.json。在团队 / 企业场景之上,还有一层 企业 managed settings,由 IT 推下来强制生效,普通用户不能改、也不能覆盖;放在 macOS 的 /Library/Application Support/ClaudeCode/managed-settings.json、Linux 的 /etc/claude-code/managed-settings.json、Windows 的 C:\Program Files\ClaudeCode\managed-settings.json(也支持 MDM / 注册表)。本卡聚焦个人/团队最常打交道的那三层,企业 managed 只在"覆盖优先级"那张图里提一下。
为什么要分这么多层?核心动机就一个:让"团队约定"和"个人偏好"互不打架,又能互相补强。比如团队希望所有人都禁止 curl 工具直接访问外网(写在项目级),但你个人喜欢用 iTerm2 + acceptEdits 模式跑得更顺手(写在 local),而你所有项目都希望默认显示中文(写在用户级)。三层叠起来,互相不冲突。
阅读这张图的两条规则:
model)由高优先级的那层赢;对象字段(如 env)逐键合并;数组字段(如 permissions.allow / permissions.deny)会被拼接 + 去重,不是覆盖。这一点最容易踩坑,单独抽出来在"常见误区"里讲。| 要素 | 三层各自的位置 | 适合放什么 |
|---|---|---|
| 用户级 | ~/.claude/settings.json | 跨项目通用偏好:默认 model、outputStyle、includeCoAuthoredBy、个人想全局放行的 permissions.allow(比如 Bash(ls *)) |
| 项目级 | <repo>/.claude/settings.json | 团队约定:项目硬性 permissions.deny(拦 .env、拦 curl)、项目的 hooks、绑定的 model、是否自动信任 .mcp.json |
| local 级 | <repo>/.claude/settings.local.json | 个人临时放行:试某个新工具时的一次性 allow、本地特殊路径(Read(/Volumes/...))、个人想覆盖团队默认的 defaultMode |
| 企业 managed | /Library/.../managed-settings.json 等 | 公司硬性策略:白名单模型 availableModels、强制登录方式 forceLoginMethod、allowManagedHooksOnly |
# 用户级(全局,所有项目共用)
ls -la ~/.claude/settings.json
# 项目级(要在仓库根目录跑)
ls -la .claude/settings.json
# local 级(同样在仓库根目录)
ls -la .claude/settings.local.json
如果文件不存在很正常——Claude Code 第一次需要写入时会自动创建。.claude/settings.local.json 在被首次创建时,Claude Code 会自动把它追加到项目的 .gitignore,不需要你手动 ignore,但养成检查习惯没坏处:
grep -n "settings.local.json" .gitignore || echo "未找到,建议手动加一行 .claude/settings.local.json"
进 Claude Code 内部,输入:
/status
/status 会列出所有 settings 来源(包括是否有企业 managed),帮你确认哪一层在生效。要交互式调配置则用:
/config
/config 提供菜单式编辑,所有改动落到对应的 JSON 文件里。
示例 A:用户级(所有项目通用偏好)
// ~/.claude/settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "claude-sonnet-4-6",
"outputStyle": "concise-zh",
"permissions": {
"allow": [
"Bash(ls *)",
"Bash(cat *)",
"Read(~/.zshrc)"
],
"defaultMode": "default"
},
"attribution": {
"commit": "Co-Authored-By: Claude <[email protected]>"
}
}
示例 B:项目级(团队共享,进 git)
// <repo>/.claude/settings.json
{
"permissions": {
"deny": [
"Bash(curl *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
],
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(npm run build)"
]
},
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "scripts/log-bash.sh" }] }
]
},
"enabledMcpjsonServers": ["filesystem", "github"]
}
示例 C:local 级(仅你这台机器,不进 git)
// <repo>/.claude/settings.local.json
{
"permissions": {
"allow": [
"Bash(docker compose *)",
"Read(/Volumes/external-dataset/**)"
],
"defaultMode": "acceptEdits"
},
"env": {
"MY_LOCAL_DEBUG_FLAG": "1"
}
}
合并以后实际生效:
model 取用户级的 claude-sonnet-4-6(项目和 local 都没改)。permissions.deny 完全保留项目级那 4 条(用户和 local 都没写 deny)。permissions.allow = 用户的 3 条 + 项目的 3 条 + local 的 2 条,8 条全部生效,去重之后即是结果。defaultMode = local 的 acceptEdits 赢(覆盖了用户级的 default)。env.MY_LOCAL_DEBUG_FLAG=1 生效。不同终端 Agent 都有自己的"全局 vs 项目"配置模型。理解差异有助于在多工具切换时不踩坑:
| 维度 | Claude Code | Codex CLI(OpenAI) | Gemini CLI(Google) | Cursor |
|---|---|---|---|---|
| 配置文件名 | settings.json | config.toml | settings.json | .cursor/rules/*.mdc + Settings UI |
| 用户级路径 | ~/.claude/settings.json | ~/.codex/config.toml | ~/.gemini/settings.json | Cursor 应用内 Settings → User Rules |
| 项目级路径 | <repo>/.claude/settings.json | <repo>/.codex/config.toml(会向上递归查找) | <repo>/.gemini/settings.json | <repo>/.cursor/rules/*.mdc |
| 个人/local 层 | ✅ settings.local.json(自动 gitignore) | ⚠️ 只能用 -c 命令行覆盖或额外 profile | ❌ 没有独立 local 文件 | ❌ 无独立 local 概念 |
| 企业/managed 层 | ✅ macOS plist / Linux /etc / Windows 注册表 | ✅ requirements.toml(强约束)+ managed_config.toml(默认值) | ✅ system overrides 作为最高优先级 | ✅ Team Rules(仅 Team/Enterprise) |
| 数组字段合并行为 | 拼接 + 去重 | 同名键由优先级高的覆盖(按 toml key) | 数组合并、mcpServers 同名取高优 | 规则文件叠加进 system prompt |
| 项目层是否受信任开关保护 | ❌ 默认信任 | ✅ 项目未 trust 时跳过 .codex/ 层 | ⚠️ 取决于工作区信任 | ⚠️ 取决于工作区信任 |
核心区别用一句话说:Claude Code 是"个人 / 团队 / 个人覆盖团队"三明治结构,且 local 层默认 gitignore;Codex 没有专门 local 层,靠 profile + -c 替代;Gemini 走"系统覆盖一切"路线,企业管控更强;Cursor 没有标量 settings 合并问题,规则全部叠加进 prompt。
迁移建议:从 Cursor 转 Claude Code 的人最容易把所有规则塞进项目级,忘了 local;从 Codex 转过来的人则容易忽略"数组合并 vs 字段覆盖"这条差异。
| 误区 | 准确理解 |
|---|---|
| 以为项目级和用户级是"二选一"——配了项目就不读用户 | 三层一直都在叠加。即便项目级写了 model,用户级里的 outputStyle、env、hooks 仍然生效,只是同名字段被项目级覆盖 |
以为 permissions.allow 在项目级写一遍就会覆盖用户级 | 数组字段是"拼接 + 去重",不是覆盖。要"撤销"用户级的某项 allow,只能用项目级的 permissions.deny,因为 deny > allow |
把敏感个人路径(如 Read(/Volumes/work-secrets/**))写到项目级 | 项目级会进 git,团队人手一份。涉及个人本地路径、机器特有目录、临时实验放行,统统写到 settings.local.json |
以为 .claude/settings.local.json 必须自己手动 gitignore | Claude Code 在首次创建该文件时会自动追加到 .gitignore。但接手已有仓库时仍建议 grep 一下确认,避免历史 commit 残留 |
用 claude config 改完之后找不到改了哪一层 | 没有指定 scope 时默认改用户级。要指定具体层,需要直接编辑文件,或在 /config 菜单里看清"Source"列。/status 是排错神器 |
| 把 hooks 写到 local 级,期望队友也能用 | local 不进 git,队友拉不到。团队级 hooks 必须写项目级 .claude/settings.json,local 只用来个人临时挂钩 |
以为修改用户级 ~/.claude/settings.json 立刻全局生效 | Claude Code 进程启动时读取一次,热重载支持有限。改完关键字段(permissions / hooks / model)建议重启 Claude Code 或重新进入 session |
| 把企业 managed 当成"高级用户级" | managed 是强制天花板,普通 settings 改不动。如果 IT 在 managed 里写了 availableModels: ["claude-haiku-4-5"],你在用户级写 model: "claude-opus-4-7" 也启动不了 |
| 优势 | 劣势 |
|---|---|
| 三层结构刚好对应"个人 / 团队 / 例外",覆盖了真实协作场景 | 三层都可能有同名字段,新手容易迷失"现在到底哪层在生效",需要 /status 才能确认 |
| 数组拼接 + 去重的合并策略,让团队和个人各加各的 allow,互不删除对方 | 数组合并机制不直观——想"用项目级关掉用户级的某条 allow"做不到,只能 deny 反制 |
settings.local.json 自动 gitignore,避免最常见的"个人路径泄露到 git"事故 | 自动追加 .gitignore 只在首次创建时执行,接手历史仓库要自己检查 |
| 企业 managed 层支持 MDM/plist/注册表/远程下发,大企业可以做到强治理 | 企业 managed 一旦覆盖,普通用户完全无法看到自己被加了什么策略,排错全靠 /status |
JSON Schema 已上线(https://json.schemastore.org/claude-code-settings.json),编辑器有补全和校验 | settings 字段在快速演进,includeCoAuthoredBy 已被 attribution 替代,跟不上版本就会写错配置 |
| 跨项目偏好(用户级)和项目约定(项目级)独立演进,切项目时模型/语言不会被重置 | local 级不会自动同步到任何地方,换电脑要手动迁移 |
参考答案:
能。数组字段是合并不是覆盖,所以最终生效的 permissions.allow 是:
["Bash(npm run lint)", // 来自用户级
"Bash(npm run *)"] // 来自项目级
通配符 Bash(npm run *) 已经包含了 npm run test,所以放行。
需要警惕的反向场景:如果项目级写了 permissions.deny: ["Bash(curl *)"],而你在用户级写了 permissions.allow: ["Bash(curl *)"],deny 永远赢 allow,所以你这台机器上的 curl 仍然会被拦——这就是为什么团队约定优先放在 deny 而不是删 allow。
参考答案:
最佳实践:放在该仓库的 .claude/settings.local.json。理由有三层:
git push origin main 都会被放行,等于绕过了团队约定在所有项目上的兜底。.claude/settings.json 通常会写 permissions.deny: ["Bash(git push origin main)"],而 deny 优先级高于 allow,所以 local 级的 allow 反而会被项目级 deny 压住。正确做法是和团队沟通后,在项目级用更细粒度的规则,比如:
// 项目级 deny 改为更精确的形式,给"机器人 user 名"开一个口子
{
"permissions": {
"deny": ["Bash(git push origin main)"],
"allow": ["Bash(GIT_AUTHOR_NAME=release-bot git push origin main)"]
}
}
或者使用 hooks 在 PreToolUse 阶段动态判断,这部分见 /cli/cards/lifecycle-hooks。
结论一句话:local 级解决"我个人例外",但当例外和团队 deny 冲突时,必须回到项目级用更精确的 allow 规则,而不是去 local 里偷偷绕过。
优先展示同分类且标签更接近的内容,方便继续串联学习。
在终端 Agent 里挂 MCP server 的两种方式 —— claude mcp add 命令 vs settings.json 配置文件,含 stdio 与远程 server 的踩坑要点
用 CLI Agent 跑"红 - 绿 - 重构"循环:先写测试 → 让 Agent 写实现 → 自动跑测试 → 验证通过 → 重构的完整工作流
Anthropic 官方终端 Agent,原生支持工具调用、MCP、Skills 与 Hooks,2026 年生态最完整的 CLI Agent