Skip to main content

agent_client_protocol/mcp_server/
server.rs

1//! MCP server construction, direct serving, and optional ACP attachment.
2
3use std::{marker::PhantomData, sync::Arc};
4
5use futures::{StreamExt, channel::mpsc};
6
7use crate::{
8    ConnectTo, Dispatch, DynConnectTo, Role,
9    jsonrpc::run::{NullRun, RunWithConnectionTo},
10    mcp_server::{McpConnectionContext, McpConnectionTo, McpServerConnect},
11    role,
12};
13
14#[cfg(feature = "unstable_mcp_over_acp")]
15use uuid::Uuid;
16
17#[cfg(feature = "unstable_mcp_over_acp")]
18use crate::{
19    Agent, Client, ConnectionTo, HandleDispatchFrom, Handled,
20    jsonrpc::DynamicHandlerGuard,
21    mcp_server::active_session::McpActiveSession,
22    schema::v1::{
23        LoadSessionRequest, McpServer as SchemaMcpServer, McpServerAcp, McpServerAcpId,
24        NewSessionRequest, ResumeSessionRequest,
25    },
26    util::MatchDispatchFrom,
27};
28
29#[cfg(feature = "unstable_mcp_over_acp")]
30use crate::role::HasPeer;
31
32#[cfg(all(feature = "unstable_mcp_over_acp", feature = "unstable_session_fork"))]
33use crate::schema::v1::ForkSessionRequest;
34
35/// A runtime-agnostic MCP server.
36///
37/// `McpServer` wraps an [`McpServerConnect`](`super::McpServerConnect`)
38/// implementation. A server whose counterpart is [`role::mcp::Client`] can be
39/// connected directly as a standalone MCP component. With the
40/// `unstable_mcp_over_acp` feature, servers can instead be attached to ACP
41/// session setup through `Builder::with_mcp_server` or
42/// `SessionBuilder::with_mcp_server`.
43///
44/// # Creating an MCP Server
45///
46/// The `agent-client-protocol-rmcp` crate provides builder APIs for MCP tools
47/// backed by the `rmcp` crate.
48///
49/// Or implement [`McpServerConnect`](`super::McpServerConnect`) for custom server behavior:
50///
51/// ```rust,ignore
52/// let server = McpServer::new(MyCustomServerConnect, NullRun);
53/// ```
54pub struct McpServer<Counterpart: Role, Run = NullRun> {
55    /// The host role that is serving up this MCP server
56    phantom: PhantomData<Counterpart>,
57
58    /// The "connect" instance
59    connect: Arc<dyn McpServerConnect<Counterpart>>,
60
61    /// The runner is a task that should be run alongside the message handler.
62    /// Some futures direct messages back through channels to this future which actually
63    /// handles responding to the client.
64    ///
65    /// Some connector implementations use this to run support tasks alongside
66    /// the message handler.
67    runner: Run,
68}
69
70impl<Counterpart: Role + std::fmt::Debug, Run: std::fmt::Debug> std::fmt::Debug
71    for McpServer<Counterpart, Run>
72{
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        f.debug_struct("McpServer")
75            .field("phantom", &self.phantom)
76            .field("runner", &self.runner)
77            .finish_non_exhaustive()
78    }
79}
80
81impl<Counterpart: Role, Run> McpServer<Counterpart, Run>
82where
83    Run: RunWithConnectionTo<Counterpart>,
84{
85    /// Create an MCP server from something that implements the [`McpServerConnect`](`super::McpServerConnect`) trait.
86    ///
87    /// # See also
88    ///
89    /// See `agent-client-protocol-rmcp` to construct MCP servers from Rust code
90    /// with `rmcp`.
91    pub fn new(c: impl McpServerConnect<Counterpart>, runner: Run) -> Self {
92        McpServer {
93            phantom: PhantomData,
94            connect: Arc::new(c),
95            runner,
96        }
97    }
98
99    /// Split this MCP server into the message handler and a future that must be run while the handler is active.
100    #[cfg(feature = "unstable_mcp_over_acp")]
101    pub(crate) fn into_handler_and_runner(self) -> (McpSessionHandler<Counterpart>, Run)
102    where
103        Counterpart: HasPeer<Agent>,
104    {
105        let Self {
106            phantom: _,
107            connect,
108            runner,
109        } = self;
110        let server_id = McpServerAcpId::new(format!("mcp-server:{}", Uuid::new_v4()));
111        (McpSessionHandler::new(server_id, connect), runner)
112    }
113}
114
115/// Message handler created from a [`McpServer`].
116#[cfg(feature = "unstable_mcp_over_acp")]
117pub(crate) struct McpSessionHandler<Counterpart: Role>
118where
119    Counterpart: HasPeer<Agent>,
120{
121    server_id: McpServerAcpId,
122    connect: Arc<dyn McpServerConnect<Counterpart>>,
123    active_session: McpActiveSession<Counterpart>,
124}
125
126#[cfg(feature = "unstable_mcp_over_acp")]
127impl<Counterpart: Role> McpSessionHandler<Counterpart>
128where
129    Counterpart: HasPeer<Agent>,
130{
131    pub fn new(server_id: McpServerAcpId, connect: Arc<dyn McpServerConnect<Counterpart>>) -> Self {
132        Self {
133            active_session: McpActiveSession::new(server_id.clone(), connect.clone()),
134            server_id,
135            connect,
136        }
137    }
138
139    /// Append this MCP server's native ACP declaration to a session setup request.
140    fn append_declaration(&self, mcp_servers: &mut Vec<SchemaMcpServer>) {
141        mcp_servers.push(SchemaMcpServer::Acp(McpServerAcp::new(
142            self.connect.name(),
143            self.server_id.clone(),
144        )));
145    }
146}
147
148#[cfg(feature = "unstable_mcp_over_acp")]
149impl<Counterpart: Role> McpSessionHandler<Counterpart>
150where
151    Counterpart: HasPeer<Agent>,
152{
153    /// Attach this server to the new session, spawning off a dynamic handler that will
154    /// manage requests coming from this session.
155    ///
156    /// # Return value
157    ///
158    /// Returns a [`DynamicHandlerGuard`] for the handler that intercepts messages
159    /// related to this MCP server. Once the value is dropped, the MCP server messages
160    /// will no longer be received, so you need to keep this value alive as long as the session
161    /// is in use. You can also invoke [`DynamicHandlerGuard::detach`] if you
162    /// want to keep the handler registered for the life of the connection.
163    pub fn into_dynamic_handler(
164        self,
165        request: &mut NewSessionRequest,
166        cx: &ConnectionTo<Counterpart>,
167    ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error>
168    where
169        Counterpart: HasPeer<Agent>,
170    {
171        self.append_declaration(&mut request.mcp_servers);
172        cx.add_dynamic_handler(self.active_session)
173    }
174}
175
176#[cfg(feature = "unstable_mcp_over_acp")]
177impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for McpSessionHandler<Counterpart>
178where
179    Counterpart: HasPeer<Client> + HasPeer<Agent>,
180{
181    async fn handle_dispatch_from(
182        &mut self,
183        message: Dispatch,
184        cx: ConnectionTo<Counterpart>,
185    ) -> Result<Handled<Dispatch>, crate::Error> {
186        let matcher = MatchDispatchFrom::new(message, &cx)
187            .if_request_from(Client, async |mut request: NewSessionRequest, responder| {
188                self.append_declaration(&mut request.mcp_servers);
189                Ok(Handled::No {
190                    message: (request, responder),
191                    retry: false,
192                })
193            })
194            .await
195            .if_request_from(
196                Client,
197                async |mut request: LoadSessionRequest, responder| {
198                    self.append_declaration(&mut request.mcp_servers);
199                    Ok(Handled::No {
200                        message: (request, responder),
201                        retry: false,
202                    })
203                },
204            )
205            .await
206            .if_request_from(
207                Client,
208                async |mut request: ResumeSessionRequest, responder| {
209                    self.append_declaration(&mut request.mcp_servers);
210                    Ok(Handled::No {
211                        message: (request, responder),
212                        retry: false,
213                    })
214                },
215            )
216            .await;
217
218        #[cfg(feature = "unstable_session_fork")]
219        let matcher = matcher
220            .if_request_from(
221                Client,
222                async |mut request: ForkSessionRequest, responder| {
223                    self.append_declaration(&mut request.mcp_servers);
224                    Ok(Handled::No {
225                        message: (request, responder),
226                        retry: false,
227                    })
228                },
229            )
230            .await;
231
232        matcher.otherwise_delegate(&mut self.active_session).await
233    }
234
235    fn describe_chain(&self) -> impl std::fmt::Debug {
236        format!("McpServer({})", self.connect.name())
237    }
238}
239
240impl<Run> ConnectTo<role::mcp::Client> for McpServer<role::mcp::Client, Run>
241where
242    Run: RunWithConnectionTo<role::mcp::Client> + 'static,
243{
244    async fn connect_to(
245        self,
246        client: impl ConnectTo<role::mcp::Server>,
247    ) -> Result<(), crate::Error> {
248        let Self {
249            connect,
250            runner,
251            phantom: _,
252        } = self;
253
254        let (tx, mut rx) = mpsc::unbounded();
255
256        role::mcp::Server
257            .builder()
258            .with_runner(runner)
259            .on_receive_dispatch(
260                async |message_from_client: Dispatch, _cx| {
261                    tx.unbounded_send(message_from_client)
262                        .map_err(|_| crate::util::internal_error("nobody listening to mcp server"))
263                },
264                crate::on_receive_dispatch!(),
265            )
266            .with_spawned(async move |connection_to_client| {
267                let spawned_server: DynConnectTo<role::mcp::Client> =
268                    connect.connect(McpConnectionTo {
269                        context: McpConnectionContext::Standalone,
270                        connection: connection_to_client.clone(),
271                    });
272
273                role::mcp::Client
274                    .builder()
275                    .on_receive_dispatch(
276                        async |message_from_server: Dispatch, _| {
277                            // when we receive a message from the server, fwd to the client
278                            connection_to_client.send_proxied_message(message_from_server)
279                        },
280                        crate::on_receive_dispatch!(),
281                    )
282                    .connect_with(spawned_server, async |connection_to_server| {
283                        while let Some(message_from_client) = rx.next().await {
284                            connection_to_server.send_proxied_message(message_from_client)?;
285                        }
286                        Ok(())
287                    })
288                    .await
289            })
290            .connect_to(client)
291            .await
292    }
293}