探索MCP协议:构建高效的LLM工具集成系统

7 阅读6分钟

LLM与工具的结合:从基础到Agent

在大型语言模型(LLM)的时代,我们常常看到LLM被用来处理文本生成、问答等任务,但当我们为其配备工具时,它们的潜力才真正被释放。想象一下,LLM不仅仅是聊天机器人,还能执行实际操作,比如读取文件、写入数据、列出目录或执行命令。这些工具包括read、write、listDir和exec等基本功能,将LLM与这些工具结合,就形成了Agent——一个能“干活”的智能系统。

这种结合带来的甜头显而易见:LLM不再局限于静态响应,而是能根据用户需求调用工具完成复杂任务。例如,在一个开发环境中,Agent可以通过工具查询数据库、处理文件或调用外部API。这让LLM从“会说话”变成“会做事”,极大提升了实用性。然而,现有的工具集成方式往往局限于单一进程或语言,难以扩展到更广阔的场景。这时候,我们需要一种更先进的机制来放大这种优势。

MCP的核心在于约定:所有工具按统一协议开发和暴露,从而让LLM在处理繁杂任务时更高效。无论是本地子进程、跨语言进程还是远程进程,MCP都能统一管理。通信部分包括stdio(标准输入输出)用于本地命令行,以及http用于远程调用。这使得LLM能执行更强大的任务,而开发者只需遵循规范提供工具和资源。

MCP协议的核心特点:跨进程调用与规范集成

MCP最大的特点是支持跨进程调用工具。这解决了传统工具集成的痛点:在复杂环境中,工具可能分布在不同进程、语言或部门,甚至远程服务器上。通过MCP,我们可以用Node的child-process创建子进程,调用Java或Rust工具,或通过网络访问远程资源。这样的规范让提供工具变得简单高效。

例如,MCP协议包括工具结果(tool result)和工具消息上下文(ToolMessage Context),确保LLM能正确处理工具输出。开源SDK如@modelcontextprotocol/sdk提供了实现基础,帮助开发者快速上手。为什么需要MCP配置?因为像Cursor或Trae这样的工具可以转变为支持MCP的Agent客户端,通过读取mcp.json文件加载需要的工具。这让集成变得即插即用。

在编写满足MCP规范的工具时,我们采用Client/Server架构。在现有工具基础上添加MCP规范,然后用服务器容器(如@modelcontextprotocol/mcp/server)托管。关键步骤包括registerTool(注册工具)和connect transport(连接传输方式)。这确保工具能在MCP生态中运行。

MCP的三者关系与工作流程

理解MCP需要把握三者关系:

  • MCP Hosts:如Cursor或Vite这样的Agent宿主,负责运行整体系统。
  • MCP Clients:遵循MCP规范的一系列工具,提供具体功能。
  • MCP Server:工具运行的服务器容器,管理工具的注册和执行。

工作流程如下:

  1. MCP Hosts通过配置文件初始化。
  2. 发送初始化请求,获取MCP Server提供的工具列表和详情。
  3. Hosts接收用户prompt任务。
  4. 检索MCP配置文件,确定通信方式。
  5. Client与工具通信,MCP Server执行并返回结果。

这种流程让MCP工具可拔插,直接集成到Agent程序中。开发者可以轻松扩展系统,而无需重写底层逻辑。

MCP开发流程:从创建到连接

开发MCP工具的流程清晰明了。首先,创建MCP Server实例:

JavaScript

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({
  name: 'my-mcp-server',
  version: '1.0.0',
});

然后,注册工具、资源或提示模板。工具包括名字、描述和执行函数。资源可以是文档或数据,提供给LLM作为上下文。提示模板则规范LLM的输入输出。

例如,注册一个查询用户信息的工具:

JavaScript

import { z } from 'zod';
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: `用户信息:\n- ID: ${user.id}\n- 姓名: ${user.name}\n- 邮箱: ${user.email}\n- 角色: ${user.role}`
        }
      ]
    }
  }
});

这里使用了Zod库定义输入 schema,确保输入验证。数据库是模拟的内存对象,实际中可替换为真实数据库。

同样,注册资源:

JavaScript

server.registerResource('使用指南', 'docs://guide', {
  description: 'MCP Server 使用文档',
  mimeType: 'text/plain',
}, async () => {
  return {
    contents: [
      {
        uri: 'docs://guide',
        mimeType: 'text/plain',
        text: `MCP Server 使用指南
          功能:提供用户查询等工具。
          使用:在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具。
        `,
      }
    ]
  }
});

这提供了上下文,帮助LLM更好地使用工具。

接下来,选择通信方式,如StdioServerTransport用于本地:

JavaScript

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const transport = new StdioServerTransport();
await server.connect(transport);

对于远程,可用HttpServerTransport。Hosts端则配置mcp.json加载这些工具。

实际代码示例:构建MCP Server和Client

以下是完整MCP Server代码示例,用于托管工具和资源:

JavaScript

// my-mcp-server.mjs
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

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" },
  }
};

const server = new McpServer({
  name: 'my-mcp-server',
  version: '1.0.0',
});

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: `用户信息:\n- ID: ${user.id}\n- 姓名: ${user.name}\n- 邮箱: ${user.email}\n- 角色: ${user.role}`
        }
      ]
    }
  }
});

server.registerResource('使用指南', 'docs://guide', {
  description: 'MCP Server 使用文档',
  mimeType: 'text/plain',
}, async () => {
  return {
    contents: [
      {
        uri: 'docs://guide',
        mimeType: 'text/plain',
        text: `MCP Server 使用指南
          功能:提供用户查询等工具。
          使用:在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具。
        `,
      }
    ]
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

这个Server通过stdio通信,适合本地跨进程调用。注意,代码中使用了内存数据库作为示例,实际应用中应考虑安全性和持久化。

现在,看Client端如何集成并使用这些工具。这里使用LangChain的MCP适配器构建Agent:

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';

const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node',
      args: ['./my-mcp-server.mjs'],
    },
  },
});

const model = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: {
    baseURL: process.env.OPENAI_API_BASE_URL,
  }
});

const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);

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} 个工具调用`));
    console.log(chalk.bgBlue(`🔍 工具调用: ${response.tool_calls.map(t => t.name).join(', ')}`));

    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);
        messages.push(new ToolMessage({
          content: toolResult,
          tool_call_id: toolCall.id
        }));
      }
    }
  }
  return messages[messages.length - 1].content;
}

const result = await runAgentWithTools("查一下用户 002 的信息");
console.log(result);
await mcpClient.close();

这个Client启动MCP Server,绑定工具到LLM模型,然后通过循环处理tool calls。示例查询“查一下用户 002 的信息”会调用query-user工具,返回用户信息。

MCP的扩展与未来展望

通过MCP,我们可以轻松集成第三方服务,将MCP视为工具的统一入口。这不仅适用于本地开发,还能扩展到分布式系统。例如,大厂的服务可以通过http暴露为MCP工具,让Agent跨部门协作。

MCP的开源性质鼓励社区贡献更多SDK和示例,推动LLM生态发展。开发者可以从简单工具入手,逐步构建复杂Agent。未来,随着更多hosts支持MCP,LLM将真正融入日常工作流。

总之,MCP协议为LLM工具集成提供了规范高效的路径。通过以上开发流程和代码示例,你可以快速上手,构建自己的Agent系统。欢迎在评论区分享你的实践经验!