1use std::{future::Future, marker::PhantomData, pin::pin, sync::Arc};
4
5use futures::future::{BoxFuture, Either};
6use futures_concurrency::future::TryJoin;
7use rmcp::{
8 ErrorData, ServerHandler,
9 model::{
10 CacheScope, CallToolResponse, CallToolResult, ListToolsResult, ProtocolVersion,
11 ServerConfig, Tool,
12 },
13};
14use schemars::JsonSchema;
15use serde::{Serialize, de::DeserializeOwned};
16use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
17
18#[cfg(feature = "unstable_mcp_over_acp")]
19use acp::mcp_server::{McpOutcome, McpRequest, McpRequestContext, McpService};
20use agent_client_protocol as acp;
21use agent_client_protocol::{
22 ByteStreams, ChainRun, ConnectTo, DynConnectTo, NullRun, RunWithConnectionTo,
23 mcp_server::{
24 McpConnectionTo, McpServer, McpServerConnect, McpTool, McpToolMetadata, McpToolRegistry,
25 },
26 role::{self, Role},
27};
28
29#[derive(Debug)]
52pub struct McpServerBuilder<Counterpart: Role, Runner>
53where
54 Runner: RunWithConnectionTo<Counterpart>,
55{
56 phantom: PhantomData<Counterpart>,
57 name: String,
58 data: McpToolRegistry<Counterpart>,
59 runner: Runner,
60}
61
62impl<Counterpart: Role> McpServerBuilder<Counterpart, NullRun> {
63 pub(super) fn new(name: String) -> Self {
64 Self {
65 name,
66 phantom: PhantomData,
67 data: McpToolRegistry::default(),
68 runner: NullRun,
69 }
70 }
71}
72
73impl<Counterpart: Role, Runner> McpServerBuilder<Counterpart, Runner>
74where
75 Runner: RunWithConnectionTo<Counterpart>,
76{
77 #[must_use]
79 pub fn instructions(mut self, instructions: impl ToString) -> Self {
80 self.data.set_instructions(instructions);
81 self
82 }
83
84 #[must_use]
86 pub fn tool(mut self, tool: impl McpTool<Counterpart> + 'static) -> Self {
87 self.data.register_tool(tool);
88 self
89 }
90
91 #[must_use]
94 pub fn disable_all_tools(mut self) -> Self {
95 self.data.disable_all_tools();
96 self
97 }
98
99 #[must_use]
102 pub fn enable_all_tools(mut self) -> Self {
103 self.data.enable_all_tools();
104 self
105 }
106
107 pub fn disable_tool(mut self, name: &str) -> Result<Self, acp::Error> {
111 self.data.disable_tool(name)?;
112 Ok(self)
113 }
114
115 pub fn enable_tool(mut self, name: &str) -> Result<Self, acp::Error> {
119 self.data.enable_tool(name)?;
120 Ok(self)
121 }
122
123 fn tool_with_runner(
126 self,
127 tool: impl McpTool<Counterpart> + 'static,
128 tool_runner: impl RunWithConnectionTo<Counterpart>,
129 ) -> McpServerBuilder<Counterpart, impl RunWithConnectionTo<Counterpart>> {
130 let this = self.tool(tool);
131 McpServerBuilder {
132 phantom: PhantomData,
133 name: this.name,
134 data: this.data,
135 runner: ChainRun::new(this.runner, tool_runner),
136 }
137 }
138
139 pub fn tool_fn_mut<P, Ret, F>(
162 self,
163 name: impl ToString,
164 description: impl ToString,
165 func: F,
166 tool_future_hack: impl for<'a> Fn(
167 &'a mut F,
168 P,
169 McpConnectionTo<Counterpart>,
170 ) -> BoxFuture<'a, Result<Ret, acp::Error>>
171 + Send
172 + 'static,
173 ) -> McpServerBuilder<Counterpart, impl RunWithConnectionTo<Counterpart>>
174 where
175 P: JsonSchema + DeserializeOwned + 'static + Send,
176 Ret: JsonSchema + Serialize + 'static + Send,
177 F: AsyncFnMut(P, McpConnectionTo<Counterpart>) -> Result<Ret, acp::Error> + Send,
178 {
179 let (tool, runner) =
180 acp::mcp_server::tool_fn_mut(name, description, func, tool_future_hack);
181 self.tool_with_runner(tool, runner)
182 }
183
184 pub fn tool_fn<P, Ret, F>(
205 self,
206 name: impl ToString,
207 description: impl ToString,
208 func: F,
209 tool_future_hack: impl for<'a> Fn(
210 &'a F,
211 P,
212 McpConnectionTo<Counterpart>,
213 ) -> BoxFuture<'a, Result<Ret, acp::Error>>
214 + Send
215 + Sync
216 + 'static,
217 ) -> McpServerBuilder<Counterpart, impl RunWithConnectionTo<Counterpart>>
218 where
219 P: JsonSchema + DeserializeOwned + 'static + Send,
220 Ret: JsonSchema + Serialize + 'static + Send,
221 F: AsyncFn(P, McpConnectionTo<Counterpart>) -> Result<Ret, acp::Error>
222 + Send
223 + Sync
224 + 'static,
225 {
226 let (tool, runner) = acp::mcp_server::tool_fn(name, description, func, tool_future_hack);
227 self.tool_with_runner(tool, runner)
228 }
229
230 pub fn build(self) -> McpServer<Counterpart, Runner> {
236 let built = McpServerBuilt {
237 name: self.name,
238 data: Arc::new(self.data),
239 };
240 #[cfg(feature = "unstable_mcp_over_acp")]
241 {
242 let standalone = McpServerBuilt {
243 name: built.name.clone(),
244 data: built.data.clone(),
245 };
246 McpServer::new_service_with_standalone(built, standalone, self.runner)
247 }
248 #[cfg(not(feature = "unstable_mcp_over_acp"))]
249 {
250 McpServer::new(built, self.runner)
251 }
252 }
253}
254
255struct McpServerBuilt<Counterpart: Role> {
256 name: String,
257 data: Arc<McpToolRegistry<Counterpart>>,
258}
259
260#[cfg(feature = "unstable_mcp_over_acp")]
261impl<Counterpart: Role> McpService<Counterpart> for McpServerBuilt<Counterpart> {
262 fn execute(
263 &self,
264 request: McpRequest,
265 context: McpRequestContext<Counterpart>,
266 ) -> BoxFuture<'static, Result<McpOutcome, acp::Error>> {
267 crate::native::execute(
268 Arc::new(McpServerConnection {
269 data: self.data.clone(),
270 mcp_connection: context.connection().clone(),
271 }),
272 request,
273 context,
274 )
275 }
276}
277
278impl<Counterpart: Role> McpServerConnect<Counterpart> for McpServerBuilt<Counterpart> {
279 fn name(&self) -> String {
280 self.name.clone()
281 }
282
283 fn connect(
284 &self,
285 mcp_connection: McpConnectionTo<Counterpart>,
286 ) -> DynConnectTo<role::mcp::Client> {
287 DynConnectTo::new(McpServerConnection {
288 data: self.data.clone(),
289 mcp_connection,
290 })
291 }
292}
293
294pub(crate) struct McpServerConnection<Counterpart: Role> {
296 data: Arc<McpToolRegistry<Counterpart>>,
297 mcp_connection: McpConnectionTo<Counterpart>,
298}
299
300impl<Counterpart: Role> ConnectTo<role::mcp::Client> for McpServerConnection<Counterpart> {
301 async fn connect_to(self, client: impl ConnectTo<role::mcp::Server>) -> Result<(), acp::Error> {
302 let (mcp_server_stream, mcp_client_stream) = tokio::io::duplex(8192);
304 let (mcp_server_read, mcp_server_write) = tokio::io::split(mcp_server_stream);
305 let (mcp_client_read, mcp_client_write) = tokio::io::split(mcp_client_stream);
306
307 let run_client = async {
308 let byte_streams =
309 ByteStreams::new(mcp_client_write.compat_write(), mcp_client_read.compat());
310 <ByteStreams<_, _> as ConnectTo<role::mcp::Client>>::connect_to(byte_streams, client)
311 .await
312 };
313
314 let run_server = async {
315 let running_server = rmcp::ServiceExt::serve(self, (mcp_server_read, mcp_server_write))
317 .await
318 .map_err(acp::Error::into_internal_error)?;
319
320 running_server
322 .waiting()
323 .await
324 .map(|_quit_reason| ())
325 .map_err(acp::Error::into_internal_error)
326 };
327
328 (run_client, run_server).try_join().await?;
329 Ok(())
330 }
331}
332
333impl<R: Role> ServerHandler for McpServerConnection<R> {
334 async fn call_tool(
335 &self,
336 request: rmcp::model::CallToolRequestParams,
337 context: rmcp::service::RequestContext<rmcp::RoleServer>,
338 ) -> Result<CallToolResponse, ErrorData> {
339 let Some(registered) = self.data.enabled_tool(&request.name) else {
341 return Err(rmcp::model::ErrorData::invalid_params(
342 format!("tool `{}` not found", request.name),
343 None,
344 ));
345 };
346
347 let serde_value = serde_json::to_value(request.arguments).expect("valid json");
349
350 let has_structured_output = registered.has_structured_output();
352 match futures::future::select(
353 registered.call_tool(serde_value, self.mcp_connection.clone()),
354 pin!(context.ct.cancelled()),
355 )
356 .await
357 {
358 Either::Left((m, _)) => match m {
360 Ok(result) => {
361 if has_structured_output {
363 Ok(CallToolResult::structured(result).into())
364 } else {
365 let content = rmcp::model::ContentBlock::text(result.to_string());
366 Ok(CallToolResult::success(vec![content]).into())
367 }
368 }
369 Err(error) => Err(to_rmcp_error(error)),
370 },
371
372 Either::Right(((), _)) => {
374 Err(rmcp::ErrorData::internal_error("operation cancelled", None))
375 }
376 }
377 }
378
379 fn list_tools(
380 &self,
381 _request: Option<rmcp::model::PaginatedRequestParams>,
382 context: rmcp::service::RequestContext<rmcp::RoleServer>,
383 ) -> impl Future<Output = Result<rmcp::model::ListToolsResult, ErrorData>> + Send {
384 let tools: Vec<_> = self
386 .data
387 .enabled_tools()
388 .map(|tool| make_tool_model(tool.metadata()))
389 .collect();
390 let mut result = ListToolsResult::with_all_items(tools);
391 if context
392 .protocol_version()
393 .is_some_and(|version| version >= ProtocolVersion::V_2026_07_28)
394 {
395 result = result.with_ttl_ms(0).with_cache_scope(CacheScope::Private);
398 }
399 std::future::ready(Ok(result))
400 }
401
402 fn get_info(&self) -> ServerConfig {
403 let base = ServerConfig::new(
405 rmcp::model::ServerCapabilities::builder()
406 .enable_tools()
407 .build(),
408 )
409 .with_server_info(rmcp::model::Implementation::default())
410 .with_protocol_version(rmcp::model::ProtocolVersion::default());
411
412 if let Some(instructions) = self.data.instructions() {
413 base.with_instructions(instructions.to_string())
414 } else {
415 base
416 }
417 }
418}
419
420fn make_tool_model(metadata: &McpToolMetadata) -> Tool {
422 let mut tool = rmcp::model::Tool::new(
423 metadata.name().to_string(),
424 metadata.description().to_string(),
425 metadata.input_schema().clone(),
426 );
427
428 if let Some(title) = metadata.title() {
429 tool = tool.with_title(title.to_string());
430 }
431
432 if let Some(schema) = metadata.output_schema() {
433 tool = tool.with_raw_output_schema(schema.clone());
434 }
435
436 tool
437}
438
439fn to_rmcp_error(error: acp::Error) -> rmcp::ErrorData {
441 rmcp::ErrorData {
442 code: rmcp::model::ErrorCode(error.code.into()),
443 message: error.message.into(),
444 data: error.data,
445 }
446}