From 91e0a9f0b2f32377d7ec571fb5277a47e05dc5ae Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 18 Jul 2026 17:00:02 +0200 Subject: [PATCH] feat(mcp): outils MCP dans la boucle agent (dispatch async + notices) Co-Authored-By: Claude Opus 4.8 --- backend/app/agent.py | 26 +++++++++++++++++++++++++- backend/app/routes/chat.py | 10 +++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/app/agent.py b/backend/app/agent.py index a602336..4f8428a 100644 --- a/backend/app/agent.py +++ b/backend/app/agent.py @@ -81,15 +81,20 @@ async def run_agent( confirm_shell: bool = True, think: bool = True, keep_alive: str | None = None, + mcp_tools: list[dict] | None = None, ) -> AsyncIterator[dict]: # enabled_tools=None -> tous les outils ; liste vide -> aucun outil. if enabled_tools is None: - tools = TOOL_DEFINITIONS + tools = list(TOOL_DEFINITIONS) elif enabled_tools: tools = [ t for t in TOOL_DEFINITIONS if t["function"]["name"] in enabled_tools ] else: + tools = [] + # Outils MCP des serveurs activés (déjà résolus par l'appelant). + tools.extend(mcp_tools or []) + if not tools: tools = None collected: list[dict] = [] text_parts: list[str] = [] @@ -289,6 +294,25 @@ async def run_agent( }) continue + # Outils MCP : dispatch asynchrone vers le serveur concerné. + if name.startswith("mcp_"): + from .mcp_client import manager as mcp_manager + mcp_res = await mcp_manager.call_tool(name, args) + status = "ok" if mcp_res["ok"] else "error" + record = {"name": name, "args": args, + "summary": mcp_res["summary"], "status": status} + collected.append(record) + yield {"type": "tool_result", **record} + convo.append({ + "role": "tool", + "tool_name": name, + "content": json.dumps( + {"ok": mcp_res["ok"], "content": mcp_res["content"]}, + ensure_ascii=False, + ), + }) + continue + try: result = run_tool(name, args) summary = result.get("summary", "terminé") diff --git a/backend/app/routes/chat.py b/backend/app/routes/chat.py index 8ecfe53..90bc329 100644 --- a/backend/app/routes/chat.py +++ b/backend/app/routes/chat.py @@ -214,15 +214,22 @@ async def chat(req: ChatRequest) -> StreamingResponse: if want_rag or want_plan or use_code: yield _sse("status", {"message": "Préparation du contexte…"}) - memories, plan, code_model = await asyncio.gather( + from ..mcp_client import manager as mcp_manager + + memories, plan, code_model, mcp_tools = await asyncio.gather( rag.recall(req.session_id, req.content, embed_model=cfg.get("embed_model")) if want_rag else asyncio.sleep(0, result=[]), enhance.make_plan(model, req.content, options=run_opts, keep_alive=keep) if want_plan else asyncio.sleep(0, result=[]), coder.pick_code_model(model, cfg.get("code_model")) if use_code else asyncio.sleep(0, result=model), + mcp_manager.tool_definitions(), ) + # Pannes MCP éventuelles : notice non bloquante dans le fil. + for mcp_notice in mcp_manager.notices(): + yield _sse("notice", {"message": mcp_notice}) + if memories: convo.insert(1, { "role": "system", @@ -279,6 +286,7 @@ async def chat(req: ChatRequest) -> StreamingResponse: confirm_shell=cfg.get("confirm_shell", True), think=cfg.get("think", True), keep_alive=cfg.get("keep_alive", "30m"), + mcp_tools=mcp_tools, ): await queue.put(event) except Exception as exc: