TellWorldAI 世界档案
问档案English

首页›实践›实践

实践

MCP 请求路由

MCP 请求路由分三类:按内容分流到专用服务,在多个节点间做负载均衡,按地区和版本选择工具端点。这里讲清每种怎么做,以及示例里要注意的坑。

这篇适合谁、读完能做到什么

适合要把多个模型、多个 MCP 服务或多个地区的工具接在一起的开发者。读完你能分清三类路由(按内容、按负载、按工具上下文)各自解决什么问题,看懂示例里的分类和选择逻辑,并知道哪些地方只是占位,不能直接上线。最后,你应当能为自己的系统画出一张简单的路由图,标出每个判断点依据什么做决定。

路由要解决什么问题

路由是把请求送到最合适的模型或服务上的过程。判断依据可以是内容类型、用户上下文和系统负载[1]。落到 MCP 里,大致分三个层面:按内容选专用服务,按负载在多个节点之间分配,按上下文决定工具调用去哪个端点[1]。

三种路由经常同时存在。一个请求可能先被归为代码类,再由负载均衡选出代码节点,它调用的工具又可能因为地区不同走向不同端点。每一层各自依据什么判断,排查问题时才知道该看哪里。

基于内容的路由:先分类,再转发

内容路由的思路很直接:代码生成类的请求送到代码模型,创意写作类的请求送到创作模型[1]。示例用 C# 实现,核心是一个字典,键是类别,即 code、creative、scientific 和 general,值是对应的 MCP 客户端[1]。

分类器是关键词匹配。示例先把提示词转成小写:包含 code、function、program 或 algorithm,归为 code;包含 story、creative、imagine 或 design,归为 creative;包含 science、research、analyze 或 study,归为 scientific。都不匹配时归为 general[1]。路由方法本身很短:

    public async Task<McpResponse> RouteAndProcessAsync(string prompt, IDictionary<string, object> parameters = null)
    {
        // Classify the prompt to determine the best specialized service
        string category = await _classifier.ClassifyPromptAsync(prompt);
        
        // Get the appropriate client or fall back to general
        var client = _specializedClients.ContainsKey(category) 
            ? _specializedClients[category] 
            : _specializedClients["general"];
            
        Console.WriteLine($"Routing request to {category} specialized service");
        
        // Send request to the selected service
        return await client.SendPromptAsync(prompt, parameters);
    }

关键词分类很粗。提示词里只要出现 design 这个词,就会被送进创作模型,哪怕它讨论的是工程设计。上线前,关键词只适合做兜底,主分类最好换成可靠的模型或规则。另外,判断顺序是固定的,同时含有 program 和 story 的提示词会先命中 code 类。关键词写法的好处是一眼能看懂、容易调试,适合在原型阶段先验证分类思路,再换成更稳的方法。

负载均衡:三种策略和示例里的细节

Java 示例实现了三种策略:轮询(round robin)、加权响应时间(weighted response time)和内容感知(content-aware)[1]。

轮询最简单。示例先筛出健康节点,再用原子计数器对健康节点数取模,依次分配请求[1]:

    public static class RoundRobinStrategy implements LoadBalancingStrategy {
        private AtomicInteger counter = new AtomicInteger(0);
        
        @Override
        public McpServerNode selectNode(List<McpServerNode> nodes, McpRequest request) {
            List<McpServerNode> healthyNodes = nodes.stream()
                .filter(McpServerNode::isHealthy)
                .collect(Collectors.toList());
            
            if (healthyNodes.isEmpty()) {
                throw new RuntimeException("No healthy nodes available");
            }
            
            int index = counter.getAndIncrement() % healthyNodes.size();
            return healthyNodes.get(index);
        }
    }

响应时间策略挑选健康节点中平均响应时间最短的那个[1]。名字里有「加权」,代码里却没有计算任何权重,直接取最小值。如果你需要真正的加权分配,要自己实现。

内容感知策略先判断请求的性质:提示词含有 code,或者允许的工具里有 codeInterpreter,视为代码请求;提示词含有 creative 或 story,视为创作请求。命中后选择专长(specialization)匹配的健康节点;找不到,就选当前负载最低的健康节点[1]。

节点出错时的处理也值得看。负载均衡器会对出错的节点调用 recordFailure() 做标记,然后从剩下的节点里选一个再试一次。没有剩余节点时,抛出 "All MCP server nodes failed to process the request"[1]。注意这里只做一次兜底,不是循环重试直到成功。

健康检查由 startHealthChecks 完成。它在 ScheduledExecutorService 上按固定间隔检查每个节点,检查抛出异常的节点会被标记为不健康[1]。

健康检查和负载均衡要配合看。检查间隔太长,节点已经挂掉却还被当作健康,请求就会先失败一轮才被兜底;间隔太短,又会给节点增加无用的探测负载。间隔没有标准答案,要按节点的恢复速度自己定。

工具路由:按地区、按版本

工具调用同样需要路由。天气工具可能要按用户所在地区切换到区域端点,计算器工具可能要固定在某个 API 版本上[1]。Python 示例的 McpToolRouter 类用三张表保存这些信息:基础工具端点、区域端点和版本端点[1]。

入口方法很简单,先选端点,再发请求:

    async def route_tool_request(self, tool_name, parameters, user_context=None):
        """Route a tool request to the appropriate endpoint based on context"""
        endpoint = self._select_endpoint(tool_name, parameters, user_context)
        
        if not endpoint:
            raise ValueError(f"No endpoint available for tool: {tool_name}")
        
        # Perform the actual request to the selected endpoint
        return await self._execute_tool_request(endpoint, tool_name, parameters)

选择端点的逻辑分几步。第一步取基础端点。第二步读取参数里的 _version,没有则使用该工具的默认版本,天气工具的默认版本是 v2[1]。第三步看用户地区,如果该地区的区域表里有这个工具,就使用区域端点[1]:

        # Check for regional routing if user region is known
        if user_context and "region" in user_context:
            user_region = user_context["region"]
            
            if user_region in self.regional_endpoints:
                regional_tools = self.regional_endpoints[user_region]
                
                if tool_name in regional_tools:
                    # Use region-specific endpoint
                    return regional_tools[tool_name]

这一步有一个容易漏掉的问题。只要地区命中,函数就直接返回区域端点,前面选定的版本被忽略了[1]。也就是说,同时带了地区和版本的请求,拿到的可能不是它要的版本。上线前应当补一条测试,明确这种组合的预期结果。

数据驻留(data residency)和延迟敏感(latency-sensitive)两个分支只有注释和 pass,什么都不做[1]。它们是占位,不是已经实现的功能,不要因为代码里有这两个名字就以为它们生效了。

调试这一段时,建议把每一步选出的端点都打印出来,而不是只看最终结果。这样才能看出是哪一步改写了端点。

执行部分用 aiohttp 向所选端点发送 JSON,内容包含 toolName 和 parameters。状态码不是 200 时抛出异常[1]。错误路径只是打印并重新抛出,注释提醒要自己加上重试或兜底逻辑[1]。

读代码时要注意的几点

  • 三个示例都是草图。端点是占位域名,分类靠关键词,健康检查只是打印结果。它们说明结构,不适合直接部署。
  • 架构图把三类路由串在一条链路上:请求先经过路由器和分析器,再由负载均衡器从服务池中选节点;模型发出的工具调用经过工具路由器,访问主工具注册表或区域工具注册表[1]。
  • 这里的「采样」指按请求内容分类,它和 MCP 规范里的 sampling 功能不是一回事。读规范时要把两者分开看。

常见问题

  • 请求总是落到 general:先检查关键词表,再看提示词里有没有表外的说法。关键词覆盖不全时,general 会吃掉大部分请求。
  • 所有节点都失败:Java 示例只在剩余节点中兜底一次,节点全部出错时会抛出上面那条异常。这时要看的是节点本身,而不是路由逻辑。
  • 健康检查误判:节点其实正常,却被标记为不健康。示例只在检查抛出异常时才标记,超时和慢响应要由检查方法自己处理,否则节点会一直被当作健康。
  • 指定的工具版本没生效:多半是请求同时带了地区,被前面说的区域分支覆盖了[1]。

下一步

采样(sampling)是下一个值得读的主题。协议层面的规则以 MCP 2026-07-28 规范为准,见脚注[2]。

本文改编自 Microsoft 开源课程《MCP for Beginners》中关于请求路由的高级主题[1],原文以 MIT 许可证发布。

来源

  1. 1
    Routing in Model Context ProtocolMicrosoft mcp-for-beginners (MIT)
  2. 2
    MCP Specification 2026-07-28Model Context Protocol

引用本页: MCP 请求路由. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-routing. 更新于 2026-11-14

相关动态