Skip to main content

agent_client_protocol/mcp_server/
context.rs

1use 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/// Describes how an MCP server connection was established.
11#[derive(Clone, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum McpConnectionContext {
14    /// The MCP server was connected directly, without an ACP transport.
15    Standalone,
16
17    /// The MCP server was attached to an ACP session.
18    #[cfg(feature = "unstable_mcp_over_acp")]
19    Acp {
20        /// The identifier advertised in the session's `McpServer::Acp` declaration.
21        server_id: McpServerAcpId,
22
23        /// The logical identifier of this independent MCP request.
24        request_id: McpRequestId,
25    },
26}
27
28impl McpConnectionContext {
29    /// Whether this MCP connection was established without an ACP transport.
30    #[must_use]
31    pub fn is_standalone(&self) -> bool {
32        matches!(self, Self::Standalone)
33    }
34
35    /// The identifier advertised in the session's `McpServer::Acp` declaration.
36    ///
37    /// Returns `None` for a standalone MCP connection.
38    #[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    /// The logical identifier of the active MCP request.
48    ///
49    /// Returns `None` for a standalone MCP connection.
50    #[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/// Connection information available to an MCP server.
61#[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    /// Describes whether this is a standalone or ACP-attached MCP connection.
88    #[must_use]
89    pub fn context(&self) -> &McpConnectionContext {
90        &self.context
91    }
92
93    /// The identifier advertised in the session's `McpServer::Acp` declaration.
94    ///
95    /// Returns `None` for a standalone MCP connection.
96    #[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    /// The logical identifier of the active MCP request.
103    ///
104    /// Returns `None` for a standalone MCP connection.
105    #[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    /// Borrow the host protocol connection.
112    ///
113    /// For an ACP-attached server, this is its host ACP connection. For a
114    /// standalone server, this is the direct MCP client connection.
115    #[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}