Skip to main content

agent_client_protocol/
capabilities.rs

1//! Capability management for the `_meta.symposium` object in ACP messages.
2//!
3//! This module provides traits and types for working with capabilities stored in
4//! the `_meta.symposium` field of `InitializeRequest` and `InitializeResponse`.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use agent_client_protocol::{MetaCapabilityExt, McpAcpTransport};
10//! # use agent_client_protocol::schema::InitializeResponse;
11//! # let init_response: InitializeResponse = unimplemented!();
12//!
13//! let response = init_response.add_meta_capability(McpAcpTransport);
14//! if response.has_meta_capability(McpAcpTransport) {
15//!     // Agent supports MCP-over-ACP bridging
16//! }
17//! ```
18
19use crate::schema::{InitializeRequest, InitializeResponse};
20use serde_json::json;
21
22/// Trait for capabilities stored in the `_meta.symposium` object.
23///
24/// Capabilities are key-value pairs that signal features or context to components
25/// in the proxy chain. Implement this trait to define new capabilities.
26pub trait MetaCapability {
27    /// The key name in the `_meta.symposium` object (e.g., "proxy", "mcp_acp_transport")
28    fn key(&self) -> &'static str;
29
30    /// The value to set when adding this capability (defaults to `true`)
31    fn value(&self) -> serde_json::Value {
32        serde_json::Value::Bool(true)
33    }
34}
35
36/// The mcp_acp_transport capability - indicates support for MCP-over-ACP bridging.
37///
38/// When present in `_meta.symposium.mcp_acp_transport`, signals that the agent
39/// supports having MCP servers with `acp:UUID` transport proxied through the conductor.
40#[derive(Debug)]
41pub struct McpAcpTransport;
42
43impl MetaCapability for McpAcpTransport {
44    fn key(&self) -> &'static str {
45        "mcp_acp_transport"
46    }
47}
48
49/// Extension trait for checking and modifying capabilities in `InitializeRequest`.
50pub trait MetaCapabilityExt {
51    /// Check if a capability is present in `_meta.symposium`
52    fn has_meta_capability(&self, capability: impl MetaCapability) -> bool;
53
54    /// Add a capability to `_meta.symposium`, creating the structure if needed
55    #[must_use]
56    fn add_meta_capability(self, capability: impl MetaCapability) -> Self;
57
58    /// Remove a capability from `_meta.symposium` if present
59    #[must_use]
60    fn remove_meta_capability(self, capability: impl MetaCapability) -> Self;
61}
62
63impl MetaCapabilityExt for InitializeRequest {
64    fn has_meta_capability(&self, capability: impl MetaCapability) -> bool {
65        self.client_capabilities
66            .meta
67            .as_ref()
68            .and_then(|meta| meta.get("symposium"))
69            .and_then(|symposium| symposium.get(capability.key()))
70            .is_some()
71    }
72
73    fn add_meta_capability(mut self, capability: impl MetaCapability) -> Self {
74        let meta = self
75            .client_capabilities
76            .meta
77            .get_or_insert_with(Default::default);
78
79        let symposium = meta.entry("symposium").or_insert_with(|| json!({}));
80
81        if let Some(symposium_obj) = symposium.as_object_mut() {
82            symposium_obj.insert("version".to_string(), json!("1.0"));
83            symposium_obj.insert(capability.key().to_string(), capability.value());
84        }
85
86        self
87    }
88
89    fn remove_meta_capability(mut self, capability: impl MetaCapability) -> Self {
90        if let Some(ref mut meta) = self.client_capabilities.meta
91            && let Some(symposium) = meta.get_mut("symposium")
92            && let Some(symposium_obj) = symposium.as_object_mut()
93        {
94            symposium_obj.remove(capability.key());
95        }
96        self
97    }
98}
99
100impl MetaCapabilityExt for InitializeResponse {
101    fn has_meta_capability(&self, capability: impl MetaCapability) -> bool {
102        self.agent_capabilities
103            .meta
104            .as_ref()
105            .and_then(|meta| meta.get("symposium"))
106            .and_then(|symposium| symposium.get(capability.key()))
107            .is_some()
108    }
109
110    fn add_meta_capability(mut self, capability: impl MetaCapability) -> Self {
111        let meta = self
112            .agent_capabilities
113            .meta
114            .get_or_insert_with(Default::default);
115
116        let symposium = meta.entry("symposium").or_insert_with(|| json!({}));
117
118        if let Some(symposium_obj) = symposium.as_object_mut() {
119            symposium_obj.insert("version".to_string(), json!("1.0"));
120            symposium_obj.insert(capability.key().to_string(), capability.value());
121        }
122
123        self
124    }
125
126    fn remove_meta_capability(mut self, capability: impl MetaCapability) -> Self {
127        if let Some(ref mut meta) = self.agent_capabilities.meta
128            && let Some(symposium) = meta.get_mut("symposium")
129            && let Some(symposium_obj) = symposium.as_object_mut()
130        {
131            symposium_obj.remove(capability.key());
132        }
133        self
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::schema::{ClientCapabilities, ProtocolVersion};
141    use serde_json::json;
142
143    #[test]
144    fn test_add_capability_to_request() {
145        let request = InitializeRequest::new(ProtocolVersion::LATEST);
146
147        let request = request.add_meta_capability(McpAcpTransport);
148
149        assert!(request.has_meta_capability(McpAcpTransport));
150        assert_eq!(
151            request.client_capabilities.meta.as_ref().unwrap()["symposium"]["mcp_acp_transport"],
152            json!(true)
153        );
154    }
155
156    #[test]
157    fn test_remove_capability_from_request() {
158        let mut meta = serde_json::Map::new();
159        meta.insert(
160            "symposium".to_string(),
161            json!({
162                "version": "1.0",
163                "mcp_acp_transport": true
164            }),
165        );
166        let client_capabilities = ClientCapabilities::new().meta(meta);
167
168        let request = InitializeRequest::new(ProtocolVersion::LATEST)
169            .client_capabilities(client_capabilities);
170
171        let request = request.remove_meta_capability(McpAcpTransport);
172
173        assert!(!request.has_meta_capability(McpAcpTransport));
174    }
175
176    #[test]
177    fn test_add_capability_to_response() {
178        let response = InitializeResponse::new(ProtocolVersion::LATEST);
179
180        let response = response.add_meta_capability(McpAcpTransport);
181
182        assert!(response.has_meta_capability(McpAcpTransport));
183        assert_eq!(
184            response.agent_capabilities.meta.as_ref().unwrap()["symposium"]["mcp_acp_transport"],
185            json!(true)
186        );
187    }
188}