掘金 人工智能 06月30日
🛠️MCP实战宝典:让AI获得超级能力的开发指南
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了Model Context Protocol (MCP)——一种旨在增强大型语言模型(LLM)能力,使其能够与外部工具和资源交互的开放标准协议。文章从技术实现角度出发,详细分析了MCP的工作原理,包括其整体架构、Hosts、Clients、Server等关键组成部分,以及JSON-RPC 2.0协议下的多种通信方式。此外,文章还通过示例代码,展示了MCP在工具注册、资源管理等方面的具体应用,帮助读者更好地理解和应用MCP。

⚙️ MCP 是一种开放标准协议,旨在扩展 AI 模型的能力,使其能够与外部工具和资源进行安全、标准化的交互,从而解决LLM的局限性。

💡 MCP 采用客户端-服务端架构,客户端负责与 MCP 服务端通信并与 LLMs 交互,服务端则提供工具、资源和提示词供客户端调用。

🛠️ Tools 是 MCP 最常用的能力,通过描述、参数、返回值等信息,使 LLMs 能够与外部资源进行交互,例如获取股票信息。

📚 Resources 能够提供各种类型的资源给模型,如文本、图片、音频、视频等,可用于实现知识库(RAG)的功能,Resource Template 可以用于注册动态资源。

🔗 MCP 使用 JSON-RPC 2.0 协议进行通信,支持多种通信模式,如 Stdio、SSE 和 Streamable HTTP,为开发者提供了灵活的选择。

随着人工智能技术的快速发展,大型语言模型(LLM)如 GPT-4Claude 等已经展现出强大的能力。然而,这些模型仍然存在一些固有的局限性,如无法访问实时信息、无法执行代码、无法与外部系统交互等。为了解决这些问题,Model Context Protocol(MCP)被开发出来。

MCP 是一种开放标准协议,旨在扩展 AI 模型的能力,使其能够与外部工具和资源进行安全、标准化的交互。官方文档已经写得非常详细,建议读者先阅读官方文档了解基础概念。本文将从技术实现角度深入分析 MCP 的工作原理,并使用TypeScript SDK的一些示例,为读者提供更深入的技术洞察。

整体架构

从上图可以看出,MCP 分为客户端和服务端,客户端负责与 MCP 服务端通信并与 LLMs 进行交互,服务端负责处理客户端请求。

Hosts

Hosts 是集成了 MCP 能力同时具备 LLM 能力的应用程序,常见的有:

名称工具资源提示词采样roots
Inspector支持支持支持不支持不支持
HyperChat支持支持支持不支持不支持
Claude Desktop支持支持支持不支持不支持
Cherry Studio支持简单支持支持不支持不支持
Cursor支持不支持不支持不支持不支持
Cline支持不支持不支持不支持不支持

这里重点推荐一下 HyperChat,虽然界面没有 Cherry Studio 优秀,但有很多高级的功能,比较更适合程序员。

Clients(客户端)

Client 是指在 Host 中用于连接 MCP Server 并提供与 MCP Server 交互的客户端,一般它由 Host 内部实现。

对于 MCP 的能力,部分能力主要需要 Client 来实现的,如下:

Server(服务端)

我们常说的 MCP Server 就是 MCP 的服务端,它负责处理客户端请求,并提供资源、提示词和工具给客户端调用。服务端包含了以下部分:

以上工具主要在服务端中实现,供客户端调用。

通信方式

MCP 通信主要通过 JSON-RPC 2.0 协议进行通信,MCP 的通信主要分为以下几种模式:

这些通信模式都是官方实现,如果你有兴趣,完全可以自己实现自己的通信模式比如 WebSocket,当然这得需要你的客户端和服务端两边都需要自己实现。

MCP功能解析

下面我们逐一分析 MCP 的能力以及使用一些示例代码来理解这些功能。

有些地方便于大家理解,使用了最原始的 JSON-RPC 2.0 数据格式,一般我们可以使用各种语言的 SDK,它封装了几乎所有功能,能更便利地使用 MCP。后面部分功能我会用 TypeScript SDK 来做示例演示。

连接

下面是一个简单的 MCP 连接示例:

sequenceDiagram    participant Client as 客户端    participant MCP as MCP服务器        Note over Client, MCP: 连接初始化流程        Client->>+MCP: initialize请求 {protocol_version, client_info}    Note over MCP: 验证协议版本和客户端信息,缓存连接    MCP-->>-Client: initialize响应 {session_id, server_info, capabilities}    Note over Client: 存储session_id,解析capabilities        Note over Client, MCP: 工具列表获取流程    Client->>+MCP: tools/list请求 {session_id}    Note over MCP: 验证session_id    MCP-->>-Client: tools/list响应 {tools: [...]}    Note over Client: 解析可用工具列表

首先 MCP 客户端会向连接的服务端发送一个初始化请求,可能像这样:

{    "method": "initialize",    "params": {        "protocolVersion": "2025-06-18",        "capabilities": {            "sampling": {},            "roots": {              "listChanged": true            }        },        "clientInfo": {            "name": "simple-mcp-client",            "version": "1.0.0"        }    },    "jsonrpc": "2.0",    "id": 0}

包含了客户端协议版本、客户端信息、客户端能力,服务端收到该消息后会根据 capabilities 来理解客户端的能力,然后返回初始化响应,可能像这样:

{    "result": {        "protocolVersion": "2025-06-18",        "capabilities": {            "tools": {                "listChanged": true            },            "prompts": {                "listChanged": true            },            "resources": {                "listChanged": true            },            "completions": {}        },        "serverInfo": {            "name": "stock-agent",            "version": "1.0.0"        }    },    "jsonrpc": "2.0",    "id": 0}

客户端收到该消息后根据 capabilities 也知道了服务端有哪些能力,如 tools 不为空,说明服务端支持工具使用,那么客户端就可以通过 tools/list 方法获取工具列表。

服务端TypeScript SDK开启服务代码如下

const server = new Server(  {    name: "stock-agent",    version: "1.0.0",  },  {    capabilities: {      tools: {        listChanged: true,      },      prompts: {        listChanged: true,      },      resources: {        listChanged: true,      },    },  });// 使用Stdio连接await server.connect(new StdioServerTransport());

使用Server的时候需要在 capabilities 中传入支持的能力如tools,或者使用封装更好的 McpServer,可以不用写 capabilities,如工具能力在注册时,会自动注册 tools 能力

const mcpServer = new McpServer({  name: "stock-agent",  version: "1.0.0",});// 使用Stdio连接await server.connect(new StdioServerTransport());

Tools(工具)

工具注册

一般对于单个工具,我们可以直接使用 McpServer,它提供了 tool 函数来注册工具:

mcpServer.tool(  "get-stock-info",  "获取股票信息",  {    stockName: z.string().describe("股票名称或股票代码"),  },  async (parameters, extra) => {    const { stockName } = parameters;    // 调用外部接口获取实时数据    let result = await api.getStockInfo(stockName);    return {      content: [        {          type: "text",          text: result.data,        },      ],      isError: false,    };  });

获取工具

上面讲了 tools/list 能获取到工具列表,它返回的数据格式长这样:

{    "result": {        "tools": [            {                "name": "get-stock-info",                "description": "获取股票信息",                "inputSchema": {                    "type": "object",                    "properties": {                        "stockName": {                            "type": "string",                            "description": "股票名称或股票代码"                        }                    },                    "required": [                        "stockName"                    ],                    "additionalProperties": false                }            },            {                "name": "get-suggestion-rate",                "description": "投资建议率",                "inputSchema": {                    "type": "object",                    "properties": {                        "stockCode": {                            "type": "string",                            "description": "股票代码"                        }                    },                    "required": [                        "stockCode"                    ],                    "additionalProperties": false                }            }        ]    },    "jsonrpc": "2.0",    "id": 1}

tools 列表下即为支持的工具列表,其中包含以下字段:

调用工具

当我们想调用工具,我们需要构造工具名称以及参数,它类似这样:

{    "method": "tools/call",    "params": {        "name": "get-stock-info",        "arguments": {            "stockName": "阿里巴巴"        }    },    "jsonrpc": "2.0",    "id": 2}

对应 TypeScript SDK 写法是:

// 客户端调用工具示例const result = await client.callTool({  name: "get-stock-info",  arguments: {    stockName: "阿里巴巴"  }});console.log(result.content);

服务端会收到该请求并执行对应的工具函数,返回结果可能像这样:

{    "result": {        "content": [            {                "type": "text",                "text": "阿里巴巴(BABA)当前股价:$85.32,涨跌:+2.1%,成交量:15.2M"            }        ],        "isError": false    },    "jsonrpc": "2.0",    "id": 2}

listChanged

capabilities下的listChanged可以告诉客户端,当工具发生变化后比如中间新增了新的工具,MCP Server会通过调用notifications/tools/list_changed消息来告知客户端,所以客户端可以通过判断listChanged字段,来监听工具变化并重新对之前获取的工具进行赋值

// ...初始化Clienttool = await client.listTools();if (client.getServerCapabilities()?.tools?.listChanged) {  client.setNotificationHandler(    ToolListChangedNotificationSchema,    async (args: ToolListChangedNotification) => {      console.log("Tool list changed:", args);      tool = await client.listTools();    }  );}

Resources(资源)

顾名思义,它能够提供资源给模型,可用于实现类似知识库(RAG)的功能。它可以是任何类型的资源,如文本、图片、音频、视频等。一个简单的资源开发如下:

// 省略初始化mcpServer.resource("股票历史", "file://documents/stock_info", () => {    const documentsFolder = 'path/stockHistory';    const files = fs.readdirSync(documentsFolder);    return {      contents: files.map(file => ({        uri: `${documentsFolder}/${file}`,        name: file,        mimeType: "text/plain",        text: fs.readFileSync(`${documentsFolder}/${file}`, 'utf-8')      }))    };  });

客户端可以通过listResources来获取资源列表,并通过readResource来读取资源内容

const resources = await client.listResource();console.log("Resources:", resources);// {//   resources: [//     { name: '股票历史', uri: 'file://documents/stock_info' },//   ]// }const resource = await client.readResource({ uri: "file://documents/stock_info" });// {//   contents: [//     {//       uri: 'file1.txt',//       mimeType: 'text/plain',//       text: '...'//     },//     {//       uri: 'file2.txt',//       mimeType: 'text/plain',//       text: '...'//     }//   ]// }

不同的resource下可以按业务类型、资源类型或者功能类型进行分类,contents中可以再进行细分。resource提供的是静态的资源的读取,对于动态资源可以使用Resource Template来实现。

Resource Template

SDK提供了ResourceTemplate用于注册动态资源,其用法如下:

mcpServer.resource(    "user-info",    new ResourceTemplate("data://user/{id}", {      list: undefined,    }),    async (uri, { id }) => {      if (id) {        const info = await getUserInfo(id);        return {          contents: [            {              uri: `data://user/${id}`,              text: JSON.stringify(info),            },          ],        };      }      return {        contents: [],      };    }  );

上面我使用ResourceTemplate注册了一个data://user/{id}的动态资源,可以通过传入id来获取用户信息提供给LLMs使用,客户端可以通过readResource来调用

const temps = await client.listResourceTemplates();console.log("Resource templates:", temps);// {//   resourceTemplates: [//     { name: 'user-info', uriTemplate: 'data://user/{id}' }//   ]// }const userInfo = await client.readResource({ uri: "data://user/123" });// { contents: [ { uri: 'data://user/123', text: '{\"name\":\"John Doe\"}' } ] }

需要注意的是,Resource Template是通过listResourceTemplates来获取MCP服务器支持哪些资源的,如果想被listResources获取到,可以在ResourceTemplate中传入list函数,比如上面的user-info例子

mcpServer.resource(    "user-info",    new ResourceTemplate("data://user/{id}", {      list: (extra) => {        return {          resources: [            {              uri: "data://user/1",              name: "用户1",            },            {              uri: "data://user/2",              name: "用户2",            },          ],        };      },    }),    async (uri, { id }) => {      if (id) {        const info = await getUserInfo(id);        return {          contents: [            {              uri: `data://user/${id}`,              text: JSON.stringify(info),            },          ],        };      }      return {        contents: [],      };    }  );

如果在客户端listResources就可以获取到data://user/1data://user/2两个资源了

const resources = await client.listResource();console.log("Resources:", resources);// {//   resources: [//     { name: '股票历史', uri: 'file://documents/stock_info' },//     { name: '用户1', uri: 'data://user/1' },//     { name: '用户2', uri: 'data://user/2' },//   ]// }

Resources也有listChanged功能。

Prompts(提示词)

提示词是MCP的一个重要概念,它可以提供引导信息、记忆等供LLMs使用。MCP中的提示词支持userassistant两个角色。

如果我有一个读代码的MCP Server,我可以再其中定义一个提示词让LLMs严格按照我指定的步骤来分析代码,以达到更好的效果。

mcpServer.prompt(    "code-analysis-steps",    "代码分析步骤",    async () => {      return {        description: "代码分析步骤",        messages: [          {            role: "user",            content: {              type: "text",              text: `请严格按照以下步骤完成代码分析:第一步:通过'get-environment'工具获取当前项目环境第二步:通过'get-code-wiki'工具获取本项目的工程简介第三步:使用'get-code-analysis'工具对代码进行分析`,            },          }        ],      };    }  );

如果我们的MCP客户端支持Prompts,那么我们可以载入上面提示词后再进行后面问答。有了Prompts,就能给模型指引,更好完成任务。就像目前大部分Agent一样,都是以提示词为主,有了提示词,MCP也可以被当作Agent使用。

Prompts也有listChanged功能。

Sampling(采样)

Sampling是MCP的一个高级功能,它允许MCP Server能够直接通过该能力直接调用LLMs来扩展MCP的能力。比如我有一个客服MCP服务,我可以使用Sampling能力来将客户的问题进行分类

mcpServer.tool(    "question-asking-assistant",    "提问小助手",    {      input: z.string().describe("用户提问"),    },    async (parameters, extra) => {      const { input } = parameters;      // 将用户提问分类      // 先判断客户端是否支持sampling能力,不支持则不记录      if (         mcpServer.isConnected() &&        mcpServer.server.getClientCapabilities()?.sampling != null       ) {        mcpServer.server.createMessage({          messages: [            {              role: "user",              content: {                type: "text",                text: "你是一个分类任务大师,请根据用户的提问,将问题进行分类,有以下类别:1. 需求 2. bug 3. 优化 4. 咨询 5. 其他。 用户提问是: " + input,              },            },          ],          maxTokens: 10,        }).then((result) => {          // 获取分类结果          const text = result.content.text;          saveClassification(text);        })      }      const info = await getSuggestion(input);      return {        content: [{ type: "text", text: info }],        isError: false,      };    }  );

很遗憾,目前几乎没有MCP Hosts实现了Sampling的能力。

Roots

Roots是MCP客户端给服务端提供的一个根路径或者URI,用于限制MCP Server的活动范围。它可以是一个目录、文件或者其他资源。

// 注意这里是MCP客户端代码client.setRequestHandler(  ListRootsRequestSchema,  (): ListRootsResult => ({    roots: [      {        uri: "file:///path/to/workspace",        name: "workspace",      },    ],  }));

服务端通过listRoots来获取客户端设置的根路径

const roots = await mcpServer.server.listRoots()// { roots: [ { uri: 'file:///path/to/workspace', name: 'workspace' } ] }

Notification (通知)

Notification是MCP客户端与服务端互相通知的一个机制,MCP内置了一些服务端向客户端通知的能力,比如工具变化通知

// 服务端代码,当工具变化后通知客户端server.sendToolListChanged();
// 客户端代码if (client.getServerCapabilities()?.tools?.listChanged) {  client.setNotificationHandler(    ToolListChangedNotificationSchema,    async (args: ToolListChangedNotification) => {      console.log("Tool list changed:", args);      tool = await client.listTools();    }  );}

除此之外,服务端还提供了如下通知:

客户除了上面cancelledprogress,还有如下消息可用于通知服务端

除此之外,我们也可以使用通知机制来实现自定义通知

// 服务端代码mcpServer.server.notification({  method: "notifications/refresh",  params: { refresh: true },});
// 客户端监听client.setNotificationHandler(  NotificationSchema.extend({    method: z.literal("notifications/refresh"),  }),  (args: any) => {    console.log("refresh changed:", args);  });

通信

Stdio

Stdio基于标准的流输入输出实现,客户端可以通过stdinstdout来与MCP Server进行通信,在node中通过使用process.stdinprocess.stdout来实现JSON的输入输出。使用此方式连接MCP Server,

Stdio一般采用环境变量来传递身份认证信息。

Stdio一般使用文件来记录日志,(因为使用console.log等打印的日志会被MCP客户端获取,从导致一些异常日志)

Streamable HTTP

Streamable Http采用了POST请求进行双向通信,同时也兼容了SSE双向通信方式。如果要开启POST通信,初始化StreamableHTTPServerTransport时需要传入enableJsonResponsetrue

transport = new StreamableHTTPServerTransport({  enableJsonResponse: true,  //...});

采用了POST通信后,Streamable HTTP请求流程:

sequenceDiagram    participant Client as MCP客户端    participant Server as MCP服务器    participant SSE as SSE流    Note over Client,Server: 1. 初始化连接    Client->>Server: POST /mcp (initialize请求)    Note right of Client: 包含协议版本、客户端能力    Server-->>Client: 初始化响应    Note left of Server: 返回sessionId和服务器能力    Note over Client,Server: 2. 建立SSE连接(默认都会建连)    Client->>Server: GET /mcp    Note right of Client: Accept: text/event-stream<br/>mcp-session-id: {sessionId}    alt 支持SSE        Server-->>SSE: 创建SSE连接        Server-->>Client: 200 OK + SSE头        SSE-->>Client: 保持连接连接    end    Note over Client,Server: 3. 正常客户端->服务端请求响应    loop 请求响应循环        Client->>Server: POST /mcp (JSON-RPC请求)        Note right of Client: tools/call, resources/read等        Server-->>Client: 直接JSON响应    end    Note over Client,Server: 4. 发送通知    Server->>SSE: 发送SSE事件    alt 支持SSE        Note left of Server: data: {JSON-RPC响应}        SSE-->>Client: 流式接收响应            end    Note over Client,Server: 5. 连接关闭    Client->>Server: DELETE /mcp (可选)    Server-->>Client: 200 OK    SSE--xClient: 关闭SSE连接

如果客户端通过POST请求获取MCP Server响应比如调用initialize,MCP Server会直接使用这次的POST请求返回对应数据,当然SSE同样还是会保留用于服务端往客户端发送通知(Notification)的,那如果SSE断了服务端如何向客户端发送通知呢,那没办法了,因为没有SSE通道,服务端就无法向客端主动发送通知了。由于服务端往客户端发送通知并不是MCP的核心链路,所以即使没有建立SSE通道,也不会影响MCP核心功能。

一般Streamable Http身份验证

    在url参数中携带身份认证信息,这种兼容性最高,几乎所有MCP Hosts都能支持
{  "mcpServers": {    "url": "https://host.com/mcp?AIPKEY={YOUR_API_KEY}",  }}
    在请求头中携带身份认证信息使用OAuth2认证

Streamable HTTP可以使用console.log来记录日志,但是由于一般一个服务器会对应多个MCP服务端(一个MCP服务端和一个MCP客户端是一对一连接),所以日志中一般需要至少标注好sessionId,这在分析问题很有用。同时也推荐重写StreamableHTTPServerTransport的handleRequest和send方法,来记录原始日志信息(Stdio同理)

class MyTransport extends StreamableHTTPServerTransport {  constructor(options: StreamableHTTPServerTransportOptions) {    super(options);  }  async handleRequest(req: any, res: any, parsedBody?: any): Promise<void> {    if (parsedBody) {      console.log(`接受到参数:${JSON.stringify(parsedBody)}`);    }    super.handleRequest(req, res, parsedBody);  }  async send(    message: JSONRPCMessage,    options?: { relatedRequestId?: RequestId }  ): Promise<void> {    if (message) {      console.log(`返回数据:${JSON.stringify(message)}`);    }    super.send(message, options);  }}

错误处理

在开发MCP Server时,合适的错误处理非常重要:

mcpServer.tool(  "risky-operation",  "可能失败的操作",  {    input: z.string()  },  async (parameters) => {    try {      const result = await someRiskyOperation(parameters.input);      return {        content: [{ type: "text", text: result }],        isError: false      };    } catch (error) {      return {        content: [{           type: "text",           text: `操作失败: ${error.message}`         }],        isError: true      };    }  });

可以通过设置isErrortrue,来让MCP客户端知道操作失败了。

MCP客户端简单实现

拿到toolsresourcesprompts列表之后,我们可以把它告诉LLMs,然后LLMs就可以使用它了。下面通过实现工具的调用来看如何简单实现一个MCP客户端。

目前主流有两种实现工具调用的方式,一是使用Tool Call实现,一个是利用模型返回XML、JSON等格式的文本来调用工具,如Cline。下面我使用TypeScript SDK来演示一下两种方式的实现

    Tool Call
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";import OpenAI from "openai";import {  ChatCompletionMessageParam,  ChatCompletionTool,} from "openai/resources";async function main() {  // 这里改为你自己的MCP服务  const transport = new StreamableHTTPClientTransport(    new URL("http://127.0.0.1:3000/mcp")  );  const client = new Client({    name: "simple-mcp-client",    version: "1.0.0",  });  await client.connect(transport);  const tool = await client.listTools();  const openAI = new OpenAI({    baseURL: process.env.OPENAI_API_BASE,    apiKey: process.env.OPENAI_API_KEY,  });  const messages: ChatCompletionMessageParam[] = [    {      role: "user",      content: "帮我分析阿里巴巴股票",    },  ];  const tools: Array<ChatCompletionTool> = tool.tools.map((tool) => {    return {      type: "function",      function: {        name: tool.name,        parameters: tool.inputSchema,        description: tool.description,      },    };  });  let response = await openAI.chat.completions.create({    model: "gpt-4o",    messages: messages,    // 传入工具    tools: tools,  });  let message = response.choices[0].message;  // 如果模型调用了工具,通过代码进行真正工具调用  while (message.tool_calls) {    // 将工具调用消息缓存成记忆用于后续再次调用模型时使用    messages.push(message);    const toolCalls = message.tool_calls;    if (toolCalls && toolCalls.length > 0) {      for (const toolCall of toolCalls) {        console.log("工具调用:" + toolCall.function.name);        const name = toolCall.function.name;        // 解析参数        const args = JSON.parse(toolCall.function.arguments);        console.log("参数:" + toolCall.function.arguments);        // 调用工具        const result = await client.callTool({ name, arguments: args });        // 将结果再次给模型        messages.push({          tool_call_id: toolCall.id,          role: "tool",          content: JSON.stringify(result.content),        });      }    }    response = await openAI.chat.completions.create({      model: "gpt-4o",      messages: messages,    });    message = response.choices[0].message;  }  console.log("LLM返回:" + response.choices[0].message.content);}main();

以上代码配置好OPENAI_API_BASEOPENAI_API_KEY后,将MCP服务改为本地已有服务即可直接运行。这就是一个最简单的MCP客户端了。

以上是利用大模型的Tool call能力来实现的MCP工具调用,但部分模型是不支持Tool Call的,如DeepSeek R1,那么还有一种让大模型返回模版的方式来实现类似Tool Call能力

    模板解析
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";import OpenAI from "openai";import {  ChatCompletionMessageParam,} from "openai/resources";async function main() {  const transport = new StreamableHTTPClientTransport(    new URL("http://127.0.0.1:3000/mcp")  );  const client = new Client({    name: "simple-mcp-client",    version: "1.0.0",  });  await client.connect(transport);  const tool = await client.listTools();  const openAI = new OpenAI({    baseURL: process.env.OPENAI_API_BASE,    apiKey: process.env.OPENAI_API_KEY,  });  const messages: ChatCompletionMessageParam[] = [    {      role: "system",      content: `你是一个任务大师,你可以通过使用工具来完成用户的任务。## 可用工具${tool.tools  ?.map((tool) => {    const schemaStr = tool.inputSchema      ? `${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n    ")}`      : "";    return `- tool_name: ${tool.name}\n- description: ${tool.description}\n- Input Schema: ${schemaStr}\n`;  })  .join("\n\n")}## 工具使用方法你可以使用XML格式的方式来调用工具,首先你会根据工具的\`description\`来选择合适的工具,然后通过XML组合工具名称(tool_name)和参数(arguments)来调用工具。参数:- tool_name: (必填)工具名称- arguments: (必填) JSON格式的参数组合,需要参考工具的\`Input Schema\`来填写。使用方法:<use_mcp_tool><tool_name>tool name here</tool_name><arguments>{  "param1": "value1",  "param2": "value2"}</arguments></use_mcp_tool>示例:我要调用get_weather工具来获取San Francisco的天气情况,然后再提供度假路线,<use_mcp_tool><tool_name>get_weather</tool_name><arguments>{  "city": "San Francisco"}</arguments></use_mcp_tool>`,    },    {      role: "user",      content: "帮我分析阿里巴巴股票",    },  ];  let response = await openAI.chat.completions.create({    model: "gpt-4o",    messages: messages,  });  const toolResults = [];  let message = response.choices[0].message.content || "";  messages.push(response.choices[0].message)  console.log("response: " + message);  // 解析responseText中的use_mcp_tool标签  const useMcpToolRegex = /<use_mcp_tool>([\s\S]*?)<\/use_mcp_tool>/g;  let match;  let callTools = false;  while ((match = useMcpToolRegex.exec(message)) !== null) {    const toolContent = match[1];    // 解析tool_name    const toolNameMatch = /<tool_name>(.*?)<\/tool_name>/s.exec(toolContent);    const toolName = toolNameMatch ? toolNameMatch[1].trim() : "";    // 解析arguments    const argumentsMatch = /<arguments>([\s\S]*?)<\/arguments>/s.exec(      toolContent    );    let toolArgs: Record<string, any> = {};    if (argumentsMatch) {      try {        toolArgs = JSON.parse(argumentsMatch[1].trim());      } catch (e) {        console.error("Failed to parse tool arguments:", e);      }    }    console.log(`Calling tool: ${toolName} with args:`, toolArgs);    if (toolName) {      try {        // 创建符合callTool参数要求的对象        const result = await client.callTool({          name: toolName,          arguments: toolArgs,        });        toolResults.push(result);        console.log(`Tool result:`, result);        // 将工具结果添加到消息中        messages.push({          role: "user",          content: `Tool result: ${JSON.stringify(result)}`,        });        callTools = true;      } catch (e) {        console.error(`Error calling tool ${toolName}:`, e);      }    }  }  if (callTools) {    const response = await openAI.chat.completions.create({      model: "gpt-4o",      messages,    });    message = response.choices[0].message.content || "";  }  console.log("LLM返回:" + message);}main();

一般建议使用XML格式来调用工具,相比JSONXML有以下好处

    更容易被正则解析更易读,能让模型和用户都能更好理解字符串内容

当然使用这种方式调用工具肯定没有Tool Call稳定,但是在某些场景使用起来兼容性更好。

功能示例

    代码生成。如莫高设计提供的magic-mcp,配合使用可以做到设计稿到本地代码的代码生成。更进一步还可以做到与本地UI库的代码生成。本地工作流自动化。例如使用filesystem+git根据本地文件及仓库日志自动生成周报,推荐使用HyperChat,可设置定时任务生成。使用高德地图MCP Server做旅游路线和行程规划

总结

从上面功能已经看出来了,MCP是作为AI Agent来设计的,为什么很少有人叫MCP为Agent,为什么大部分MCP Server只有Tools,我认为有以下原因:

不过虽然现在看起来MCP只是被当成工具使用,但不得不承认它还是有发展成高阶智能体的潜质。不管怎么样,MCP目前都在AI市场中占据着比较重要的地位,掌握MCP的能力也能帮助我们更好地掌握AI和使用AI。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

MCP LLM AI 工具 协议
相关文章