mcp_streamable_proxy/
server.rs1use anyhow::{Result, bail};
7use mcp_common::{McpServiceConfig, check_windows_command, wrap_process_v9};
8use rmcp::{
9 ServiceExt,
10 transport::{
11 TokioChildProcess,
12 streamable_http_server::{StreamableHttpServerConfig, StreamableHttpService},
13 },
14};
15use std::process::Stdio;
16use std::sync::Arc;
17use tracing::{error, info, warn};
18
19use process_wrap::tokio::{CommandWrap, KillOnDrop};
22
23use crate::backend_client::{BackendNotificationBridge, UpstreamPeerRegistry};
24use crate::{ProxyAwareSessionManager, ProxyHandler};
25
26pub async fn run_stream_server_from_config(
40 config: McpServiceConfig,
41 std_listener: &std::net::TcpListener,
42 quiet: bool,
43) -> Result<()> {
44 check_windows_command(&config.command);
53
54 info!(
55 "[Subprocess][{}] Command: {} {:?}",
56 config.name,
57 config.command,
58 config.args.as_ref().unwrap_or(&vec![])
59 );
60
61 let mut wrapped_cmd = CommandWrap::with_new(&config.command, |command| {
62 if let Some(ref cmd_args) = config.args {
63 command.args(cmd_args);
64 }
65 if let Some(ref env_vars) = config.env {
68 for (k, v) in env_vars {
69 command.env(k, v);
70 }
71 }
72 });
73
74 wrap_process_v9!(wrapped_cmd);
76
77 wrapped_cmd.wrap(KillOnDrop);
79
80 let (tokio_process, child_stderr) = TokioChildProcess::builder(wrapped_cmd)
83 .stderr(Stdio::piped())
84 .spawn()?;
85
86 if let Some(stderr_pipe) = child_stderr {
88 mcp_common::spawn_stderr_reader(stderr_pipe, config.name.clone());
89 }
90
91 let upstream_peers = Arc::new(UpstreamPeerRegistry::new());
93 let bridge = BackendNotificationBridge::with_default_info(upstream_peers);
94 let client = bridge.serve(tokio_process).await?;
95
96 info!(
98 "[Subprocess startup] Streamable HTTP - Service name: {}, Command: {} {:?}",
99 config.name,
100 config.command,
101 config.args.as_ref().unwrap_or(&vec![])
102 );
103
104 if !quiet {
105 eprintln!("✅ The child process has been started");
106
107 match client.list_tools(None).await {
109 Ok(tools_result) => {
110 let tools = &tools_result.tools;
111 if tools.is_empty() {
112 warn!(
113 "[Tool list] Tool list is empty - Service name: {}",
114 config.name
115 );
116 eprintln!("⚠️Tool list is empty");
117 } else {
118 info!(
119 "[Tool list] Service name: {}, Number of tools: {}",
120 config.name,
121 tools.len()
122 );
123 eprintln!("🔧 Available tools ({}):", tools.len());
124 for tool in tools.iter().take(10) {
125 let desc = tool.description.as_deref().unwrap_or("无描述");
126 let desc_short = if desc.len() > 50 {
127 format!("{}...", &desc[..50])
128 } else {
129 desc.to_string()
130 };
131 eprintln!(" - {} : {}", tool.name, desc_short);
132 }
133 if tools.len() > 10 {
134 eprintln!("... and {} other tools", tools.len() - 10);
135 }
136 }
137 }
138 Err(e) => {
139 error!(
140 "[Tool List] Failed to obtain tool list - Service name: {}, Error: {}",
141 config.name, e
142 );
143 eprintln!("⚠️ Failed to obtain tool list: {}", e);
144 }
145 }
146 } else {
147 match client.list_tools(None).await {
149 Ok(tools_result) => {
150 info!(
151 "[Tool list] Service name: {}, Number of tools: {}",
152 config.name,
153 tools_result.tools.len()
154 );
155 }
156 Err(e) => {
157 error!(
158 "[Tool List] Failed to obtain tool list - Service name: {}, Error: {}",
159 config.name, e
160 );
161 }
162 }
163 }
164
165 let proxy_handler = if let Some(tool_filter) = config.tool_filter {
167 ProxyHandler::with_tool_filter(client, config.name.clone(), tool_filter)
168 } else {
169 ProxyHandler::with_mcp_id(client, config.name.clone())
170 };
171
172 let listener = tokio::net::TcpListener::from_std(std_listener.try_clone()?)?;
174 run_stream_server(proxy_handler, listener, quiet).await
175}
176
177pub async fn run_stream_server(
191 proxy_handler: ProxyHandler,
192 listener: tokio::net::TcpListener,
193 quiet: bool,
194) -> Result<()> {
195 let bind_addr = listener
196 .local_addr()
197 .map(|a| a.to_string())
198 .unwrap_or_else(|_| "<unknown>".to_string());
199 let mcp_id = proxy_handler.mcp_id().to_string();
200
201 info!(
203 "[HTTP service startup] Streamable HTTP service startup - Address: {}, MCP ID: {}",
204 bind_addr, mcp_id
205 );
206
207 if !quiet {
208 eprintln!("📡 Streamable HTTP service startup: http://{}", bind_addr);
209 eprintln!("💡 MCP client can be used directly: http://{}", bind_addr);
210 eprintln!("✨ Feature: stateful_mode (session management + server push)");
211 eprintln!("🔄 Backend version control: Enable (automatically handles reconnections)");
212 eprintln!("💡 Press Ctrl+C to stop the service");
213 }
214
215 let handler = Arc::new(proxy_handler);
217
218 let session_manager = ProxyAwareSessionManager::new(handler.clone());
220
221 let handler_for_service = handler.clone();
229 let mut server_config = StreamableHttpServerConfig::default();
230 server_config.legacy_session_mode = true; let service = StreamableHttpService::new(
232 move || Ok((*handler_for_service).clone()),
233 session_manager.into(), server_config,
235 );
236
237 let router = axum::Router::new().fallback_service(service);
239
240 tokio::select! {
244 result = axum::serve(listener, router) => {
245 if let Err(e) = result {
246 error!(
247 "[HTTP Service Error] Streamable HTTP Server Error - MCP ID: {}, Error: {}",
248 mcp_id, e
249 );
250 bail!("服务器错误: {}", e);
251 }
252 }
253 _ = tokio::signal::ctrl_c() => {
254 info!(
255 "[HTTP service shutdown] Received exit signal, closing Streamable HTTP service - MCP ID: {}",
256 mcp_id
257 );
258 if !quiet {
259 eprintln!("\\n🛑 Received exit signal, closing...");
260 }
261 }
262 }
263
264 Ok(())
265}