Skip to main content

agent_client_protocol/schema/
proxy_protocol.rs

1//! Protocol types for proxy and MCP-over-ACP communication.
2//!
3//! These types are intended to become part of the ACP protocol specification.
4
5use crate::{JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, UntypedMessage};
6use agent_client_protocol_schema::InitializeResponse;
7use serde::{Deserialize, Serialize};
8
9// =============================================================================
10// Successor forwarding protocol
11// =============================================================================
12
13/// JSON-RPC method name for successor forwarding.
14pub const METHOD_SUCCESSOR_MESSAGE: &str = "_proxy/successor";
15
16/// A message being sent to the successor component.
17///
18/// Used in `_proxy/successor` when the proxy wants to forward a message downstream.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SuccessorMessage<M: JsonRpcMessage = UntypedMessage> {
21    /// The message to be sent to the successor component.
22    #[serde(flatten)]
23    pub message: M,
24
25    /// Optional `_meta` metadata.
26    #[serde(
27        rename = "_meta",
28        alias = "meta",
29        skip_serializing_if = "Option::is_none"
30    )]
31    pub meta: Option<serde_json::Value>,
32}
33
34impl<M: JsonRpcMessage> JsonRpcMessage for SuccessorMessage<M> {
35    fn matches_method(method: &str) -> bool {
36        method == METHOD_SUCCESSOR_MESSAGE
37    }
38
39    fn method(&self) -> &str {
40        METHOD_SUCCESSOR_MESSAGE
41    }
42
43    fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
44        UntypedMessage::new(
45            METHOD_SUCCESSOR_MESSAGE,
46            SuccessorMessage {
47                message: self.message.to_untyped_message()?,
48                meta: self.meta.clone(),
49            },
50        )
51    }
52
53    fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error> {
54        if method != METHOD_SUCCESSOR_MESSAGE {
55            return Err(crate::Error::method_not_found());
56        }
57        let outer = crate::util::json_cast_params::<_, SuccessorMessage<UntypedMessage>>(params)?;
58        if !M::matches_method(&outer.message.method) {
59            return Err(crate::Error::method_not_found());
60        }
61        let inner = M::parse_message(&outer.message.method, &outer.message.params)?;
62        Ok(SuccessorMessage {
63            message: inner,
64            meta: outer.meta,
65        })
66    }
67}
68
69impl<Req: JsonRpcRequest> JsonRpcRequest for SuccessorMessage<Req> {
70    type Response = Req::Response;
71}
72
73impl<Notif: JsonRpcNotification> JsonRpcNotification for SuccessorMessage<Notif> {}
74
75// =============================================================================
76// MCP-over-ACP protocol
77// =============================================================================
78
79/// JSON-RPC method name for MCP connect requests
80pub const METHOD_MCP_CONNECT_REQUEST: &str = "_mcp/connect";
81
82/// Creates a new MCP connection. This is equivalent to "running the command".
83#[derive(Debug, Clone, Serialize, Deserialize, crate::JsonRpcRequest)]
84#[request(method = "_mcp/connect", response = McpConnectResponse, crate = crate)]
85pub struct McpConnectRequest {
86    /// The ACP identifier for the server (e.g., "acp:uuid"), matching `McpServerAcp.id`
87    pub acp_id: String,
88
89    /// Optional `_meta` metadata.
90    #[serde(
91        rename = "_meta",
92        alias = "meta",
93        skip_serializing_if = "Option::is_none"
94    )]
95    pub meta: Option<serde_json::Value>,
96}
97
98/// Response to an MCP connect request
99#[derive(Debug, Clone, Serialize, Deserialize, crate::JsonRpcResponse)]
100#[response(crate = crate)]
101pub struct McpConnectResponse {
102    /// Unique identifier for the established MCP connection
103    pub connection_id: String,
104
105    /// Optional `_meta` metadata.
106    #[serde(
107        rename = "_meta",
108        alias = "meta",
109        skip_serializing_if = "Option::is_none"
110    )]
111    pub meta: Option<serde_json::Value>,
112}
113
114/// JSON-RPC method name for MCP disconnect notifications
115pub const METHOD_MCP_DISCONNECT_NOTIFICATION: &str = "_mcp/disconnect";
116
117/// Disconnects the MCP connection.
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, crate::JsonRpcNotification)]
119#[notification(method = "_mcp/disconnect", crate = crate)]
120pub struct McpDisconnectNotification {
121    /// The id of the connection to disconnect.
122    pub connection_id: String,
123
124    /// Optional `_meta` metadata.
125    #[serde(
126        rename = "_meta",
127        alias = "meta",
128        skip_serializing_if = "Option::is_none"
129    )]
130    pub meta: Option<serde_json::Value>,
131}
132
133/// JSON-RPC method name for MCP requests over ACP
134pub const METHOD_MCP_MESSAGE: &str = "_mcp/message";
135
136/// An MCP request sent via ACP. This could be an MCP-server-to-MCP-client request
137/// (in which case it goes from the ACP client to the ACP agent,
138/// note the reversal of roles) or an MCP-client-to-MCP-server request
139/// (in which case it goes from the ACP agent to the ACP client).
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct McpOverAcpMessage<M = UntypedMessage> {
143    /// id given in response to `_mcp/connect` request.
144    pub connection_id: String,
145
146    /// Request to be sent to the MCP server or client.
147    #[serde(flatten)]
148    pub message: M,
149
150    /// Optional `_meta` metadata.
151    #[serde(
152        rename = "_meta",
153        alias = "meta",
154        skip_serializing_if = "Option::is_none"
155    )]
156    pub meta: Option<serde_json::Value>,
157}
158
159impl<M: JsonRpcMessage> JsonRpcMessage for McpOverAcpMessage<M> {
160    fn matches_method(method: &str) -> bool {
161        method == METHOD_MCP_MESSAGE
162    }
163
164    fn method(&self) -> &str {
165        METHOD_MCP_MESSAGE
166    }
167
168    fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
169        let message = self.message.to_untyped_message()?;
170        UntypedMessage::new(
171            METHOD_MCP_MESSAGE,
172            McpOverAcpMessage {
173                connection_id: self.connection_id.clone(),
174                message,
175                meta: self.meta.clone(),
176            },
177        )
178    }
179
180    fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error> {
181        if method != METHOD_MCP_MESSAGE {
182            return Err(crate::Error::method_not_found());
183        }
184        let outer = crate::util::json_cast_params::<_, McpOverAcpMessage<UntypedMessage>>(params)?;
185        if !M::matches_method(&outer.message.method) {
186            return Err(crate::Error::method_not_found());
187        }
188        let inner = M::parse_message(&outer.message.method, &outer.message.params)?;
189        Ok(McpOverAcpMessage {
190            connection_id: outer.connection_id,
191            message: inner,
192            meta: outer.meta,
193        })
194    }
195}
196
197impl<R: JsonRpcRequest> JsonRpcRequest for McpOverAcpMessage<R> {
198    type Response = R::Response;
199}
200
201impl<R: JsonRpcNotification> JsonRpcNotification for McpOverAcpMessage<R> {}
202
203// =============================================================================
204// Proxy initialization protocol
205// =============================================================================
206
207/// JSON-RPC method name for proxy initialization.
208pub const METHOD_INITIALIZE_PROXY: &str = "_proxy/initialize";
209
210/// Initialize request for proxy components.
211///
212/// This is sent to components that have a successor in the chain.
213/// Components that receive this (instead of `InitializeRequest`) know they
214/// are operating as a proxy and should forward messages to their successor.
215#[derive(Debug, Clone, Serialize, Deserialize, crate::JsonRpcRequest)]
216#[request(method = "_proxy/initialize", response = InitializeResponse, crate = crate)]
217pub struct InitializeProxyRequest {
218    /// The underlying initialize request data.
219    #[serde(flatten)]
220    pub initialize: agent_client_protocol_schema::InitializeRequest,
221}
222
223impl From<agent_client_protocol_schema::InitializeRequest> for InitializeProxyRequest {
224    fn from(initialize: agent_client_protocol_schema::InitializeRequest) -> Self {
225        Self { initialize }
226    }
227}