2222
2323use sacp:: JrConnection ;
2424use sacp:: schema:: {
25- ContentBlock , InitializeRequest , NewSessionRequest , PromptRequest , RequestPermissionOutcome ,
26- RequestPermissionRequest , RequestPermissionResponse , SessionNotification , TextContent ,
27- VERSION as PROTOCOL_VERSION ,
25+ ContentBlock , InitializeRequest , McpServer , NewSessionRequest , PromptRequest ,
26+ RequestPermissionOutcome , RequestPermissionRequest , RequestPermissionResponse ,
27+ SessionNotification , TextContent , VERSION as PROTOCOL_VERSION ,
2828} ;
29- use sacp_tokio:: { AcpAgent , JrConnectionExt } ;
3029use std:: path:: PathBuf ;
31- use std:: str:: FromStr ;
30+ use tokio:: process:: Child ;
31+ use tokio_util:: compat:: { TokioAsyncReadCompatExt , TokioAsyncWriteCompatExt } ;
32+
33+ // NOTE: This code is inlined here to avoid a circular dependency between `sacp` and `sacp-tokio`.
34+ // If you're writing your own client, you can use `sacp_tokio::AcpAgent` and `JrConnectionExt::to_agent()`
35+ // to simplify this setup significantly.
36+
37+ /// Parse agent configuration from either a command string or JSON.
38+ fn parse_agent_config ( s : & str ) -> Result < McpServer , Box < dyn std:: error:: Error > > {
39+ let trimmed = s. trim ( ) ;
40+
41+ // If it starts with '{', try to parse as JSON
42+ if trimmed. starts_with ( '{' ) {
43+ let server: McpServer = serde_json:: from_str ( trimmed) ?;
44+ return Ok ( server) ;
45+ }
46+
47+ // Otherwise, parse as a command string
48+ let parts = shell_words:: split ( trimmed) ?;
49+ if parts. is_empty ( ) {
50+ return Err ( "Command string cannot be empty" . into ( ) ) ;
51+ }
52+
53+ let command = PathBuf :: from ( & parts[ 0 ] ) ;
54+ let args = parts[ 1 ..] . to_vec ( ) ;
55+ let name = command
56+ . file_name ( )
57+ . and_then ( |n| n. to_str ( ) )
58+ . unwrap_or ( "agent" )
59+ . to_string ( ) ;
60+
61+ Ok ( McpServer :: Stdio {
62+ name,
63+ command,
64+ args,
65+ env : vec ! [ ] ,
66+ } )
67+ }
68+
69+ /// Spawn a process for the agent and get stdio streams.
70+ fn spawn_agent_process (
71+ server : & McpServer ,
72+ ) -> Result <
73+ (
74+ tokio:: process:: ChildStdin ,
75+ tokio:: process:: ChildStdout ,
76+ Child ,
77+ ) ,
78+ Box < dyn std:: error:: Error > ,
79+ > {
80+ match server {
81+ McpServer :: Stdio {
82+ command,
83+ args,
84+ env,
85+ name : _,
86+ } => {
87+ let mut cmd = tokio:: process:: Command :: new ( command) ;
88+ cmd. args ( args) ;
89+ for env_var in env {
90+ cmd. env ( & env_var. name , & env_var. value ) ;
91+ }
92+ cmd. stdin ( std:: process:: Stdio :: piped ( ) )
93+ . stdout ( std:: process:: Stdio :: piped ( ) ) ;
94+
95+ let mut child = cmd. spawn ( ) ?;
96+ let child_stdin = child. stdin . take ( ) . ok_or ( "Failed to open stdin" ) ?;
97+ let child_stdout = child. stdout . take ( ) . ok_or ( "Failed to open stdout" ) ?;
98+
99+ Ok ( ( child_stdin, child_stdout, child) )
100+ }
101+ McpServer :: Http { .. } => Err ( "HTTP transport not yet supported" . into ( ) ) ,
102+ McpServer :: Sse { .. } => Err ( "SSE transport not yet supported" . into ( ) ) ,
103+ }
104+ }
32105
33106#[ tokio:: main]
34107async fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
@@ -53,12 +126,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
53126 let agent_config = & args[ 2 ] ;
54127
55128 // Parse the agent configuration
56- let agent = AcpAgent :: from_str ( agent_config) ?;
129+ let server = parse_agent_config ( agent_config) ?;
57130
58131 eprintln ! ( "🚀 Spawning agent and connecting..." ) ;
59132
133+ // Spawn the agent process
134+ let ( child_stdin, child_stdout, mut child) = spawn_agent_process ( & server) ?;
135+
136+ // Create a JrConnection with the agent's stdio streams
137+ // NOTE: Using sacp_tokio::JrConnectionExt::to_agent() would simplify this setup
138+ let connection = JrConnection :: new ( child_stdin. compat_write ( ) , child_stdout. compat ( ) ) ;
139+
60140 // Run the client
61- JrConnection :: to_agent ( agent ) ?
141+ connection
62142 . on_receive_notification ( async move |notification : SessionNotification , _cx| {
63143 // Print session updates to stdout (so 2>/dev/null shows only agent output)
64144 println ! ( "{:?}" , notification. update) ;
@@ -133,5 +213,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
133213 } )
134214 . await ?;
135215
216+ // Kill the child process when done
217+ let _ = child. kill ( ) . await ;
218+
136219 Ok ( ( ) )
137220}
0 commit comments