fix(web): démarre le serveur MCP custom CLI via idea-serve au lieu de node — #224

This commit is contained in:
2026-08-20 12:49:00 +02:00
parent b25c17c095
commit 9a3ca51f6b
9 changed files with 600 additions and 56 deletions

View File

@ -3441,23 +3441,9 @@ fn opencode_config_json(
}),
);
let (command, args) = match runtime {
Some(rt) => (
rt.exe.clone(),
vec![
"mcp-server".to_owned(),
"--endpoint".to_owned(),
rt.endpoint.clone(),
"--project".to_owned(),
rt.project_id.clone(),
"--requester".to_owned(),
rt.requester.clone(),
],
),
None => ("idea".to_owned(), vec!["mcp-server".to_owned()]),
};
let command_array = std::iter::once(command)
.chain(args)
let wiring = mcp_server_wiring(domain::profile::McpTransport::Stdio, runtime);
let command_array = std::iter::once(wiring.command)
.chain(wiring.args)
.map(serde_json::Value::String)
.collect::<Vec<_>>();
root.insert(
@ -5334,6 +5320,37 @@ command = "idea-mcp"
assert!(body.get("permission").is_none());
}
#[test]
fn opencode_config_json_with_runtime_uses_idea_exe_not_node() {
let config =
domain::profile::OpenCodeConfig::new("http://localhost:8080", None, "m", None, None)
.unwrap();
let runtime = McpRuntime {
exe: "/opt/idea/idea-serve".to_owned(),
endpoint: "/run/user/1000/idea-mcp/proj.sock".to_owned(),
project_id: "0123456789abcdef0123456789abcdef".to_owned(),
requester: "11112222-3333-4444-5555-666677778888".to_owned(),
};
let body = opencode_config_json(&config, "/project", Some(&runtime), None);
let command = body["mcp"]["idea"]["command"]
.as_array()
.expect("OpenCode local command is an argv array")
.iter()
.filter_map(serde_json::Value::as_str)
.collect::<Vec<_>>();
assert_eq!(command[0], "/opt/idea/idea-serve");
assert_ne!(command[0], "node");
assert_eq!(command[1], "mcp-server");
assert!(command.contains(&"--endpoint"));
assert!(command.contains(&"/run/user/1000/idea-mcp/proj.sock"));
assert!(command.contains(&"--project"));
assert!(command.contains(&"0123456789abcdef0123456789abcdef"));
assert!(command.contains(&"--requester"));
assert!(command.contains(&"11112222-3333-4444-5555-666677778888"));
}
#[test]
fn opencode_config_json_projects_allow_and_deny() {
let config =

View File

@ -2594,6 +2594,80 @@ async fn launch_opencode_writes_isolated_config_and_env() {
);
}
#[tokio::test]
async fn launch_opencode_chat_with_runtime_uses_idea_bridge_without_pty_or_node() {
let profile = opencode_profile(pid(9));
let (launch, agent, fs, pty, _bus, _sessions, tr, _session) = launch_fixture_with_profile(
profile,
Some(ContextInjectionPlan::File {
target: "AGENTS.md".to_owned(),
}),
);
let factory = FakeStructuredFactory::new(Arc::clone(&tr), sid(888));
let structured = Arc::new(StructuredSessions::new());
let launch = launch
.with_structured_routing_mode(StructuredRoutingMode::HumanPtyFallback)
.with_structured(Arc::new(factory.clone()), Arc::clone(&structured));
let runtime = application::McpRuntime {
exe: "/opt/idea/idea-serve".to_owned(),
endpoint: "/run/user/1000/idea-mcp/proj.sock".to_owned(),
project_id: "0123456789abcdef0123456789abcdef".to_owned(),
requester: agent.id.to_string(),
};
let mut input = launch_input_with_runtime(agent.id, runtime);
input.require_structured = true;
input.node_id = Some(nid(224));
let out = launch.execute(input).await.unwrap();
assert_eq!(factory.starts(), vec![pid(9)]);
assert!(
pty.spawns().is_empty(),
"web custom chat must stay structured"
);
assert!(
out.structured.is_some(),
"backend returns a chat descriptor"
);
assert_eq!(
structured.node_for_agent_in_project(project().id, &agent.id),
Some(nid(224))
);
let run_dir = format!("/home/me/proj/.ideai/run/{}", agent.id);
let opencode = writes_ending_with(&fs, "/opencode.json");
assert_eq!(opencode.len(), 1, "one OpenCode config is written");
let body: serde_json::Value = serde_json::from_slice(&opencode[0].1).unwrap();
let command = body["mcp"]["idea"]["command"]
.as_array()
.expect("OpenCode local command is an argv array")
.iter()
.filter_map(serde_json::Value::as_str)
.collect::<Vec<_>>();
assert_eq!(command[0], "/opt/idea/idea-serve");
assert_ne!(command[0], "node");
assert_eq!(command[1], "mcp-server");
assert!(command.contains(&"--endpoint"));
assert!(command.contains(&"/run/user/1000/idea-mcp/proj.sock"));
assert!(command.contains(&"--project"));
assert!(command.contains(&"0123456789abcdef0123456789abcdef"));
assert!(command.contains(&"--requester"));
let requester = agent.id.to_string();
assert!(command.contains(&requester.as_str()));
let env: std::collections::HashMap<_, _> = factory
.envs()
.into_iter()
.next()
.expect("structured start receives env")
.into_iter()
.collect();
assert_eq!(
env.get("OPENCODE_CONFIG").map(String::as_str),
Some(format!("{run_dir}/opencode.json").as_str())
);
}
#[tokio::test]
async fn launch_mcp_flag_appends_flag_and_path_to_args() {
// Test 5 — Flag { flag: "--mcp-config" } ⇒ spec.args carries the flag followed