agent_client_protocol/mcp_server/
context.rs1use 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#[derive(Clone, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum McpConnectionContext {
14 Standalone,
16
17 #[cfg(feature = "unstable_mcp_over_acp")]
19 #[non_exhaustive]
20 Acp {
21 server_id: McpServerAcpId,
23
24 request_id: McpRequestId,
26 },
27}
28
29impl McpConnectionContext {
30 #[must_use]
32 pub fn is_standalone(&self) -> bool {
33 matches!(self, Self::Standalone)
34 }
35
36 #[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 #[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#[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 #[must_use]
90 pub fn context(&self) -> &McpConnectionContext {
91 &self.context
92 }
93
94 #[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 #[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 #[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}