
1. 项目概述为什么MCP突然火了最近在技术社区和面试准备中一个词的出现频率越来越高MCP全称 Model Context Protocol。如果你正在关注大模型应用开发或者准备相关岗位的面试却对这个概念感到既熟悉又陌生那你来对地方了。简单来说MCP 是一个旨在解决大模型与外部工具、数据源之间“连接”问题的标准化协议。它不是某个具体的工具或框架而是一套“通信规则”。想象一下你开发了一个强大的 AI 助手你想让它能帮你查天气、读数据库、操作文件系统。传统做法是你需要为每个功能写一套特定的代码告诉模型如何调用这些 API处理各种不同的数据格式和错误。这个过程繁琐、不通用且难以维护。MCP 的出现就是为了定义一套统一的“语言”让大模型如 Claude、GPT能够以一种标准化的方式发现、描述并调用任何外部资源服务器、工具、数据源而无需为每个资源编写特定的适配代码。这就像为互联网世界定义了 HTTP 协议一样MCP 试图为大模型的“工具使用”世界建立一个通用的通信基础。从面试角度看理解 MCP 意味着你抓住了当前大模型应用架构的一个核心演进方向从封闭的、定制化的智能体转向开放的、可插拔的智能体平台。它考察的不仅是协议本身更是你对大模型能力边界、扩展性设计以及未来生态发展的思考。2. MCP 核心概念与架构拆解要理解 MCP不能只停留在“协议”这个词上。我们需要把它拆解成几个核心组成部分看看它到底是如何工作的。2.1 核心角色Server, Client 与 ResourcesMCP 的架构非常清晰主要包含三个角色MCP 服务器 (Server)这是提供能力的“服务方”。一个 MCP 服务器可以暴露多种“资源”给模型使用。这些资源可以是工具 (Tools)可执行的操作例如“搜索网络”、“执行SQL查询”、“发送邮件”。每个工具都有名称、描述、输入参数定义。提示词模板 (Prompts)预定义的、参数化的提示词片段模型可以调用并填充具体参数用于生成更精准的指令或内容。数据资源 (Resources)只读的数据源例如一个数据库表的模式定义、一份项目文档、一个实时股票行情数据流。它们以 URI 的形式标识内容可以是文本、JSON 或其他格式。MCP 客户端 (Client)这是消费能力的“调用方”。通常这就是我们使用的 AI 应用平台本身例如 Claude Desktop、Cursor IDE或者任何集成了 MCP 客户端库的应用。客户端的核心职责是连接并管理一个或多个 MCP 服务器。将服务器提供的资源列表“呈现”给内部的大模型。接收模型的指令将其转化为对特定服务器工具的调用并将结果返回给模型。处理资源如提示词、数据的读取请求。大模型 (LLM)虽然模型本身不是 MCP 协议的直接参与方但它是整个流程的“大脑”。客户端将 MCP 服务器提供的工具和资源描述作为上下文Context的一部分提供给模型。模型根据用户的问题决定是否需要、以及如何调用这些工具并生成包含工具调用请求的回复。这个架构的关键在于“解耦”。工具提供者Server和工具消费者Client/LLM通过 MCP 这个标准接口连接彼此独立演化。开发者可以专注于编写好用的工具服务器而 AI 应用开发者则可以轻松集成海量工具无需关心其内部实现。2.2 协议基石SSE 与 JSON-RPCMCP 不是一个全新的网络协议它巧妙地建立在现有成熟技术之上这降低了实现和使用的门槛。通信层SSE (Server-Sent Events)MCP 服务器和客户端之间通常使用 SSE 进行通信。这是一种基于 HTTP 的轻量级协议允许服务器主动向客户端推送数据流。对于 MCP 来说这非常合适因为服务器需要主动通知客户端新资源的可用性例如文件系统监视器发现新文件而客户端也需要向服务器发送请求并接收异步响应。相比 WebSocketSSE 更简单天然支持 HTTP 生态对于工具调用这种请求-响应模式为主辅以少量服务器推送的场景是更优雅的选择。消息层JSON-RPC 2.0在 SSE 的数据流中传输的具体消息格式遵循 JSON-RPC 2.0 规范。这是一个非常轻量级的远程过程调用协议。每一条消息都是一个 JSON 对象包含jsonrpc: “2.0”、method方法名、params参数和id请求ID等字段。客户端调用工具时会发送一个request消息method为tools/call。服务器执行工具后会返回一个response消息包含结果或错误信息。服务器也可以主动发送notification消息无id来通知客户端资源列表的变更。这种设计意味着只要你熟悉 HTTP 和 JSON实现或理解一个基本的 MCP 服务器/客户端在技术上并不复杂。协议本身的复杂性被转移到了“资源模型”的定义和语义上。2.3 与相似概念的对比Plugin, Function Calling, Skill面试中常被问到“MCP 和 OpenAI 的 Function Calling、ChatGPT Plugins 有什么区别” 厘清这个概念至关重要。OpenAI Function Calling / Assistant API Tools这是 OpenAI 为其模型定义的一套工具调用规范。它规定了模型如何理解工具、如何输出工具调用请求。然而它没有规定工具从哪里来、如何被发现、如何被管理。它更像是模型侧的“接口定义语言”IDL。MCP 可以看作是这套规范在工具供给和管理层面的补充和标准化。一个 MCP 客户端可以将服务器提供的工具转换成 OpenAI 的 Function Calling 格式再交给 GPT 模型使用。ChatGPT Plugins / GPTs这是 OpenAI 在其 ChatGPT 产品中构建的封闭生态。开发者需要遵循 OpenAI 的审核和发布流程将工具以特定 API 格式描述集成到 ChatGPT 中。这是一个平台锁定的解决方案。MCP 则是一个开放协议任何兼容 MCP 的客户端如 Claude Desktop, Cursor都可以连接任何 MCP 服务器实现了跨平台的工具共享。Claude Skills (已弃用)Anthropic 最初为其 Claude 模型设计了 Skills 系统功能上与早期的插件类似但同样局限于 Claude 生态。MCP 实际上是 Anthropic 推出的、用于取代 Skills 的、更开放的方案。MCP 不是 Claude 专属的它是一个开放标准理论上任何大模型平台都可以采用。简单总结Function Calling 是“怎么叫”MCP 是“叫什么”和“从哪叫”。Plugins/Skills 是“苹果商店”MCP 是“USB-C 接口标准”。3. 从零实现一个简易 MCP 服务器理解了概念最好的巩固方式就是动手实现。我们将用 Node.js 实现一个最简单的 MCP 服务器它提供一个“查询当前时间”的工具和一个“获取服务器信息”的只读资源。3.1 环境准备与项目初始化首先确保你的系统安装了 Node.js (版本 18 或以上)。我们使用modelcontextprotocol/sdk这个官方 SDK它能极大简化开发。# 1. 创建项目目录并初始化 mkdir my-first-mcp-server cd my-first-mcp-server npm init -y # 2. 安装 MCP SDK 和必要的依赖 npm install modelcontextprotocol/sdk创建一个入口文件server.js。3.2 构建服务器骨架与工具定义MCP SDK 采用了基于类Class的清晰结构。我们首先导入 SDK然后创建一个继承自Server的类。// server.js const { Server } require(modelcontextprotocol/sdk/server/index.js); const { StdioServerTransport } require(modelcontextprotocol/sdk/server/stdio.js); const { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } require(modelcontextprotocol/sdk/types.js); class MyFirstMCPServer { constructor() { // 1. 初始化 MCP Server 实例 this.server new Server( { name: my-first-mcp-server, version: 1.0.0, }, { capabilities: { // 声明本服务器支持的能力工具、资源、提示词等 tools: {}, resources: {}, // prompts: {}, // 本例暂不实现提示词 }, } ); // 2. 设置请求处理器 this.setupRequestHandlers(); // 3. 初始化工具和资源列表模拟数据 this.tools [ { name: get_current_time, description: 获取服务器的当前日期和时间, inputSchema: { type: object, properties: { // 这个工具不需要输入参数所以 properties 为空对象 }, }, }, ]; this.resources [ { uri: file:///server/info, name: 服务器信息, description: 关于这个 MCP 服务器的基本信息, mimeType: text/plain, }, ]; } setupRequestHandlers() { // 处理客户端查询工具列表的请求 this.server.setRequestHandler(ListToolsRequestSchema, async () ({ tools: this.tools.map(tool ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, })), })); // 处理客户端查询资源列表的请求 this.server.setRequestHandler(ListResourcesRequestSchema, async () ({ resources: this.resources.map(resource ({ uri: resource.uri, name: resource.name, description: resource.description, mimeType: resource.mimeType, })), })); // 处理客户端调用工具的请求 this.server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; if (name get_current_time) { // 执行工具逻辑 const now new Date(); const timeString now.toLocaleString(zh-CN, { timeZone: Asia/Shanghai, hour12: false, }); return { content: [ { type: text, text: 当前服务器时间北京时间是${timeString}, }, ], }; } // 如果收到未知的工具调用请求抛出错误 throw new Error(未知的工具${name}); }); // 处理客户端读取资源的请求 this.server.setRequestHandler(ReadResourceRequestSchema, async (request) { const { uri } request.params; if (uri file:///server/info) { const infoText 服务器名称${this.server.serverInfo.name}\n版本${this.server.serverInfo.version}\n这是一个用于演示的简易 MCP 服务器。; return { contents: [ { uri: uri, mimeType: text/plain, text: infoText, }, ], }; } throw new Error(资源未找到${uri}); }); } async run() { // 使用标准输入输出stdio作为传输层。 // 这是 MCP 服务器最常见的运行方式由客户端如 Claude Desktop启动并管理其生命周期。 const transport new StdioServerTransport(); await this.server.connect(transport); console.error(MCP 服务器已启动正在通过 stdio 通信...); } } // 启动服务器 const serverInstance new MyFirstMCPServer(); serverInstance.run().catch(console.error);代码解析与注意事项能力声明 (Capabilities)在Server初始化时必须明确声明服务器支持哪些功能tools,resources,prompts。即使初始为空对象也要声明这是协议的要求。请求处理器 (Request Handlers)这是服务器的核心。我们为四种标准请求设置了处理器listTools,listResources,callTool,readResource。SDK 提供了强类型的 Schema 来确保我们处理正确的请求格式。工具定义每个工具都需要name唯一标识符、description供模型理解用途和inputSchema定义输入参数的 JSON Schema。即使没有参数inputSchema也必须是一个有效的 JSON Schema 对象。资源定义资源通过uri唯一标识。mimeType告诉客户端如何解释内容。资源是只读的服务器可以主动通知客户端资源列表的变化本例未演示。传输层 (Transport)StdioServerTransport是最常用的方式。这意味着服务器通过命令行标准输入/输出与客户端通信。客户端如 Claude Desktop会以子进程形式启动这个服务器脚本并通过管道进行通信。这种方式部署简单无需处理网络端口。3.3 配置客户端以 Claude Desktop 为例要让 Claude Desktop 使用我们的服务器需要创建一个配置文件。找到 Claude Desktop 的配置目录macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.jsonLinux:~/.config/Claude/claude_desktop_config.json编辑或创建claude_desktop_config.json文件添加mcpServers配置项{ mcpServers: { my-time-server: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/my-first-mcp-server/server.js ], env: { NODE_ENV: development } } } }重要提示command必须是能在系统 PATH 中找到的命令如node,python。args中的路径必须使用绝对路径。Windows 用户注意路径分隔符\可能需要转义或使用正斜杠/。保存配置文件并完全重启 Claude Desktop 应用不是关闭聊天窗口而是退出整个应用再重新打开。3.4 测试与验证重启 Claude Desktop 后新建一个对话。如果配置成功你应该能在输入框上方或模型回复中看到 Claude 已经识别到了新的工具。你可以尝试提问“现在几点了” 或者 “使用 get_current_time 工具”。Claude 应该会理解你的意图并调用我们编写的服务器工具返回当前时间。你也可以尝试让 Claude “读取服务器信息”它会调用readResource来获取我们定义的file:///server/info资源内容。常见启动问题排查“Failed to start server”检查配置文件 JSON 格式是否正确路径是否存在且为绝对路径Node.js 是否已安装并可在命令行中运行。工具不出现检查 Claude Desktop 是否已完全重启。查看 Claude Desktop 的应用日志通常可在配置目录找到或通过开发者工具查看里面会有 MCP 服务器启动失败的详细错误信息。权限问题确保脚本文件有可执行权限并且 Node 命令可用。4. 深入进阶构建一个实用的文件系统 MCP 服务器一个“查询时间”的服务器是很好的入门但缺乏实用性。接下来我们构建一个更贴近真实场景的服务器一个文件系统浏览器。它将提供工具如列出目录、读取文件和资源以file://URI 形式暴露文件内容。4.1 设计工具集与资源模型我们的文件系统服务器将提供以下功能工具 (Tools):list_directory列出指定路径下的文件和子目录。输入参数path(字符串目录路径)。输出结构化的列表包含名称、类型文件/目录、大小等。read_file读取指定文件的内容。输入参数path(字符串文件路径)。输出文件内容的文本。search_files在指定目录下递归搜索包含特定文本的文件。输入参数directory(字符串搜索根目录)query(字符串搜索关键词)。输出匹配的文件路径列表。资源 (Resources):我们将文件系统映射为资源。例如文件/home/user/document.txt可以作为一个资源其 URI 为file:///home/user/document.txt。当客户端模型需要引用某个文件内容时可以直接通过这个 URI 请求服务器返回文件内容。4.2 实现细节与安全考量实现这个服务器核心挑战在于安全性和错误处理。我们绝不能允许模型通过服务器任意访问整个文件系统。// file_server.js const fs require(fs/promises); const path require(path); const { Server } require(modelcontextprotocol/sdk/server/index.js); const { StdioServerTransport } require(modelcontextprotocol/sdk/server/stdio.js); const { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } require(modelcontextprotocol/sdk/types.js); class FileSystemMCPServer { constructor(allowedBasePath) { // 将允许访问的根路径解析为绝对路径并规范化 this.allowedBasePath path.resolve(allowedBasePath); // 安全检查确保 basePath 存在且是一个目录 fs.access(this.allowedBasePath, fs.constants.R_OK) .then(() fs.stat(this.allowedBasePath)) .then(stats { if (!stats.isDirectory()) { throw new Error(配置的路径 ${this.allowedBasePath} 不是一个目录); } }) .catch(err { console.error(初始化错误无法访问基础路径 ${this.allowedBasePath}, err); process.exit(1); }); this.server new Server( { name: file-system-server, version: 1.0.0, }, { capabilities: { tools: {}, resources: { subscribe: false }, // 我们不支持资源动态订阅 }, } ); this.setupRequestHandlers(); } // 关键安全函数将用户提供的路径解析并限制在 allowedBasePath 下 resolveSafePath(userPath) { if (!userPath || typeof userPath ! string) { throw new Error(路径参数无效); } // 解析用户路径可能是相对路径或绝对路径 const requestedPath path.resolve(this.allowedBasePath, userPath); const normalizedRequested path.normalize(requestedPath); // 安全检查确保解析后的路径仍在允许的根目录之下 if (!normalizedRequested.startsWith(this.allowedBasePath path.sep) normalizedRequested ! this.allowedBasePath) { throw new Error(访问被拒绝路径 ${userPath} 超出了允许的范围 (${this.allowedBasePath})); } return normalizedRequested; } setupRequestHandlers() { // 列出工具 this.server.setRequestHandler(ListToolsRequestSchema, async () ({ tools: [ { name: list_directory, description: 列出指定目录下的文件和子文件夹, inputSchema: { type: object, properties: { path: { type: string, description: 要列出的目录路径相对于服务器配置的根目录, }, }, required: [path], }, }, { name: read_file, description: 读取指定文本文件的内容, inputSchema: { type: object, properties: { path: { type: string, description: 要读取的文件路径相对于服务器配置的根目录, }, }, required: [path], }, }, { name: search_files, description: 在指定目录下递归搜索包含特定文本的文件仅限文本文件, inputSchema: { type: object, properties: { directory: { type: string, description: 搜索的起始目录路径, }, query: { type: string, description: 要搜索的文本内容, }, }, required: [directory, query], }, }, ], })); // 列出资源这里我们动态生成将 allowedBasePath 下的文件视为资源 // 注意对于大型目录树动态列出所有资源不现实。通常这里返回一个“根”资源或空列表。 // 更常见的做法是当模型通过工具发现某个文件后再通过其 file:// URI 来读取。 this.server.setRequestHandler(ListResourcesRequestSchema, async () ({ resources: [ { uri: file://${this.allowedBasePath}, name: 文件系统根目录, description: 可访问的文件系统根目录${this.allowedBasePath}, mimeType: application/json, // 目录列表可以用JSON表示 }, ], })); // 调用工具 this.server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; try { switch (name) { case list_directory: { const safePath this.resolveSafePath(args.path); const stats await fs.stat(safePath); if (!stats.isDirectory()) { throw new Error(路径 ${args.path} 不是一个目录); } const items await fs.readdir(safePath, { withFileTypes: true }); const list await Promise.all( items.map(async (item) { const itemPath path.join(safePath, item.name); const itemStats await fs.stat(itemPath); return { name: item.name, type: item.isDirectory() ? directory : file, size: itemStats.size, modified: itemStats.mtime.toISOString(), }; }) ); return { content: [{ type: text, text: 目录 ${args.path} 的内容\n list.map(it - [${it.type}] ${it.name} (${it.size} bytes)).join(\n), }], }; } case read_file: { const safePath this.resolveSafePath(args.path); const stats await fs.stat(safePath); if (!stats.isFile()) { throw new Error(路径 ${args.path} 不是一个文件); } // 简单检查文件大小避免读取超大文件 if (stats.size 1024 * 1024) { // 1MB throw new Error(文件过大${stats.size} bytes出于安全考虑拒绝读取); } const content await fs.readFile(safePath, utf-8); return { content: [{ type: text, text: 文件 ${args.path} 的内容\n---\n${content}\n---, }], }; } case search_files: { const safeDir this.resolveSafePath(args.directory); const results []; // 一个简单的递归搜索函数注意对于深层目录可能需要优化或限制深度 const search async (dir, query) { const items await fs.readdir(dir, { withFileTypes: true }); for (const item of items) { const fullPath path.join(dir, item.name); try { if (item.isDirectory()) { // 递归搜索子目录 await search(fullPath, query); } else if (item.isFile()) { // 检查文件扩展名只读取可能的文本文件 const ext path.extname(item.name).toLowerCase(); const textExtensions [.txt, .md, .json, .js, .py, .html, .css]; if (textExtensions.includes(ext)) { const stats await fs.stat(fullPath); if (stats.size 1024 * 1024) { // 小于1MB const content await fs.readFile(fullPath, utf-8); if (content.includes(query)) { // 计算相对路径便于用户理解 const relativePath path.relative(this.allowedBasePath, fullPath); results.push(relativePath); } } } } } catch (err) { // 忽略无权限访问的文件 console.error(搜索时跳过 ${fullPath}:, err.message); } } }; await search(safeDir, args.query); return { content: [{ type: text, text: 在目录 ${args.directory} 中搜索 ${args.query} 的结果${results.length} 个匹配项\n (results.length 0 ? results.map(r - ${r}).join(\n) : 未找到匹配文件。), }], }; } default: throw new Error(未知的工具${name}); } } catch (error) { // 统一错误处理返回给客户端 return { content: [{ type: text, text: 执行工具 ${name} 时出错${error.message}, }], isError: true, }; } }); // 读取资源处理 file:// URI this.server.setRequestHandler(ReadResourceRequestSchema, async (request) { const { uri } request.params; // 检查是否是我们的 file:// 资源 if (uri.startsWith(file://)) { // 移除 file:// 协议头得到文件系统路径 const filePath uri.slice(7); // 注意这里简化处理实际应处理不同操作系统的路径和编码 try { const safePath this.resolveSafePath(filePath); const stats await fs.stat(safePath); if (!stats.isFile()) { throw new Error(URI ${uri} 不是一个文件); } if (stats.size 1024 * 1024) { throw new Error(文件过大拒绝读取); } const content await fs.readFile(safePath, utf-8); // 尝试根据文件扩展名推断 MIME 类型 const ext path.extname(safePath).toLowerCase(); const mimeMap { .txt: text/plain, .md: text/markdown, .json: application/json, .js: application/javascript, .html: text/html, .css: text/css, }; const mimeType mimeMap[ext] || text/plain; return { contents: [{ uri: uri, mimeType: mimeType, text: content, }], }; } catch (error) { throw new Error(读取资源 ${uri} 失败${error.message}); } } throw new Error(不支持的资源 URI 模式${uri}); }); } async run() { const transport new StdioServerTransport(); await this.server.connect(transport); console.error(文件系统 MCP 服务器已启动根目录${this.allowedBasePath}); } } // 从环境变量或命令行参数获取允许访问的根目录 const allowedPath process.env.ALLOWED_PATH || process.argv[2] || process.cwd(); const server new FileSystemMCPServer(allowedPath); server.run().catch(console.error);关键实现解析与避坑指南路径安全解析 (resolveSafePath)这是整个服务器的安全基石。必须将用户输入的路径解析并严格限制在预设的allowedBasePath之下。使用path.resolve和path.normalize处理..等相对路径符号然后检查解析后的路径是否以allowedBasePath开头。绝对不要直接使用用户输入的路径访问文件系统。错误处理与用户反馈所有文件操作fs.stat,fs.readdir都必须用try...catch包裹。错误信息应清晰反馈给用户通过模型但避免泄露服务器内部路径等敏感信息。MCP 响应中的isError: true字段可以标记这是一个错误响应。资源与工具的协同在这个设计中工具如list_directory用于发现和操作而资源file://URI用于让模型直接引用文件内容。当模型通过list_directory工具看到一个文件后它可以在后续对话中直接说“请分析file:///project/README.md的内容”客户端会自动发起readResource请求。性能与限制search_files工具是递归的对于大型目录树可能很慢甚至导致服务器无响应。在实际产品中必须添加深度限制、超时机制或使用更高效的搜索库。读取文件也应有大小限制防止服务器内存被耗尽。配置化通过环境变量 (ALLOWED_PATH) 或命令行参数来指定可访问的根目录使得服务器更灵活、更安全。4.3 部署与客户端配置进阶现在我们可以用更安全的方式配置 Claude Desktop 来使用这个服务器{ mcpServers: { my-file-server: { command: node, args: [ /path/to/your/file_server.js, /Users/YourName/SafeProjectFolder // 只允许访问这个文件夹 ] } } }这样模型就只能在你指定的SafeProjectFolder内进行操作无法越界。5. MCP 生态、最佳实践与面试思考5.1 现有生态与工具MCP 虽然年轻但生态正在快速成长。了解这些现有工具能帮你更好地理解其应用场景官方与社区服务器brave-search-mcp/tavily-mcp网络搜索服务器。为模型提供实时网络搜索能力。filesystemAnthropic 官方提供的文件系统服务器功能比我们上面实现的更完善。github连接 GitHub API管理仓库、Issue、PR等。sql连接数据库执行安全的 SQL 查询。postgres/mysql特定数据库的服务器。你可以在 npm 或 PyPI 上搜索mcp-*或mcp-server-*找到大量社区项目。支持 MCP 的客户端Claude Desktop最主流的客户端开箱即用。Cursor IDE代码编辑器深度集成 MCP可以直接在编辑器内使用各种工具。Windsurf/Continue其他 AI 编程助手。任何应用都可以通过集成 MCP 客户端 SDK 来获得连接 MCP 服务器的能力。5.2 开发 MCP 服务器的最佳实践安全第一始终假设传入的参数是恶意、未经校验的。实施严格的输入验证、路径限制、权限检查和资源配额如最大文件大小、超时时间。清晰的工具描述description和inputSchema中的参数描述要尽可能清晰、具体。模型依赖这些描述来理解工具的用途和如何调用。好的描述能极大提升工具调用的准确率。结构化输出工具返回的内容优先使用结构化的text或考虑使用image、embedding等类型。清晰的格式有助于模型理解和进一步处理。错误信息友好返回的错误信息应对最终用户通过模型友好能指导其更正输入同时避免暴露内部细节。处理异步操作如果工具执行时间很长如调用一个慢速 API应实现异步通知机制。MCP 支持progress通知可以向客户端发送执行进度更新。利用资源Resources对于只读的、可能被多次引用的数据将其定义为资源Resources而非工具Tools的返回结果更符合协议设计也能让模型更自然地将其作为上下文引用。5.3 面试视角下的深度思考题如果你在面试中被问到 MCP面试官可能不会只满足于让你解释概念。他们更想考察你的理解和思考深度。以下是一些可能的深入问题及回答思路Q1: MCP 解决了 Function Calling 的什么问题它的核心优势是什么A:Function Calling 定义了模型“如何请求调用”但没有定义工具“如何被提供和管理”。这导致每个 AI 应用都需要自己构建一套工具集成、发现和生命周期管理的框架造成重复劳动和生态割裂。MCP 的核心优势在于标准化和解耦。它定义了一个统一的工具供给协议使得工具开发者、模型提供者和 AI 应用开发者可以各自独立工作通过标准接口连接极大地促进了工具生态的繁荣和互操作性。Q2: 在设计一个 MCP 服务器时你会如何保证其安全性A:这是一个系统工程我会从多个层面考虑认证与授权虽然基础 MCP over stdio 通常依赖客户端如 Claude Desktop的信任但在网络传输场景下必须实现服务器和客户端的双向认证如 TLS 证书。输入验证与沙箱对所有输入参数进行严格的类型、范围和语义验证。对于执行代码或系统命令的工具必须在隔离的沙箱环境如 Docker 容器、无权限用户中运行。资源隔离与限额像我们的文件服务器一样严格限制可访问的文件系统范围。对内存、CPU 时间、网络请求次数、文件读取大小设置硬性上限。审计与日志记录所有工具调用请求和结果脱敏后用于监控异常行为和事后审计。最小权限原则服务器进程本身应以最低必要的系统权限运行。Q3: MCP 的局限性或挑战是什么A:目前看来有几个方面协议成熟度协议仍在快速发展中一些高级特性如双向流、复杂资源订阅的客户端支持可能不完善。工具描述的局限性当前主要依靠文本描述description和 JSON Schema对于复杂工具模型可能仍无法准确理解其使用场景和边界。工具组合与编排MCP 定义了单个工具的调用但多个工具之间的顺序执行、条件判断、结果传递等“工作流”编排目前落在客户端或模型逻辑上缺乏协议层面的原生支持。状态管理服务器通常被设计为无状态的但对于需要会话状态或复杂配置的工具状态管理会变得棘手。Q4: 对比其他类似协议如 OpenAI 的 Assistants API Tools、LangChain ToolsMCP 的定位有何不同A:OpenAI Assistants API Tools是 OpenAI 平台生态内的一套解决方案它包含了工具定义、调用和执行环境如代码解释器但它是平台绑定的。LangChain Tools是一个 Python/JS 框架内的抽象层它定义了工具接口并提供了大量现成工具实现但它主要服务于使用 LangChain 框架构建的应用。MCP 的定位更底层、更通用。它不关心你用什么框架可以用 LangChain 实现 MCP 服务器也不绑定特定平台。它是一个跨平台、跨框架的“通信协议标准”旨在成为大模型工具生态的“基础连接件”。你可以用 LangChain 的工具包装成一个 MCP 服务器然后被任何兼容 MCP 的客户端使用。从写一个简单的“Hello World”式服务器到一个具备基本安全意识的文件系统服务器我们走完了 MCP 从概念到代码实现的核心路径。关键在于理解其“协议”的本质——它是一套约定规定了工具提供者和消费者之间如何对话。这种解耦的设计正是其生命力的源泉。在实际开发中比起从头造轮子更推荐基于成熟的 SDK 和社区项目进行扩展。多看看 Anthropic 官方和社区的服务器实现是快速提升理解的最佳方式。最后安全永远是悬在头上的达摩克利斯之剑赋予模型操作现实世界的能力时必须用最谨慎的态度去设计每一道边界。