首页 > 其他分享 >MCP(Model Context Protocol)模型上下文协议 实战篇

MCP(Model Context Protocol)模型上下文协议 实战篇

时间:2024-12-09 19:27:35浏览次数:5  
标签:实战篇 Protocol name return client Context const tools MCP

2024年11月底,Anthropic公司发布了全新的MCP(Model Context Protocol)协议,即模型上下文协议。该协议作为一种开放标准,旨在实现大型语言模型(LLM)应用程序与外部数据源和工具之间的无缝集成。无论是在开发AI驱动的集成开发环境(IDE)、增强聊天界面,还是创建自定义AI工作流程,MCP都提供了一种标准化的方式来连接LLM与所需的环境。

内容简介

MCP(模型上下文协议)是一种开放协议,它能够实现AI应用程序与本地或远程资源之间的安全、可控的交互。

不过由于协议刚刚发布,大部分的演示和视频都是基于Claude Desktop App来直接使用的。Claude Desktop的具体代码实现并未开源,因此今天我们将一起动手开发一个开源APP,接入MCP协议。如果你更倾向于直接查看项目代码,可以跳至文末收藏项目链接。

在开始开发之前,让我们先来分解一下MCP的工作原理:

总体架构

在其核心部分,MCP遵循client-server架构,其中主机应用程序可以连接到多个服务器:

MCP Hosts: Claude桌面程序、IDE或AI工具,通过MCP访问资源

MCP Clients: 用于与服务器保持1:1连接的协议客户端

MCP Servers: 轻量级程序,通过标准化的MCP协议暴露特定功能

Local Resources: 你的计算机资源(数据库、文件、服务)

Remote Resources: 可通过互联网访问的资源(例如通过API)

需要注意的是,尽管MCP server在名称中带有server字样,但在架构图中明确指出它应部署在本地个人电脑上。因此,我们可以直接从GitHub库中选择合适的server进行安装和使用,无需重复开发这部分功能。

开发阶段

我将用TypeScript基于Electron简要展示如何实现一个支持MCP的APP。

基本组件

在开发App之前,我们需要明确其核心组件,以确保系统架构的清晰和功能的完整性:

  1. 渲染器(Renderer):负责获取并展示所有工具(tools)提供的数据和功能,这些工具将直接与语言模型进行交互。
  2. 主进程(Main):作为App的核心控制中心,通过进程间通信(IPC)机制,将客户端(client)的请求和调用传递给渲染器,确保数据和操作的流畅传递。
  3. 客户端(Client):作为App与服务端(server)之间的桥梁,负责处理与服务器的通信,确保数据的准确传输和响应。

客户端(Client)

定义一个名为 initializeClient 的异步函数,用于初始化并连接一个服务端(server):

export async function initializeClient(name: String, config: ServerConfig) {
    const transport = new StdioClientTransport({
        command: config.command,
        args: config.args,
    });
    const client_name = `${name}-client`;
    const client = new Client({
        name: client_name,
        version: "1.0.0",
    }, {
        capabilities: {}
    });
    await client.connect(transport);
    console.log(`${client_name} connected.`);
    return client;
}

定义两个异步函数listTools和callTools,分别用于列出工具和调用工具。这两个函数都依赖于刚刚创建的client的对象:

export async function listTools(client: Client) {
    const tools = await client.request(
        { method: "tools/list" },
        ListToolsResultSchema
    );
    console.log('List Tools:', tools);
    return tools;
}
export async function callTools(client: Client, params: any) {
    const call_tools = await client.request(
        {
            method: "tools/call",
            params: params
        },
        CallToolResultSchema
    );
    console.log('Call Tools:', call_tools);
    return call_tools;
}

主进程(Main)

根据配置文件初始化多个客户端,并返回这些客户端的数组:

async function initClient(): Promise<ClientObj[]> {
  const config = readConfig(configPath);
  if (config) {
    console.log('Config loaded:', config);
    const clients = await Promise.all(
      Object.entries(config.mcpServers).map(async ([name, serverConfig]) => {
        console.log(`Initializing client for ${name} with config:`, serverConfig);
        const client = await initializeClient(name, serverConfig);
        console.log(`${name} initialized.`);
        return { name, client };
      })
    );
    console.log('All clients initialized.');
    return clients
  }
  console.log('NO clients initialized.');
  return []
}

其中config配置文件可以参考官方(https://github.com/modelcontextprotocol/servers)库中的JSON格式配置文件,所以initClient这里直接解析其内容即可。

接下来,使用 Electron 框架中的ipcMain模块来处理主进程和渲染进程之间的通信:

  ipcMain.handle('list-clients', () => {
    return clients.map(({ name }) => name);
  });

  clients.forEach(({ name, client }) => {
    console.log(`IPC Main list-tools-${name}`)
    ipcMain.handle(`list-tools-${name}`, async () => {
      return await listTools(client);
    });

    console.log(`IPC Main call-tools-${name}`)
    ipcMain.handle(`call-tools-${name}`, async (event, params) => {
      return await callTools(client, params);
    });
  });

以上,我们已经为每个客户端注册两个 IPC 处理函数:

  • list-tools-${name}: 用于列出该客户端的所有工具。
  • call-tools-${name}: 用于调用该客户端的某个工具,并传递参数。

此外,还有一个全局的 list-clients 处理函数,用于列出所有客户端的名称。

预加载脚本(Preload)

主要功能是与 Electron 的 ipcRenderer 模块进行交互,并将结果暴露给渲染端(Renderer):

async function listClients(): Promise<string[]> {
  return await ipcRenderer.invoke('list-clients');
}

async function exposeAPIs() {
  const clients = await listClients();
  const api: MCPAPI = {};

  clients.forEach(client => {
    api[client] = {
      list: () => {
        return ipcRenderer.invoke(`list-tools-${client}`);
      },
      call: (params : any) => {
        return ipcRenderer.invoke(`call-tools-${client}`, params);
      }
    };
  });

  contextBridge.exposeInMainWorld('mcpServers', api);
}

为每个客户端创建一个对象,包含两个方法:

  • list: 调用 ipcRenderer.invoke 方法,发送一个名为 list-tools-${client} 的 IPC 调用,返回该客户端的工具列表。
  • call: 调用 ipcRenderer.invoke 方法,发送一个名为 call-tools-${client} 的 IPC 调用,并传递参数 params,用于调用该客户端的工具。

渲染端(Renderer)

我使用了自己的一个开源UI(https://github.com/AI-QL/chat-ui),只需一个HTML文件即可实现。在此过程中,我们需要特别关注渲染端如何调用工具:

listTools: async function (resourceName) {
    const mcpServers = this.getServers
    if (!mcpServers) {
        return null
    }
    const mcpKeys = Object.keys(mcpServers)
    const mcpTools = []
    await Promise.all(mcpKeys.map(async (key) => {
        const tools = await mcpServers[key].list();
        for (const tool of tools.tools) {
            mcpTools.push({
                type: 'function',
                function: {
                    name: tool.name,
                    description: tool.description,
                    parameters: tool.inputSchema,
                }
            })
        }
    }));
    return mcpTools
}

这个 listTools 函数的主要功能是从多个服务端(server)中获取工具(tool)列表,并将这些工具的信息整理成统一的格式返回。返回的工具信息包含了工具的名称、描述和输入参数的结构。

最后我们将获取的 tools 列表添加到请求体中即可:

if (chatbotStore.mcp) {
    body["tools"] = await mcpStore.listTools()
}

const request = {
    headers: headers,
    method: chatbotStore.method,
    body: JSON.stringify(body),
};

const completion = await fetch(
    chatbotStore.url + chatbotStore.path,
    request
);

测试

消息请求

首先,发送一条基本chat completions请求,观察请求体payload中携带的tools:

可以看到每个tool就是一个详细的函数名称、参数和格式的定义,在TypeScript中,使用 Zod 来定义和验证工具的参数和格式是一种常见做法。

文件访问

然后,以filesystem为例,我们测试一下模型访问文件的能力:

我在此处使用了gpt-4o-mini模型,结果显示该模型能够顺利完成任务。需要注意的是,MCP server出于安全考虑,并不会真正删除文件。即使提出删除请求,server也只会对文件进行重命名操作。

网页访问

然后,我们测试一下模型访问网页的能力:

对于一般的文字搜索,直接利用搜索引擎通常能获得较高的成功率。然而,在处理特定项目的搜索时,例如天气状况查询,模型可能会因无法准确输入合适的天气网站而失败。在此情况下,我选择了国产的qwen-turbo模型,因为它在处理这类任务时表现更为稳定。相比之下,使用GPT模型时,由于其倾向于查询国外网站,可能会导致超时失败的问题。

多次实验后,这类复杂工具(如网页查询所用的Puppeteer)的调用功能稳定性较差,失败率较高。通常需要多次尝试才能成功,且在面对防机器人机制时,模型因缺乏类人的点击习惯,容易陷入死循环。因此,目前仅能将其视为一次体验性质的尝试。

总结

由于我在编写代码时,MCP 刚刚发布一周,因此仅完成了初步的开发尝试。许多功能和内容仍有待完善,这篇文章的内容也较为仓促,恳请各位读者见谅。

文中所用代码已经开源并上传至 GitHub,欢迎有兴趣的小伙伴们自行尝试实现。得益于 MCP 开源服务器的支持,开发类似的 AI 代理应用门槛已大幅降低。对于熟悉 Python 或 TypeScript 的开发者来说,只需稍加研究 SDK 库,便能快速构建出一个功能相近的原型应用。

然而,目前整个系统的瓶颈仍在于模型的能力尚不足以轻松应对如此复杂的多个服务端。当前不少小伙伴都在密切关注OpenAI正在进行的长达12天的马拉松发布会,期待能有更多创新性的内容涌现。

完整代码:https://github.com/AI-QL/chat-mcp

UI部分:https://github.com/AI-QL/chat-ui

标签:实战篇,Protocol,name,return,client,Context,const,tools,MCP
From: https://blog.csdn.net/aiqlcom/article/details/144324496

相关文章

  • 介绍一下 WebApplicationContext 思维导图 代码示例(java 架构)
    WebApplicationContext是Spring框架中的一个接口,它是ApplicationContext的扩展,专门用于Web应用程序。它提供了对Web特定功能的支持,例如解析主题(themes)、管理国际化资源、以及与Servlet容器集成等。下面是一个关于WebApplicationContext的思维导图大纲和一些代码示例。WebAp......
  • "context deadline exceeded" 错误
    在Go语言中,context包提供了一种跨API和进程边界传递请求作用域值、取消信号以及超时信号的方式。使用context可以帮助更好地控制goroutine,避免goroutine泄漏等问题。出现“contextdeadlineexceeded”错误通常是因为在请求上下文中设置了超时时间,但请求在超时时间内......
  • RFC 3161 是由 IETF(Internet Engineering Task Force)发布的一项标准,定义了数字时间戳
    RFC3161标准:时间戳协议概述RFC3161是由IETF(InternetEngineeringTaskForce)发布的一项标准,定义了数字时间戳协议(DigitalTimestampingProtocol)。其主要目的是为数字签名提供独立的时间戳服务,确保签名在特定时间内有效,即使签名的证书过期或撤销。该协议的核心功能是为文件......
  • AudioContext有什么应用场景?
    AudioContext在前端开发中有很多应用场景,主要围绕着处理和操作音频。以下是一些常见的例子:音频可视化:可以使用AudioContext的AnalyserNode来获取音频的频率和时间域数据,然后用这些数据来创建各种视觉效果,例如频谱图、波形图等,以响应音乐或其他音频的播放。游戏音效:可......
  • [ComfyUI教程]CATVTON-Flux:电商虚拟换衣!基于黑森林F1重绘和阿里In Context LORA电商
    前言CATVTON-Flux:基于黑森林F1和阿里In-Context-LORA虚拟换衣......
  • useContext和redux的区别
    useContext‌:主要用于解决组件间跨层级的状态共享问题。它允许组件树中的任何组件直接访问全局状态,而无需通过每层手动传递props。这适用于全局状态管理,如用户认证信息、主题设置等‌;useContext通过context的API创建的,在上层组件中使用Provider来包裹状态,并在需要访......
  • 视频流媒体播放器EasyPlayer.js出现WebGL: CONTEXT_LOST_WEBGL错误的原因
    选择一个兼容性好、性能稳定的H5视频播放器非常重要。市面上有几款实用的H.265网页播放器,例如EasyPlayer.js播放器,它支持H264和H265视频格式,并且针对低延迟直播进行了优化。那么播放器为什么会显示WebGL:CONTEXT_LOST_WEBGL错误呢?WebGL的CONTEXT_LOST_WEBGL错误通常表示WebGL......
  • Seed Lab实验:Attacks on the TCP Protocol
    一、docker使用docker换源:vim/etc/docker/daemon.json{"registry-mirrors":["https://docker.1panel.live"]}docker创建:docker-composebuilddocker开启:docker-composeupctrl+shift+T新建一个终端查询docker状态:dockps切换docker中的主机,例如:docker......
  • LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Ei
    报错内容spring-boot3.2.3Causedby:java.lang.IllegalArgumentException:LoggerFactoryisnotaLogbackLoggerContextbutLogbackisontheclasspath.EitherremoveLogbackorthecompetingimplementation(classorg.apache.logging.slf4j.Log4jLoggerFactorylo......
  • RAG与长上下文LLM(Long-Context LLM):一场AI领域的对决
    当前AI领域快速发展,各种新概念层出不穷,其中Retrieval-AugmentedGeneration(简称RAG)和长上下文LargeLanguageModels(LLMs,以下简称长上下文LLM)成为了当下热议的话题。开发者和研究人员在特定AI场景中,往往难以抉择是选择基于检索增强生成的系统架构(RAG(RetrievalAugmentedGenera......