AI 时代的“USB-C”接口:MCP 核心原理与实战

在上一次的学习手写mini cursor中,我们给大模型提供tools的方式是利用langchain的tool方法自己在本地手写tools函数,其实这是非常不优雅的方式,这样的tools工具缺乏复用性,接下来,我将用全新的方式让大模型调用tools。

在过去的很长一段时间里,我们在开发 AI 应用或智能体(Agent)时,常常会遇到一个令人头疼的痛点:如何优雅地让大语言模型(LLM)使用外部工具?

以前,每一个 AI 应用都需要为不同的模型单独编写复杂的工具调用适配层。我们要处理各种各样的接口、不同的数据格式,如果想要集成跨语言的工具或者第三方的服务,对接联调的复杂性往往会呈指数级上升。

这不禁让人思考:难道就没有一个统一的标准吗?

直到 Anthropic 在 24 年底发布了 Model Context Protocol(MCP) 协议,并在 25 年底将其贡献给开源社区。MCP 的出现,彻底改变了 LLM 与外部世界交互的范式。

一、 什么是 MCP?AI 界的"USB-C"

MCP,本质上就是一种开放协议。它的核心目的,是为大模型与外部工具、数据源、系统环境之间的交互建立统一的标准。

我们可以把 MCP 形象地比作大模型的「USB-C 接口标准」。就像 USB-C 统一了各种电子设备的连接方式一样,MCP 试图统一模型如何读取上下文、调用工具、访问本地资源和外部系统。

现在,只要大模型和工具都支持 MCP,它们就可以按照这个标准协议进行无缝通信。这意味着什么?这意味着未来的软件生态可能会发生巨变。甚至可以说,80% 的 App 可能会消失,它们将化身为第三方的 MCP 服务,本质上变成了一个个随时待命的 Tool。大型互联网公司也会倾向于将自己的服务以 MCP 的方式向外提供。

MCP 的三大核心角色

要理解 MCP 是如何运转的,我们需要认识它的三个核心组件:

  • MCP Host(宿主): 这是承载 AI Agent 或 LLM 会话,并负责工具编排的运行环境。比如我们常用的 Cursor 编程软件,或者基于 LangChain 开发的程序,它们就是 Host。Host 只需要知道如何使用 MCP 标准插头,就能驱动各种异构的工具。
  • MCP Client(客户端): 这是遵循 MCP 协议的"工具接口实现"层。你可以把它理解为"插座的转接头"。它负责将 Host 的请求翻译并传递给后端的 Server。
  • MCP Server(服务端): 这是实际执行工具逻辑的进程或服务,也就是真正的执行层。

强大的跨进程与通信能力

MCP 最显著的特点之一就是支持跨进程调用工具

  • 它可以调用本地的子进程(例如 Node.js 的 child_process)。
  • 它能够实现跨语言的调用,比如 Node.js 去调用 Java、Python 或 Rust 编写的工具。
  • 它甚至支持远程进程的调用。

在通信协议方面,MCP 支持两种主要方式:

  1. stdio: 用于本地命令行调用。
  2. http: 用于远程网络调用。

这种设计让繁杂的本地、跨语言、跨部门、远程协作变得井然有序,让 LLM 能够去执行更加庞大和复杂的任务。

二、 动手实践:编写你的第一个 MCP Server

理论说完了,让我们直接进入代码实战。我们要编写一个满足 MCP 协议规范的 Tool。

首先,你需要安装官方提供的 SDK,在 Node.js 中执行 pnpm i @modelcontextprotocol/sdk,这个库可用于实现 MCP Server 或 Client。

下面是一个名为 my-mcp-server.mjs 的本地 MCP 服务端代码示例。它的功能是模拟一个数据库查询工具。

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

// 1. 模拟一个简单的用户数据库
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" },
    }
}

// 2. 初始化一个 MCP 服务器,命名为 my-mcp-server,版本 1.0.0
const server = new McpServer({
    name: 'my-mcp-server',
    version: '1.0.0',
});

// 3. 注册核心功能:工具(Tool)
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} 不存在。可用的ID: 001, 002, 003` }] 
        }
    } else {
        return {
            content: [{
                type: 'text',
                text: `用户ID ${userId} 的信息如下:\n姓名 ${user.name},\n邮箱 ${user.email},\n角色 ${user.role}`
            }]
        }
    }
})

// 4. 注册资源(Resource):提供给 LLM 的上下文知识
server.registerResource('使用指南', 'docs://guide', {
    description: 'MCP Server 使用文档',
    mimeType: 'text/plain',
}, async () => {
    return {
        contents: [{
            uri: 'docs://guide',
            mimeType: 'text/plain',
            text: `MCP Server 使用指南\n功能:提供用户查询等工具。\n使用: 在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具`
        }]
    }
})

// 5. 启动服务器并连接标准输入输出传输层
const transport = new StdioServerTransport();
await server.connect(transport);

代码解析:

  • McpServer 实例: 开发流程的第一步是 new McpServer 创建服务实例。
  • 注册工具与资源: 使用 server.registerToolserver.registerResource 注册名称、描述和处理函数。注意,MCP 的上下文(Context)包含了 Tool、Resource 以及 PromptTemplate。Resource 使用 URI 作为统一资源定位符来进行唯一标识。
  • 通信通道: 最后,我们创建了一个基于标准输入输出(stdio)的传输通道 StdioServerTransport,调用 server.connect(transport) 开始监听来自客户端的 stdin 请求,并通过 stdout 输出。

三、 组装大脑:在 LangChain 中集成 MCP Client

Server 准备好了,接下来我们需要一个"大脑"(Host)来调用它。这里我们使用 LangChain 来搭建一个具备自主调度能力的 Agent。

这是 langchain-host.mjs 的核心实现,它展示了 MCP 是如何即插即用的:

JavaScript 复制代码
import 'dotenv/config';
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, ToolMessage } from '@langchain/core/messages';
import chalk from 'chalk';

// 1. 初始化 LLM 模型 (大模型的配置)
const model = new ChatOpenAI({
    modelName: process.env.OPENAI_API_MODEL_NAME,
    apiKey: process.env.OPENAI_API_KEY,
    configuration: { baseURL: process.env.OPENAI_API_BASE_URL }
});

// 2. 初始化 MCP Client (充当适配器)
const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        'my-mcp-server': {
            command: 'node',
            // 这里填写刚才编写的 Server 代码的绝对路径
            args: ['D:/workspace/lesson_zp/ai/agent/mini-cursor/mcp/mcp-tool/my-mcp-server.mjs']
        },
    }
});

// 获取所有工具,并绑定到大模型上
const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);

// 3. Agent 核心运行循环
async function runAgentWithTools(query, maxIterations = 30) {
    const messages = [new HumanMessage(query)];

    for (let i = 0; i < maxIterations; i++) {
        console.log(chalk.bgGreen('⏳正在等待AI思考...'));
        const response = await modelWithTools.invoke(messages);
        messages.push(response); // 保存模型回复

        // 如果模型没有决定调用工具,说明任务完成,直接返回结果
        if (!response.tool_calls || response.tool_calls.length === 0) {
            console.log(`\n AI 最终回复:\n ${response.content}\n`);
            return response.content;
        }

        console.log(chalk.bgBlue(`🔧 检测到 ${response.tool_calls.length} 个工具调用: ${response.tool_calls.map(t => t.name).join(',')}`));

        // 4. 执行模型请求的工具调用
        for (const toolCall of response.tool_calls) {
            const foundTool = tools.find(t => t.name === toolCall.name);
            if (foundTool) {
                const toolResult = await foundTool.invoke(toolCall.args);
                // 将工具调用的结果封装为 ToolMessage 塞回上下文
                messages.push(new ToolMessage({
                    content: toolResult,
                    too_call_id: toolCall.id
                }));
            }
        }
    }
    return messages[messages.length - 1].content;
}

// 运行测试
const result = await runAgentWithTools('查一下用户 002 的信息');
await mcpClient.close();

端到端的工作流是怎样的?

结合上面的代码,我们可以清晰地看到一次标准的 MCP 调用流程:

  1. 发现: Host(我们的 Node.js 脚本)启动时,读取配置并通过 MCP Client 发现了我们编写的 query-user 工具的名称、参数 schema 等信息。
  2. 调用: 用户输入查询需求,LLM 思考后认为需要调用该工具。Host 向 Client 发起调用请求。
  3. 转发与执行: Client 校验参数后将请求转发给我们的 my-mcp-server 进程。Server 访问内存数据库,查到了"李四"的信息。
  4. 回传: Server 将结果回传给 Client,再回传给 Host,最终附加到上下文中反馈给 LLM 生成最终的人类可读文本。

四、 进阶实战:高阶组合,玩转多源 MCP

MCP 最震撼的威力在于组合。当你拥有了统一的标准,你就可以像搭积木一样,将各种复杂的第三方服务整合到一个 Agent 中。

在下面这个高阶示例中,我们同时接入了三个不同维度的 MCP Server:

JavaScript 复制代码
// ... 引入模块省略 ...

const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        // 1. 远程 HTTP MCP 服务:高德地图
        "amap-maps-streamableHTTP": {
            "url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`
        },
        // 2. 官方提供的本地文件系统 MCP 服务(Stdio)
        "filesystem": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "D:/workspace/lesson_zp/ai/agent/mcp_in_action/mcp-test"
            ]
        },
        // 3. 浏览器自动化 MCP 服务(Stdio)
        "chrome-devtools": {
            "command": "npx",
            "args": [
                "-y",
                "chrome-devtools-mcp@latest",
                "--isolated"
            ]
        }
    }
})

// ... Agent 循环逻辑与之前类似,增加了完善的异常捕获与序列化处理 ...

// 震撼的组合调度任务:
await runAgentWithTools(`北京南站附近的3个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片,每个tab一个url展示,并且把那个页面标题改为酒店名`);
await mcpClient.close();

在这个例子中,Agent 被赋予了三个截然不同的能力:

  1. 远程 API 交互: 通过高德地图的 HTTP MCP 服务获取真实世界的位置和路线信息。
  2. 本地系统控制: 通过 npx 调用的 server-filesystem 可以直接在本地硬盘读写 Markdown 文档。
  3. 浏览器控制: 利用 chrome-devtools-mcp 直接操控浏览器,打开特定的 URL 修改标题。

当我们抛出 北京南站附近的3个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片... 这样一个极其复杂的复合型任务时,得益于 MCP 的标准化,大模型能够准确无误地规划并逐个调用这些异构工具,最终完成自动化操作流。

五、 总结

MCP 绝不仅仅是一个简单的通信库,它是通向真正的通用智能体(General-purpose Agent)的一块关键拼图。它极大地降低了开发者对接各种工具的复杂度,让我们可以将精力从"写适配器"转移到"设计业务逻辑与核心 Prompt"上。

未来的 AI 时代,得标准者得天下。掌握 MCP,就是掌握了让你的 AI 模型连接万物的钥匙。

相关推荐
大模型真好玩1 天前
大模型训练全流程实战指南工具篇(八)——EasyDataset问答数据集生成流程
人工智能·langchain·deepseek
gustt1 天前
MCP协议进阶:构建多工具Agent实现智能查询与浏览器交互
人工智能·agent·mcp
烛阴2 天前
MCP 从入门到实战完整教程(Windows 版)
claude·mcp
gustt2 天前
探索MCP协议:构建高效的LLM工具集成系统
llm·agent·mcp
helloweilei2 天前
javascript 结构化克隆
javascript·node.js
哈里谢顿3 天前
LangGraph 框架完全指南:构建生产级 AI 工作流
langchain·llm
哈里谢顿3 天前
LangChain 框架完全指南:从入门到精通
langchain
小蜜蜂dry3 天前
nestjs学习 - 控制器、提供者、模块
前端·node.js·nestjs
San303 天前
手写 Mini Cursor:基于 Node.js 与 LangChain 的开发实战
langchain·node.js·agent