Streamable HTTP流式传输在LLM+MCP场景中的应用

1,239 阅读20分钟

在作者上篇文章《大模型流式输出:七大底层传输技术对比探究》中,对大模型流式输出的底层传输技术完成系统性梳理与对比。

而立足2025年人工智能大模型领域,最受瞩目的核心热点当属MCP(模型控制协议)技术。因为对外部工具的调用能力,正是大模型从基础对话机器人向多功能助手AI Agent跨越的关键,而MCP技术恰恰依托大模型Function Calling的底层能力,凭借高效统一的开发规范,迅速成为行业关注的焦点。

结合业界主流大模型的接入建议,Streamable HTTP已成为对接MCP Server 的优选方式。

基于此,本文将从理论解析到代码实践,深入拆解Streamable HTTP MCP 工作原理,并进行具体实践,带您透彻理解其底层通信机制与核心运行逻辑。

一、Streamable HTTP 协议详解

Streamable HTTP 技术是一种基于标准HTTP协议的实时数据传输协议,通过单一端点实现请求发送和响应接收,服务器可以动态决定响应方式,既支持普通HTTP响应,也能根据需要升级为SSE流式传输。通过此设计解决了传统HTTP在实时通信方面的局限性,同时保持了与现有Web基础设施的兼容性。

1.1 统一端点设计

Streamable HTTP 采用统一的HTTP端点设计,同时支持HTTP POST和GET请求,客户端和服务器消息都可以通过同一个端点发送和接收。所有工具调用请求都通过同一个URL路径处理,这种设计显著简化了API设计和客户端实现:

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: text/event-stream, application/json
Mcp-Session-Id: sessionId123

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "generate_content",
    "arguments": {"topic": "AI未来"}
  }
}

1.2 动态响应模式

Streamable HTTP支持两种响应模式:即时响应和流式响应,服务器可以根据请求性质动态选择响应方式。

即时响应模式(适合简单请求):

HTTP/1.1 200 OK
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {"status": "completed"}
}

流式响应模式(适合复杂请求):

HTTP/1.1 200 OK
Content-Type: text/event-stream
Transfer-Encoding: chunked

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"AI"}],"isComplete":false}}

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"的未来"}],"isComplete":false}}

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"充满潜力"}],"isComplete":true}}

1.3 会话管理与恢复

Streamable HTTP引入了Mcp-Session-IdLast-Event-ID头,实现了会话管理与断线恢复功能。

GET /mcp HTTP/1.1
Mcp-Session-Id: sessionId123
Last-Event-ID: 42

客户端可以在重连时携带最后接收的Event-ID和会话ID,服务器根据这些信息从断点续传或返回增量更新,确保了断线恢复的可靠性。

import requests
import json

initial_response = requests.post(
    'https://api.example.com/mcp',
    headers={'Content-Type': 'application/json'},
    json={
        'toolName': 'mcp_test',
        'args': { 'test_id': '1' },
        'stream': True
    }
)

# 获取会话ID
session_id = initial_response.headers.get('MCP-Session-ID')

# 假设连接中断,尝试恢复
recover_response = requests.post(
    'https://api.example.com/mcp',
    headers={
        'Content-Type': 'application/json',
        'MCP-Session-ID': session_id  # 提供会话ID进行恢复
    },
    json={
        'toolName': 'recover_mcp_test',
        'args': { 'test_id': '1' },
        'stream': True
    }
)

1.4 安全增强机制

Streamable HTTP与MCP 2025-03-26版本紧密结合,引入了OAuth 2.1认证机制,强制使用PKCE和HTTPS,消除了隐式流风险,更适应AI工具的高权限场景。

GET /.well-known/oauth-authorization-server HTTP/1.1  
Host: api.example.com  
MCP-Protocol-Version: version123 

HTTP/1.1 200 OK  
{  
  "issuer": "https://api.example.com",  
  "authorization_endpoint": "https://auth.example.com/authorize",  
  "token_endpoint": "https://auth.example.com/token",  
  "capabilities": ["PKCE", "TOKEN_ROTATION"]  
}

二、Streamable HTTP关键技术特性分析

Streamable HTTP技术具有多项关键技术特性,使其在通信领域具有显著优势。

2.1 动态协议协商机制

Streamable HTTP实现了智能的协议协商机制,能够根据客户端能力和网络环境动态调整通信参数:

  1. 能力探测:客户端与服务器首次通信时交换能力信息

  2. 参数协商:根据双方能力协商最优的传输参数

  3. 自适应调整:根据网络状况动态调整传输参数

协议协商过程使用JSON格式的能力描述对象,示例如下:

// 客户端能力描述
{
  "version": "1.0",
  "features": {
    "streaming": true,
    "batching": true,
    "compression": ["gzip", "br"],
    "maxChunkSize": 16384
  },
  "extensions": ["retry", "checksum"]
}

// 服务器能力描述
{
  "version": "1.0",
  "features": {
    "streaming": true,
    "batching": true,
    "compression": ["gzip"],
    "maxChunkSize": 32768
  },
  "extensions": ["retry", "auth_tokens"]
}

// 协商结果
{
  "version": "1.0",
  "features": {
    "streaming": true,
    "batching": true,
    "compression": "gzip",
    "maxChunkSize": 16384
  },
  "extensions": ["retry"]
}

2.2 双向通信隧道

在SSE流开启期间,客户端可通过附加HTTP POST发送新请求,服务端通过Mcp-Request-Id头部实现多路复用,确保多个请求之间不会混淆。

2 .3 批量请求处理

Streamable HTTP支持JSON-RPC 2.0批处理规范,允许在一个请求中发送多个方法调用,显著减少网络开销。

批量请求的JSON-RPC格式示例:

POST /mcp/v1/batch HTTP/1.1
Host: api.example.com
Content-Type: application/json

[
  {
    "id": "req1",
    "method": "invoke",
    "params": {
      "toolName": "get_weather",
      "args": {"location": "beijing"}
    }
  },
  {
    "id": "req2",
    "method": "invoke",
    "params": {
      "toolName": "get_traffic",
      "args": {"route": "airport"}
    }
  },
  {
    "id": "req3",
    "method": "invoke",
    "params": {
      "toolName": "get_news",
      "args": {"category": "tech"}
    }
  }
]

服务器响应:

[  {    "id": "req1",    "result": {      "temperature": 25,      "condition": "sunny",      "humidity": 45    }  },  {    "id": "req2",    "result": {      "status": "heavy",      "delay": 25,      "alternatives": ["highway", "express"]
    }
  },
  {
    "id": "req3",
    "result": {
      "total": 10,
      "articles": ["article1", "article2", ...]
    }
  }
]

2 .4 多模态支持

Streamable HTTP 支持多种数据类型和模态的传输,以适应不同应用场景的需求:

  1. 文本数据:支持JSON、XML、纯文本等格式

  2. 二进制数据:支持图片、音频、视频等二进制数据传输

  3. 混合数据:支持在同一请求或响应中传输多种类型的数据

多模态数据传输示例(音频流):

// 客户端发送音频流
async function streamAudio() {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
  
  mediaRecorder.ondataavailable = async (event) => {
    if (event.data.size > 0) {
      // 读取音频数据并发送
      const arrayBuffer = await event.data.arrayBuffer();
      const base64Audio = btoa(
        new Uint8Array(arrayBuffer).reduce(
          (data, byte) => data + String.fromCharCode(byte),
          ''
        )
      );
      
      await fetch('/mcp', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({
          toolName: 'speech_recognition',
          args: {
            audio: base64Audio,
            format: 'webm'
          },
          stream: true
        })
      });
    }
  };
  
  mediaRecorder.start(1000); // 每1秒发送一次数据
}

三、基于Streamable HTTP MCP 的开发应用分析

在人工智能技术飞速发展的浪潮中,大语言模型(LLM)已经不在局限于对话问答,Function Calling技术让LLM的能力边界不断的拓展。如何让LLM高效地、可靠地与外部服务进行交互呢?Anthropic于2024年11月受语言服务器协议 LSP )启发提出模型上下文协议(Model Context Protocol,简称MCP) ,被誉为AI世界的 "USB-C 接口"。它通过规范化接口,使LLM能够无缝调用各类外部工具、访问数据源并执行复杂操作,从根本上打破了不同厂商插件接口互不兼容的困境 —— 在MCP出现前,开发者需为不同LLM和工具编写定制化连接代码,效率极低。2025年初Anthropic的Manus多智能体框架落地后,作为底层工具调用协议的MCP迅速成为行业焦点。

Streamable HTTP是MCP协议为解决原有"HTTP+SSE"传输机制缺陷而推出的全新传输层实现,2025年3月随 MCP v2025.03.26 版本正式引入。

下面将从MCP服务开发框架支持情况、MCP服务与客户端通信流程、MCP服务配置与部署以及MCP客户端集成等几个方面,讲解利用Stream HTTP技术结合MCP实现大模型通过Function Calling技术完成外部服务交互。

3.1 MCP 官方SDK及开发框架支持

随着Streamable HTTP的广泛采用,MCP 生态系统已经形成了一套完整的开发框架,支持多种编程语言和平台。

目前主流的官方SDK包括:

  1. Python SDK:官方Python SDK,提供对Streamable HTTP的完整支持,提供了简单的API接口用于创建MCP Server和Client。

  2. TypeScript SDK:官方TypeScript SDK,支持浏览器和Node.js环境

  3. Java SDK:官方Java SDK,与Spring AI框架深度集成

  4. C# SDK:官方C# SDK,支持.NET 平台

除了官方SDK外,还有一系列第三方框架和工具,进一步提升开发效率和体验:

  1. Spring AI:Spring AI框架提供了McpClientTransport实现,支持Streamable HTTP模式,使得Java开发者可以方便地构建MCP应用。

  2. FastAPI:基于Python的FastAPI框架被广泛用于构建高性能的Streamable HTTP MCP Server,支持异步请求处理和流式响应。

  3. Higress:作为AI原生的API网关,Higress提供了完整的开源MCP Server托管解决方案,支持Streamable HTTP协议。

3.2 MCP 服务端与客户端通信流程

早在2025年3月26日,MCP官方github就出现HTTP流式传输服务器通信标准的提议用来代替现在HTTP SSE的通信方式。该提议详细说明了Streamable HTTP MCP 服务器与客户端之间的通信流程,以及外部工具调用信息同步格式与流程,如下图所示:

结合上图按照客户端首次启动>成功连接服务器>等待用户提问的过程来说明Streamable HTTP MCP详细的请求响应顺序,让大家明白每一步在做什么。

当客户端启动与服务端进行连接时,需要进行3步握手动作,此时用户还没有输入任何信息。

步骤HTTP方法作用响应
1POST /mcpinitialize协商协议版本&能力result.protocolVersion = 协议版本号 result.capabilities.tools.listChanged = true
2POST /mcpnotifications/initialized客户端告诉服务器“我已就绪”(通知服务器只回 204 No Content)HTTP 204 无包体
3POST /mcptools/list向服务器请求工具清单result.tools 数组 + nextCursor(下一流式点)

当用户开始提问时,大模型判断要使用工具,客户端向服务端发起工具调用请求。

步骤HTTP方法作用响应
4POST /mcptools/call调用服务端服务params.name = get_weather``params.arguments.city 或 location
5流式响应stream/result服务器逐行推送响应结果result.content

客户端在收到5的result.content后,会把文本回填到大模型对话记录中,大模型再输出给终端,此时就可以看到MCP服务执行的结果信息。

如果有多次的工具调用,那么会重复执行步骤4和5,每次请求的id都会发生改变。

3.3 MCP 服务端示例

在了解了MCP官方SDK及开发框架支持情况,以及明白了Streamable HTTP MCP详细的请求响应顺序后,以FastAPI为例,编写一个可以查询天气的Streamable HTTP MCP Server。查询天气的功能需要用到心知天气的免费API KEY(通过www.seniverse.com/products?ii… HTTP MCP服务器的示例代码如下:

  1. 导入所需python库并定义全局变量:
import argparse
import asyncio
import json
from typing import Any, AsyncIterator, Union

import requests
from fastapi import FastAPI, Request, Response, status
from fastapi.responses import StreamingResponse
import uvicorn

SERVER_NAME = "weather_server" # 定义服务器名称
SERVER_VERSION = "1.0.0" #定义服务器版本
PROTOCOL_VERSION = "version123" #定义协议版本号

API_KEY = "your_api_key" #申请的seniverse.com的KEY

2. 编写天气请求天气函数fetch_weather,编写天气流式响应函数stream_weather使用生成器将请求天气改写为流传输的形式,在传输jsonrpc协议中记录了req_id标识请求和响应:

# 编写请求天气函数
async def fetch_weather(city: str):
    try:
        url="https://api.seniverse.com/v3/weather/now.json"
        params={
            "key": API_KEY,
            "location": city,
            "language": "zh-Hans",
            "unit": "c"
        }
        response = requests.get(url, params=params)
        temperature = response.json()['results'][0]['now']
    except Exception:
        return "error"
    return json.dumps(temperature)

# 构造天气流式响应, 使用生成器yield流式返回天气内容(符合MCP协议的逐行JSON)
async def stream_weather(city: str, req_id: Union[int, str]):
    yield json.dumps({"jsonrpc": "2.0", "id": req_id, "stream": f"查询 {city} 天气中…"}).encode() + b"\n"

    await asyncio.sleep(0.3)
    data = await fetch_weather(city)

    if data == "error":
        yield json.dumps({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32000, "message": data["error"]}}).encode() + b"\n"
        return
    
    yield json.dumps({
        "jsonrpc": "2.0", "id": req_id,
        "result": {
            "content": [
                {"type": "text", "text": data}
            ],
            "isError": False
        }
    }).encode() + b"\n"

3. 定义服务器的工具列表,这里只有一个get_weather工具用来获取天气情况。因为是从底层编写,需要详细的JSON格式让天气函数被大模型Function Calling功能识别:

TOOLS_REGISTRY = {
    "tools": [
        {
            "name": "get_weather",
            "description": "用于进行天气信息查询的函数,输入城市英文名称,即可获得当前城市天气信息。",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. 'Hangzhou'"
                    }
                },
                "required": ["city"]
            }
        }
    ]

4. 使用FastAPI模拟Streamable HTTP请求与响应的流程,结合【3.2中MCP服务端与客户端的通信流程】,更清晰直观的了解请求与响应的全过程:

app = FastAPI(title="Weather Streamble HTTP MCP SERVER")

@app.get("/mcp")
async def mcp_initialize_via_get():
    #  GET 请求也执行了 initialize 方法
    return {
        "jsonrpc": "2.0",
        "id": 0,
        "result": {
            "protocolVersion": PROTOCOL_VERSION,
            "capabilities": {
                "streaming": True,
                "tools": {"listChanged": True}
            },
            "serverInfo": {
                "name": SERVER_NAME,
                "version": SERVER_VERSION
            },
            "instructions": "Use the get_weather tool to fetch weather by city name."
        }
    }

@app.post("/mcp")
async def mcp_endpoint(request: Request):
    try:
        body = await request.json()
    except Exception:
        return {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
    req_id = body.get("id", 1)
    method = body.get("method")
    
    if method is None:
        return {"jsonrpc": "2.0", "id": req_id, "result": {"status": "MCP server online."}}
    
    if method == "notifications/initialized":
        return Response(status_code=status.HTTP_204_NO_CONTENT)
    
    if method == "initialize":
        return {
            "jsonrpc": "2.0", 
            "id": req_id,
            "result": {
                "protocolVersion": PROTOCOL_VERSION,
                "capabilities": {
                    "streaming": True,
                    "tools": {"listChanged": True}
                },
                "serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
                "instructions": "Use the get_weather tool to fetch weather by city name."
            }
        }
    
    if method == "tools/list":
        print(json.dumps(TOOLS_REGISTRY, indent=2, ensure_ascii=False))
        return {"jsonrpc": "2.0", "id": req_id, "result": TOOLS_REGISTRY}
    
    if method == "tools/call":
        params = body.get("params", {})
        tool_name = params.get("name")
        args = params.get("arguments", {})

        if tool_name != "get_weather":
            return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32602, "message": "Unknown tool"}}

        city = args.get("city")
        if not city:
            return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32602, "message": "Missing city"}}

        return StreamingResponse(stream_weather(city, req_id), media_type="application/json")

    return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}

5. 解析命令行参数获得服务端要运行的IP和端口,启动服务端

def main() -> None:
    parser = argparse.ArgumentParser(description="Weather Streamble HTTP MCP SERVER")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8000)
    args = parser.parse_args()

    uvicorn.run(app, host=args.host, port=args.port, log_level="info")

if __name__ == "__main__":
    main()

3.4 MCP 服务端配置与部署

Streamable HTTP MCP服务端的配置和部署非常灵活,可以根据不同的规模和需求进行调整:

  1. 单机部署:适用于开发和小规模测试场景

  2. 容器化 部署:支持Docker容器化部署,便于环境一致性和扩缩容

  3. 集群部署:支持多实例集群部署,提高可用性和性能

  4. 云原生部署:支持Kubernetes等云原生平台部署

以下是一个Docker Compose配置示例,用于部署Streamable HTTP MCP服务端:

version: 'version123'
services:
  mcp-server:
    build: .
    ports:
      - "8000:8000"
    environment:
      - NODE_ENV=production
      - MCP_PORT=8000
      - MCP_MAX_CONNECTIONS=1000
      - MCP_STREAM_TIMEOUT=300000
    depends_on:
      - redis
    restart: always
  
  redis:
    image: redis:6-alpine
    volumes:
      - redis-data:/data
    restart: always
    
volumes:
  redis-data:

3.5 MCP 客户端集成方式

Streamable HTTP MCP提供了多种客户端集成方式,适应不同的应用场景和开发环境:

  1. 官方 SDK 集成: 已封装协议细节(如会话管理、流式处理、错误重试),支持主流编程语言,开箱即用,是最常用的集成方式

    1. Python SDK集成:适用于Python后端、数据分析脚本或AI应用开发,支持Streamable HTTP的全特性
    2. TypeScript/JavaScript SDK集成:适用于Node.js后端或浏览器端,支持异步/await语法
  2. 原生 HTTP/ HTTPS 集成: 当官方SDK不支持目标语言或需自定义协议细节时,可直接通过HTTP/HTTPS请求实现MCP协议,需手动处理会话、流式解析等逻辑

    1. 无状态工具调用:适用于单次工具调用(无需上下文),直接发送POST请求到MCP端点
    2. 有状态流式调用:适用于多轮对话或长时间任务(如文件分析),需维护Mcp-Session-Id并处理SSE流式响应
  3. 框架插件集成(企业级场景,低侵入): 适用于已使用主流开发框架(如 Spring Boot、FastAPI)的项目,通过框架插件快速集成MCP,无需修改核心业务代码

    1. Spring Boot集成(Java):通过spring-ai插件,将MCP能力注入Spring容器,支持依赖注入和自动配置

    2. FastAPI 集成(Python):通过fastapi-mcp插件(第三方),将MCP工具注册为 API 接口,支持自动生成文档

3.6 MCP 客户端示例

通过对MCP客户端集成方式的认识,以deepseek大模型为例(通过platform.deepseek.com申请注册API KEY),模拟服务器通信流程,支持多轮对话,能自动识别是否需要调用工具,自动处理工具参数解析与调用逻辑,通过读取配置文件的方式支持多个服务器。以下是Python客户端集成示例:

  1. 导入相关包并定义客户端依赖大模型:
import asyncio
import json
import logging
import os

from contextlib import AsyncExitStack
from typing import Any, Dict, List, Optional

import httpx
from openai import OpenAI

class Configuration:
    def __init__(self) -> None:
        self.api_key = "you_api_key" # 修改为API的KEY
        self.base_url = "https://api.deepseek.com" # 可修改为其他FC模型
        self.model = "deepseek-chat"

    # 添加mcp 配置文件
    @staticmethod
    def load_config(path: str) -> Dict[str, Any]:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)

2. 编写与单个Streambale HTTP MCP Server交互的类,模拟服务器通信流程, 支持四个核心操作, 包括发送连接请求、初始化连接请求、获取工具列表和工具调用流式读取:

class HTTPMCPServer:
    def __init__(self, name: str, endpoint: str) -> None:
        self.name = name
        self.endpoint = endpoint.rstrip("/")  # e.g. http://localhost:8000/mcp
        self.session: Optional[httpx.AsyncClient] = None
        self.protocol_version: str = "version123" # 与server.py中定义的协议版本一致

    async def _post_json(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        assert self.session is not None
        r = await self.session.post(self.endpoint, json=payload, headers={"Accept": "application/json"})
        if r.status_code == 204 or not r.content:
            return {}          # ← 通知无响应体
        r.raise_for_status()
        return r.json()

    async def initialize(self) -> None: #客户端发起
        self.session = httpx.AsyncClient(timeout=httpx.Timeout(30.0))
        # 1) 步骤1发送连接请求
        init_req = {
            "jsonrpc": "2.0",
            "id": 0,
            "method": "initialize",
            "params": {
                "protocolVersion": self.protocol_version,
                "capabilities": {},
                "clientInfo": {"name": "Streamable HTTP Client Demo", "version": "0.1"},
            },
        }
        r = await self._post_json(init_req)
        if "error" in r:
            raise RuntimeError(f"Initialize error: {r['error']}")
        # 2) 步骤二,发送请求初始化包,通知服务器已连接
        await self._post_json({"jsonrpc": "2.0", "method": "notifications/initialized"})

    # 步骤三 请求服务端 tools列表
    async def list_tools(self) -> List[Dict[str, Any]]:
        req = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
        res = await self._post_json(req)
        return res["result"]["tools"]

    # 步骤四 发起工具调用并将流式结果拼接为完整文本
    async def call_tool_stream(self, tool_name: str, arguments: Dict[str, Any]) -> str:
        req = {
            "jsonrpc": "2.0",
            "id": 3,
            "method": "tools/call",
            "params": {"name": tool_name, "arguments": arguments},
        }
        assert self.session is not None
        async with self.session.stream(
            "POST", self.endpoint, json=req, headers={"Accept": "application/json"}
        ) as resp:
            if resp.status_code != 200:
                raise RuntimeError(f"HTTP {resp.status_code}")
            collected_text: List[str] = []
            async for line in resp.aiter_lines():
                if not line:
                    continue
                chunk = json.loads(line)
                if "stream" in chunk:
                    continue  # 中间进度
                if "error" in chunk:
                    raise RuntimeError(chunk["error"]["message"])
                if "result" in chunk:
                    # 根据协议,文本在 result.content[0].text
                    for item in chunk["result"]["content"]:
                        if item["type"] == "text":
                            collected_text.append(item["text"])
            return "\n".join(collected_text)

    async def close(self) -> None:
        if self.session:
            await self.session.aclose()
            self.session = None

3. 编写类封装大模型对话的请求响应:

# 大模型客户端
class LLMClient:
    def __init__(self, api_key: str, base_url: Optional[str], model: str) -> None:
        self.client = OpenAI(api_key=api_key, base_url=base_url)
        self.model = model

    def chat(self, messages: List[Dict[str, Any]], tools: Optional[List[Dict[str, Any]]]):
        return self.client.chat.completions.create(model=self.model, messages=messages, tools=tools)

4. 利用单服务器类与大模型类编写多服务MCP客户端,让Client更通用:

class MultiHTTPMCPClient:
    def __init__(self, servers_conf: Dict[str, Any], api_key: str, base_url: Optional[str], model: str) -> None:
        self.servers: Dict[str, HTTPMCPServer] = {
            name: HTTPMCPServer(name, cfg["endpoint"]) for name, cfg in servers_conf.items()
        }
        self.llm = LLMClient(api_key, base_url, model)
        self.all_tools: List[Dict[str, Any]] = [] 

    async def start(self):
        for srv in self.servers.values():
            await srv.initialize()
            tools = await srv.list_tools()
            for t in tools:
                full_name = f"{srv.name}_{t['name']}"
                self.all_tools.append({
                    "type": "function",
                    "function": {
                        "name": full_name,
                        "description": t["description"],
                        "parameters": t["inputSchema"],
                    },
                })
        logging.info("已连接服务器并汇总工具:%s", [t["function"]["name"] for t in self.all_tools])

    async def call_local_tool(self, full_name: str, args: Dict[str, Any]) -> str:
        srv_name, tool_name = full_name.split("_", 1)
        srv = self.servers[srv_name]
        city = args.get("city")
        if not city:
            raise ValueError("Missing city/location")
        return await srv.call_tool_stream(tool_name, {"city": city})

    async def chat_loop(self):
        print("HTTP MCP + Function Calling 客户端已启动,输入 quit 退出")
        messages: List[Dict[str, Any]] = []
        while True:
            user = input("你: ").strip()
            if user.lower() == "quit":
                break
            messages.append({"role": "user", "content": user})
            resp = self.llm.chat(messages, self.all_tools)
            choice = resp.choices[0]
            if choice.finish_reason == "tool_calls":
                tc = choice.message.tool_calls[0]
                tool_name = tc.function.name
                tool_args = json.loads(tc.function.arguments)
                print(f"[调用工具] {tool_name}{tool_args}")
                tool_resp = await self.call_local_tool(tool_name, tool_args)
                messages.append(choice.message.model_dump())
                messages.append({"role": "tool", "content": tool_resp, "tool_call_id": tc.id})
                resp2 = self.llm.chat(messages, self.all_tools)
                print("AI:", resp2.choices[0].message.content)
                messages.append(resp2.choices[0].message.model_dump())
            else:
                print("AI:", choice.message.content)
                messages.append(choice.message.model_dump())

    async def close(self):
        for s in self.servers.values():
            await s.close()

5. 编写main函数,读取MCP Server配置文件,启动客户端:

async def main():
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
    conf = Configuration()
    servers_conf = conf.load_config("./servers_config.json").get("mcpServers", {})
    client = MultiHTTPMCPClient(servers_conf, conf.api_key, conf.base_url, conf.model)
    try:
        await client.start()
        await client.chat_loop()
    finally:
        await client.close()

if __name__ == "__main__":
    asyncio.run(main())

6. 新建servers_config.json文件用于写入Streamable HTTP MCP Server的传输服务器地址和名称,让客户端接入MCP服务端:

{
  "mcpServers": {
    "weather": {
      "endpoint": "http://127.0.0.1:8000/mcp"
    }
  }
}

7. 启动server.py

  1. 启动client.py

可以看到日志输入了【3.2 MCP 服务端与客户端通信流程】中的1、2、3步骤中的动作,完成了服务器的连接并汇总了获取天气服务的工具,等待用户进行提问

提问后,可以看到日志输出了4、5步骤中的动作,完成了天气服务工具的调用,并以流的形式返回了结果并输出给了客户端。

四、Streamable HTTP ****MCP 优势分析

在Stream HTTP出现之前,MCP服务的通信方式有Stdio方式和HTTP SSE两种,下面将通过性能、资源效率、部署及运维复杂度以及企业级场景下的性能表现四个方面将HTTP协议下的SSE和Streamable进行对比,了解Streamable HTTP MCP优势。

4.1 性能测试数据对比

通过对比测试,Streamable HTTP在多个关键性能指标上显著优于传统HTTP+SSE模式。以下是基于Higress网关的官方测试数据:

技术指标HTTP+SSEStreamable HTTP提升幅度
吞吐量3,247 req/s8,934 req/s175%
内存使用892MB234MB74%节省
错误率0.12%0.03%75%改善
连接建立时间平均120ms平均35ms71%减少
响应延迟平均450ms平均120ms73%减少

4.2 资源效率对比分析

Streamable HTTP在资源利用效率方面表现出色,主要体现在以下几个方面:

  • 内存 占用减少:每个客户端连接在HTTP+SSE模式下平均占用约892MB内存,而在Streamable HTTP模式下仅需234MB,节省了74%的内存资源。

  • 连接管理优化:HTTP+SSE需要维护两个长连接,而Streamable HTTP无需长期维护连接,仅在需要时建立临时连接,大大降低了服务器的连接管理负担。

  • 基础设施友好:Streamable HTTP基于标准HTTP协议,兼容现有的CDN、Web防火墙等基础设施,无需特殊配置即可部署,降低了企业级应用的部署难度。

  • 监控简化:Streamable HTTP使用标准HTTP指标,如http_request_duration_seconds、http_requests_total等,无需像HTTP+SSE那样定义大量自定义监控指标,减少了75%的监控规则。

4.3 部署与运维复杂度对比

Streamable HTTP在部署和运维方面也具有显著优势:

评估维度HTTP+SSEStreamable HTTP优势幅度
开发复杂度高 (127行客户端代码)低 (42行客户端代码)67%降低
部署复杂度高 (42个配置项)低 (8个配置项)81%简化
运维复杂度高 (42个监控规则)低 (8个监控规则)81%减少

这种复杂度的降低不仅减少了开发和维护成本,还提高了系统的稳定性和可靠性。以Nginx配置为例,HTTP+SSE需要特殊配置支持SSE流,而Streamable HTTP使用标准HTTP配置即可:

HTTP+SSE的Nginx配置

upstream mcp_events {
    server mcp-sse-server:3001;
    keepalive 100; # SSE需要保持连接池
}

server {
    location /events {
        proxy_pass http://mcp_events;
        proxy_http_version 1.1;
        proxy_set_header Connection '';
        proxy_buffering off;           # 关键:必须关闭缓冲
        proxy_cache off;              # 关键:必须关闭缓存
        proxy_read_timeout 24h;       # 长连接超时设置
    }
}

Streamable HTTP的Nginx配置

upstream mcp_backend {
    server mcp-streamable-server:3000;
}

server {
    location /mcp {
        proxy_pass http://mcp_backend;
        proxy_http_version 1.1;
        # 标准HTTP配置,无需特殊设置
    }
}

综上对比,Streamable HTTP在企业级场景中能提供更高效、更可靠的性能表现。

图片

本文总结:本文深入分析Streamable HTTP协议的技术特性,说明其在MCP调用场景中的应用实践,并进行优势分析,让读者更好地理解在大模型LLM+MCP场景中,为何选择Streamable HTTP作为基础通讯协议。

本文作者:钟离离

本文原载:公众号“木昆子记录AI”