Skip to content

Commit b1c0df2

Browse files
committed
fix(mcp-bridge): correct backend CLI formats and escape dash-prefixed prompts
- Codex: use 'codex exec [PROMPT]' with positional prompt instead of -p flag (codex interprets -p as --profile, breaking argument parsing) - Gemini: prefix prompts starting with '-' with a space to prevent yargs from treating them as flags (e.g., '--- FILE:' composite prompts in research tool) - Add tests for codex exec mode and dash-prefix escaping
1 parent c131647 commit b1c0df2

1 file changed

Lines changed: 80 additions & 6 deletions

File tree

src/mcp/bridge/backends.rs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,27 @@ pub fn build_command_args(
151151
.unwrap_or_else(|| "llama3.2".to_string());
152152
args.push(model);
153153
args.push(prompt.to_string());
154+
} else if backend.name == "codex" {
155+
// codex exec [OPTIONS] [PROMPT] — non-interactive mode with positional prompt
156+
args.push("exec".to_string());
157+
if auto_approve {
158+
if let Some(flag) = backend.auto_approve_flag {
159+
args.push(flag.to_string());
160+
}
161+
}
162+
if let Some(m) = model_override.or(backend.model.as_deref()) {
163+
args.push("--model".to_string());
164+
args.push(m.to_string());
165+
}
166+
// System prompt prepended (codex has no native --system-prompt)
167+
if let Some(sp) = system_prompt {
168+
args.push(format!("SYSTEM: {}\n\nTASK: {}", sp, prompt));
169+
} else {
170+
args.push(prompt.to_string());
171+
}
154172
} else {
155-
// Standard pattern: [binary] [auto_approve_flag] [-p prompt]
173+
// Standard pattern: [binary] [auto_approve_flag] -p <prompt>
174+
// Used by gemini, claude, grok.
156175
if auto_approve {
157176
if let Some(flag) = backend.auto_approve_flag {
158177
args.push(flag.to_string());
@@ -171,15 +190,22 @@ pub fn build_command_args(
171190
args.push(m.to_string());
172191
}
173192
args.push("-p".to_string());
174-
// For backends without native system prompt support, prepend it
175-
if let Some(sp) = system_prompt {
193+
// For backends without native system prompt support, prepend it.
194+
// Prefix with a space if the prompt starts with '-' to prevent
195+
// yargs-based CLIs (gemini) from misinterpreting it as a flag.
196+
let final_prompt = if let Some(sp) = system_prompt {
176197
if backend.name != "claude" {
177-
args.push(format!("SYSTEM: {}\n\nTASK: {}", sp, prompt));
198+
format!("SYSTEM: {}\n\nTASK: {}", sp, prompt)
178199
} else {
179-
args.push(prompt.to_string());
200+
prompt.to_string()
180201
}
181202
} else {
182-
args.push(prompt.to_string());
203+
prompt.to_string()
204+
};
205+
if final_prompt.starts_with('-') {
206+
args.push(format!(" {}", final_prompt));
207+
} else {
208+
args.push(final_prompt);
183209
}
184210
}
185211

@@ -280,6 +306,54 @@ mod tests {
280306
assert!(args.contains(&"hello".to_string()));
281307
}
282308

309+
#[test]
310+
fn test_build_command_args_codex() {
311+
let backend = BackendConfig {
312+
name: "codex",
313+
display_name: "Codex CLI",
314+
binary: "/usr/bin/codex".to_string(),
315+
model: None,
316+
auto_approve_flag: Some("--full-auto"),
317+
api_key_env: Some("OPENAI_API_KEY"),
318+
};
319+
let (bin, args) = build_command_args(&backend, "hello", None, None, true);
320+
assert_eq!(bin, "/usr/bin/codex");
321+
assert_eq!(args, vec!["exec", "--full-auto", "hello"]);
322+
}
323+
324+
#[test]
325+
fn test_build_command_args_codex_with_system_prompt() {
326+
let backend = BackendConfig {
327+
name: "codex",
328+
display_name: "Codex CLI",
329+
binary: "/usr/bin/codex".to_string(),
330+
model: None,
331+
auto_approve_flag: Some("--full-auto"),
332+
api_key_env: Some("OPENAI_API_KEY"),
333+
};
334+
let (_, args) = build_command_args(&backend, "task", None, Some("You are helpful"), true);
335+
assert_eq!(args[0], "exec");
336+
assert!(args.last().unwrap().contains("SYSTEM: You are helpful"));
337+
assert!(args.last().unwrap().contains("TASK: task"));
338+
}
339+
340+
#[test]
341+
fn test_build_command_args_dash_prefix_escaped() {
342+
let backend = BackendConfig {
343+
name: "gemini",
344+
display_name: "Gemini CLI",
345+
binary: "/usr/bin/gemini".to_string(),
346+
model: None,
347+
auto_approve_flag: Some("-y"),
348+
api_key_env: Some("GEMINI_API_KEY"),
349+
};
350+
let (_, args) = build_command_args(&backend, "--- FILE: test ---\ncontent", None, None, true);
351+
// Prompt should be space-prefixed to avoid yargs misinterpreting leading dashes
352+
let prompt_arg = args.last().unwrap();
353+
assert!(prompt_arg.starts_with(' '));
354+
assert!(prompt_arg.contains("--- FILE: test ---"));
355+
}
356+
283357
#[test]
284358
fn test_all_backend_specs_returns_all() {
285359
let specs = all_backend_specs();

0 commit comments

Comments
 (0)