Skip to content

Commit 45fdf04

Browse files
committed
Ext methods and notifications
1 parent d14b276 commit 45fdf04

11 files changed

Lines changed: 515 additions & 8 deletions

File tree

docs/protocol/schema.mdx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,48 @@ An environment variable to set when launching an MCP server.
10161016
The value to set for the environment variable.
10171017
</ResponseField>
10181018

1019+
## <span class="font-mono">ExtMethodRequest</span>
1020+
1021+
Request parameters for extension method calls.
1022+
1023+
**Type:** Object
1024+
1025+
**Properties:**
1026+
1027+
<ResponseField name="method" type={"string"} required>
1028+
The identifier for the extension method.
1029+
1030+
To help avoid conflicts, it's a good practice to prefix extension
1031+
methods with a unique identifier such as domain name.
1032+
1033+
</ResponseField>
1034+
<ResponseField name="params" type={"object"} required>
1035+
The parameters for the extension method, can be any JSON value.
1036+
</ResponseField>
1037+
1038+
## <span class="font-mono">ExtMethodResponse</span>
1039+
1040+
Response from extension method calls.
1041+
1042+
## <span class="font-mono">ExtNotification</span>
1043+
1044+
Extension notification parameters
1045+
1046+
**Type:** Object
1047+
1048+
**Properties:**
1049+
1050+
<ResponseField name="method" type={"string"} required>
1051+
The identifier for the extension method.
1052+
1053+
To help avoid conflicts, it's a good practice to prefix extension
1054+
methods with a unique identifier such as domain name.
1055+
1056+
</ResponseField>
1057+
<ResponseField name="params" type={"object"} required>
1058+
The parameters for the extension notification, can be any JSON value.
1059+
</ResponseField>
1060+
10191061
## <span class="font-mono">FileSystemCapability</span>
10201062

10211063
File system capabilities that a client may support.

rust/acp.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ mod agent;
5454
mod client;
5555
mod content;
5656
mod error;
57+
mod ext;
5758
mod plan;
5859
mod rpc;
5960
#[cfg(test)]
@@ -66,6 +67,7 @@ pub use agent::*;
6667
pub use client::*;
6768
pub use content::*;
6869
pub use error::*;
70+
pub use ext::*;
6971
pub use plan::*;
7072
pub use stream_broadcast::{
7173
StreamMessage, StreamMessageContent, StreamMessageDirection, StreamReceiver,
@@ -232,6 +234,38 @@ impl Agent for ClientSideConnection {
232234
Some(ClientNotification::CancelNotification(notification)),
233235
)
234236
}
237+
238+
async fn ext_method(
239+
&self,
240+
method: Arc<str>,
241+
params: serde_json::Value,
242+
) -> Result<serde_json::Value, Error> {
243+
let response: ExtMethodResponse = self
244+
.conn
245+
.request(
246+
EXT_METHOD_NAME,
247+
Some(ClientRequest::ExtMethodRequest(ExtMethodRequest {
248+
method,
249+
params,
250+
})),
251+
)
252+
.await?;
253+
Ok(response.0)
254+
}
255+
256+
async fn ext_notification(
257+
&self,
258+
method: Arc<str>,
259+
params: serde_json::Value,
260+
) -> Result<(), Error> {
261+
self.conn.notify(
262+
EXT_NOTIFICATION_NAME,
263+
Some(ClientNotification::ExtNotification(ExtNotification {
264+
method,
265+
params,
266+
})),
267+
)
268+
}
235269
}
236270

237271
/// Marker type representing the client side of an ACP connection.
@@ -340,6 +374,12 @@ impl<T: Client> MessageHandler<ClientSide> for T {
340374
self.kill_terminal(args).await?;
341375
Ok(ClientResponse::KillTerminalResponse)
342376
}
377+
AgentRequest::ExtMethodRequest(args) => {
378+
let response = self.ext_method(args.method, args.params).await?;
379+
Ok(ClientResponse::ExtMethodResponse(ExtMethodResponse(
380+
response,
381+
)))
382+
}
343383
}
344384
}
345385

@@ -348,6 +388,9 @@ impl<T: Client> MessageHandler<ClientSide> for T {
348388
AgentNotification::SessionNotification(notification) => {
349389
self.session_notification(notification).await?;
350390
}
391+
AgentNotification::ExtNotification(args) => {
392+
self.ext_notification(args.method, args.params).await?;
393+
}
351394
}
352395
Ok(())
353396
}
@@ -509,6 +552,38 @@ impl Client for AgentSideConnection {
509552
Some(AgentNotification::SessionNotification(notification)),
510553
)
511554
}
555+
556+
async fn ext_method(
557+
&self,
558+
method: Arc<str>,
559+
params: serde_json::Value,
560+
) -> Result<serde_json::Value, Error> {
561+
let response: ExtMethodResponse = self
562+
.conn
563+
.request(
564+
EXT_METHOD_NAME,
565+
Some(AgentRequest::ExtMethodRequest(ExtMethodRequest {
566+
method,
567+
params,
568+
})),
569+
)
570+
.await?;
571+
Ok(response.0)
572+
}
573+
574+
async fn ext_notification(
575+
&self,
576+
method: Arc<str>,
577+
params: serde_json::Value,
578+
) -> Result<(), Error> {
579+
self.conn.notify(
580+
EXT_NOTIFICATION_NAME,
581+
Some(AgentNotification::ExtNotification(ExtNotification {
582+
method,
583+
params,
584+
})),
585+
)
586+
}
512587
}
513588

514589
/// Marker type representing the agent side of an ACP connection.
@@ -595,6 +670,12 @@ impl<T: Agent> MessageHandler<AgentSide> for T {
595670
let response = self.set_session_mode(args).await?;
596671
Ok(AgentResponse::SetSessionModeResponse(response))
597672
}
673+
ClientRequest::ExtMethodRequest(args) => {
674+
let response = self.ext_method(args.method, args.params).await?;
675+
Ok(AgentResponse::ExtMethodResponse(ExtMethodResponse(
676+
response,
677+
)))
678+
}
598679
}
599680
}
600681

@@ -603,6 +684,9 @@ impl<T: Agent> MessageHandler<AgentSide> for T {
603684
ClientNotification::CancelNotification(notification) => {
604685
self.cancel(notification).await?;
605686
}
687+
ClientNotification::ExtNotification(args) => {
688+
self.ext_notification(args.method, args.params).await?;
689+
}
606690
}
607691
Ok(())
608692
}

rust/agent.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use anyhow::Result;
99
use schemars::JsonSchema;
1010
use serde::{Deserialize, Serialize};
1111

12+
use crate::ext::{
13+
EXT_METHOD_NAME, EXT_NOTIFICATION_NAME, ExtMethodRequest, ExtMethodResponse, ExtNotification,
14+
};
1215
use crate::{ClientCapabilities, ContentBlock, Error, ProtocolVersion, SessionId};
1316

1417
/// Defines the interface that all ACP-compliant agents must implement.
@@ -114,6 +117,24 @@ pub trait Agent {
114117
///
115118
/// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
116119
fn cancel(&self, args: CancelNotification) -> impl Future<Output = Result<(), Error>>;
120+
121+
/// Extension method
122+
///
123+
/// Allows the Client to send an arbitrary request that is not part of the ACP spec.
124+
fn ext_method(
125+
&self,
126+
method: Arc<str>,
127+
params: serde_json::Value,
128+
) -> impl Future<Output = Result<serde_json::Value, Error>>;
129+
130+
/// Extension notification
131+
///
132+
/// Allows the Client to send an arbitrary notification that is not part of the ACP spec.
133+
fn ext_notification(
134+
&self,
135+
method: Arc<str>,
136+
params: serde_json::Value,
137+
) -> impl Future<Output = Result<(), Error>>;
117138
}
118139

119140
// Initialize
@@ -581,6 +602,10 @@ pub struct AgentMethodNames {
581602
pub session_prompt: &'static str,
582603
/// Notification for cancelling operations.
583604
pub session_cancel: &'static str,
605+
/// Extension method for custom functionality.
606+
pub ext_method: &'static str,
607+
/// Extension notification for custom functionality.
608+
pub ext_notification: &'static str,
584609
}
585610

586611
/// Constant containing all agent method names.
@@ -593,6 +618,8 @@ pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames {
593618
session_set_mode: SESSION_SET_MODE_METHOD_NAME,
594619
session_prompt: SESSION_PROMPT_METHOD_NAME,
595620
session_cancel: SESSION_CANCEL_METHOD_NAME,
621+
ext_method: EXT_METHOD_NAME,
622+
ext_notification: EXT_NOTIFICATION_NAME,
596623
};
597624

598625
/// Method name for the initialize request.
@@ -628,6 +655,7 @@ pub enum ClientRequest {
628655
#[cfg(feature = "unstable")]
629656
SetSessionModeRequest(SetSessionModeRequest),
630657
PromptRequest(PromptRequest),
658+
ExtMethodRequest(ExtMethodRequest),
631659
}
632660

633661
/// All possible responses that an agent can send to a client.
@@ -647,6 +675,7 @@ pub enum AgentResponse {
647675
#[cfg(feature = "unstable")]
648676
SetSessionModeResponse(SetSessionModeResponse),
649677
PromptResponse(PromptResponse),
678+
ExtMethodResponse(ExtMethodResponse),
650679
}
651680

652681
/// All possible notifications that a client can send to an agent.
@@ -660,6 +689,7 @@ pub enum AgentResponse {
660689
#[schemars(extend("x-docs-ignore" = true))]
661690
pub enum ClientNotification {
662691
CancelNotification(CancelNotification),
692+
ExtNotification(ExtNotification),
663693
}
664694

665695
/// Notification to cancel ongoing operations for a session.

rust/client.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use serde::{Deserialize, Serialize};
1111

1212
#[cfg(feature = "unstable")]
1313
use crate::SessionModeId;
14+
use crate::ext::{
15+
EXT_METHOD_NAME, EXT_NOTIFICATION_NAME, ExtMethodRequest, ExtMethodResponse, ExtNotification,
16+
};
1417
use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
1518

1619
/// Defines the interface that ACP-compliant clients must implement.
@@ -120,6 +123,24 @@ pub trait Client {
120123
#[doc(hidden)]
121124
#[cfg(feature = "unstable")]
122125
fn kill_terminal(&self, args: KillTerminalRequest) -> impl Future<Output = Result<(), Error>>;
126+
127+
/// Extension method
128+
///
129+
/// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
130+
fn ext_method(
131+
&self,
132+
method: Arc<str>,
133+
params: serde_json::Value,
134+
) -> impl Future<Output = Result<serde_json::Value, Error>>;
135+
136+
/// Extension notification
137+
///
138+
/// Allows the Agent to send an arbitrary notifiation that is not part of the ACP spec.
139+
fn ext_notification(
140+
&self,
141+
method: Arc<str>,
142+
params: serde_json::Value,
143+
) -> impl Future<Output = Result<(), Error>>;
123144
}
124145

125146
// Session updates
@@ -560,6 +581,10 @@ pub struct ClientMethodNames {
560581
/// Method for killing a terminal.
561582
#[cfg(feature = "unstable")]
562583
pub terminal_kill: &'static str,
584+
/// Extension method for custom functionality.
585+
pub ext_method: &'static str,
586+
/// Extension notification for custom functionality.
587+
pub ext_notification: &'static str,
563588
}
564589

565590
/// Constant containing all client method names.
@@ -578,6 +603,8 @@ pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
578603
terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
579604
#[cfg(feature = "unstable")]
580605
terminal_kill: TERMINAL_KILL_METHOD_NAME,
606+
ext_method: EXT_METHOD_NAME,
607+
ext_notification: EXT_NOTIFICATION_NAME,
581608
};
582609

583610
/// Notification name for session updates.
@@ -627,6 +654,7 @@ pub enum AgentRequest {
627654
WaitForTerminalExitRequest(WaitForTerminalExitRequest),
628655
#[cfg(feature = "unstable")]
629656
KillTerminalRequest(KillTerminalRequest),
657+
ExtMethodRequest(ExtMethodRequest),
630658
}
631659

632660
/// All possible responses that a client can send to an agent.
@@ -652,6 +680,7 @@ pub enum ClientResponse {
652680
WaitForTerminalExitResponse(WaitForTerminalExitResponse),
653681
#[cfg(feature = "unstable")]
654682
KillTerminalResponse,
683+
ExtMethodResponse(ExtMethodResponse),
655684
}
656685

657686
/// All possible notifications that an agent can send to a client.
@@ -663,6 +692,8 @@ pub enum ClientResponse {
663692
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
664693
#[serde(untagged)]
665694
#[schemars(extend("x-docs-ignore" = true))]
695+
#[allow(clippy::large_enum_variant)]
666696
pub enum AgentNotification {
667697
SessionNotification(SessionNotification),
698+
ExtNotification(ExtNotification),
668699
}

rust/example_agent.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,32 @@ impl acp::Agent for ExampleAgent {
107107
log::info!("Received cancel request {args:?}");
108108
Ok(())
109109
}
110+
111+
async fn ext_method(
112+
&self,
113+
method: std::sync::Arc<str>,
114+
params: serde_json::Value,
115+
) -> Result<serde_json::Value, acp::Error> {
116+
log::info!(
117+
"Received extension method call: method={}, params={:?}",
118+
method,
119+
params
120+
);
121+
Ok(serde_json::json!({"example": "response"}))
122+
}
123+
124+
async fn ext_notification(
125+
&self,
126+
method: std::sync::Arc<str>,
127+
params: serde_json::Value,
128+
) -> Result<(), acp::Error> {
129+
log::info!(
130+
"Received extension notification: method={}, params={:?}",
131+
method,
132+
params
133+
);
134+
Ok(())
135+
}
110136
}
111137

112138
#[tokio::main(flavor = "current_thread")]

0 commit comments

Comments
 (0)