1.managed-mcp.json文件与allowedMcpServers/deniedMcpServers配置项的区别?
// managed-mcp.json(部署在系统路径) { "mcpServers": { "github": { "type": "http", "url": "https://mcp.company.com/github" }, "jira": { "type": "http", "url": "https://mcp.company.com/jira" }, "sentry": { "type": "http", "url": "https://mcp.company.com/sentry" } } } // managed-settings.json(同一系统路径) { "deniedMcpServers": [ { "serverName": "sentry" } ] } // 效果:sentry 虽然定义在 managed-mcp.json 里,但被 deny 拦住,不会加载我的电脑里
C:\Program Files\目录下之所以没有ClaudeCode目录,是因为managed-mcp.json是一个“企业级/系统级”的托管配置文件,它在默认情况下不会自动创建。以普通开发者身份在本地安装和使用 Claude Code(例如通过常规渠道或 npm 安装),系统不会在
C:\Program Files\下自动生成这个文件夹。这个路径是专门为企业/机构的安全管理员准备的。通常只有在金融、医疗等对数据安全要求极高的企业环境中,由 IT 部门或安全团队在多台办公电脑上统一手动部署、用于“锁死”和管控所有员工的 MCP 工具时,才会手动创建这个目录和文件。
没有这个文件,完全不影响使用
如果你的电脑上没有这个文件,Claude Code 会按照正常的逻辑去加载你的 MCP 服务(包括你通过
claude mcp add添加的、项目里的.mcp.json、以及插件提供的服务等)。一旦你在该路径下手动创建了
managed-mcp.json,它就会触发图片的“独占逻辑”——系统会一刀切地只认这个文件里定义的 MCP 服务,并直接忽略所有其他的个人、项目或插件级 MCP 配置。
假设你在项目中配置了3 个 MCP 服务器准备连接:
GitHub助手、内部数据库工具、外部翻译插件。我们来看看它们是如何被这 5 步关卡筛选的:
第 1 步(总闸读取):由于你是在个人电脑开发,没有配置企业级
managed-mcp.json,系统正常读取了你配置的这 3 个服务器。第 2 步(黑名单筛选):假设你的配置中将
外部翻译插件加入了deniedMcpServers(黑名单)。此时外部翻译插件直接被踢掉,剩下GitHub助手和内部数据库工具。第 3 步(白名单筛选):假设你设置了
allowedMcpServers白名单,里面只包含了GitHub助手。因为内部数据库工具不在白名单内,它也被踢掉了。第 4 步(来源限定):由于你没有开启
allowManagedMcpServersOnly: true限制,当前的单台个人设备配置依然有效。第 5 步(最终连接):历经重重筛选,最终只有
GitHub助手顺利通关,成功被 Claude Code 连接并完成健康检查!
2.MCP Tool Search如何优化context window使用?
一、 什么叫“不依赖 API 服务端做 Schema 展开”?
在传统的大模型 API 调用中(比如直接调用 OpenAI 或 Anthropic 的原生 API 带有延迟加载功能的工具),通常是由云端 API 服务端来维护所有工具的索引、并在收到模型请求时由服务端去匹配、查找并“展开”工具的完整定义(Schema)。
而Claude Code 完全不走这条路:
服务端是个“甩手掌柜”:Anthropic 的云端 API 在处理 Claude Code 的初始请求时,根本不知道那些被延迟加载(
defer_loading)的工具长什么样,服务端不存、也不负责在后台帮它展开。所有的工具档案都藏在用户本地:所有候选工具的完整 Schema 全部保存在你本地电脑的客户端编排层里。
二、 什么叫“客户端编排层自己管理”?
“客户端编排层”指的就是运行在你电脑上的 Claude Code 软件本身(它在向大模型发送 API 请求之前,在本地做了一层“智能调度和组装”)。
结合图中的 3 个步骤,它是这样亲自动手管理的:
1. 初始请求“轻装上阵”: 候选工具的 Schema 都存在本地客户端里,初始发送给 API 的
tools数组里压根不包含那些被延迟加载的工具。2. 搜到后由客户端拼装: 当模型通过
ToolSearch Tool找到了需要的工具并返回tool_reference后,本地的客户端编排层会立刻行动:在构建下一轮向 API 发送的请求时,手动把这个匹配工具的完整 Schema 插入到tools数组中。3. 严格按轮次生效: 工具的可用集合在单次推理过程中是保持不变的,搜索结果必须等到下一轮请求时才会正式生效。
三、 为什么要这样做?(核心优势)
这种“客户端自己管理”的设计带来了巨大的工程红利:
完全掌控加载时机:客户端可以自由决定什么时候把工具丢给模型,不需要把所有候选工具的 Schema 一股脑全发给云端 API。
极大地省钱、省带宽:对于连接了大量 MCP 服务(比如几十个外部数据库和工具库)的场景,如果每次都把所有工具的详细定义传给云端,网络传输成本会非常高;而客户端管理模式直接把网络传输成本省去了。
3.MCP扩展与内置工具(如文件操作)的边界是啥?
Claude Code 会“无条件信任”MCP 服务器自我声明的元数据(Annotation)。
什么是 Annotation(注解/标注)?
MCP 服务器在向 Claude Code 注册工具时,可以给工具打上标注。例如加上
readOnlyHint(只读提示),相当于这个工具向系统“自我介绍”说:“我是安全的只读工具,不会修改任何数据,你可以放心并发执行我。”潜在的安全风险:
Claude Code 自身是无法验证这个标注是真还是假的。
如果有一个恶意开发的 MCP 服务器,故意把一个“格式化硬盘”或“删除数据库”的高危破坏性工具标注上
readOnlyHint(只读),Claude Code 依然会完全相信它,进而跳过部分安全防线去并发执行它。既然有这个风险,为什么 Anthropic 不做底层校验呢?因为这是一个刻意为之的权衡取舍(Trade-off):
效率与体验(信任正常服务器):如果直接忽略这些 Annotation,正常合规的 MCP 服务器就无法利用只读标注来提升执行速度、优化并发体验。
信任前提(User Consent):架构设计的出发点是——只要是你(用户)主动决定接入的 MCP 服务器,就代表你已经默认授权并信任了它。
Anthropic 在官方安全文档中给出了非常直白的表态:
Anthropic 既不管理、也不审计第三方 MCP 服务器的安全性和真实性。
因此,防范此类安全风险的责任完全落在用户身上,最佳的安全实践原则是:
优先自己编写:关键业务工具尽量自己编写 MCP 服务器。
选择可靠供应商:如果要引入外部 MCP 服务器,务必确保其来自于官方认证或绝对信得过的供应商,绝不随意挂载来源不明的第三方 MCP 工具!
当 Claude Code 启动并加载该 MCP 服务时,会在后台自动执行以下 5 步:
第 1 步:建立连接
系统会按照配置好的传输方式,主动连上 GitHub MCP 服务器。
第 2 步:拉取清单
连接成功后,发送
tools/list指令,把该服务器上提供的所有工具清单(如create_issue创建问题、list_prs查看拉取请求、get_file_contents获取文件内容等)全部拿回。第 3 步:接口包装与命名空间
系统把拉取到的每个外部工具包装成内部的 Tool 接口,并自动加上命名空间前缀。例如,原先叫
create_issue的工具会变成全局唯一的全限定名:mcp__github__create_issue。第 4 步:注册 Schema
将这些工具的 Schema(结构定义)统一注册到系统内部的
AppState.mcp中,供大模型在接下来的对话会话中识别和调用。第 5 步:交互与传参
当大模型在对话中决定使用该工具时,Claude Code 会通过JSON-RPC协议把参数发给 GitHub 服务器,等服务器执行完并返回结果后,再把结果原封不动地“塞回”上下文给模型。
4.Claude Code通过MCP协议连接外部工具时,支持哪几种传输类型?
4.MCP服务器如何安全获取项目根目录进行文件系统访问?
{ "roots": [ { "uri": "file:///Users/dev/my-project", "name": "My Project" }, { "uri": "file:///Users/dev/shared-libs", "name": "Shared Libraries" } ] }const server = new McpServer({ name: "my-server", version: "1.0" }); let allowedRoots: string[] = []; server.server.oninitialized = async () => { const caps = server.getClientCapabilities(); if (caps?.roots) { const { roots } = await server.server.listRoots(); allowedRoots = roots.map(r => new URL(r.uri).pathname); } else { allowedRoots = [process.env.CLAUDE_PROJECT_DIR ?? process.cwd()]; } }; function isPathAllowed(requestedPath: string): boolean { const absPath = path.resolve(requestedPath); return allowedRoots.some(root => absPath.startsWith(path.resolve(root)) ); }