MCP(Model Context Protocol)深度解析:从面试概念到代码实现

MCP(Model Context Protocol)深度解析:从面试概念到代码实现

带你从"面试官视角"理解 MCP 协议,再深入到 my-mcp-server.mjs + langchain-host.mjs 的每一行代码。


第一部分:面试视角看 MCP------如果你被问到"什么是 MCP"

1.1 一句话回答

MCP(Model Context Protocol)是一个标准化的 C/S 协议,让 AI 模型通过统一的接口发现和调用外部工具、访问数据源。

如果面试官追问"跟 LangChain 的 Tool 有什么区别",核心区别在于:LangChain Tool 是框架私有的工具定义方式,MCP 是跨框架、跨平台的开放协议。一个 MCP Server 写完后,Cursor、Claude Desktop、LangChain 都能用。

1.2 用"点外卖"类比理解 MCP

arduino 复制代码
你(Client / Host)         餐厅(MCP Server)
   │                              │
   │── 我要菜单 ──────────────►   │   ← tools/list
   │◀── 菜单来了(宫保鸡丁、鱼香肉丝)│
   │                              │
   │── 点菜:宫保鸡丁 ──────────►  │   ← tools/call
   │◀── 菜做好了,请享用 ────────  │
  • = AI 模型(通过 MCP Host 接入)
  • 餐厅 = MCP Server(提供工具和资源的服务端)
  • 菜单 = Server 暴露的 Tool 列表
  • 点菜 = AI 调用某个 Tool
  • 上菜 = Tool 返回执行结果

1.3 MCP 为什么诞生?------解决"组合爆炸"

在 MCP 出现之前:

css 复制代码
每个 AI 平台 × 每个外部工具 = 都要写单独的集成代码

Cursor  ⇢ 连接数据库  →  写一套代码
Cursor  ⇢ 连接 GitHub  →  再写一套
Claude Desktop ⇢ 连接数据库 → 又写一套
...

这是 N 个 AI × M 个工具 = N×M 种集成 的组合爆炸。

MCP 的做法:

arduino 复制代码
每个工具只需实现一次 MCP Server
    ↓
所有兼容 MCP 的 AI 都能直接用

数据库 MCP Server → Cursor、Claude Desktop、LangChain 全能用
GitHub MCP Server  → 同上

这是 N+M 的关系,不再相乘。

1.4 MCP 三大核心组件(面试高频考点)

MCP 协议规定了 Server 可以向 Client 暴露三类内容:

组件 英文 一句话理解
工具 Tool "我能为你做什么"------可调用的函数
资源 Resource "我能访问什么数据"------可读取的内容
提示模板 PromptTemplate "怎么更好地让我帮你"------预制的提示词

面试重点:为什么协议叫 "Model Context Protocol" 而不是 "Model Tool Protocol"?

因为 Context(上下文)= Tool + Resource + PromptTemplate 的总和 。协议设计的初衷不只是让模型调用工具,而是让模型在"理解世界"的基础上去做事------Resource 提供知识,Tool 提供能力,PromptTemplate 提供方法。

1.5 通信方式:stdio 和 HTTP

MCP 支持两种传输方式:

方式 原理 适用场景
stdio 标准输入输出流,Client 启动 Server 为子进程 本地工具
HTTP Server-Sent Events (SSE),Server 独立运行 远程服务、团队共享

本文的代码使用的是 stdio 方式 ------Host 通过 spawn 启动 MCP Server 作为子进程,通过标准输入输出通信。


第二部分:my-mcp-server.mjs------MCP Server 端详解

2.1 整体结构

js 复制代码
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

两个核心组件:

  • McpServer:创建 MCP Server 实例,注册工具和资源
  • StdioServerTransport:通过标准输入输出与外部通信

注意:这个文件不直接导入 LangChain 的任何东西------这正是 MCP 的核心价值:Server 与调用方完全解耦。

2.2 模拟数据库------Server 拥有的"数据资产"

js 复制代码
const database = {
    users: {
        "001": { id: "001", name: "张三", email: "zhangsan@example.com", role: "admin" },
        "002": { id: "002", name: "李四", email: "lisi@example.com", role: "user" },
        "003": { id: "003", name: "王五", email: "wangwu@example.com", role: "user" },
    }
}

真实场景下,这里可以连接到 MySQL、PostgreSQL、外部 API。这个对象模拟了 Server 独有、Client 不知道也访问不到的数据------Tool 就是把这些数据"暴露"给 AI 的桥梁。

2.3 创建 Server 实例

js 复制代码
const server = new McpServer({
    name: 'my-mcp-server',
    version: '1.0.0',
});

name + version 是 MCP 协议的强制字段,用于 Client 识别和版本管理。

2.4 registerTool------定义 Tool 的 MCP 方式

js 复制代码
server.registerTool('query-user', {
    description: '查询数据库中的用户信息。输入用户ID, 返回该用户的详细信息(姓名、邮箱、角色)。',
    inputSchema: {
        userId: z.string().describe("用户 ID, 例如:001, 002, 003")
    }
}, async ({ userId }) => {
    const user = database.users[userId];
    if (!user) {
        return {
            content: [{ type: 'text', text: `用户ID ${userId} 不存在。` }]
        }
    }
    return {
        content: [{ type: 'text', text: `用户信息:n- ID: ${user.id}n- 姓名: ${user.name}n...` }]
    }
})

与 LangChain tool() 的关键区别:

维度 LangChain tool() MCP registerTool
定义方式 tool(handler, config) server.registerTool(name, config, handler)
参数校验 schema: z.object({...}) inputSchema: { key: z.string().describe(...) }
返回值格式 return "字符串" return { content: [{ type: 'text', text: '...' }] }
所属框架 LangChain 专有 MCP 协议标准,跨框架通用

最关键的差异是返回值格式 。MCP 要求返回结构化的 { content: [...] } 数组,每个元素带 type 字段(textimageresource 等)。这设计是为了支持多模态------一个 Tool 可以同时返回文字 + 图片 + 文件链接。

2.5 registerResource------不只是工具,还有"知识"

js 复制代码
server.registerResource('使用指南', 'docs://guide', {
    description: 'MCP Server 使用文档',
    mimeType: 'text/plain',
}, async () => {
    return {
        contents: [{
            uri: 'docs://guide',
            mimeType: 'text/plain',
            text: `MCP Server 使用指南n功能:提供用户查询工具。n...`
        }]
    }
})

Resource 和 Tool 的区别:

arduino 复制代码
Tool  →  "做":查询用户、发送邮件、创建文件(行为)
Resource → "看":使用文档、数据库 schema、API 文档(知识)

为什么需要 Resource? 因为 AI 除了"做事"还需要"知道怎么做事"。比如这个 docs://guide 资源,AI 可以读取它来理解如何使用这个 Server 提供的工具------相当于"给工具配了使用说明书"。

注意 comments 中的点睛之笔:

js 复制代码
// 为什么叫 Model Context Protocol 不叫 Model Tool/Resource/PromptTemplate Protocol
// Context = Tool + Resource + PromptTemplate

这就是 MCP 设计的哲学高度------不是做一个"工具调用协议",而是一个上下文交换协议

2.6 启动:打开通信通道

js 复制代码
const transport = new StdioServerTransport();
await server.connect(transport);

StdioServerTransport 做了什么?

c 复制代码
MCP Host(langchain-host.mjs)
      │
      │  spawn 子进程 ──►  my-mcp-server.mjs
      │                         │
      │  ◀── stdout ────────────┘  Server 通过 stdout 发送响应
      │  ── stdin ──────────────►  Server 通过 stdin 接收请求

整个通信过程就是 JSON 序列化后的标准输入输出。不需要 HTTP 端口,不需要网络配置------一个进程,两根管道,协议就跑起来了。


第三部分:langchain-host.mjs------Host 端详解

3.1 核心问题:LangChain 不认识 MCP

LangChain 的模型只能绑定 LangChain 格式的 Tool。而 MCP Server 返回的是 MCP 格式的 Tool。需要一个适配器。

js 复制代码
import { MultiServerMCPClient } from '@langchain/mcp-adapters';

@langchain/mcp-adapters 就是这座桥梁------它把 MCP Server 提供的工具转换成 LangChain 能理解的 Tool 对象。

3.2 配置 MCP Client:告诉 Host 怎么启动 Server

js 复制代码
const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        'my-mcp-server': {
            command: 'node',
            args: ['C:/Users/.../my-mcp-server.mjs']
        }
    }
})

这里的配置告诉 MultiServerMCPClient

  1. 有一个叫 my-mcp-server 的 MCP Server
  2. 启动方式:执行 node my-mcp-server.mjs
  3. Client 会自动 spawn 这个子进程,建立 stdio 通信

关键设计 :MCP Server 是一个独立进程 ,可以被任何支持 MCP 的 Client 启动。同一个 my-mcp-server.mjs,Cursor 能用,Claude Desktop 能用,LangChain 也能用------这就是协议的力量。

3.3 动态获取工具:getTools() 的神奇之处

js 复制代码
const tools = await mcpClient.getTools();
console.log(tools, '/////');
const modelWithTools = model.bindTools(tools);

对比之前的做法:

ini 复制代码
之前(all_tools.mjs):
  import { readFileTool, writeFileTool, ... } from './all_tools.mjs';
  const tools = [readFileTool, writeFileTool, ...];
  → 工具是写死在 Host 代码里的

现在(MCP):
  const tools = await mcpClient.getTools();
  → 工具是动态从 MCP Server 发现的!

getTools() 背后发生了什么:

c 复制代码
Host                              MCP Server
  │                                    │
  │── stdout: tools/list ────────────► │  "请告诉我你能提供什么工具"
  │◀── stdin: 工具列表 ────────────── │  返回 query-user 工具的元数据
  │                                    │   (name, description, inputSchema)
  │                                    │
  │  LangChain 适配器将 MCP 格式       │
  │  转换为 LangChain Tool 格式         │
  │                                    │

这意味着:MCP Server 上新增或修改工具,Host 代码完全不用改。热插拔。

3.4 Agent 循环:与之前一模一样的逻辑

js 复制代码
async function runAgentWithTools(query, maxIterations = 30) {
    const messages = [new HumanMessage(query)];

    for (let i = 0; i < maxIterations; i++) {
        const response = await modelWithTools.invoke(messages);
        messages.push(response);

        if (!response.tool_calls || response.tool_calls.length === 0) {
            return response.content;
        }

        for (const tool_call of response.tool_calls) {
            const foundTool = tools.find(t => t.name === tool_call.name);
            if (foundTool) {
                const toolResult = await foundTool.invoke(tool_call.args);
                messages.push(new ToolMessage({
                    content: toolResult,
                    tool_call_id: tool_call.id
                }));
            }
        }
    }
}

这个循环和上一篇 mini-cursor 的循环完全一样。 这就是 MCP 最美的地方------它只改变了"工具从哪来",完全不改变"怎么用工具"。Agent 循环是一种通用模式,工具来源可以是本地定义、MCP Server、甚至 REST API,循环逻辑不变。

3.5 执行与清理

js 复制代码
const result = await runAgentWithTools('查询用户ID为001的用户信息');
console.log(result);
await mcpClient.close();

mcpClient.close() 会:

  1. 关闭与 MCP Server 的 stdio 通信
  2. 终止 MCP Server 子进程
  3. 释放相关资源

这是生产级代码应有的资源管理意识------启动了别人的进程,就要负责把它关掉

3.6 完整数据流:一次查询的全景

css 复制代码
用户: "查询用户ID为001的用户信息"
         │
         ▼
┌─ langchain-host.mjs ─────────────────────────────────────┐
│                                                           │
│  1. modelWithTools.invoke() → tool_calls: [query-user]    │
│  2. tools.find("query-user") → 找到(来自 MCP 适配器)     │
│  3. tool.invoke({userId: "001"})                          │
│        │                                                  │
│        ▼                                                  │
│    适配器将调用转发给 MCP Server 子进程 ─────────────┐      │
│                                                      │     │
│  ┌─ my-mcp-server.mjs (子进程) ──────────────────┐   │     │
│  │                                                │   │     │
│  │  stdin 收到: {method: "tools/call", "query-user", args:{userId:"001"}}
│  │  database.users["001"] → { name:"张三", ... }   │   │     │
│  │  stdout 返回: {content: [{type:"text", text:"用户信息:..."}]}
│  │                                                │   │     │
│  └────────────────────────────────────────────────┘   │     │
│                                                      │     │
│     ◀── 适配器将结果转换为 ToolMessage ──────────────┘      │
│                                                           │
│  4. modelWithTools.invoke(messages) → "用户001是张三..."    │
│  5. mcpClient.close() → 关闭子进程                         │
└───────────────────────────────────────────────────────────┘

四、三种工具集成方式的对比总结

结合前三篇文章,我们现在看到了三种定义 Tool 的方式:

方式 文章 工具定义 适用场景
本地 LangChain Tool 1.md(第一篇) tool(handler, config) 直接写在代码里 简单功能、原型验证
本地模块化 Tool 2.md(第二篇) 独立 all_tools.mjs 文件 + import 多工具项目
MCP 远程 Tool 3.md(本篇) MCP Server 独立进程 + getTools() 动态发现 生产级、跨平台复用

进化路径:

arduino 复制代码
单体 Tool(写死)→ 模块化 Tool(import)→ MCP Tool(协议发现)
  耦合度:高                中                  零
  复用性:无              项目内             跨框架跨平台

五、总结

MCP 做了什么?用一句话概括:

把"工具怎么被 AI 使用"和"工具怎么被实现"彻底解耦。

Server 只管"我能做什么",Host 只管"让 AI 用工具",中间通过 stdio 管道 + JSON 协议通信。从此你的数据库查询工具、文件操作工具、API 调用工具都可以写成 MCP Server,然后给 Cursor、Claude Desktop、LangChain 任何 MCP 兼容的平台用------写一次,到处跑。

对于面试来说,记住三个关键词:Tool(做)、Resource(看)、PromptTemplate(教)------合起来就是 Model 需要的全部 Context。

相关推荐
阳明山水2 小时前
LightGBM调优降MAPE至19%关键策略
人工智能·机器学习·微信·微信公众平台·微信开放平台
云朵观自在2 小时前
企业媒体宣发为何选择JHMS?——一家策略导向的媒体传讯服务商
大数据·人工智能·经验分享·媒体·jhms
学习论之费曼学习法2 小时前
AutoGen框架详解:微软Multi-Agent开发利器
人工智能·microsoft
Maddie_Mo4 小时前
Unity 联动 Trae AI 项目开发基础教学
人工智能·unity·游戏引擎
光锥智能4 小时前
Google 与百度同步布局智能体:AI 竞争进入全栈能力比拼阶段
人工智能·百度
一点一木9 小时前
深度体验TRAE SOLO移动端7天:作为独立开发者,我把工作流揣进了兜里
前端·人工智能·trae
Lee川10 小时前
mini-cursor 揭秘:从 Tool 定义到 Agent 循环的完整实现
前端·人工智能·后端
weelinking11 小时前
【产品】00_产品经理用Claude实现产品系列介绍
数据库·人工智能·sql·数据挖掘·github·产品经理
Agent产品评测局11 小时前
制造业模具管理AI系统,主流产品能力对比详解:2026年智能制造选型深度洞察
人工智能·ai·chatgpt·制造