agent_client_protocol/mcp_server/
server.rs1use 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
35pub struct McpServer<Counterpart: Role, Run = NullRun> {
55 phantom: PhantomData<Counterpart>,
57
58 connect: Arc<dyn McpServerConnect<Counterpart>>,
60
61 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 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 #[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#[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 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 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 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}