1. 项目概述为什么需要组合多个远程MCP服务器最近在折腾AI Agent的开发发现一个挺有意思的痛点单个Agent的能力往往很局限。比如我想让一个Agent帮我规划出行路线它需要调用高德地图的API来获取实时路况和地点信息同时我又希望它能分析某个网页的性能问题这就需要连接Chrome DevTools Protocol来获取页面加载数据最后生成的路线报告和性能分析结果最好还能自动保存到本地文件系统里。如果为每一个功能都单独写一个Agent或者在一个Agent里硬编码所有服务的调用逻辑那代码会变得无比臃肿维护起来简直是噩梦。这时候模型上下文协议Model Context Protocol, MCP的价值就凸显出来了。MCP本质上是一种标准化的通信协议它允许AI模型或基于其构建的Agent以一种统一、安全的方式与外部工具、数据源和服务进行交互。你可以把它想象成AI世界的“USB-C接口”——无论后端连接的是地图服务、浏览器调试工具还是本地文件系统对于前端的Agent来说它只需要学会使用MCP这一种“插头”即可。所以这个项目的核心目标就是搭建一个能够同时接入并灵活调度多个远程MCP Server的AI Agent系统。具体来说我们要实现高德地图MCP Server将高德地图的POI搜索、路径规划、逆地理编码等能力封装成标准的MCP工具。Chrome DevTools MCP Server将Chrome浏览器的页面检查、网络请求捕获、性能指标获取等调试能力暴露给Agent。文件系统MCP Server提供安全的、受控的本地文件读写能力让Agent可以持久化数据。最终我们的Agent能够理解用户的复合指令例如“帮我找一下公司附近评分高于4.5的咖啡馆然后分析一下公司官网首页的加载速度最后把结果整理成一份报告保存到桌面。” Agent会自主决定调用顺序分别请求对应的MCP Server完成任务。这个方案的直接好处是解耦和复用。每个MCP Server可以独立开发、部署和升级Agent无需关心其内部实现。任何新的能力比如接入数据库、调用内部API都可以通过增加一个新的MCP Server来实现极大地扩展了Agent的能力边界。1.1 核心组件与架构预览在深入细节之前我们先从高层视角看看整个系统是如何组装的。这有助于理解后续每个部分扮演的角色。整个架构可以看作一个“星型”网络中心是我们的AI Agent或MCP Client四周连接着各个远程MCP Server。Agent与每个Server之间通过MCP协议通常基于SSE或WebSocket进行通信。[用户/前端] - [AI Agent (MCP Client)] | | (MCP over HTTP/SSE/WebSocket) | ----------------------------------------- | | | [高德地图 MCP Server] [Chrome DevTools MCP Server] [文件系统 MCP Server] | | | (高德API) (Chrome CDP) (本地FS)Agent (MCP Client)这是系统的大脑。它通常是一个集成了大语言模型LLM的应用负责理解用户意图、规划任务步骤、调用合适的工具并整合结果。它通过加载多个MCP Server的配置与它们建立连接。MCP Server这是系统的手和脚。每个Server都是一个独立的进程或服务它向Agent注册自己提供的“工具Tools”列表和“资源Resources”访问能力。监听来自Agent的“工具调用Tool Call”请求。执行具体的业务逻辑如调用高德API、操作浏览器、读写文件。将执行结果返回给Agent。MCP 传输层这是系统的神经网络。它定义了Client和Server之间如何交换数据。目前主流的方式是SSE (Server-Sent Events)适用于Server向Client单向推送数据流的场景在MCP中常用于Server主动通知Client有新的资源可用。Stdio (标准输入/输出)最简单的方式Server作为一个命令行程序启动通过stdin接收请求stdout发送响应。适合本地、简单的集成。HTTP或WebSocket对于远程Server这是更通用的选择。Agent通过HTTP POST发送请求Server返回JSON响应或者建立持久的WebSocket连接进行双向通信。我们本次的远程Server主要采用这种方式。理解了整体架构我们就可以分步拆解看看如何从零开始搭建这三个MCP Server并让Agent学会同时使用它们。2. 核心细节解析与实操要点构建一个稳健的多MCP Server系统关键在于每个Server的规范实现以及Agent端的正确配置。下面我们逐一拆解三个Server的核心实现逻辑和需要特别注意的“坑”。2.1 高德地图MCP Server封装第三方API的典范高德地图提供了丰富的HTTP API我们的任务就是将这些API“翻译”成MCP协议定义的工具。这里以“地点搜索”和“路径规划”两个核心工具为例。工具定义MCP协议 每个工具都需要在Server初始化时通过initialize握手过程告知Client。工具定义需要包含名称、描述、输入参数schema。清晰的描述对于LLM能否正确调用至关重要。# 伪代码示例工具定义 tools [ { name: amap_place_search, description: 根据关键词和城市搜索高德地图上的地点信息如餐馆、酒店。返回名称、地址、坐标、评分等。, inputSchema: { type: object, properties: { keywords: {type: string, description: 搜索关键词如‘咖啡馆’、‘清华大学’}, city: {type: string, description: 城市名称如‘北京’。建议限定城市以提高准确性。}, page: {type: integer, description: 页码默认为1} }, required: [keywords] } }, { name: amap_driving_route, description: 计算驾车路径规划。提供起点和终点的坐标或地址返回路线距离、预计时间、具体步骤和坐标点。, inputSchema: { type: object, properties: { origin: {type: string, description: 起点可以是地址如‘北京市朝阳区望京SOHO’或坐标经度,纬度。}, destination: {type: string, description: 终点格式同起点。}, strategy: {type: integer, description: 路径策略0-速度优先1-费用优先2-距离优先3-不走高速4-多策略综合5-不走高速且避免收费6-不走高速且躲避拥堵7-躲避拥堵8-不走高速且躲避拥堵9-躲避拥堵且不走高速, default: 0} }, required: [origin, destination] } } ]核心实现逻辑 Server收到形如{name: amap_place_search, arguments: {keywords: 星巴克, city: 杭州}}的调用请求后需要参数验证与转换检查必填参数将城市名转换为高德API需要的adcode城市编码。这里可以内置一个简单的城市-编码映射表或者调用高德的行政区域查询API动态获取。签名生成高德API要求所有请求必须使用key和sig参数。sig是对请求参数按特定规则排序、拼接后进行MD5加密得到的签名。这是最容易出错的地方务必严格按照高德官方文档的示例生成。异常处理与重试网络请求可能失败API可能返回错误码如INVALID_USER_KEY。Server必须捕获这些异常并以MCP协议规定的错误格式返回给Agent而不是让进程崩溃。对于网络波动可以考虑加入指数退避的重试机制。结果过滤与格式化高德返回的原始JSON可能包含大量Agent不需要的字段。Server应该做一层精简和格式化提取核心信息如名称、位置、评分并以清晰、结构化的JSON返回便于LLM理解和后续处理。实操心得API Key的管理绝对不要将高德API Key硬编码在代码或配置文件里更不要上传到公开仓库。推荐的做法是使用环境变量注入。在Server启动时读取AMAP_API_KEY环境变量。在生产环境可以使用密钥管理服务如Vault、AWS Secrets Manager。同时在Web控制台严格限制该Key的调用频率和IP白名单防止盗用。2.2 Chrome DevTools MCP Server与浏览器进程交互与高德API这种简单的请求-响应模式不同Chrome DevTools Protocol (CDP) 是一个基于WebSocket的双向协议需要管理浏览器实例的生命周期和长连接。我们的MCP Server需要扮演一个CDP Client的角色。核心挑战与方案浏览器实例管理Server启动时是应该启动一个新的Chrome/Chromium进程还是连接到一个已存在的浏览器为了隔离性和稳定性推荐为每个MCP Server会话启动一个独立的无头浏览器实例。可以使用puppeteer或playwright这类库来简化操作。会话与连接CDP连接是基于WebSocket的。Server需要维护这个连接并在其上创建多个“会话”来操作不同的目标如页面、Service Worker。当Agent调用工具时Server通过这个连接发送CDP命令如Page.navigate,Network.getResponseBody,Performance.getMetrics。工具设计CDP能力繁多我们应封装最常用的、对Agent有价值的工具。例如devtools_navigate: 导航到指定URL。devtools_capture_har: 捕获页面加载过程中的所有网络请求生成HAR格式数据供分析性能瓶颈。devtools_get_dom_element: 获取页面中特定选择器元素的属性、尺寸等信息。devtools_execute_script: 在页面上下文中执行JavaScript代码并返回结果。实现片段示例# 伪代码使用playwright async def handle_cdp_tool_call(tool_name, arguments): if tool_name devtools_capture_har: url arguments[url] # 1. 启动浏览器如果尚未启动 if not browser: browser await playwright.chromium.launch(headlessTrue) context await browser.new_context() # 2. 创建页面并启用HAR收集 page await context.new_page() await page.route_from_har(pathNone) # 或使用专门HAR导出库 # 3. 导航并等待 await page.goto(url, wait_untilnetworkidle) # 4. 获取HAR数据 har_data await page.evaluate(() window.performance.getEntries()) # 简化示例 # 5. 关闭页面保留浏览器上下文以供后续工具使用 await page.close() return {har_entries: har_data}注意事项资源清理与超时浏览器实例非常消耗内存。必须设计合理的生命周期管理。一种策略是Server启动时初始化浏览器在长时间无活动后例如10分钟自动关闭。另一种是为每个“会话”如一次完整的页面分析任务启动和关闭一个独立浏览器。同时务必为所有CDP操作设置超时防止因页面卡死导致Agent请求一直挂起。2.3 文件系统MCP Server在安全与便利间平衡让AI直接操作文件系统听起来有点危险。因此这个Server的核心设计原则是“最小权限”和“沙箱化”。安全边界设计工作目录限制Server启动时指定一个唯一的工作根目录如/mnt/agent_workspace/session_id。所有文件操作都被限制在此目录及其子目录下。Agent无法通过../../../这样的路径逃逸。工具粒度控制不要提供一个万能的execute_command工具。而是提供具体的、功能明确的工具fs_read_file: 读取指定路径的文本文件内容。fs_write_file: 向指定路径写入内容。可提供mode参数append或overwrite。fs_list_directory: 列出指定目录下的文件和子目录。fs_search_in_files: 在指定目录下递归搜索包含特定文本的文件。路径验证在Server端对所有传入的文件路径进行规范化并严格检查其是否位于允许的工作根目录之内。实现示例import os import pathlib WORKSPACE_ROOT pathlib.Path(os.environ.get(AGENT_WORKSPACE, /tmp/agent_workspace)).resolve() def safe_resolve_path(user_path: str) - pathlib.Path: 将用户提供的路径安全地解析为绝对路径并确保其在工作区内 requested_path (WORKSPACE_ROOT / user_path).resolve() # 关键安全检查确保解析后的路径仍然以WORKSPACE_ROOT开头 if not str(requested_path).startswith(str(WORKSPACE_ROOT)): raise PermissionError(fAccess to path {user_path} is denied.) return requested_path async def handle_fs_write(arguments): file_path safe_resolve_path(arguments[path]) # 确保父目录存在 file_path.parent.mkdir(parentsTrue, exist_okTrue) mode arguments.get(mode, overwrite) content arguments[content] if mode overwrite: file_path.write_text(content, encodingutf-8) elif mode append: with file_path.open(a, encodingutf-8) as f: f.write(content) return {status: success, path: str(file_path.relative_to(WORKSPACE_ROOT))}实操心得文件编码与并发始终明确指定文件编码如UTF-8避免在不同环境下出现乱码。如果Agent可能并发操作同一个文件需要考虑简单的锁机制如使用文件锁fcntl或数据库记录状态防止写冲突。对于大型文件读写可以采用流式处理避免一次性加载到内存。3. 实操过程与核心环节实现现在我们进入实战环节看看如何将上述三个Server运行起来并配置一个Agent以Claude Desktop/Claude for VS Code或自定义Client为例来同时连接它们。3.1 构建并部署远程MCP Server我们将三个Server都实现为基于HTTP的MCP Server这样它们可以部署在任何能运行Python/Node.js的环境里。技术栈选择语言Python。生态丰富有官方MCP SDK (anthropic-mcp)开发速度快。Web框架FastAPI。异步性能好自动生成API文档与MCP over HTTP天然契合。MCP库使用anthropic-mcp库它提供了Server和Client的抽象处理了协议握手、工具调用等底层细节。高德地图Server实现骨架# amap_mcp_server.py import os import hashlib import urllib.parse from typing import Any import httpx from fastapi import FastAPI, HTTPException from mcp import Server, types app FastAPI() mcp_server Server(amap-mcp-server) # 1. 初始化声明工具 mcp_server.list_tools() async def handle_list_tools() - list[types.Tool]: return [ types.Tool( nameplace_search, description搜索地点, inputSchema{ type: object, properties: { keywords: {type: string}, city: {type: string}, }, required: [keywords] } ), # ... 其他工具定义 ] # 2. 处理工具调用 mcp_server.call_tool() async def handle_call_tool(name: str, arguments: dict[str, Any]) - list[types.TextContent]: if name place_search: result await amap_place_search(arguments[keywords], arguments.get(city)) return [types.TextContent(typetext, textresult)] # ... 处理其他工具 raise HTTPException(404, fTool {name} not found) async def amap_place_search(keywords: str, city: str None) - str: api_key os.environ[AMAP_API_KEY] base_url https://restapi.amap.com/v3/place/text params {key: api_key, keywords: keywords, city: city, output: JSON} # 生成签名略 async with httpx.AsyncClient() as client: resp await client.get(base_url, paramsparams) resp.raise_for_status() data resp.json() # 格式化结果 pois data.get(pois, []) formatted [f{p[name]} {p[address]} (评分:{p.get(biz_ext,{}).get(rating,无)}) for p in pois[:5]] return \n.join(formatted) # 3. 将MCP Server挂载到FastAPI路由 app.mount(/mcp, mcp_server.create_asgi_app()) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)部署与运行为三个Server分别创建项目目录安装依赖fastapi,uvicorn,anthropic-mcp,httpx,playwright等。设置环境变量export AMAP_API_KEYyour_key。分别运行python amap_server.py,python cdp_server.py,python fs_server.py。它们将分别在8000, 8001, 8002端口监听。生产环境建议使用gunicorn或uvicorn配合多进程并使用Nginx反向代理配置SSL/TLS。3.2 配置AI Agent连接多个Server这里以配置Claude Desktop为例它原生支持MCP。你需要编辑其配置文件如~/Library/Application Support/Claude/claude_desktop_config.jsonon macOS。{ mcpServers: { amap: { command: npx, args: [ -y, modelcontextprotocol/server-amap, --api-key, ${AMAP_API_KEY} ], env: { AMAP_API_KEY: your_key_here } }, chrome-devtools: { command: node, args: [ /path/to/your/cdp-mcp-server/build/index.js ], env: { CHROME_PATH: /usr/bin/google-chrome-stable } }, filesystem: { command: python, args: [ -m, filesystem_mcp_server ], env: { AGENT_WORKSPACE: /path/to/secure/workspace } } } }如果你的Server是远程HTTP服务Claude Desktop目前可能不支持直接配置HTTP端点。这时你需要自己编写一个轻量级的MCP桥接客户端Bridge Client。自定义桥接Client实现思路使用anthropic-mcp的Client库通过Stdio方式启动就像上面配置文件那样但这个“本地命令”实际上是一个你自己的脚本。这个脚本桥接Client的工作是与本地Claude建立Stdio连接同时与远程的HTTP MCP Server建立连接。它作为中间人将Claude发出的MCP请求如tools/call转发给对应的远程HTTP Server再将响应返回给Claude。# bridge_client.py 简化示例 import asyncio import json import sys from mcp import ClientSession, StdioServerParameters import httpx async def forward_to_remote_server(tool_name, arguments, server_url): async with httpx.AsyncClient() as client: # 这里需要根据远程Server的HTTP接口格式进行适配 resp await client.post(f{server_url}/tools/call, json{name: tool_name, arguments: arguments}) return resp.json() async def main(): # 1. 与Claude Desktop通过stdio通信 server_params StdioServerParameters(commandsys.executable, args[dummy.py]) # 2. 初始化会话Claude会发送initialize请求 # 3. 在收到tools/call请求时根据工具名判断应该转发到哪个远程Server # if tool_name.startswith(amap_): target_url http://localhost:8000 # elif tool_name.startswith(devtools_): target_url http://localhost:8001 # ... # 4. 调用forward_to_remote_server并将结果格式化成MCP响应返回 if __name__ __main__: asyncio.run(main())然后在Claude配置中将command指向这个桥接脚本。这样就实现了Claude与多个远程Server的透明通信。3.3 Agent的提示工程与任务规划当Agent连接了多个强大的工具后如何让它高效、准确地使用这些工具就是提示工程的任务了。系统提示词System Prompt设计要点工具介绍清晰列出所有可用工具的名称、描述和参数。这是最重要的部分。使用规则原子操作一次只调用一个工具。等待结果返回后再根据结果决定下一步。参数检查调用前在心里“模拟”一下工具需要的参数是否齐全、格式是否正确。错误处理如果工具调用失败分析错误信息不要盲目重试同样的参数。任务分解范例给出一两个复杂任务被分解成多个工具调用的例子。用户请求“看看上海陆家嘴附近有什么好吃的本帮菜然后查一下从人民广场开车过去要多久最后把推荐的前三家餐厅和用时信息存到一个叫‘美食推荐.txt’的文件里。”Agent思考过程调用amap_place_search参数keywords“本帮菜” city“上海”。从结果中筛选陆家嘴附近的可能需要结合amap_around_search或对结果进行坐标判断。假设得到“餐厅A餐厅B餐厅C”获取它们的详细地址。调用amap_driving_route参数origin“人民广场” destination“餐厅A地址”。记录时间。对餐厅B、C重复步骤3。整理信息调用fs_write_file参数path“美食推荐.txt” content“...”。在Claude Desktop中你可以通过配置systemPrompt字段来注入这些指令。对于自定义的Agent应用则需要在每次与LLM对话时将这部分系统提示词放在消息列表的开头。4. 常见问题与排查技巧实录在实际搭建和运行过程中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案。4.1 连接与通信问题问题1Agent报告“无法连接到MCP Server”或“握手失败”。排查思路检查Server进程首先用ps aux | grep python或lsof -i:8000确认你的MCP Server进程是否在运行并且监听在正确的端口。检查网络可达性从Agent所在机器使用curl http://server_ip:port/mcp/initialize如果是HTTP Server测试连通性。如果失败检查防火墙规则如云服务器的安全组。检查协议兼容性确认Agent或桥接Client和Server使用的MCP协议版本是否兼容。查看双方的日志握手阶段initialize请求和响应的日志最为关键。检查Stdio Server如果使用Stdio方式检查配置文件中的command和args路径是否正确该命令是否有执行权限。问题2工具调用超时或无响应。排查思路Server端日志查看MCP Server的日志看是否收到了请求以及卡在哪个环节。是高德API调用慢还是浏览器启动超时设置超时在Server实现中为所有外部调用网络请求、CDP命令设置合理的超时时间如10-30秒并在超时后向Agent返回明确的错误信息而不是让请求挂起。资源泄漏检查Chrome DevTools Server是否打开了太多浏览器页面或实例没有关闭导致内存耗尽。实现自动清理机制。4.2 工具调用逻辑问题问题3Agent调用了工具但参数不对导致Server返回错误。排查思路优化工具描述LLM依赖工具定义中的description和inputSchema里的参数描述来理解如何使用工具。确保描述清晰无歧义。例如对于city参数写明“请输入完整的城市中文名如‘北京市’‘杭州市’”。提供更详细的错误信息当Server端参数验证失败时返回的错误信息应尽可能指导LLM修正。例如不要只返回“city is required”可以返回“参数‘city’缺失请提供城市名称以缩小搜索范围例如‘city’‘上海’。”在系统提示词中强化规则在给Agent的系统指令中加入“调用工具前请确保你已完全理解每个参数的含义和格式要求”。问题4Agent陷入循环调用或调用顺序不合理。排查思路分析Agent的思考过程如果使用的LLM支持中间链式思考如Claude的思考过程查看它是如何推理和规划的。可能是任务分解逻辑有误。调整系统提示词在提示词中提供更优秀的任务分解范例强调“逐步思考”、“拿到上一步的结果后再进行下一步”。实施强制中断在自定义Client中可以设置一个工具调用次数上限如20次达到上限后强制停止并提示Agent反思。4.3 性能与稳定性问题问题5系统响应慢尤其是涉及浏览器操作的任务。优化策略浏览器池对于Chrome DevTools Server不要为每个工具调用都启动新浏览器。维护一个小的浏览器实例池实现复用。缓存对于高德地图的某些查询如城市编码转换、固定的地点信息可以在Server端添加一个内存缓存如functools.lru_cache设置合理的过期时间。异步并发如果Agent的任务中有几个步骤是互不依赖的例如同时查询去多个地点的路线可以在Agent端或一个协调性的Server端尝试并发调用多个工具。但这需要更复杂的任务规划和结果聚合逻辑。问题6文件系统Server出现权限错误或路径混乱。排查与加固绝对路径与相对路径明确约定。建议在工具定义中说明“请使用相对于工作空间的路径例如‘reports/today.md’”。日志记录在Server端详细记录每个文件操作请求的原始路径、解析后的绝对路径、操作用户或会话ID。一旦出错日志是唯一的排查依据。定期清理设置一个定时任务清理工作空间目录中过于陈旧的文件如超过7天防止磁盘被写满。4.4 安全与监控问题7如何监控多个MCP Server的健康状态解决方案健康检查端点为每个HTTP MCP Server增加一个/health端点返回服务状态、依赖状态如高德API连通性、浏览器进程是否存活。集中式日志使用structlog或logging库将日志输出到标准输出然后通过Docker或系统服务管理器如systemd收集到ELK或Loki等日志平台。基础监控使用Prometheus等工具在Server中暴露关键指标如工具调用次数、耗时、错误率并设置告警。问题8如何控制对MCP Server的访问权限解决方案网络隔离将MCP Server部署在内网Agent桥接Client通过内网访问。如果必须暴露公网使用反向代理如Nginx配置IP白名单或API网关进行认证。传输加密务必使用HTTPSWSS for WebSocket。自签名证书可用于测试生产环境使用Let‘s Encrypt或购买证书。应用层认证可以在MCP协议之上在HTTP头部添加一个简单的API Key认证。在Server端验证这个Key是否有效。虽然MCP协议本身未定义认证但这是一种实用的增强。搭建这样一个多MCP Server的Agent系统就像为AI组装一套多功能瑞士军刀。初期调试会花费一些时间尤其是协议对接和错误处理。但一旦跑通你会发现Agent的能力获得了质的飞跃。它不再是一个只会聊天的模型而是一个能真正替你操作数字世界、获取并处理信息的智能助手。最关键的是这个架构是模块化和可扩展的未来任何新的能力都可以通过开发一个新的、专注的MCP Server来快速集成。