@@ -687,7 +687,7 @@ pub fn ensure_cursor_mcp_config(
687687 Ok ( changed)
688688}
689689
690- /// - `cli`: CLI tool name (e.g. "claude", "codex", "gemini", "droid", "opencode", "cursor")
690+ /// - `cli`: CLI tool name (e.g. "claude", "codex", "gemini", "droid", "grok", " opencode", "cursor")
691691/// - `agent_name`: the name of the agent being spawned
692692/// - `api_key`: optional relay API key (empty or `None` means omit)
693693/// - `base_url`: optional relay base URL (empty or `None` means omit)
@@ -765,6 +765,7 @@ pub async fn configure_agent_relay_mcp_with_result(
765765 let is_gemini = cli_lower == "gemini" ;
766766 let is_droid = cli_lower == "droid" ;
767767 let is_opencode = cli_lower == "opencode" ;
768+ let is_grok = cli_lower == "grok" ;
768769 let is_cursor = cli_lower == "cursor" || cli_lower == "cursor-agent" || cli_lower == "agent" ; // "agent" is cursor-agent's binary name
769770
770771 let api_key = api_key. map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) ;
@@ -920,6 +921,17 @@ pub async fn configure_agent_relay_mcp_with_result(
920921 None ,
921922 )
922923 . await ?;
924+ } else if is_grok {
925+ configure_grok_mcp (
926+ cli,
927+ api_key,
928+ base_url,
929+ Some ( agent_name) ,
930+ agent_token,
931+ workspaces_json,
932+ default_workspace,
933+ )
934+ . await ?;
923935 } else if is_opencode && !existing_args. iter ( ) . any ( |a| a == "--agent" ) {
924936 ensure_opencode_config_with_result (
925937 cwd,
@@ -1110,6 +1122,138 @@ fn gemini_droid_mcp_add_args_with_result(
11101122 args
11111123}
11121124
1125+ fn grok_mcp_add_args (
1126+ api_key : Option < & str > ,
1127+ base_url : Option < & str > ,
1128+ agent_name : Option < & str > ,
1129+ agent_token : Option < & str > ,
1130+ workspaces_json : Option < & str > ,
1131+ default_workspace : Option < & str > ,
1132+ ) -> Vec < String > {
1133+ let mut args = vec ! [ "mcp" . to_string( ) , "add" . to_string( ) ] ;
1134+ if let Some ( key) = api_key {
1135+ args. push ( "--env" . to_string ( ) ) ;
1136+ args. push ( format ! ( "RELAY_API_KEY={key}" ) ) ;
1137+ }
1138+ if let Some ( url) = base_url {
1139+ args. push ( "--env" . to_string ( ) ) ;
1140+ args. push ( format ! ( "RELAY_BASE_URL={url}" ) ) ;
1141+ }
1142+ if let Some ( name) = agent_name. map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) {
1143+ args. push ( "--env" . to_string ( ) ) ;
1144+ args. push ( format ! ( "RELAY_AGENT_NAME={name}" ) ) ;
1145+ args. push ( "--env" . to_string ( ) ) ;
1146+ args. push ( "RELAY_AGENT_TYPE=agent" . to_string ( ) ) ;
1147+ args. push ( "--env" . to_string ( ) ) ;
1148+ args. push ( "RELAY_STRICT_AGENT_NAME=1" . to_string ( ) ) ;
1149+ }
1150+ if let Some ( token) = agent_token. map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) {
1151+ args. push ( "--env" . to_string ( ) ) ;
1152+ args. push ( format ! ( "RELAY_AGENT_TOKEN={token}" ) ) ;
1153+ args. push ( "--env" . to_string ( ) ) ;
1154+ args. push ( "RELAY_SKIP_BOOTSTRAP=1" . to_string ( ) ) ;
1155+ }
1156+ if let Some ( wj) = workspaces_json. map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) {
1157+ args. push ( "--env" . to_string ( ) ) ;
1158+ args. push ( format ! ( "RELAY_WORKSPACES_JSON={wj}" ) ) ;
1159+ }
1160+ if let Some ( dw) = default_workspace. map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) {
1161+ args. push ( "--env" . to_string ( ) ) ;
1162+ args. push ( format ! ( "RELAY_DEFAULT_WORKSPACE={dw}" ) ) ;
1163+ }
1164+ args. push ( AGENT_RELAY_MCP_SERVER . to_string ( ) ) ;
1165+ let mcp_command = agent_relay_mcp_command ( ) ;
1166+ args. push ( "--command" . to_string ( ) ) ;
1167+ args. push ( mcp_command. command ) ;
1168+ args. push ( "--args" . to_string ( ) ) ;
1169+ args. extend ( mcp_command. args ) ;
1170+ args
1171+ }
1172+
1173+ fn grok_manual_mcp_add_cmd ( cli : & str ) -> String {
1174+ let mcp_command = agent_relay_mcp_command ( ) ;
1175+ let mut rendered_parts = vec ! [ mcp_command. command] ;
1176+ rendered_parts. extend ( mcp_command. args ) ;
1177+ let rendered_mcp_command = rendered_parts. join ( " " ) ;
1178+ format ! (
1179+ "{cli} mcp add --env RELAY_API_KEY=<key> --env RELAY_BASE_URL=<url> {AGENT_RELAY_MCP_SERVER} --command {rendered_mcp_command}"
1180+ )
1181+ }
1182+
1183+ #[ allow( clippy:: too_many_arguments) ]
1184+ async fn configure_grok_mcp (
1185+ cli : & str ,
1186+ api_key : Option < & str > ,
1187+ base_url : Option < & str > ,
1188+ agent_name : Option < & str > ,
1189+ agent_token : Option < & str > ,
1190+ workspaces_json : Option < & str > ,
1191+ default_workspace : Option < & str > ,
1192+ ) -> Result < ( ) > {
1193+ let exe = shlex:: split ( cli)
1194+ . and_then ( |parts| parts. first ( ) . cloned ( ) )
1195+ . unwrap_or_else ( || cli. trim ( ) . to_string ( ) ) ;
1196+ let manual_cmd = grok_manual_mcp_add_cmd ( & exe) ;
1197+
1198+ for server_name in [ AGENT_RELAY_MCP_SERVER , LEGACY_RELAYCAST_SERVER ] {
1199+ let _ = std:: process:: Command :: new ( & exe)
1200+ . args ( [ "mcp" , "remove" , server_name] )
1201+ . stdin ( Stdio :: null ( ) )
1202+ . stdout ( Stdio :: null ( ) )
1203+ . stderr ( Stdio :: null ( ) )
1204+ . spawn ( )
1205+ . and_then ( |mut c| c. wait ( ) ) ;
1206+ }
1207+
1208+ let mut mcp_cmd = Command :: new ( & exe) ;
1209+ mcp_cmd. args ( grok_mcp_add_args (
1210+ api_key,
1211+ base_url,
1212+ agent_name,
1213+ agent_token,
1214+ workspaces_json,
1215+ default_workspace,
1216+ ) ) ;
1217+ mcp_cmd
1218+ . stdin ( Stdio :: null ( ) )
1219+ . stdout ( Stdio :: null ( ) )
1220+ . stderr ( Stdio :: piped ( ) ) ;
1221+
1222+ match mcp_cmd. spawn ( ) {
1223+ Ok ( mut child) => match tokio:: time:: timeout ( Duration :: from_secs ( 15 ) , child. wait ( ) ) . await {
1224+ Ok ( Ok ( status) ) if !status. success ( ) => {
1225+ anyhow:: bail!(
1226+ "failed to configure Agent Relay MCP for {cli}: `{cli} mcp add` exited with code {:?}. \
1227+ Please configure the Agent Relay MCP server manually:\n {manual_cmd}",
1228+ status. code( )
1229+ ) ;
1230+ }
1231+ Ok ( Err ( error) ) => {
1232+ anyhow:: bail!(
1233+ "failed to configure Agent Relay MCP for {cli}: {error}. \
1234+ Please configure the Agent Relay MCP server manually:\n {manual_cmd}"
1235+ ) ;
1236+ }
1237+ Err ( _) => {
1238+ let _ = child. kill ( ) . await ;
1239+ anyhow:: bail!(
1240+ "failed to configure Agent Relay MCP for {cli}: `{cli} mcp add` timed out after 15s. \
1241+ Please configure the Agent Relay MCP server manually:\n {manual_cmd}"
1242+ ) ;
1243+ }
1244+ _ => { }
1245+ } ,
1246+ Err ( error) => {
1247+ anyhow:: bail!(
1248+ "failed to configure Agent Relay MCP for {cli}: {error}. \
1249+ Please configure the Agent Relay MCP server manually:\n {manual_cmd}"
1250+ ) ;
1251+ }
1252+ }
1253+
1254+ Ok ( ( ) )
1255+ }
1256+
11131257#[ allow( clippy:: too_many_arguments) ]
11141258async fn configure_gemini_droid_mcp (
11151259 cli : & str ,
@@ -1584,6 +1728,32 @@ mod tests {
15841728 assert_eq ! ( args[ 0 ] , "--mcp-config" ) ;
15851729 }
15861730
1731+ #[ test]
1732+ fn grok_mcp_add_args_use_command_and_args_flags ( ) {
1733+ let args = super :: grok_mcp_add_args (
1734+ Some ( "rk_live_xyz" ) ,
1735+ Some ( "https://api.relaycast.dev" ) ,
1736+ Some ( "GrokWorker" ) ,
1737+ Some ( "tok_grok_123" ) ,
1738+ None ,
1739+ None ,
1740+ ) ;
1741+
1742+ assert ! ( args. starts_with( & [ "mcp" . to_string( ) , "add" . to_string( ) ] ) ) ;
1743+ assert ! ( args. contains( & "--env" . to_string( ) ) ) ;
1744+ assert ! ( args. contains( & "RELAY_API_KEY=rk_live_xyz" . to_string( ) ) ) ;
1745+ assert ! ( args. contains( & "RELAY_AGENT_NAME=GrokWorker" . to_string( ) ) ) ;
1746+ assert ! ( args. contains( & "RELAY_AGENT_TOKEN=tok_grok_123" . to_string( ) ) ) ;
1747+ let server_idx = args
1748+ . iter ( )
1749+ . position ( |arg| arg == "agent-relay" )
1750+ . expect ( "agent-relay arg" ) ;
1751+ assert_eq ! ( args[ server_idx + 1 ] , "--command" ) ;
1752+ assert_eq ! ( args[ server_idx + 2 ] , "npx" ) ;
1753+ assert_eq ! ( args[ server_idx + 3 ] , "--args" ) ;
1754+ assert_eq ! ( args[ server_idx + 4 ] , "-y" ) ;
1755+ }
1756+
15871757 #[ test]
15881758 fn droid_mcp_add_args_include_option_separator ( ) {
15891759 let args = super :: gemini_droid_mcp_add_args (
0 commit comments