feat(mcp): ajoute l'outil idea_ask_agents pour la délégation parallèle (#2)

Wrapper MCP backend pur : dispatche N requêtes idea_ask_agent en parallèle
et retourne un résultat par requête, dans l'ordre, en rejetant les cibles
dupliquées avant dispatch. Aucun changement de domaine/application — pose
la primitive nécessaire au SDK (chantier feature/sdk-integration).

QA vert (ticket #2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 16:56:22 +02:00
parent 454f8ad57d
commit 1ef1fd9e40
4 changed files with 456 additions and 10 deletions

View File

@ -61,6 +61,7 @@ pub const READ_ONLY_TOOLS: &[&str] = &[
/// Canonical write/action MCP tools denied by default.
pub const WRITE_ACTION_TOOLS: &[&str] = &[
"idea_ask_agent",
"idea_ask_agents",
"idea_run_in_background",
"idea_launch_agent",
"idea_stop_agent",
@ -125,6 +126,7 @@ pub fn tool_returns_reply(tool: &str) -> bool {
matches!(
tool,
"idea_ask_agent"
| "idea_ask_agents"
| "idea_list_agents"
| "idea_context_read"
| "idea_memory_read"
@ -180,6 +182,32 @@ pub fn catalogue() -> Vec<ToolDef> {
"additionalProperties": false
}),
},
ToolDef {
name: "idea_ask_agents",
description: "Ask several IdeA agents in parallel and return one result per request, \
in the same order. Each request is equivalent to one idea_ask_agent \
call; duplicate targets are rejected before dispatch.",
input_schema: json!({
"type": "object",
"properties": {
"requests": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"target": { "type": "string", "description": "Target agent display name." },
"task": { "type": "string", "description": "Task or question to send to the target agent." }
},
"required": ["target", "task"],
"additionalProperties": false
}
}
},
"required": ["requests"],
"additionalProperties": false
}),
},
ToolDef {
name: "idea_run_in_background",
description: "Run a command as a first-class IdeA background task. Returns the task id \
@ -659,6 +687,18 @@ mod tests {
);
}
#[test]
fn ask_agents_is_classified_and_returns_inline_reply() {
assert_eq!(
tool_access("idea_ask_agents"),
Some(McpToolAccess::WriteAction)
);
assert!(catalogue()
.into_iter()
.any(|tool| tool.name == "idea_ask_agents"));
assert!(tool_returns_reply("idea_ask_agents"));
}
#[test]
fn run_in_background_maps_to_background_command_with_handshake_owner() {
let cmd = map_tool_call(