feat(mcp): outils MCP dans la boucle agent (dispatch async + notices)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-07-18 17:00:02 +02:00
1 parent e09af9c9bb
commit 91e0a9f0b2
2 files changed
+34 -2

No files matched your search

+25 -1
View File
@@ -81,15 +81,20 @@ async def run_agent(
confirm_shell: bool = True, confirm_shell: bool = True,
think: bool = True, think: bool = True,
keep_alive: str | None = None, keep_alive: str | None = None,
mcp_tools: list[dict] | None = None,
) -> AsyncIterator[dict]: ) -> AsyncIterator[dict]:
# enabled_tools=None -> tous les outils ; liste vide -> aucun outil. # enabled_tools=None -> tous les outils ; liste vide -> aucun outil.
if enabled_tools is None: if enabled_tools is None:
tools = TOOL_DEFINITIONS tools = list(TOOL_DEFINITIONS)
elif enabled_tools: elif enabled_tools:
tools = [ tools = [
t for t in TOOL_DEFINITIONS if t["function"]["name"] in enabled_tools t for t in TOOL_DEFINITIONS if t["function"]["name"] in enabled_tools
] ]
else: 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 tools = None
collected: list[dict] = [] collected: list[dict] = []
text_parts: list[str] = [] text_parts: list[str] = []
@@ -289,6 +294,25 @@ async def run_agent(
}) })
continue 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: try:
result = run_tool(name, args) result = run_tool(name, args)
summary = result.get("summary", "terminé") summary = result.get("summary", "terminé")
+9 -1
View File
@@ -214,15 +214,22 @@ async def chat(req: ChatRequest) -> StreamingResponse:
if want_rag or want_plan or use_code: if want_rag or want_plan or use_code:
yield _sse("status", {"message": "Préparation du contexte…"}) 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")) rag.recall(req.session_id, req.content, embed_model=cfg.get("embed_model"))
if want_rag else asyncio.sleep(0, result=[]), if want_rag else asyncio.sleep(0, result=[]),
enhance.make_plan(model, req.content, options=run_opts, keep_alive=keep) enhance.make_plan(model, req.content, options=run_opts, keep_alive=keep)
if want_plan else asyncio.sleep(0, result=[]), if want_plan else asyncio.sleep(0, result=[]),
coder.pick_code_model(model, cfg.get("code_model")) coder.pick_code_model(model, cfg.get("code_model"))
if use_code else asyncio.sleep(0, result=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: if memories:
convo.insert(1, { convo.insert(1, {
"role": "system", "role": "system",
@@ -279,6 +286,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
confirm_shell=cfg.get("confirm_shell", True), confirm_shell=cfg.get("confirm_shell", True),
think=cfg.get("think", True), think=cfg.get("think", True),
keep_alive=cfg.get("keep_alive", "30m"), keep_alive=cfg.get("keep_alive", "30m"),
mcp_tools=mcp_tools,
): ):
await queue.put(event) await queue.put(event)
except Exception as exc: except Exception as exc: