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::v1::schema::{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    #[non_exhaustive]
20    Acp {
21        /// The identifier advertised in the session's `McpServer::Acp` declaration.
22        server_id: McpServerAcpId,
23
24        /// The logical identifier of this independent MCP request.
25        request_id: McpRequestId,
26    },
27}
28
29impl McpConnectionContext {
30    /// Whether this MCP connection was established without an ACP transport.
31    #[must_use]
32    pub fn is_standalone(&self) -> bool {
33        matches!(self, Self::Standalone)
34    }
35
36    /// The identifier advertised in the session's `McpServer::Acp` declaration.
37    ///
38    /// Returns `None` for a standalone MCP connection.
39    #[cfg(feature = "unstable_mcp_over_acp")]
40    #[must_use]
41    pub fn server_id(&self) -> Option<&McpServerAcpId> {
42        match self {
43            Self::Standalone => None,
44            Self::Acp { server_id, .. } => Some(server_id),
45        }
46    }
47
48    /// The logical identifier of the active MCP request.
49    ///
50    /// Returns `None` for a standalone MCP connection.
51    #[cfg(feature = "unstable_mcp_over_acp")]
52    #[must_use]
53    pub fn request_id(&self) -> Option<&McpRequestId> {
54        match self {
55            Self::Standalone => None,
56            Self::Acp { request_id, .. } => Some(request_id),
57        }
58    }
59}
60
61/// Connection information available to an MCP server.
62#[derive(Clone, Debug)]
63pub struct McpConnectionTo<Counterpart: Role> {
64    pub(super) context: McpConnectionContext,
65    pub(super) connection: ConnectionTo<Counterpart>,
66    #[cfg(feature = "unstable_mcp_over_acp")]
67    pub(super) cleanup: Option<Arc<Mutex<Vec<oneshot::Receiver<()>>>>>,
68}
69
70impl<Counterpart: Role> McpConnectionTo<Counterpart> {
71    #[cfg(all(feature = "unstable_mcp_over_acp", feature = "schemars"))]
72    pub(crate) fn register_cleanup(&self, done: oneshot::Receiver<()>) {
73        if let Some(cleanup) = &self.cleanup {
74            cleanup.lock().expect("MCP cleanup poisoned").push(done);
75        }
76    }
77
78    #[cfg(feature = "unstable_mcp_over_acp")]
79    pub(crate) async fn wait_cleanup(&self) {
80        if let Some(cleanup) = &self.cleanup {
81            let pending = std::mem::take(&mut *cleanup.lock().expect("MCP cleanup poisoned"));
82            for done in pending {
83                let _finished = done.await;
84            }
85        }
86    }
87
88    /// Describes whether this is a standalone or ACP-attached MCP connection.
89    #[must_use]
90    pub fn context(&self) -> &McpConnectionContext {
91        &self.context
92    }
93
94    /// The identifier advertised in the session's `McpServer::Acp` declaration.
95    ///
96    /// Returns `None` for a standalone MCP connection.
97    #[cfg(feature = "unstable_mcp_over_acp")]
98    #[must_use]
99    pub fn server_id(&self) -> Option<&McpServerAcpId> {
100        self.context.server_id()
101    }
102
103    /// The logical identifier of the active MCP request.
104    ///
105    /// Returns `None` for a standalone MCP connection.
106    #[cfg(feature = "unstable_mcp_over_acp")]
107    #[must_use]
108    pub fn request_id(&self) -> Option<&McpRequestId> {
109        self.context.request_id()
110    }
111
112    /// Borrow the host protocol connection.
113    ///
114    /// For an ACP-attached server, this is its host ACP connection. For a
115    /// standalone server, this is the direct MCP client connection.
116    #[must_use]
117    pub fn connection(&self) -> &ConnectionTo<Counterpart> {
118        &self.connection
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::McpConnectionContext;
125
126    #[test]
127    fn standalone_context_is_explicit() {
128        let context = McpConnectionContext::Standalone;
129
130        assert!(context.is_standalone());
131
132        #[cfg(feature = "unstable_mcp_over_acp")]
133        {
134            assert_eq!(context.server_id(), None);
135            assert_eq!(context.request_id(), None);
136        }
137    }
138
139    #[cfg(feature = "unstable_mcp_over_acp")]
140    #[test]
141    fn acp_context_exposes_server_and_request_ids() {
142        use crate::v1::schema::{McpRequestId, McpServerAcpId};
143
144        let server_id = McpServerAcpId::new("server-id");
145        let request_id = McpRequestId::new("request-id");
146        let context = McpConnectionContext::Acp {
147            server_id: server_id.clone(),
148            request_id: request_id.clone(),
149        };
150
151        assert!(!context.is_standalone());
152        assert_eq!(context.server_id(), Some(&server_id));
153        assert_eq!(context.request_id(), Some(&request_id));
154    }
155}