MCP Server 运行模式入门(Streamable HTTP / stdio)
目标:把你当前项目里“关键类/方法/字段”与 MCP 协议运行流程对上号,尽量解释“它在做什么、为什么需要它”。
目录
- 一、Streamable HTTP 模式(基于 WebFlux)
- 二、stdio 模式(基于标准输入输出)
- 三、两种模式对比与选型
- 四、项目内关键类逐段解读(对照源码)
- 五、常见疑问与排查思路
一、Streamable HTTP 模式(基于 WebFlux)
1. 适用场景
- 需要把 MCP Server 作为常驻服务部署(本机、服务器、容器)。
- 需要通过HTTP 接入(便于反向代理、网关、HTTPS、负载均衡)。
- 多客户端或并发调用场景更合适。
2. 核心概念
- Streamable HTTP:MCP 的一种传输方式,统一通过单一 HTTP 端点传输消息。
- 端点:例如 /mcp/message,客户端在此进行初始化、调用工具与接收服务端推送。
- WebFlux:Spring 的响应式 Web 容器,用于承载 Streamable HTTP 的路由与流式消息。
3. 运行流程(概念图)
MCP Server (WebFlux)MCP ClientMCP Server (WebFlux)MCP Client#mermaid-svg-M6akczFylZZLRalk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M6akczFylZZLRalk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M6akczFylZZLRalk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M6akczFylZZLRalk .error-icon{fill:#552222;}#mermaid-svg-M6akczFylZZLRalk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M6akczFylZZLRalk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M6akczFylZZLRalk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M6akczFylZZLRalk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M6akczFylZZLRalk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M6akczFylZZLRalk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M6akczFylZZLRalk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M6akczFylZZLRalk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M6akczFylZZLRalk .marker.cross{stroke:#333333;}#mermaid-svg-M6akczFylZZLRalk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M6akczFylZZLRalk p{margin:0;}#mermaid-svg-M6akczFylZZLRalk .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M6akczFylZZLRalk text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-M6akczFylZZLRalk .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M6akczFylZZLRalk .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-M6akczFylZZLRalk .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-M6akczFylZZLRalk .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-M6akczFylZZLRalk #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-M6akczFylZZLRalk .sequenceNumber{fill:white;}#mermaid-svg-M6akczFylZZLRalk #sequencenumber{fill:#333;}#mermaid-svg-M6akczFylZZLRalk #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-M6akczFylZZLRalk .messageText{fill:#333;stroke:none;}#mermaid-svg-M6akczFylZZLRalk .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M6akczFylZZLRalk .labelText,#mermaid-svg-M6akczFylZZLRalk .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-M6akczFylZZLRalk .loopText,#mermaid-svg-M6akczFylZZLRalk .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-M6akczFylZZLRalk .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M6akczFylZZLRalk .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-M6akczFylZZLRalk .noteText,#mermaid-svg-M6akczFylZZLRalk .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-M6akczFylZZLRalk .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M6akczFylZZLRalk .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M6akczFylZZLRalk .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M6akczFylZZLRalk .actorPopupMenu{position:absolute;}#mermaid-svg-M6akczFylZZLRalk .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-M6akczFylZZLRalk .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M6akczFylZZLRalk .actor-man circle,#mermaid-svg-M6akczFylZZLRalk line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-M6akczFylZZLRalk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}HTTP POST /mcp/message (initialize)1initialize response (capabilities)2HTTP POST /mcp/message (tool call)3tool result / streaming events4
4. 关键配置项(你的项目里)
- spring.ai.mcp.server.stdio=false
- 含义:关闭 stdio,启用 HTTP 传输。
- spring.main.web-application-type=reactive
- 含义:使用 WebFlux 作为容器。
- spring.ai.mcp.server.sse-message-endpoint=/mcp/message
- 含义:Streamable HTTP 端点路径。
- server.port=8101
- 含义:HTTP 服务监听端口。
5. WebFlux 与 MVC 的区别(简要对比)
| 编程模型 | 响应式(Reactive Streams) | 同步阻塞 |
| 线程模型 | 少量线程处理大量连接 | 一请求一线程 |
| 适合场景 | 高并发、流式传输、SSE | 传统表单/REST、同步调用 |
| 依赖容器 | Netty/响应式容器 | Servlet 容器 |
| MCP 适配 | Streamable HTTP 更自然 | 需要额外适配或走 stdio |
二、stdio 模式(基于标准输入输出)
1. 适用场景
- MCP Server 不需要暴露 HTTP 服务,仅在本机被 MCP Client 调用。
- 更适合开发调试、小工具、单用户场景。
2. 核心概念
- stdio:MCP Client 拉起 MCP Server 进程,通过 stdin/stdout 进行 JSON-RPC 通信。
- 进程生命周期:通常由客户端控制,客户端启动时拉起,结束时退出。
3. 运行流程(概念图)
MCP Server (stdio)MCP ClientMCP Server (stdio)MCP Client#mermaid-svg-TpgsZNbtrV8ORyJu{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TpgsZNbtrV8ORyJu .error-icon{fill:#552222;}#mermaid-svg-TpgsZNbtrV8ORyJu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TpgsZNbtrV8ORyJu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TpgsZNbtrV8ORyJu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TpgsZNbtrV8ORyJu .marker.cross{stroke:#333333;}#mermaid-svg-TpgsZNbtrV8ORyJu svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TpgsZNbtrV8ORyJu p{margin:0;}#mermaid-svg-TpgsZNbtrV8ORyJu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TpgsZNbtrV8ORyJu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TpgsZNbtrV8ORyJu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TpgsZNbtrV8ORyJu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TpgsZNbtrV8ORyJu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TpgsZNbtrV8ORyJu .sequenceNumber{fill:white;}#mermaid-svg-TpgsZNbtrV8ORyJu #sequencenumber{fill:#333;}#mermaid-svg-TpgsZNbtrV8ORyJu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TpgsZNbtrV8ORyJu .messageText{fill:#333;stroke:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TpgsZNbtrV8ORyJu .labelText,#mermaid-svg-TpgsZNbtrV8ORyJu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .loopText,#mermaid-svg-TpgsZNbtrV8ORyJu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TpgsZNbtrV8ORyJu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TpgsZNbtrV8ORyJu .noteText,#mermaid-svg-TpgsZNbtrV8ORyJu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TpgsZNbtrV8ORyJu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TpgsZNbtrV8ORyJu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TpgsZNbtrV8ORyJu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TpgsZNbtrV8ORyJu .actorPopupMenu{position:absolute;}#mermaid-svg-TpgsZNbtrV8ORyJu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TpgsZNbtrV8ORyJu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TpgsZNbtrV8ORyJu .actor-man circle,#mermaid-svg-TpgsZNbtrV8ORyJu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TpgsZNbtrV8ORyJu :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}启动服务端进程1stdin 写入 initialize2stdout 返回 capabilities3stdin 写入 tool call4stdout 返回结果5关闭进程6
4. 关键配置项(思路说明)
- spring.ai.mcp.server.stdio=true
- 含义:启用 stdio 模式。
- spring.main.web-application-type=none
- 含义:不启动 Web 容器。
三、两种模式对比与选型
| 通信方式 | HTTP 单端点流式 | stdin/stdout |
| 部署形态 | 常驻服务 | 被客户端拉起 |
| 并发能力 | 好 | 一般 |
| 网络要求 | 需要端口 | 无需端口 |
| 适用场景 | 多用户/生产 | 单机/开发 |
四、项目内关键类逐段解读(对照源码)
下面按你项目里的核心类解释“它的意义”和“它做了什么”。
1. StreamableMcpServerConfiguration
文件:mcp-core/src/main/java/com/xbk/mcp/server/infrastructure/mcp/StreamableMcpServerConfiguration.java
(1) 类级别注解
- @Configuration
- 标识这是 Spring 配置类,负责创建 Bean。
- @EnableConfigurationProperties(McpServerProperties.class)
- 绑定 spring.ai.mcp.server.* 配置。
- @ConditionalOnClass(…)
- 只有当 MCP + WebFlux 相关类存在时才启用(避免缺依赖启动失败)。
- @ConditionalOnExpression("${spring.ai.mcp.server.enabled:true} && !${spring.ai.mcp.server.stdio:true}")
- 配置启用 MCP 且 stdio 关闭时,才启用 Streamable HTTP。
(2) streamableServerTransportProvider(…)
- 作用:提供 Streamable HTTP 传输实现。
- 关键点:读取 sseMessageEndpoint(默认 /mcp/message),并构建 WebFlux 传输提供器。
(3) mcpStreamableRouterFunction(…)
- 作用:把 MCP 的 HTTP 路由注册到 WebFlux 中。
- 结果:WebFlux 会把 /mcp/message 交给 MCP 处理。
(4) mcpServerCapabilitiesBuilder()
- 作用:创建 MCP ServerCapabilities 的 Builder。
- 结果:后续会把“工具/资源/提示”能力声明进去。
(5) mcpStreamableSyncServer(…)
- 作用:创建 MCP Server 实例并注册能力。
- 具体流程:
- 组装 serverInfo(名称、版本)。
- 创建 McpServer.sync(transportProvider)。
- 从 Spring 容器里收集 Tool/Resource/Prompt。
- 注册到 serverBuilder。
- 设置 capabilitiesBuilder。
- build() 生成 MCP Server。
(6) 关键字段含义
- McpStreamableServerTransportProvider transportProvider
- HTTP 传输层(Streamable HTTP),负责消息通道。
- McpSchema.ServerCapabilities.Builder capabilitiesBuilder
- MCP 服务端能力声明。
- ObjectProvider<List<ToolCallback>> toolCallbacksProvider
- Spring 容器里所有 @McpTool 形成的回调。
2. McpServerProperties
文件:mcp-core/src/main/java/com/xbk/mcp/server/infrastructure/mcp/McpServerProperties.java
- enabled:是否启用 MCP Server。
- name / version:用于 initialize 返回的服务端标识。
- stdio:是否走 stdio 传输。
- sseMessageEndpoint:Streamable HTTP 端点。
- toolChangeNotification / resourceChangeNotification / promptChangeNotification:能力变更通知开关。
3. HttpClientLogInterceptor
文件:mcp-core/src/main/java/com/xbk/mcp/server/infrastructure/logging/HttpClientLogInterceptor.java
- 作用:统一记录外部 HTTP 请求的请求头、请求体、响应体、耗时。
- 关键点:
- requestBody:读取并压成单行日志。
- response.peekBody(…):读取响应,不消费原始响应流。
- toSingleLine:把换行替换成 \\\\n 以保证日志单行。
4. CsdnToolApplication
文件:mcp-tool-csdn/src/main/java/com/xbk/mcp/server/CsdnToolApplication.java
- 作用:启动 MCP Server(CSDN 模块)并输出启动日志。
- 说明:它不直接管 MCP 逻辑,真正的 MCP Server 在 mcp-core 里装配。
五、常见疑问与排查思路
1. Claude Code 显示 not authenticated
- 通常是 MCP 配置 schema 不匹配(type 写错、url 未指向 /mcp/message)。
2. 日志出现协议版本警告
- 客户端与服务端支持版本不一致,一般会降级继续运行。
3. 工具调用不上
- 检查:
- @McpTool 是否被扫描到
- 服务端是否启用了 MCP
- Tool 是否在容器里生成 Bean
备注:如果你希望“对照源码逐行讲解”
你可以告诉我你最不理解的类或方法,我可以进一步拆成“每一段在做什么”。
