agent_client_protocol/mcp_server/
context.rs1use crate::{ConnectionTo, role::Role};
2
3#[cfg(feature = "unstable_mcp_over_acp")]
4use crate::schema::v1::{McpRequestId, McpServerAcpId};
5#[cfg(feature = "unstable_mcp_over_acp")]
6use futures::channel::oneshot;
7#[cfg(feature = "unstable_mcp_over_acp")]
8use std::sync::{Arc, Mutex};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum McpConnectionContext {
14 Standalone,
16
17 #[cfg(feature = "unstable_mcp_over_acp")]
19 Acp {
20 server_id: McpServerAcpId,
22
23 request_id: McpRequestId,
25 },
26}
27
28impl McpConnectionContext {
29 #[must_use]
31 pub fn is_standalone(&self) -> bool {
32 matches!(self, Self::Standalone)
33 }
34
35 #[cfg(feature = "unstable_mcp_over_acp")]
39 #[must_use]
40 pub fn server_id(&self) -> Option<&McpServerAcpId> {
41 match self {
42 Self::Standalone => None,
43 Self::Acp { server_id, .. } => Some(server_id),
44 }
45 }
46
47 #[cfg(feature = "unstable_mcp_over_acp")]
51 #[must_use]
52 pub fn request_id(&self) -> Option<&McpRequestId> {
53 match self {
54 Self::Standalone => None,
55 Self::Acp { request_id, .. } => Some(request_id),
56 }
57 }
58}
59
60#[derive(Clone, Debug)]
62pub struct McpConnectionTo<Counterpart: Role> {
63 pub(super) context: McpConnectionContext,
64 pub(super) connection: ConnectionTo<Counterpart>,
65 #[cfg(feature = "unstable_mcp_over_acp")]
66 pub(super) cleanup: Option<Arc<Mutex<Vec<oneshot::Receiver<()>>>>>,
67}
68
69impl<Counterpart: Role> McpConnectionTo<Counterpart> {
70 #[cfg(all(feature = "unstable_mcp_over_acp", feature = "schemars"))]
71 pub(crate) fn register_cleanup(&self, done: oneshot::Receiver<()>) {
72 if let Some(cleanup) = &self.cleanup {
73 cleanup.lock().expect("MCP cleanup poisoned").push(done);
74 }
75 }
76
77 #[cfg(feature = "unstable_mcp_over_acp")]
78 pub(crate) async fn wait_cleanup(&self) {
79 if let Some(cleanup) = &self.cleanup {
80 let pending = std::mem::take(&mut *cleanup.lock().expect("MCP cleanup poisoned"));
81 for done in pending {
82 let _finished = done.await;
83 }
84 }
85 }
86
87 #[must_use]
89 pub fn context(&self) -> &McpConnectionContext {
90 &self.context
91 }
92
93 #[cfg(feature = "unstable_mcp_over_acp")]
97 #[must_use]
98 pub fn server_id(&self) -> Option<&McpServerAcpId> {
99 self.context.server_id()
100 }
101
102 #[cfg(feature = "unstable_mcp_over_acp")]
106 #[must_use]
107 pub fn request_id(&self) -> Option<&McpRequestId> {
108 self.context.request_id()
109 }
110
111 #[must_use]
116 pub fn connection(&self) -> &ConnectionTo<Counterpart> {
117 &self.connection
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::McpConnectionContext;
124
125 #[test]
126 fn standalone_context_is_explicit() {
127 let context = McpConnectionContext::Standalone;
128
129 assert!(context.is_standalone());
130
131 #[cfg(feature = "unstable_mcp_over_acp")]
132 {
133 assert_eq!(context.server_id(), None);
134 assert_eq!(context.request_id(), None);
135 }
136 }
137
138 #[cfg(feature = "unstable_mcp_over_acp")]
139 #[test]
140 fn acp_context_exposes_server_and_request_ids() {
141 use crate::schema::v1::{McpRequestId, McpServerAcpId};
142
143 let server_id = McpServerAcpId::new("server-id");
144 let request_id = McpRequestId::new("request-id");
145 let context = McpConnectionContext::Acp {
146 server_id: server_id.clone(),
147 request_id: request_id.clone(),
148 };
149
150 assert!(!context.is_standalone());
151 assert_eq!(context.server_id(), Some(&server_id));
152 assert_eq!(context.request_id(), Some(&request_id));
153 }
154}