使用 Python 入门 Model Context Protocol(MCP)——流式 HTTP(Streamable HTTP)

460 阅读19分钟

在第 4 章中,我们讨论了使用 服务器发送事件(Server-Sent Events,SSE) 作为传输方式来构建 MCP 服务器。在那一章里你了解到:如果希望用户在 Web 上访问你的 MCP 服务器,就不能使用 STDIO,而需要使用一种传输方式,比如 SSE,或者像本章要介绍的 Streamable HTTP

因此,在本章中,你将学习以下内容:

  • Streamable HTTP 传输
  • 为什么应优先使用它而不是 SSE
  • 如何处理 通知(notifications)可恢复性(resumability) 等概念

本章涵盖以下主题:

  • Streamable HTTP 与 SSE 的比较,以及为何它是新的标准
  • MCP 中的 Streamable HTTP
  • 可恢复性(Resumability)
  • 通知(Notifications)
  • 使用 Streamable HTTP 创建并测试服务器
  • 测试服务器
  • 测试可恢复性

Streamable HTTP 与 SSE 的比较,以及为何它是新的标准

在为你的应用选择合适技术时,理解 SSEStreamable HTTP 的差异非常重要。

有几个关键点。首先,在 MCP 中 SSE 已被视为弃用(deprecated) ;你应当改用 Streamable HTTP

那为什么本书还单独写了一章讲 SSE?原因在于本书既面向 MCP 服务器的开发者,也面向服务器的使用者;你可能需要为一个现有、仍在使用 SSE 的服务器编写客户端。简而言之,出于遗留代码的现实,你需要同时懂得处理这两种传输方式。事实上,文章 github.com/modelcontex… 指出:在宣布弃用时,有 20 个参考服务器50+ 个官方集成、以及 186 个社区开发的服务器与客户端 使用了 SSE。这意味着在进行 MCP 开发时,即便你用 Streamable HTTP 开发新服务器,也要考虑同时处理 SSEStreamable HTTP

那么,为什么要弃用 SSE?因为 Streamable HTTP 有以下优势:

  • 单一端点的简洁性:客户端与服务器通过单个端点(如 /mcp)通信,同时支持 POSTGET。这简化了实现并减少连接开销。
  • 可恢复性支持:Streamable HTTP 通过 Last-Event-IDMcp-Session-ID 等请求头支持可恢复会话,使客户端能可靠地重连与恢复流。当客户端掉线后,可从断开前的位置继续接收数据,而非从头开始。
  • 更好的兼容性:它能与现代 HTTP 基础设施(负载均衡、代理、API 网关)无缝协作;SSE 往往在这些环境中失效或需要权宜之计。
  • 双向通信能力:SSE 是单向的;而 Streamable HTTP 可以升级以支持双向流,更适合代理-到-代理或客户端-服务器交互。
  • 面向未来:Streamable HTTP 与演进中的 MCP 标准与社区最佳实践保持一致。它模块化、可扩展,既支持无状态模型也支持基于会话的模型。无状态服务器更轻量、更易构建,而能为不同场景选择合适模型也极具吸引力。

MCP 中的 Streamable HTTP

提到“流式”,你可能会想到把文件分块,或让 AI 模型分片返回结果。但在 MCP 语境下,“流式”更多指遵循 streamable 标准的 HTTP 传输方式:使用 Streamable HTTP 的客户端通常会发送如下 Accept 请求头:

Accept: application/json, text/event-stream

这告诉服务器:客户端既能处理批量 JSON 响应,也能处理通过 SSE 发送的流式事件。服务器可根据请求类型与上下文选择合适的响应模式。

仅此而已吗?不止如此,关键还在于可恢复性(resumability)

可恢复性(Resumability)

可恢复性指:当客户端在数据传输过程中与服务器失去连接时,重连后可以从断点继续数据交换,而不是从头开始。对于长耗时操作,这将极大改善体验。从技术上说,SSE 与 Streamable HTTP 都能实现可恢复性,但在 MCP 协议的上下文中,只有 Streamable HTTP 支持它。

如下图示意:

image.png

图 5.1——可恢复性

如图所示,客户端无需从头开始,而是可以继续进行。这是因为客户端在重连时会发送 mcp-session-idlast-event-id 请求头。需要注意:在断开连接时,客户端应当优雅地断开并保存这两个头,以便后续使用。

服务器端要支持可恢复性,需要做什么?需要:

  1. 创建会话存储(session store) ,用于放置已生成的消息。
  2. 设置中间件(middleware) 来处理入站请求与出站响应,确保消息被正确存储并能在会话恢复时取回。具体做法因框架而异:
# 1. 创建内存存储;生产环境应使用持久化存储
event_store = InMemoryEventStore()

# 2. 使用我们的 app 与事件存储创建会话管理器
session_manager = StreamableHTTPSessionManager(
    app=app,
    event_store=event_store,  # 启用可恢复性
    json_response=json_response,
)

# 3. 启动会话管理器
@contextlib.asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
    """管理会话管理器生命周期。"""
    async with session_manager.run():
        logger.info("Application started with StreamableHTTP session manager!")
        try:
            yield
        finally:
            logger.info("Application shutting down…")

# 4. 创建 Web 服务器并指定 lifespan 处理器
starlette_app = Starlette(
    debug=True,
    routes=[
        Mount("/mcp", app=handle_streamable_http),
    ],
    lifespan=lifespan,
)

让我们拆解一下上述代码:

  • 先创建一个会话存储,用于保存与检索消息。
  • 然后创建一个 StreamableHTTPSessionManager 类型的会话管理器来控制对存储的访问。
  • 启动会话管理器,并创建带有合适 lifespan 处理器 的 Web 服务器。
  • 最后,创建 Web 服务器并绑定该 lifespan 处理器。

至此一切就绪,任何客户端都可以连接到服务器并启动会话。

既然我们已经了解了可恢复性如何显著改善用户体验——客户端在断线处继续接收消息——接下来谈另一个概念:通知(notifications)

通知(Notifications)

通知并非 Streamable HTTP 独有的概念,在 SSE 中也可使用。但当通知与**可恢复性(resumability)**结合时,会变得尤为强大。先说明什么是通知,再谈它与可恢复性的关系。

通知有多种形式,用于告知“发生了重要事件”。它们是实时更新,在 SDK 层面会被单独处理。这意味着 SDK 为监听通知提供了专门的处理器(handler),稍后你会看到示例。

一些适合用通知表达的场景包括:

  • 状态更新(Status updates)
  • 进度通知(Progress notifications)
  • 错误消息(Error messages)
  • 信息性消息(Informational messages)

例如,客户端发送给服务器的最后一条消息通常是名为 notifications/initialized 的通知,表示客户端与服务器已完成握手,接下来可以进行常规操作(如列出工具、读取资源等)。其 JSON-RPC 结构如下:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

发送(Producing)通知

如何产生通知?本质上,通知就是一条 JSON-RPC 消息;各 SDK 通常会提供专门的方法来便捷发送不同类型的通知。因为通知类型不同,所以关键在于用对方法并传入合适参数

要发送通知,我们需要拿到**上下文对象(context)**的引用(例如把它作为工具的入参)。有了引用后,就可以调用特定的通知方法,如 debuginfowarningerror,分别用于发送调试信息、一般信息、警告与错误:

from mcp.server.session import ServerSession

# 1. 获取上下文对象
@mcp.tool(description="A simple tool returning file content")
async def echo(message: str, ctx: Context[ServerSession, None]) -> str:
    # 2. 选择合适的方法发送对应类型的通知
    await ctx.debug(f"Debug: Processing '{data}'")
    await ctx.info("Info: Starting processing")
    await ctx.warning("Warning: This is experimental")
    await ctx.error("Error: (This is just a demo)")
    return "Final result"

在上面的代码中,工具 echo 会产生四条不同类型的通知,并返回最终结果。

注:原文示例中 ctx: ctx: Context[...] 疑为排版错误,这里按 ctx: Context[...] 书写。

处理(Handling)通知

作为客户端消费通知时,它们出现的位置与常规消息不同:通知不走常规消息流,而是通过独立的回调处理:

# 1. 定义消息处理器
def message_handler(
    message: RequestResponder[types.ServerRequest, types.ClientResult]
    | types.ServerNotification
    | Exception,
) -> None:
    print("Received message:", message)
    if isinstance(message, Exception):
        raise message
    else:
        if isinstance(message, types.ServerNotification):
            print("NOTIFICATION:", message)
        elif isinstance(message, RequestResponder):
            print("REQUEST_RESPONDER:", message)
        else:
            print("SERVER_REQUEST:", message)

# 2. 创建客户端会话,并把处理器赋给 message_handler 属性
async with ClientSession(
    read_stream,
    write_stream,
    message_handler=message_handler,
) as session:
    await session.initialize()
    print("Session initialized, ready to call tools.")
    # 调用工具
    tool_result = await session.call_tool("echo", {"message": "hello"})

在该客户端中我们:

  • 定义了消息处理器,能够识别并分别处理不同类型的消息;
  • 创建客户端会话,并把处理器设置到 message_handler 属性上。

可以看到,调用工具、读取资源等常规功能响应仍按正常通道处理;而通知则通过消息处理器 message_handler 专门分流处理。

Inspector 工具中的通知

了解如何发送与接收通知后,再看看它们在 Inspector 可视化工具中的呈现方式,方便你在正确的位置查看到它们。

当以可视化模式启动 Inspector,你会看到类似下图的界面(图 5.2)。

image.png

运行某个工具后,会看到该工具的结果;在其下方有一个区域显示通知(图 5.3)。

image.png

带可恢复性的通知

正如前文所述,SSEStreamable HTTP 都能发送通知,但在 Streamable HTTP 中通知尤为关键。想象客户端因为网络不稳多次断开连接;得益于客户端的自动重连逻辑与服务器对可恢复性的支持,终端用户依然能获得良好体验:他们不会错过任何通知或常规消息(例如某个工具的响应)。

现在,让我们继续进入创建服务器的内容。

使用 Streamable HTTP 创建并测试服务器

让我们创建一个服务器,并在此过程中集成通知(notifications) 。在完成服务器构建后,下一节将使用多种工具来测试它:自己编写客户端、使用 Inspector 工具,以及用 cURL 进行测试。

要编写服务器代码,我们需要完成几件事:

  • 将传输方式设置为 Streamable HTTP
  • 添加功能(features)
  • 在调用工具时发送通知的代码

很好,有了计划就开始实现。下面是对前两点的实现尝试:

# server.py
from mcp.server.fastmcp import FastMCP, Context
from typing import Optional, Dict, Any, List, AsyncGenerator
from mcp.types import (
    LoggingMessageNotificationParams,
    TextContent
)

# Create an MCP server
mcp = FastMCP("Streamable DEMO")

# 2. Adding features
@mcp.tool(description="A simple tool returning file content")
async def echo(message: str, ctx: Context) -> str:
    return f"Here's the file content: {message}"

# 1. Set up the transport as streamable HTTP.
mcp.run(transport="streamable-http")

要添加发送通知的能力,需要把 Context 对象加入方法签名,例如:

@mcp.tool(description="A simple tool returning file content")
async def echo(message: str, ctx: Context) -> str:

最后,在工具中通过 Context 对象发送通知:

@mcp.tool(description="A simple tool returning file content")
async def echo(message: str, ctx: Context) -> str:
    # 3. Send a notification
    await ctx.info(f"Processing file 1/3:")
    await ctx.info(f"Processing file 2/3:")
    await ctx.info(f"Processing file 3/3:")
    return f"Here's the file content: {message}"

完整代码如下:

from mcp.server.fastmcp import FastMCP, Context
from typing import Optional, Dict, Any, List, AsyncGenerator
from mcp.types import (
    LoggingMessageNotificationParams,
    TextContent
)

# Create an MCP server
mcp = FastMCP("Streamable DEMO")

# 2. Adding features
@mcp.tool(description="A simple tool returning file content")
async def echo(message: str, ctx: Context) -> str:
    # 3. Send a notification
    await ctx.info(f"Processing file 1/3:")
    await ctx.info(f"Processing file 2/3:")
    await ctx.info(f"Processing file 3/3:")
    return f"Here's the file content: {message}"

# 1. Set up the transport as streamable HTTP.
mcp.run(transport="streamable-http")

接下来看看如何测试服务器。

测试服务器

一如既往,我们可以用多种方式验证服务器功能:

  • 编写客户端
  • 使用 Inspector 工具
  • 使用 cURL 测试

下面逐一尝试。

使用 Inspector 工具

本章前面已经介绍过 Inspector,这里快速回顾如何与我们新建的服务器配合使用。我们可以这样启动 Inspector 的网页界面,并在界面中选择如下字段:

  • Transport Type:HTTP
  • Server URLhttp://localhost:8000/mcp(如有需要调整端口)

点击 Connect 连接服务器后即可使用其功能。启动命令为:

npx @modelcontextprotocol/inspector

可以看到,与测试 SSE 服务器很相似,只需更改传输类型,并确保 URL 以 /mcp 结尾(而不是 /sse)。

使用 cURL 测试

上一章介绍了 cURL,这里同样适用。要获取会话 ID,你需要先发送一条 initialize 消息;该消息应包含你支持的能力(例如 tools)。

发送如下消息:

curl -X POST "http://127.0.0.1:8000/mcp" \
  -H "Accept: text/event-stream, application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": { "protocolVersion": "2025-03-26", "capabilities": { "tools": {} }, "clientInfo": { "name": "ExampleClient", "version": "1.0.0" } }
}'

注意要同时发送 AcceptContent-Type 头。记录返回的会话 ID,后续调用都要用它。

打开第二个终端窗口,执行下列命令(把 mcp-session-id 的值替换为上一步获取的会话 ID):

curl -X POST "http://127.0.0.1:8000/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream, application/json" \
  -H "mcp-session-id: 39a0b504364140ce97d8eded79b1c244" \
  -d '{
    "jsonrpc": "2.0",
    "method": "notifications/initialized"
}'

请注意,会话 ID 不再是查询参数 session_id,而是请求头 mcp-session-id。这条消息是 notifications/initialized 类型的通知,表示握手流程的最后一步。之后即可进行常规操作(列出工具、调用工具等)。

继续在第二个终端中(替换 mcp-session-id 值)调用工具:

curl -X POST "http://127.0.0.1:8000/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream, application/json" \
  -H "mcp-session-id: 39a0b504364140ce97d8eded79b1c244" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "echo",
      "arguments": { "message": "chris" }
    }
}'

在这条 tools/call 消息中,我们调用名为 echo 的工具,传入参数 chris,应能看到类似如下的工具响应(会在第二个终端中显示):

event: message
data: {"method":"notifications/message","params":{"level":"info","data":"Processing file 1/3:"},"jsonrpc":"2.0"}
event: message
data: {"method":"notifications/message","params":{"level":"info","data":"Processing file 2/3:"},"jsonrpc":"2.0"}
event: message
data: {"method":"notifications/message","params":{"level":"info","data":"Processing file 3/3:"},"jsonrpc":"2.0"}
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Here's the file content: chris"}],"structuredContent":{"result":"Here's the file content: chris"},"isError":false}}

这表明我们收到了三条通知以及最终的工具返回,说明 MCP 服务器按预期工作。

可见 InspectorcURL 都是测试服务器的好工具。不过在实际方案中,我们更可能通过自定义客户端来集成 MCP 服务器,下一节继续。

编写能处理通知的客户端

谈谈客户端。客户端通常需要额外配置来处理通知,这属于常规功能之外的补充。先制定实现计划:

  • 创建 Streamable HTTP 传输与客户端
  • 设置通知处理器(notification handler)
  • 调用一个工具

针对第一点,代码如下:

# 1. Create a streamable HTTP transport and client
async with streamablehttp_client(f"http://localhost:{port}/mcp") as (
        read_stream,
        write_stream,
        session_callback,
    ):
        # Create a session using the client streams
        async with ClientSession(
            read_stream,
            write_stream
        ) as session:

这里使用 streamablehttp_client 创建 Streamable HTTP 客户端,然后通过 ClientSession 基于其读写流创建会话。

为了支持接收通知,需要给客户端会话配置一个 message_handler 回调函数:

# 2. Set up a notification handler
async def message_handler(
        message: RequestResponder[types.ServerRequest, types.ClientResult]
        | types.ServerNotification
        | Exception,
    ) -> None:
        print("Received message:", message)
        if isinstance(message, Exception):
            raise message
        else:
            if isinstance(message, types.ServerNotification):
                print("NOTIFICATION:", message)
            elif isinstance(message, RequestResponder):
                print("REQUEST_RESPONDER:", message)
            else:
                print("SERVER_REQUEST:", message)

# omitted code for brevity
async with ClientSession(
            read_stream,
            write_stream,
            message_handler=message_handler,
        ) as session:

可以看到,我们把 message_handler 函数赋给 ClientSession 的同名参数,用来处理所有入站消息并打印输出。

就这样,我们就完成了一个 Streamable HTTP 客户端,并具备接收通知的能力。完整代码如下:

from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
import asyncio
from typing import Optional, Dict, Any, List
import mcp.types as types
from mcp.types import (
    LoggingMessageNotificationParams,
    TextContent,
)
from mcp.shared.session import RequestResponder

port = 8000

# I get normal messages, notifications, and exceptions
# 2. Set up a notification handler
async def message_handler(
        message: RequestResponder[types.ServerRequest, types.ClientResult]
        | types.ServerNotification
        | Exception,
    ) -> None:
        print("Received message:", message)
        if isinstance(message, Exception):
            raise message

async def main():
    print("Starting client...")

    # 1. Create a streamable HTTP transport and client
    async with streamablehttp_client(f"http://localhost:{port}/mcp") as (
        read_stream,
        write_stream,
        session_callback,
    ):
        # 2. Set up a notification handler
        async with ClientSession(
            read_stream,
            write_stream,
            message_handler=message_handler,
        ) as session:
            # Initialize the connection
            await session.initialize()

            # 3. Call a tool
            results = []
            tool_result = await session.call_tool("echo",
                {"message": "hello"})
            print("Tool result:", tool_result)

asyncio.run(main())

现在运行该客户端,你会看到类似如下输出,表明这些确实是通知

NOTIFICATION: root=LoggingMessageNotification(method='notifications/message', params=LoggingMessageNotificationParams(meta=None, level='info', logger=None, data='Processing file 3/3:'), jsonrpc='2.0')

结论是:通过 message_handler,我们能够捕获服务器发送的所有消息、通知与异常,从而进行恰当处理并向用户提供反馈。

使用可恢复性进行测试

SDK 中其实提供了一个可恢复性的示例实现。我们来试试那段代码,看看有何不同。你可以在此处找到它的简化版本:github.com/PacktPublis…

这段代码做的事是定义了一个只有一个工具 process-files 的服务器。调用该工具时,你会收到三条通知以及一条最终响应。测试该服务器最简单的方法是使用 cURL。借助 cURL,我们可以完成握手流程、调用工具,甚至发送定制请求来**回放(replay)**事件。让我们一步步来:

  1. 先启动服务器

  2. 开启第二个终端,用下面的 cURL 负载发起请求,以交换客户端与服务器双方支持的特性:

    curl -X POST "http://127.0.0.1:8000/mcp" -H "Accept: text/event-stream, application/json" -H "Content-Type: application/json" -d '{
      "jsonrpc": "2.0",
      "id": 1,
      "method": "initialize",
      "params": { "protocolVersion": "2025-03-26", "capabilities": { "tools": {}, "logging": {} }, "clientInfo": { "name": "ExampleClient", "version": "1.0.0" } }
    }'
    

    去第一个终端窗口查看响应。你应能看到服务器显示的会话 ID;把它复制下来备用。

  3. 发送 initialized 通知以结束服务端—客户端的握手;把 mcp-session-id 的值替换为上一步复制的会话 ID:

    curl -X POST "http://127.0.0.1:8000/mcp" -H "Content-Type: application/json" -H "Accept: text/event-stream, application/json" -H "mcp-session-id: 957f11af-4766-4c1c-a1f2-5bd6776cca6a" -d '{
        "jsonrpc": "2.0",
        "method": "notifications/initialized"
    }'
    
  4. 调用工具:在第二个终端窗口中粘贴以下命令(先替换 "mcp-session-id" 的值):

    curl -X POST "http://127.0.0.1:8000/mcp" -H "Content-Type: application/json" -H "Accept: text/event-stream, application/json" -H "mcp-session-id: 957f11af-4766-4c1c-a1f2-5bd6776cca6a" -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {
          "name": "process-files",
          "arguments": { "message": "chris" }
        }
    }'
    

    此时你应能在第二个终端窗口中看到一串通知及最终结果,如下所示:

    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976937_z6m5xbyc
    data: {"method":"notifications/message","params":{"level":"info","data":"sales1.csv processed"},"jsonrpc":"2.0"}
    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976940_meh2n52f
    data: {"method":"notifications/message","params":{"level":"info","data":"sales2.csv processed"},"jsonrpc":"2.0"}
    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976943_e3v55tmn
    data: {"method":"notifications/message","params":{"level":"info","data":"sales3.csv processed"},"jsonrpc":"2.0"}
    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976946_sgpvardt
    data: {"result":{"content":[{"type":"text","text":"Files processed: 3"}]},"jsonrpc":"2.0","id":1}
    

    我们重点关注下面这条消息:它是一条通知,表示我们正在处理 第 2/3 个文件。假设我们此时进了隧道、网络断了。记下该条消息的 ID,因为这是断线前你看到的最后一条

    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976940_meh2n52f
    data: {"method":"notifications/message","params":{"level":"info","data":"sales2.csv processed"},"jsonrpc":"2.0"}
    
  5. 回放缺失事件:最后一步,我们需要向 /mcp 端点发送一个 GET 请求,并在请求头里带上 会话 ID最后事件 ID。服务器随后会回放我们缺失的消息——此处应是 sales3.csv 的通知与最终工具结果。粘贴下面命令前,请把 mcp-session-idlast-event-id 都替换为你的值:

    curl "http://127.0.0.1:8000/mcp" -H "Content-Type: application/json" -H "Accept: text/event-stream, application/json" -H "mcp-session-id: 957f11af-4766-4c1c-a1f2-5bd6776cca6a" -H "last-event-id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976940_meh2n52f"
    

    运行后,你应在第二个终端窗口看到如下输出:

    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976943_e3v55tmn
    data: {"method":"notifications/message","params":{"level":"info","data":"sales3.csv processed"},"jsonrpc":"2.0"}
    event: message
    id: 3a9d76c3-36d8-45f3-bd6e-8b9c82826de8_1757284976946_sgpvardt
    data: {"result":{"content":[{"type":"text","text":"Files processed: 3"}]},"jsonrpc":"2.0","id":1}
    

    我们缺失的一条通知和工具结果都回来了!太棒了——没有丢任何消息。

补充说明:如果你要编写能利用重放能力的代码,那么在失去网络连接时应监听浏览器事件,从而有机会保存 会话 ID最后事件 ID,并记得使用 GET /mcp(而不是 POST /mcp)进行重连;后者会开启一个新会话

另外,如果你使用的是文中的事件存储(event store)实现,请注意它不适用于生产环境;要达到生产级别,需要把消息持久化到数据库或类似存储中。

总结

本章我们探讨了 Streamable HTTP 的概念及其与 SSE 的差异。我们了解到,流式传输可以实现实时数据下发,这对需要即时数据访问的应用(如实时事件大文件)非常有益。

此外,我们实现了一个支持 Streamable HTTP 的 MCP 服务器,并示范了如何使用 MCP SDK 来消费流式数据。我们还讨论了**通知(notifications)**在向客户端实时推送更新方面的重要性,以及如何有效处理这些通知。

在下一章中,我们将说明如何使用低层级的服务器 API,因为在某些用例中你可能希望直接使用这些接口。