全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-04-16_前端搭建 MCP Client(Web版)+ Server + Agent 实践

您的位置:首页 >> 新闻 >> 行业资讯

前端搭建 MCP Client(Web版)+ Server + Agent 实践 关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding 先上个效果图,上图是在 web 版Client中使用todoist-mcp-server帮我新建了一条todolist。 本文主要介绍整体的思路和实现以及踩坑记录。 前言?MCP(Model Context Protocol)是一种开放协议,旨在通过标准化接口实现大语言模型(LLMs)与外部数据源及工具的无缝集成。MCP由 Anthropic 公司在2024年底推出,其设计理念类似于USB接口,为AI模型提供了一个“即插即用”的扩展能力,使其能够轻松连接至不同的工具和数据源?。想深入了解可查看官方文档,这里只做实战经验分享。 概念介绍?MCP Hosts(MCP 应用):如Claude Desktop、IDE、AI应用等,希望通过MCP访问数据或工具。MCP Clients(MCP 客户端):与一对一与服务端进行连接,相当于我们应用中实现数据库交互需要实现的一个客户端。MCP Servers(MCP 服务端):基于MCP协议实现特定功能的程序。Local Data Sources:本地数据源,公MCP服务器进行本地访问。Remote Services:远端服务,供MCP服务器访问远端访问,例如api的方式。?本文主要搭建Web 版本的 MCP Client和MCP Server。 技术栈系统要求:Node.js = 18(本地用了v20) 核心依赖库:CopilotKit、LangChain及其生态。 CopilotKit:React UI + 适用于 AI Copilot、AI 聊天机器人和应用内 AI 代理的优雅基础架构。LangChain.js和LangGraph:LangChain相关主要用于开发agent。langchainjs-mcp-adapters:提供了一个轻量级的包装器,使得 MCP 与 LangChain.js 兼容。modelcontextprotocol/typescript-sdk:MCP TypeScript SDKopen-mcp-client:CopilotKit 开源的 MCP Client。mcp-server-supos:一个可用的 MCP Server。Client页面大概这样,包括:左侧管理MCP Server、右侧聊天机器人 技术方案?声明:此 Client 是基于CopilotKit 开源的 MCP Clientopen-mcp-client二次改造 该代码库主要分为两个部分: /agent– 连接到 MCP Server并调用其工具的LangGraph代理(Python)。/app– 使用 CopilotKit 进行UI和状态同步的前端应用程序(Next.js)。由于Python的agent在Windows环境下运行时报错: 本人Python编码能力有限,基于此改造成了基于JS的agent(/agent-js部分),后续均以agent-js为例;想用Python的也可按后续的改动点对/agent进行修改。 一、agent部分文件结构 核心代码「agent.js」- 基于langgraph创建workflow,其中主要节点为chat_node,该节点功能点: 定义LLMimport{ ChatOpenAI }from"@langchain/openai"; // import { HttpsProxyAgent } from "https-proxy-agent"; // const agentProxy = new HttpsProxyAgent("http://127.0.0.1:xxxx"); ... // 1 Define the model constmodel =newChatOpenAI( { temperature:0, model:"gpt-4o", }, // todo: test, 走本地代理便于翻墙 // { // httpAgent: agentProxy, // } ... ?「注意:本地联调需访问 openai 时,如果是使用的代理工具,还是需要在代码里指定代理地址(HttpsProxyAgent)」 ?从state获取MCP Server Configs,创建MCP Client连接到MCP Server,连通后获取Server的tools。(@langchain/mcp-adapters)constmcpConfig: any = state.mcp_config || {}; // 重要:设置环境变量时,最好把当前进程的环境变量也传递过去,确保执行Server的子进程需要的环境变量都存在 letnewMcpConfig: any = {}; Object.keys(mcpConfig).forEach((key) ={ newMcpConfig[key] = { ...mcpConfig[key] }; if(newMcpConfig[key].env) { newMcpConfig[key].env = { ...process.env, ...newMcpConfig[key].env }; } console.log("****mcpConfig****", mcpConfig); // 2 Create client constclient =newMultiServerMCPClient(newMcpConfig); // examples // const client = new MultiServerMCPClient({ // math: { // transport: "stdio", // command: "npx", // args: ["-y", "mcp-server-supos"], // env: {"SUPOS_API_KEY": "xxxxx"} // }, // }); // 3 Initialize connection to the server awaitclient.initializeConnections(); consttools = client.getTools(); 基于model和tools创建代理,并调用模型发送状态中的消息// 4 Create the React agent width model and tools constagent = createReactAgent({ llm: model, tools, // 5 Invoke the model with the system message and the messages in the state constresponse =awaitagent.invoke({messages: state.messages }); 「完整代码」 agent.js /** * This is the main entry point for the agent. * It defines the workflow graph, state, tools, nodes and edges. */ import{ RunnableConfig }from"@langchain/core/runnables"; import{ MemorySaver, START, StateGraph, Command, END, }from"@langchain/langgraph"; import{ createReactAgent }from"@langchain/langgraph/prebuilt"; import{ Connection, MultiServerMCPClient }from"@langchain/mcp-adapters"; import{ AgentState, AgentStateAnnotation }from"./state"; import{ getModel }from"./model"; // 判断操作系统 constisWindows = process.platform ==="win32"; constDEFAULT_MCP_CONFIG: Recordstring, Connection = { supos: { command: isWindows ?"npx.cmd":"npx", args: [ "-y", "mcp-server-supos", ], env: { SUPOS_API_URL: process.env.SUPOS_API_URL ||"", SUPOS_API_KEY: process.env.SUPOS_API_KEY ||"", SUPOS_MQTT_URL: process.env.SUPOS_MQTT_URL ||"", }, transport:"stdio", }, }; asyncfunctionchat_node(state: AgentState, config: RunnableConfig){ // 1 Define the model constmodel = getModel(state); constmcpConfig: any = { ...DEFAULT_MCP_CONFIG, ...(state.mcp_config || {}) }; // 重要:设置环境变量时,最好把当前进程的环境变量也传递过去,确保执行Server的子进程需要的环境变量都存在 letnewMcpConfig: any = {}; Object.keys(mcpConfig).forEach((key) ={ newMcpConfig[key] = { ...mcpConfig[key] }; if(newMcpConfig[key].env) { newMcpConfig[key].env = { ...process.env, ...newMcpConfig[key].env }; } console.log("****mcpConfig****", mcpConfig); // 2 Create client constclient =newMultiServerMCPClient(newMcpConfig); // const client = new MultiServerMCPClient({ // math: { // transport: "stdio", // command: "npx", // args: ["-y", "mcp-server-supos"], // env: {"SUPOS_API_KEY": "xxxxx"} // }, // }); // 3 Initialize connection to the server awaitclient.initializeConnections(); consttools = client.getTools(); // 4 Create the React agent width model and tools constagent = createReactAgent({ llm: model, tools, // 5 Invoke the model with the system message and the messages in the state constresponse =awaitagent.invoke({messages: state.messages }); // 6 Return the response, which will be added to the state return[ newCommand({ goto: END, update: {messages: response.messages }, }), } // Define the workflow graph constworkflow =newStateGraph(AgentStateAnnotation) .addNode("chat_node", chat_node) .addEdge(START,"chat_node"); constmemory =newMemorySaver(); exportconstgraph = workflow.compile({ checkpointer: memory, }); model.js /** * This module provides a function to get a model based on the configuration. */ import{ BaseChatModel }from"@langchain/core/language_models/chat_models"; import{ AgentState }from"./state"; import{ ChatOpenAI }from"@langchain/openai"; import{ ChatAnthropic }from"@langchain/anthropic"; import{ ChatMistralAI }from"@langchain/mistralai"; // import { HttpsProxyAgent } from "https-proxy-agent"; // todo test agentProxy // const agentProxy = new HttpsProxyAgent("http://127.0.0.1:7897"); functiongetModel(state: AgentState):BaseChatModel{ /** * Get a model based on the environment variable. */ conststateModel = state.model; conststateModelSdk = state.modelSdk; // 解密 conststateApiKey = atob(state.apiKey ||""); constmodel = process.env.MODEL || stateModel; console.log( `Using stateModelSdk:${stateModelSdk}, stateApiKey:${stateApiKey}, stateModel:${stateModel}` if(stateModelSdk ==="openai") { returnnewChatOpenAI({ temperature:0, model: model ||"gpt-4o", apiKey: stateApiKey ||undefined, } // { // httpAgent: agentProxy, // } } if(stateModelSdk ==="anthropic") { returnnewChatAnthropic({ temperature:0, modelName: model ||"claude-3-7-sonnet-latest", apiKey: stateApiKey ||undefined, } if(stateModelSdk ==="mistralai") { returnnewChatMistralAI({ temperature:0, modelName: model ||"codestral-latest", apiKey: stateApiKey ||undefined, } thrownewError("Invalid model specified"); } export{ getModel }; state.js import{ Annotation }from"@langchain/langgraph"; import{ CopilotKitStateAnnotation }from"@copilotkit/sdk-js/langgraph"; import{ Connection }from"@langchain/mcp-adapters"; // Define the AgentState annotation, extending MessagesState exportconstAgentStateAnnotation = Annotation.Root({ model: Annotationstring, modelSdk: Annotationstring, apiKey: Annotationstring, mcp_config: AnnotationConnection, ...CopilotKitStateAnnotation.spec, }); exporttype AgentState =typeofAgentStateAnnotation.State; 构建和运行定义langgraph.json配置文件,定义agent相关配置,比如agent名称:sample_agent等{ "node_version":"20", "dockerfile_lines": [], "dependencies": ["."], "graphs": { "sample_agent":"./src/agent.ts:graph"// 定义agent名称等,用于前端指定使用 }, "env":".env"// 指定环境变量从.env文件中获取,生产环境可以删除该配置,从系统变量中获取 } 在本地运行时,在根路径/agent-js添加.env文件 LANGSMITH_API_KEY=lsv2_...OPENAI_API_KEY=sk-...2.借助命令行工具@langchain/langgraph-cli进行构建和运行,在package.json中定义脚本:"scripts": { "start":"npx @langchain/langgraph-cli dev --host localhost --port 8123", "dev":"npx @langchain/langgraph-cli dev --host localhost --port 8123 --no-browser" }, ?加上--no-browser不会自动打开本地调试的studio页面 ?运行后可以在Studio预览联调等 (Studio:https://smith.langchain.com/studio/thread?baseUrl=http%3A%2F%2Flocalhost%3A8123) 注意点(踩坑记录)1. 引入modelcontextprotocol/typescript-sdk报错:?@modelcontextprotocol/sdk fails in CommonJS projects due to incompatible ESM-only dependency (pkce-challenge) 主要是modelcontextprotocol/ typescript-sdk的cjs包里面引用的pkce-challenge不支持cjs 官方的issues也有提出些解决方案,但目前为止官方还未发布解决了该问题的版本 ?「解决:package.json添加"type": "module"字段,声明项目使用「ES Modules (ESM)」 规范」 ?2. 配置MCP Server环境变量env问题?例如:Node.js 的child_process.spawn()方法无法找到例如npx等可执行文件。 「环境变量PATH缺失」,系统未正确识别npx的安装路径。 ?可能的原因: 1)MCP Server配置了env参数后,导致传入的env覆盖了默认从父进程获取的环境变量?「解决:对配置了env的Server,将当前的环境变量合并传入」 constmcpConfig: any = state.mcp_config || {}; // 重要:设置环境变量时,最好把当前进程的环境变量也传递过去,确保执行Server的子进程需要的环境变量都存在 letnewMcpConfig: any = {}; Object.keys(mcpConfig).forEach((key) ={ newMcpConfig[key] = { ...mcpConfig[key] }; if(newMcpConfig[key].env) { newMcpConfig[key].env = { ...process.env, ...newMcpConfig[key].env }; } 2)「跨平台路径问题」:比如在 Windows 中直接调用npx需使用npx.cmd// 判断操作系统 constisWindows = process.platform ==="win32"; constDEFAULT_MCP_CONFIG: Recordstring, Connection = { supos: { command: isWindows ?"npx.cmd":"npx", args: [ "-y", "mcp-server-supos", ], env: { SUPOS_API_URL: process.env.SUPOS_API_URL ||"", SUPOS_API_KEY: process.env.SUPOS_API_KEY ||"", SUPOS_MQTT_URL: process.env.SUPOS_MQTT_URL ||"", }, transport:"stdio", }, }; 二、前端应用部分前端应用部分改动主要是页面上的一些功能添加等,例如支持选模型,支持配置env参数等,页面功能相关的内容就略过,可以直接看open-mcp-client,这里简单介绍下整体的一个架构。 架构方案主要是CopilotKit+Next.js,先看下「CopilotKit」官方的一个架构图: 根据本文实际用到的简化下(本文采用的CoAgents模式) 核心代码(以Next.js为例)?核心依赖@copilotkit/react-ui@copilotkit/react-core@copilotkit/runtime 1. 设置运行时端点/app/api/copilotkit/route.ts:设置agent远程端点 import{ CopilotRuntime, ExperimentalEmptyAdapter, copilotRuntimeNextJSAppRouterEndpoint, langGraphPlatformEndpoint }from"@copilotkit/runtime";; import{ NextRequest }from"next/server"; // You can use any service adapter here for multi-agent support. constserviceAdapter =newExperimentalEmptyAdapter(); construntime =newCopilotRuntime({ remoteEndpoints: [ langGraphPlatformEndpoint({ // agent部署地址 deploymentUrl:`${process.env.AGENT_DEPLOYMENT_URL ||'http://localhost:8123'}`, langsmithApiKey: process.env.LANGSMITH_API_KEY, agents: [ { name:'sample_agent',// agent 名称 description:'A helpful LLM agent.', } ] }), ], }); exportconstPOST =async(req: NextRequest) = { const{ handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ runtime, serviceAdapter, endpoint:"/api/copilotkit", returnhandleRequest(req); }; 2. 页面接入 CopilotKit UI/app/layout.tsx:页面最外层用CopilotKit包裹,配置runtimeUrl和agent importtype { Metadata }from"next"; import{ Geist, Geist_Mono }from"next/font/google"; import"./globals.css"; import"@copilotkit/react-ui/styles.css"; import{ CopilotKit }from"@copilotkit/react-core"; constgeistSans = Geist({ variable:"--font-geist-sans", subsets: ["latin"], }); constgeistMono = Geist_Mono({ variable:"--font-geist-mono", subsets: ["latin"], }); exportconstmetadata: Metadata = { title:"Open MCP Client", description:"An open source MCP client built with CopilotKit ??", }; exportdefaultfunctionRootLayout({ children, }: Readonly{ children: React.ReactNode; }){ return( htmllang="en" body className={`${geistSans.variable} ${geistMono.variable}antialiasedw-screenh-screen`} CopilotKit runtimeUrl="/api/copilotkit" agent="sample_agent" showDevConsole={false} {children} /CopilotKit /body /html } /app/page.tsx:选择需要的聊天组件,例如CopilotPopup "use client"; import{ CopilotPopup }from"@copilotkit/react-ui"; exportfunctionHome(){ return( YourMainContent/ CopilotChat className="h-full flex flex-col" instructions={ "Youareassistingtheuserasbestasyoucan.Answerinthebestwaypossiblegiventhedatayouhave." } labels={{ title:"MCPAssistant", initial:"Needanyhelp?", }} / } 构建和运行这里就参照 Next.js 官方即可 package.json "scripts": { "dev-frontend":"pnpm i && next dev --turbopack", "dev-agent-js":"cd agent-js && pnpm i && npx @langchain/langgraph-cli dev --host 0.0.0.0 --port 8123 --no-browser", "dev-agent-py":"cd agent && poetry install && poetry run langgraph dev --host 0.0.0.0 --port 8123 --no-browser", "dev":"pnpx concurrently \"pnpm dev-frontend\" \"pnpm dev-agent-js\" --names ui,agent --prefix-colors blue,green", "build":"next build", "start":"next start", "lint":"next lint" }, Server建议直接参考MCP Server Typescript SDK示例开发,官网文档的用法更新没那么及时,容易走弯路。 ?mcp-server-supos是一个可用的 MCP Server,也发布了对应的npm 包。 ?这里截取核心代码片段,想了解更多可点击查看源码和使用文档等。 核心代码提供tool-调用API查询信息实时订阅MQTT topic数据进行缓存,用于提供 tool 查询分析最新数据示例 server.resourceindex.ts #!/usr/bin/env node import{ McpServer }from"@modelcontextprotocol/sdk/server/mcp.js"; import{ StdioServerTransport }from"@modelcontextprotocol/sdk/server/stdio.js"; importfetchfrom"node-fetch"; import{ z }from"zod"; importfs, { readFileSync }from"fs"; import_from"lodash"; importmqttfrom"mqtt"; import{ pathToFileURL }from"url"; import{ createFilePath }from"./utils.js"; letSUPOS_API_URL = process.env.SUPOS_API_URL; letSUPOS_API_KEY = process.env.SUPOS_API_KEY; letSUPOS_MQTT_URL = process.env.SUPOS_MQTT_URL; if(!SUPOS_API_URL) { console.error("SUPOS_API_URL environment variable is not set"); process.exit(1); } if(!SUPOS_API_KEY) { console.error("SUPOS_API_KEY environment variable is not set"); process.exit(1); } constfilePath = createFilePath(); constfileUri = pathToFileURL(filePath).href; asyncfunctiongetModelTopicDetail(topic: string):Promiseany{ consturl =`${SUPOS_API_URL}/open-api/supos/uns/model?topic=${encodeURIComponent( topic )}`; constresponse =awaitfetch(url, { headers: { apiKey:`${SUPOS_API_KEY}`, }, if(!response.ok) { thrownewError(`SupOS API error:${response.statusText}`); } returnawaitresponse.json(); } functiongetAllTopicRealtimeData(){ // 缓存实时数据,定时写入缓存文件 constcache =newMap(); lettimer: any =null; constoptions = { clean:true, connectTimeout:4000, clientId:"emqx_topic_all", rejectUnauthorized:false, reconnectPeriod:0,// 不进行重连 constconnectUrl = SUPOS_MQTT_URL; if(!connectUrl) { return; } constclient = mqtt.connect(connectUrl, options); client.on("connect",function(){ client.subscribe("#",function(err){ // console.log("err", err); client.on("message",function(topic, message){ cache.set(topic, message.toString()); client.on("error",function(error){ // console.log("error", error); client.on("close",function(){ if(timer) { clearInterval(timer); } // 每 5 秒批量写入一次 timer = setInterval(()={ constcacheJson =JSON.stringify( Object.fromEntries(Array.from(cache)), null, 2 // 将更新后的数据写入 JSON 文件 fs.writeFile( filePath, cacheJson, { encoding:"utf-8", }, (error) = { if(error) { fs.writeFile( filePath, JSON.stringify({msg:"写入数据失败"},null,2), {encoding:"utf-8"}, () = {} } } },5000); } functioncreateMcpServer(){ constserver =newMcpServer( { name:"mcp-server-supos", version:"0.0.1", }, { capabilities: { tools: {}, }, } // Static resource server.resource("all-topic-realtime-data", fileUri,async(uri) = ({ contents: [ { uri: uri.href, text: readFileSync(filePath, {encoding:"utf-8"}), }, ], })); server.tool( "get-model-topic-detail", {topic: z.string() }, async(args: any) = { constdetail =awaitgetModelTopicDetail(args.topic); return{ content: [{type:"text",text:`${JSON.stringify(detail)}`}], } server.tool("get-all-topic-realtime-data", {},async() = { return{ content: [ { type:"text", text: readFileSync(filePath, {encoding:"utf-8"}), }, ], asyncfunctionrunServer(){ consttransport =newStdioServerTransport(); constserverConnect =awaitserver.connect(transport); console.error("SupOS MCP Server running on stdio"); returnserverConnect; } runServer().catch((error) ={ console.error("Fatal error in main():", error); process.exit(1); } asyncfunctionmain(){ try{ createMcpServer(); getAllTopicRealtimeData(); }catch(error) { console.error("Error in main():", error); process.exit(1); } } main(); utils.ts importfsfrom"fs"; importpathfrom"path"; exportfunctioncreateFilePath( filedir: string =".cache", filename: string ="all_topic_realdata.json" ){ // 获取项目根路径 constrootPath = process.cwd(); // 创建缓存目录 constfilePath = path.resolve(rootPath, filedir, filename); constdirPath = path.dirname(filePath); // 检查目录是否存在,如果不存在则创建 if(!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, {recursive:true } returnfilePath; } exportfunctionreadFileSync(filePath: string, options: any){ try{ returnfs.readFileSync(filePath, options); }catch(err) { return`读取文件时出错:${err}`; } } 如何使用「Client」:目前支持MCP协议的客户端已有很多,比如桌面端应用Claude for Desktop,或者IDE的一些插件等(VSCode的Cline插件),想了解已支持的客户端可访问Model Context Protocol Client 「Server」:除了官方例子Model Context Protocol Client外,已有很多网站整合了 MCP Servers,例如mcp.so,Glama等。 下面列举几个介绍下: 1. 配合本文 web 版 Client 使用(以todoist-mcp-server为例子)1)配置2)使用2. 配合 Claude 使用具体可参考:mcp-server-supos README.md,服务换成自己需要的即可 3. 使用VSCode的Cline插件由于使用npx找不到路径,这里以node执行本地文件为例 1)配置2)使用结语以上便是近期使用MCP的一点小经验~ 整理完后看了下,如果只是单纯想集成些MCP Server,其实可以不用agent形式,直接使用copilotkit的标准模式,在本地服务调用langchainjs-mcp-adapters和 LLM 即可,例如: import{ CopilotRuntime, LangChainAdapter, copilotRuntimeNextJSAppRouterEndpoint, }from'@copilotkit/runtime'; import{ ChatOpenAI }from"@langchain/openai"; import{ NextRequest }from'next/server'; // todo: 调用 @langchain/mcp-adapters 集成 MCP Server 获取 tools 给到大模型 ... const model =newChatOpenAI({model:"gpt-4o",apiKey: process.env.OPENAI_API_KEY }); constserviceAdapter =newLangChainAdapter({ chainFn:async({ messages, tools }) = { returnmodel.bindTools(tools).stream(messages); // or optionally enable strict mode // return model.bindTools(tools, { strict: true }).stream(messages); } }); construntime =newCopilotRuntime(); exportconstPOST =async(req: NextRequest) = { const{ handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ runtime, serviceAdapter, endpoint:'/api/copilotkit', returnhandleRequest(req); }; 但这样可能少了些上下文状态等,具体可以下来都试试~ 点击关注公众号,“技术干货” 及时达! 阅读原文

上一篇:2019-06-16_上海:藏在弄堂里的烟火气 | 一天 下一篇:2022-08-17_国家部委主办、高端产业平台!国内规格最高的人工智能大赛火热报名中!

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价