agent_client_protocol/
acp.rs

1pub mod mcp_types;
2pub use mcp_types::*;
3
4use std::{fmt, path::PathBuf, sync::Arc};
5
6use schemars::{JsonSchema, generate::SchemaSettings};
7use serde::{Deserialize, Serialize};
8
9#[derive(Serialize)]
10pub struct AgentMethods {
11    pub new_session: &'static str,
12    pub load_session: &'static str,
13    pub prompt: &'static str,
14    pub session_update: &'static str,
15}
16
17pub const AGENT_METHODS: AgentMethods = AgentMethods {
18    new_session: "acp/new_session",
19    load_session: "acp/load_session",
20    prompt: "acp/prompt",
21    session_update: "acp/session_update",
22};
23
24// New session
25
26#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
27#[serde(rename_all = "camelCase")]
28pub struct NewSessionArguments {
29    pub mcp_servers: Vec<McpServer>,
30    pub client_tools: ClientTools,
31    pub cwd: PathBuf,
32}
33
34impl NewSessionArguments {
35    pub fn schema() -> serde_json::Value {
36        schema_for::<Self>()
37    }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41#[serde(rename_all = "camelCase")]
42pub struct NewSessionOutput {
43    pub session_id: SessionId,
44}
45
46impl NewSessionOutput {
47    pub fn schema() -> serde_json::Value {
48        schema_for::<Self>()
49    }
50}
51// Load session
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54#[serde(rename_all = "camelCase")]
55pub struct LoadSessionArguments {
56    pub mcp_servers: Vec<McpServer>,
57    pub client_tools: ClientTools,
58    pub cwd: PathBuf,
59    pub session_id: SessionId,
60}
61
62impl LoadSessionArguments {
63    pub fn schema() -> serde_json::Value {
64        schema_for::<Self>()
65    }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
69#[serde(transparent)]
70pub struct SessionId(pub Arc<str>);
71
72impl fmt::Display for SessionId {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", self.0)
75    }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
79#[serde(rename_all = "camelCase")]
80pub struct McpServer {
81    pub name: String,
82    pub command: PathBuf,
83    pub args: Vec<String>,
84    pub env: Vec<EnvVariable>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
88#[serde(rename_all = "camelCase")]
89pub struct EnvVariable {
90    pub name: String,
91    pub value: String,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
95#[serde(rename_all = "camelCase")]
96pub struct McpToolId {
97    pub mcp_server: String,
98    pub tool_name: String,
99}
100
101// Prompt
102
103#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "camelCase")]
105pub struct PromptArguments {
106    pub session_id: SessionId,
107    pub prompt: Vec<ContentBlock>,
108}
109
110impl PromptArguments {
111    pub fn schema() -> serde_json::Value {
112        schema_for::<Self>()
113    }
114}
115
116// Session updates
117
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
119#[serde(rename_all = "camelCase")]
120pub struct SessionNotification {
121    pub session_id: SessionId,
122    #[serde(flatten)]
123    pub update: SessionUpdate,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127#[serde(tag = "sessionUpdate", rename_all = "camelCase")]
128pub enum SessionUpdate {
129    UserMessageChunk { content: ContentBlock },
130    AgentMessageChunk { content: ContentBlock },
131    AgentThoughtChunk { content: ContentBlock },
132    ToolCall(ToolCall),
133    ToolCallUpdate(ToolCallUpdate),
134    Plan(Plan),
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "camelCase")]
139pub struct ToolCall {
140    #[serde(rename = "toolCallId")]
141    pub id: ToolCallId,
142    pub label: String,
143    pub kind: ToolKind,
144    pub status: ToolCallStatus,
145    #[serde(default, skip_serializing_if = "Vec::is_empty")]
146    pub content: Vec<ToolCallContent>,
147    #[serde(default, skip_serializing_if = "Vec::is_empty")]
148    pub locations: Vec<ToolCallLocation>,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub raw_input: Option<serde_json::Value>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
154#[serde(rename_all = "camelCase")]
155pub struct ToolCallUpdate {
156    #[serde(rename = "toolCallId")]
157    pub id: ToolCallId,
158    #[serde(flatten)]
159    pub fields: ToolCallUpdateFields,
160}
161
162#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
163#[serde(rename_all = "camelCase")]
164pub struct ToolCallUpdateFields {
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub kind: Option<ToolKind>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub status: Option<ToolCallStatus>,
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub label: Option<String>,
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub content: Option<Vec<ToolCallContent>>,
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub locations: Option<Vec<ToolCallLocation>>,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub raw_input: Option<serde_json::Value>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
180#[serde(transparent)]
181pub struct ToolCallId(pub Arc<str>);
182
183#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
184#[serde(rename_all = "camelCase")]
185pub enum ToolKind {
186    Read,
187    Edit,
188    Delete,
189    Move,
190    Search,
191    Execute,
192    Think,
193    Fetch,
194    Other,
195}
196
197#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
198#[serde(rename_all = "camelCase")]
199pub enum ToolCallStatus {
200    /// The tool call is currently running
201    Pending,
202    /// The tool call is currently running
203    InProgress,
204    /// The tool call completed successfully
205    Completed,
206    /// The tool call failed
207    Failed,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
211#[serde(tag = "type", rename_all = "camelCase")]
212pub enum ToolCallContent {
213    Content {
214        content: ContentBlock,
215    },
216    Diff {
217        #[serde(flatten)]
218        diff: Diff,
219    },
220}
221
222impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
223    fn from(content: T) -> Self {
224        ToolCallContent::Content {
225            content: content.into(),
226        }
227    }
228}
229
230impl From<Diff> for ToolCallContent {
231    fn from(diff: Diff) -> Self {
232        ToolCallContent::Diff { diff }
233    }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
237#[serde(rename_all = "camelCase")]
238pub struct Diff {
239    pub path: PathBuf,
240    pub old_text: Option<String>,
241    pub new_text: String,
242}
243
244#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
245#[serde(tag = "type", rename_all = "camelCase")]
246pub struct ToolCallLocation {
247    pub path: PathBuf,
248    #[serde(default, skip_serializing_if = "Option::is_none")]
249    pub line: Option<u32>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
253#[serde(rename_all = "camelCase")]
254pub struct Plan {
255    pub entries: Vec<PlanEntry>,
256}
257
258/// A single entry in the execution plan.
259///
260/// Represents a task or goal that the assistant intends to accomplish
261/// as part of fulfilling the user's request.
262#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
263#[serde(rename_all = "camelCase")]
264pub struct PlanEntry {
265    /// Description of what this task aims to accomplish
266    pub content: String,
267    /// Relative importance of this task
268    pub priority: PlanEntryPriority,
269    /// Current progress of this task
270    pub status: PlanEntryStatus,
271}
272
273/// Priority levels for plan entries.
274///
275/// Used to indicate the relative importance or urgency of different
276/// tasks in the execution plan.
277#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone)]
278#[serde(rename_all = "snake_case")]
279pub enum PlanEntryPriority {
280    High,
281    Medium,
282    Low,
283}
284
285/// Status of a plan entry in the execution flow.
286///
287/// Tracks the lifecycle of each task from planning through completion.
288#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone)]
289#[serde(rename_all = "snake_case")]
290pub enum PlanEntryStatus {
291    Pending,
292    InProgress,
293    Completed,
294}
295
296// Client tools
297
298#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
299#[serde(rename_all = "camelCase")]
300pub struct ClientTools {
301    pub request_permission: Option<McpToolId>,
302    pub write_text_file: Option<McpToolId>,
303    pub read_text_file: Option<McpToolId>,
304}
305
306// Permission
307
308#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
309#[serde(rename_all = "camelCase")]
310pub struct RequestPermissionArguments {
311    pub session_id: SessionId,
312    pub tool_call: ToolCall,
313    pub options: Vec<PermissionOption>,
314}
315
316impl RequestPermissionArguments {
317    pub fn schema() -> serde_json::Value {
318        schema_for::<Self>()
319    }
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
323pub struct PermissionOption {
324    #[serde(rename = "optionId")]
325    pub id: PermissionOptionId,
326    pub label: String,
327    pub kind: PermissionOptionKind,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
331#[serde(transparent)]
332pub struct PermissionOptionId(pub Arc<str>);
333
334impl fmt::Display for PermissionOptionId {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        self.0.fmt(f)
337    }
338}
339
340#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
341#[serde(rename_all = "camelCase")]
342pub enum PermissionOptionKind {
343    AllowOnce,
344    AllowAlways,
345    RejectOnce,
346    RejectAlways,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
350#[serde(rename_all = "camelCase")]
351pub struct RequestPermissionOutput {
352    // This extra-level is unfortunately needed because the output must be an object
353    pub outcome: RequestPermissionOutcome,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
357#[serde(tag = "outcome", rename_all = "camelCase")]
358pub enum RequestPermissionOutcome {
359    Canceled,
360    #[serde(rename_all = "camelCase")]
361    Selected {
362        option_id: PermissionOptionId,
363    },
364}
365
366// Write text file
367
368#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
369#[serde(rename_all = "camelCase")]
370pub struct WriteTextFileArguments {
371    pub session_id: SessionId,
372    pub path: PathBuf,
373    pub content: String,
374}
375
376impl WriteTextFileArguments {
377    pub fn schema() -> serde_json::Value {
378        schema_for::<Self>()
379    }
380}
381
382// Read text file
383
384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
385#[serde(rename_all = "camelCase")]
386pub struct ReadTextFileArguments {
387    pub session_id: SessionId,
388    pub path: PathBuf,
389    #[serde(default, skip_serializing_if = "Option::is_none")]
390    pub line: Option<u32>,
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub limit: Option<u32>,
393}
394
395impl ReadTextFileArguments {
396    pub fn schema() -> serde_json::Value {
397        schema_for::<Self>()
398    }
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
402#[serde(rename_all = "camelCase")]
403pub struct ReadTextFileOutput {
404    pub content: String,
405}
406
407impl ReadTextFileOutput {
408    pub fn schema() -> serde_json::Value {
409        schema_for::<Self>()
410    }
411}
412
413fn schema_for<T: JsonSchema>() -> serde_json::Value {
414    let mut settings = SchemaSettings::draft2020_12();
415    settings.inline_subschemas = true;
416    settings.into_generator().into_root_schema_for::<T>().into()
417}