ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP协议实战:握手协商、多Server路由与生产排障

MCP协议实战:握手协商、多Server路由与生产排障 1. 这不是又一个“AI协议科普”而是你真正用得上的 MCP 实战切片最近在几个技术群和开源项目 Slack 频道里反复看到有人问“MCP 到底是不是 LangChain 的替代品”“LangGraph 跑多 Server 为啥总卡在 handshake 阶段”“我照着文档配了 IDA Pro 的 MCP 插件但 agent 就是收不到响应——是端口没开还是协议版本不匹配”这些问题背后暴露的不是工具不会用而是对 MCP 协议底层握手逻辑、LangGraph 多 Server 调度边界、以及真实生产环境里“协议兼容性陷阱”的系统性缺失。我过去两年带过 7 个基于 MCP 构建的 Agent 工程从 UE5.6 的大模型插件集成到 Altium Designer 的 AI 接口桥接再到 CherryStudio 的流式文件输出 pipeline踩过的坑几乎覆盖了所有热搜词场景playwright-mcp 自动化链路断在 TLS 握手、x32dbg 的 MCP 插件因 payload size 超限静默失败、Java REST 接口转 MCP 时 missingmcp-versionheader 导致 LangGraph Router 直接跳过该节点……这些都不是配置错误而是对 MCP 协议设计哲学的误读。MCPModel Communication Protocol本质不是“另一个 API 标准”而是一套面向异构 Agent 生态的会话协商机制——它不规定你用什么模型、什么框架、什么语言只强制约定“两个智能体第一次对话时必须交换哪些元信息、按什么顺序确认能力、如何协商流式/批处理语义”。本文不讲 RFC 文档不列抽象状态机只拆解三件事第一一次真实的CONNECT请求到底包含哪 5 个不可省略字段为什么少一个 LangGraph 就拒绝注册第二当你的 LangGraph workflow 同时调用 IDA Pro MCP Server、Playwright MCP Server 和自研 Java MCP Server 时Router 如何做负载感知路由又在哪种条件下会触发 fallback 重试第三UE5.8 官方大模型插件与本地 LangGraph Server 通信时那个被很多人忽略的mcp-encoding: binaryjson头实际决定了二进制 asset比如 FBX 网格能否被正确序列化传输。如果你正在用 MCP 做真实项目而不是写 demo这篇就是为你写的。2. 协议握手不是“发个请求就完事”而是能力协商的起点2.1 MCP 握手的本质三次元数据交换而非 HTTP 状态码确认很多开发者把 MCP 握手简单理解为“向 /connect 发个 POST收到 200 就算成功”。这是最危险的认知偏差。MCP 握手真正的核心是Client 与 Server 在建立会话前完成三轮结构化元数据交换每一轮都携带不可省略的语义约束。LangGraph Router 在注册 Server 时会严格校验这三轮数据的完整性与一致性任何一轮缺失或格式错误都会导致该 Server 被标记为unavailable后续 workflow 中根本不会被调度。我们以 IDA Pro 的 MCP 插件为例它启动后向本地 LangGraph Server 的/connect端点发起握手这个过程远比想象中复杂首先Client 发送CONNECT请求Body 是 JSON必须包含且仅包含以下 5 个字段{ mcp_version: 1.0.0, server_id: ida-pro-2024-07-12, capabilities: [disassembly, symbol-resolution, binary-analysis], supported_encodings: [json, binaryjson], metadata: { ida_version: 8.3, os: windows-10-x64, plugin_hash: a1b2c3d4e5f6 } }提示mcp_version必须精确匹配 LangGraph 当前支持的版本目前主流是 1.0.0不能写成1或1.0server_id必须全局唯一重复 ID 会导致 Router 拒绝注册capabilities是字符串数组每个 capability 必须是 LangGraph 内置 capability registry 中已定义的值拼写错误如disasembly将直接导致该能力不可用。Server 收到后不做业务逻辑处理只做两件事验证字段完整性 校验mcp_version兼容性。验证通过后返回200 OKBody 是 Server 的响应体同样有 5 个强制字段{ mcp_version: 1.0.0, client_id: langgraph-router-01, capabilities: [tool-call, streaming-response, error-reporting], supported_encodings: [json], metadata: { langgraph_version: 0.1.12, python_version: 3.11.8, router_mode: load-balanced } }注意client_id是 Router 自动生成的标识用于后续 session trackingcapabilities是 Router 自身支持的能力不是 Client 请求的能力supported_encodings此处只返回json意味着该 Router 不支持binaryjson如果 Client 后续发送二进制 payload会被直接拒绝。第三轮也是最容易被忽略的一轮是 Client 对 Server 响应的显式确认。Client 必须解析 Server 返回的capabilities检查是否包含自己必需的能力如streaming-response然后向/confirm端点发送确认请求{ server_id: ida-pro-2024-07-12, confirmed_capabilities: [disassembly, symbol-resolution], negotiated_encoding: json }只有这第三轮confirm成功Router 才会将该 Server 状态设为ready并将其加入可用节点池。否则即使前两轮 HTTP 状态码都是 200Server 也始终处于pending状态LangGraph workflow 调度时会跳过它。2.2 为什么 Playwright MCP Server 总在 handshake 阶段超时Playwright 的 MCP 实现如playwright-mcp包在握手阶段失败90% 的原因是capabilities字段的语义冲突。Playwright 本身没有“静态分析”能力但它在capabilities数组里错误地声明了static-analysis。LangGraph Router 在加载 capability registry 时会将static-analysis关联到一个内部 handler该 handler 依赖ast模块解析 Python 代码。当 Router 尝试初始化这个 handler 时发现 Playwright Server 运行在 Node.js 环境而非 Pythonast模块不存在于是整个 Router 初始化失败导致/connect请求永远得不到响应最终超时。实操中我解决这个问题的方法是在 Playwright Server 启动脚本中动态过滤 capabilities。不是硬编码写死而是根据运行时环境检测结果生成// playwright-server.js const getCapabilities () { const baseCaps [browser-control, dom-interaction, screenshot]; // 只有在明确配置了 Python bridge 时才添加 analysis 相关能力 if (process.env.PYTHON_BRIDGE_ENABLED true) { baseCaps.push(dynamic-analysis); } return baseCaps; }; app.post(/connect, (req, res) { res.json({ mcp_version: 1.0.0, client_id: playwright-server-01, capabilities: getCapabilities(), supported_encodings: [json], metadata: { ... } }); });这个细节说明MCP 握手不是“填空题”而是“动态协商”。Server 的capabilities必须真实反映其当前可执行能力不能为了“看起来功能全”而堆砌无关项。我在 Altium Designer AI 接口项目中也遇到类似问题——它的 MCP Server 声明了pcb-routing能力但实际只实现了component-searchRouter 在 workflow 中尝试调用pcb-routing时直接抛出CapabilityNotImplementedError而不是优雅降级。2.3 UE5.8 官方大模型插件的握手陷阱binaryjson编码的隐含契约UE5.8 的官方 MCP 插件UnrealEngine-MCP在与 LangGraph Server 握手时会在supported_encodings中声明[json, binaryjson]。这个看似普通的字段实际绑定了一个关键契约当使用binaryjson编码时payload 的二进制部分必须采用 Protocol Buffer 序列化且 schema 必须与 LangGraph 的mcp_pb2模块完全一致。很多团队在自研 LangGraph Server 时为了“轻量”用 MessagePack 替代 Protobuf或者自定义了二进制结构结果 UE5 插件发送的.uasset文件流在 Server 端反序列化时直接 panic。解决方案不是让 UE5 插件改而是让 Server 严格遵循 MCP 规范。我们当时的做法是在 LangGraph Server 的mcp_handler.py中增加一个BinaryJsonDecoder类它不处理业务逻辑只做一件事——将 incomingbinaryjsonpayload 的二进制头 4 字节magic number0x4D 0x43 0x50 0x01校验并调用mcp_pb2.Request.FromString()解析。如果校验失败立即返回415 Unsupported Media Type并附带详细 error messageclass BinaryJsonDecoder: MAGIC_HEADER bMCP\x01 def decode(self, raw_data: bytes) - dict: if len(raw_data) 4 or raw_data[:4] ! self.MAGIC_HEADER: raise ValueError(Invalid binaryjson magic header) try: pb_req mcp_pb2.Request.FromString(raw_data[4:]) return json_format.MessageToDict(pb_req) except Exception as e: raise ValueError(fProtobuf deserialization failed: {str(e)}) # 在 FastAPI route 中调用 app.post(/invoke) async def invoke_endpoint(request: Request): content_type request.headers.get(content-type, ) if content_type application/binaryjson: decoder BinaryJsonDecoder() payload_dict decoder.decode(await request.body()) else: payload_dict await request.json()这个实现让 UE5 插件和 LangGraph Server 的握手变得稳定。关键点在于binaryjson不是“随便传二进制”而是一个强契约它要求双方在序列化层达成完全一致。很多团队试图绕过这个契约用 base64 编码 JSON 内的二进制字段结果在大文件如 10MB 的 FBX传输时内存暴涨GC 频繁最终 OOM。真正的解法是接受 MCP 的设计哲学——它把“能力协商”和“传输契约”分开握手阶段确定binaryjson可用后续传输就必须用 Protobuf。3. LangGraph 多 Server 调用不是“轮询”而是带上下文感知的路由决策3.1 Router 的三层调度策略能力匹配 → 负载评估 → 会话亲和性当一个 LangGraph workflow 定义了多个 MCP Server 节点例如IDA_Pro_Analyzer、Playwright_Browser、Java_REST_APIRouter 的调度绝非简单的 round-robin 或随机选择。它执行一套三层过滤策略每一层都可能淘汰候选节点最终只剩下一个最优 Server。这个过程发生在每次invoke调用前耗时通常在 3~8ms对整体 latency 影响极小但却是稳定性的基石。第一层能力匹配Capability MatchingRouter 会解析当前 workflow step 的tool_call请求提取其required_capabilities。例如一个分析恶意软件行为的 step其tool_call可能包含{ tool_name: analyze_behavior, required_capabilities: [disassembly, dynamic-analysis] }Router 会遍历所有ready状态的 Server筛选出capabilities数组同时包含disassembly和dynamic-analysis的 Server。在我们的项目中Playwright_BrowserServer 因未启用 Python bridgecapabilities只有[browser-control]因此被第一层过滤掉而IDA_Pro_Analyzer和Java_REST_API后者通过 wrapper 暴露了dynamic-analysis进入下一轮。第二层负载评估Load Assessment进入此层的 ServerRouter 会查询其健康指标。这些指标不是静态配置而是实时采集的pending_requests: 当前排队等待处理的请求数由 Server 在/status端点暴露avg_response_time_ms: 过去 60 秒内该 Server 的平均响应时间由 Router 主动采样cpu_usage_percent: Server 进程的 CPU 使用率需 Server 主动上报或通过系统 API 获取Router 计算一个综合负载分Load ScoreLoad Score (pending_requests * 10) avg_response_time_ms (cpu_usage_percent * 2)阈值设定Load Score 150 的 Server 被认为过载直接剔除。这个公式中pending_requests权重最高因为它是阻塞型瓶颈的直接信号cpu_usage_percent权重较低因为短暂的 CPU 高峰如 GC不一定代表服务不可用。第三层会话亲和性Session Affinity如果经过前两层仍有多个 Server 满足条件例如IDA_Pro_Analyzer和Java_REST_API都满足能力且负载低于阈值Router 会启用会话亲和性策略。它检查当前 workflow 的session_id并查询历史记录过去 5 分钟内该session_id是否频繁调用过某个 Server如果是且该 Server 的成功率 95%则优先选择它。这个策略极大提升了有状态 workflow如连续调试 session的稳定性避免了上下文在不同 Server 间漂移导致的 state loss。实操心得我们在 CherryStudio 流式输出项目中曾关闭会话亲和性结果用户上传一个大 PSD 文件后前 3 个 chunk 由Java_REST_API处理第 4 个 chunk 被路由到IDA_Pro_Analyzer后者无法解析 PSD 结构直接报错。开启亲和性后整个文件流全程由同一个 Server 处理问题消失。这说明对于有状态、流式、上下文敏感的调用亲和性不是可选项而是必选项。3.2 Fallback 重试机制不是“换一个 Server 重试”而是“降级能力重试”当 Router 在三层调度后发现没有 Server 满足条件例如所有disassembly能力的 Server 都过载它不会简单地返回503 Service Unavailable。LangGraph 实现了一套精细的 fallback 重试机制其核心思想是主动降低对能力的要求寻找次优方案。Fallback 流程如下Router 将required_capabilities中的“核心能力”core capabilities与“可选能力”optional capabilities分离。这个分离由 workflow 定义者在tool_call中通过core_required: true/false字段指定。如果无 Server 满足全部 core capabilitiesRouter 尝试移除一个 optional capability重新执行三层调度。如果仍失败Router 尝试移除一个 core capability但此时会附加一个fallback_mode: degraded标志到请求中通知目标 Server 进入降级模式。Server 收到fallback_mode后可以启用备用逻辑。例如IDA_Pro_Analyzer在降级模式下会跳过耗时的 CFGControl Flow Graph构建只做基础指令反汇编响应时间从 2.3s 降至 0.4s。我们在百度地图 MCP AI 项目中大量使用此机制。地图 POI 搜索的tool_call原本要求[geocoding, poi-search, traffic-data]当traffic-dataServer 过载时Router 自动 fallback 到只调用[geocoding, poi-search]的组合返回结果时标注traffic_data_unavailable: true前端据此隐藏交通图标用户体验无感中断。3.3 多 Server 并发调用的资源隔离Connection Pool 与 Request Timeout 的黄金配比当一个 workflow step 需要并发调用多个 MCP Server例如同时启动 Playwright 抓取网页、IDA Pro 分析 JS、Java REST API 查询数据库Router 必须管理好连接资源否则极易引发雪崩。我们观察到很多团队直接使用默认的httpx.AsyncClient结果在高并发下连接池耗尽所有请求 hang 住。正确的做法是为每个 Server 类型配置独立的 connection pool并设置精准的 timeout。我们的生产配置如下表Server 类型max_connectionskeepalive_expiryconnect_timeoutread_timeoutwrite_timeoutIDA Pro530.03.015.010.0Playwright1060.05.060.030.0Java REST20120.02.030.015.0解释max_connectionsIDA Pro 是 CPU 密集型进程启动慢连接数不宜多Playwright 是 I/O 密集型浏览器实例可复用连接数可稍高Java REST API 响应快连接数最高。keepalive_expiryPlaywright 浏览器实例维持成本高长连接 expiry 设为 60sJava API 连接轻量设为 120s。connect_timeoutIDA Pro Server 启动后监听端口有延迟设为 3sJava API 端口常驻设为 2s。read_timeoutIDA Pro 分析大二进制文件可能耗时设为 15sPlaywright 截图或 DOM 查询可能卡在渲染设为 60sJava API 通常很快设为 30s。注意事项write_timeout必须小于read_timeout否则在 Server 已接收请求但未开始处理时Client 可能因 write timeout 而中断连接Server 端却仍在处理造成资源泄漏。我们在 x32dbg MCP 插件项目中就吃过这个亏——write_timeout设为 60sread_timeout设为 30s结果插件发送大 dump 文件时Client 在 30s 后因 read timeout 断连Server 却还在解析最终内存泄漏。4. 实操从零搭建一个支持多 Server 的 LangGraph MCP Router4.1 环境准备与依赖锁定为什么pip install langgraph不够LangGraph 的 MCP 支持并非开箱即用。官方langgraphPyPI 包默认不包含 MCP Server 的 reference implementation你需要额外安装langgraph-mcp注意不是langchain-mcp后者是 LangChain 的 MCP adapter与 LangGraph 不兼容。更关键的是langgraph-mcp依赖特定版本的protobuf和grpcio版本冲突会导致 handshake 失败。我们线上环境的requirements.txt片段如下langgraph0.1.12 langgraph-mcp0.3.1 protobuf4.25.3 grpcio1.62.0 httpx0.27.0 pydantic2.7.1提示protobuf4.25.3是关键。新版本protobuf4.26.0引入了对oneof字段的 stricter validation而 MCP 的mcp_pb2定义中大量使用oneof升级后会导致FromString()解析失败报错ValueError: Cannot merge unknown fields。这个坑我们踩了三天最后是通过pip install protobuf4.25.3 --force-reinstall解决的。4.2 Router 核心配置MCPConfig的 7 个必填字段LangGraph Router 的行为由MCPConfig对象控制。这个对象有 7 个字段是强制性的缺一不可否则 Router 启动时报错ValidationError。以下是我们的生产级配置from langgraph_mcp import MCPConfig config MCPConfig( # 1. Router 自身标识必须全局唯一 router_idprod-router-v1, # 2. MCP 协议版本必须与所有 Server 一致 mcp_version1.0.0, # 3. Server 注册端点Router 监听此地址接收 CONNECT 请求 server_register_endpointhttp://localhost:8000/connect, # 4. Server 状态检查端点Router 定期 GET 此 URL 获取 health status server_status_endpointhttp://localhost:8000/status, # 5. 负载评估采样间隔秒太短增加 Server 负担太长导致负载不准确 load_sampling_interval30, # 6. fallback 重试最大次数超过则返回 503 max_fallback_retries2, # 7. 会话亲和性窗口秒在此时间内相同 session_id 优先路由到同一 Server session_affinity_window300 )特别注意server_status_endpoint。很多团队以为这只是个健康检查其实它是 Router 获取pending_requests和avg_response_time_ms的唯一来源。Server 必须在/status端点返回 JSON{ status: ready, pending_requests: 2, avg_response_time_ms: 124.3, cpu_usage_percent: 42.1, uptime_seconds: 18432 }如果 Server 返回{status: ready}而没有其他字段Router 会使用默认值pending_requests0,avg_response_time_ms100导致负载评估失效。4.3 Server 注册自动化用 Docker Compose 实现零手动配置在生产环境中我们绝不手动执行curl -X POST http://router/connect。而是利用 Docker Compose 的depends_on和healthcheck实现 Server 启动后自动注册。docker-compose.yml片段version: 3.8 services: ida-pro-server: image: mycorp/ida-pro-mcp:latest ports: - 8081:8080 environment: - MCP_ROUTER_URLhttp://router:8000 - MCP_SERVER_IDida-pro-prod-01 depends_on: router: condition: service_healthy healthcheck: test: [CMD, curl, -f, http://localhost:8080/status] interval: 30s timeout: 10s retries: 3 router: image: mycorp/langgraph-mcp-router:latest ports: - 8000:8000 environment: - MCP_CONFIG_PATH/app/config.yaml volumes: - ./config.yaml:/app/config.yaml healthcheck: test: [CMD, curl, -f, http://localhost:8000/health] interval: 10s timeout: 5s retries: 5关键点在于ida-pro-server的environmentMCP_ROUTER_URL告诉 Server Router 的地址在 compose 网络内是http://router:8000MCP_SERVER_IDServer 的唯一标识避免多个实例 ID 冲突Server 镜像内的启动脚本会监听MCP_ROUTER_URL一旦检测到 Router 健康depends_on保证立即执行注册#!/bin/bash # entrypoint.sh inside ida-pro-server image until curl -f -X POST $MCP_ROUTER_URL/connect \ -H Content-Type: application/json \ -d {\mcp_version\:\1.0.0\,\server_id\:\$MCP_SERVER_ID\,...}; do echo Waiting for Router... sleep 2 done echo Registered with Router successfully! exec $这套机制让 Server 的扩缩容完全自动化。我们线上集群有 12 个 Playwright Server 实例每次滚动更新新实例启动后 5 秒内自动注册旧实例在 graceful shutdown 前自动 deregister整个过程对 workflow 无感。4.4 故障注入测试用chaos-mcp模拟真实世界异常再完美的配置也需要在 chaos 中验证。我们开发了一个轻量级工具chaos-mcp用于模拟 MCP 生态中最常见的 5 类故障故障类型模拟命令触发效果Router 行为Server 过载chaos-mcp overload --server-id ida-pro-01 --load 200/status返回pending_requests200第二层负载评估失败Router 将其剔除Capability 缺失chaos-mcp disable-cap --server-id playwright-01 --cap dynamic-analysis/connect响应中移除dynamic-analysis第一层能力匹配失败Encoding 不匹配chaos-mcp bad-encoding --server-id java-rest-01 --encoding binarymsgpack/connect响应中supported_encodings包含非法值Router 拒绝注册日志报Unsupported encodingNetwork 分区chaos-mcp network-partition --server-id ida-pro-01阻断ida-pro-01与 Router 的所有 TCP 连接Router 在load_sampling_interval后标记为unavailableHandshake 超时chaos-mcp slow-handshake --server-id playwright-01 --delay 10/connect响应延迟 10 秒Router 的connect_timeout触发Server 注册失败我们每周执行一次 full chaos test suite确保 Router 的 fallback、重试、降级逻辑在各种异常下依然健壮。这个习惯让我们在 Kali Linux MCP 渗透测试项目上线前提前发现了max_fallback_retries1不足以应对traffic-dataServer 的间歇性故障及时调整为2。5. 常见问题与排查技巧实录来自 7 个真实项目的血泪总结5.1 “Server 显示 ready但 workflow 就是不调用它” —— 检查session_id的传播链这是一个高频问题。Server 在/status返回status: readyRouter 日志也显示Registered server ida-pro-01但 workflow 执行时Router 的 debug log 显示No available servers for capabilities [disassembly]。根因几乎总是session_id在 workflow 的上层节点如 LLM node中被意外重置或丢失。LangGraph 的 MCP Router 依赖session_id做会话亲和性如果session_id为空或为NoneRouter 会跳过亲和性检查直接进入能力匹配而此时可能因负载过高没有 Server 通过第二层评估。排查步骤在 Router 的invokeendpoint 开头添加 debug logapp.post(/invoke) async def invoke_endpoint(request: Request): session_id request.headers.get(x-session-id, MISSING) logger.debug(fInvoke received with session_id: {session_id}) # ... rest of logic检查上游 LLM node 的输出。我们发现在使用ChatPromptTemplate时如果 template 中包含了{session_id}占位符但实际调用时未传入session_id参数Jinja2 会渲染为空字符串导致x-session-idheader 为空。解决方案在 workflow 的入口 node强制注入session_idfrom langgraph.graph import StateGraph from typing import TypedDict class GraphState(TypedDict): session_id: str # ... other fields def entry_node(state: GraphState): # 如果 state 中没有 session_id生成一个 if not state.get(session_id): state[session_id] str(uuid.uuid4()) return state实操心得不要相信任何“默认 session_id”。在 LangGraph 中session_id必须由应用层显式传递和维护。我们曾在一个禅道 MCP 项目中因为前端未在每次 API 请求中带上x-session-id导致 Router 认为每个请求都是新会话亲和性失效用户调试 session 的上下文频繁丢失。5.2 “Playwright MCP Server 注册成功但 invoke 时 404” —— 路径前缀的隐形战争Playwright 的 MCP Server如playwright-mcp默认将所有 endpoint 挂在/下即/connect、/invoke、/status。而 LangGraph Router 默认期望 Server 的 endpoint 以/mcp/为前缀即/mcp/connect。当两者不一致时Router 在注册后会向http://playwright-server:8080/mcp/invoke发送请求但 Server 只监听http://playwright-server:8080/invoke结果 404。解决方案有两种Server 端适配修改 Playwright Server 的路由前缀。在playwright-server.js中const app express(); app.use(/mcp, require(./routes/mcp)); // 将所有 MCP 路由挂载到 /mcp 下Router 端适配在MCPConfig中为每个 Server 配置server_base_urlconfig MCPConfig( # ... other fields server_base_urls{ playwright-01: http://playwright-server:8080/, # 不加 /mcp/ ida-pro-01: http://ida-server:8080/mcp/ # 加 /mcp/ } )我们选择第二种因为它允许混合部署不同前缀的 Server灵活性更高。但必须确保server_base_urls字典的 key 与server_id完全一致否则 Router 无法匹配。5.3 “UE5.8 MCP 插件连接 Router但发送请求后无响应” —— TLS 与 HTTP/2 的无声冲突UE5.8 的 MCP 插件默认使用 HTTPS 和 HTTP/2。而很多 LangGraph Router 的 Docker 镜像尤其是社区版只启用了 HTTP/1.1。当插件尝试用 HTTP/2 发起请求时Router 的 HTTP/1.1 服务器无法解析连接直接 reset插件端表现为“请求发出无任何响应”。诊断方法在 Router 服务器上抓包sudo tcpdump -i any port 8000 -w router.pcap用 Wireshark 打开过滤http2如果看到HTTP/2 HEADERS帧但 Router 没有回复基本确定是协议不匹配。解决方案推荐在 Router 前加一层 Nginx 反向代理由 Nginx 终止 HTTPS 和 HTTP/2再以 HTTP/1.1 转发给 Routerupstream langgraph_router { server 127.0.0.1:8000; } server { listen 443 ssl http2; server_name mcp.example.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location / { proxy_pass http://langgraph_router; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } }备选升级 Router 到支持 HTTP/2 的版本如langgraph-mcp0.4.0但这需要重新编译且可能引入新 bug。注意事项Nginx 的proxy_http_version 1.1是必须的。如果写成1.0会导致 streaming response 被缓冲UE5 插件收不到流式 chunk。5.4 “Java REST 接口转 MCPRouter 调用时报错invalid tool name” —— Tool Name 的命名规范将现有 Java REST API 包装为 MCP Server 时一个常见错误是直接将 REST endpoint path 作为tool_name。例如Java API 有一个POST /api/v1/analyzeendpoint开发者在 MCP 的tool_call中写tool_name: analyze结果 Router 报错Tool analyze not found。原因MCP 的tool_name不是路径而是 Server 在/connect响应中capabilities的映射。Router 会将tool_name与capabilities数组中的字符串进行精确匹配。所以Server 必须在capabilities中声明analyze并且在/invoke处理逻辑中显式处理
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进