feat(fusion): serveur MCP complété (ACC-5.1) + pont MCP ObsiGate (ACC-2.2) — 7.83.0
ACC-5.1 (#24) — app/routers/mcp_server.py : catalogue §7.2 complété (list_projects, get_project, read_page tronqué proprement, list_project_tasks), search_pages en plein texte, liens profonds + kind sur chaque résultat, scopes du jeton appliqués (écriture refusée -32002 sans scope write) et journalisation §7.4 de chaque tools/call dans api_audit_log (clés d'arguments seulement). ACC-2.2 (#20) — pont MCP Flowdeck → ObsiGate, côté client : - mcp_client : serveurs à session (capture/réémission de Mcp-Session-Id, handshake avant tools/call), décodage SSE multi-messages par id de réponse, classification des outils à la découverte (readOnlyHint sinon heuristique ; lecture par défaut). - preset connecteur « obsigate » (URL dérivée dev/prod), kinds MCP factorisés (connectors.MCP_KINDS), option dans le panneau agent. - garde-fou n° 2 : permission_manager.is_write_tool couvre les outils MCP écrivants → portes politique d'approbation (agent_engine) et rôles (assert_can) ; McpTool bloque tout apply_* distant (patron §7.5). Tests : test_fusion_mcp_server 18, nouveau test_fusion_pont_obsigate 7 (faux serveur ObsiGate à session stricte via le vrai _rpc). Preuve réelle T0-T5 : attend la clé ObsiGate dédiée (1 vault) et Q-A/Q-B côté Bruno.
This commit is contained in:
@@ -1230,9 +1230,23 @@ class McpTool(Tool):
|
||||
async def execute(self, args, *, user_id=None) -> ToolResult:
|
||||
from app.services import mcp_client
|
||||
|
||||
# Garde-fou du pont (ACC-2.2, patron §7.5) : un outil distant
|
||||
# « apply_* » EXÉCUTE une écriture croisée — il n'est jamais
|
||||
# utilisable par un simple appel d'outil depuis un agent
|
||||
# Flowdeck. L'écriture passe par « propose_* » (classé
|
||||
# écrivant : rôles + approbation) puis par l'approbation dans
|
||||
# l'application propriétaire de la donnée.
|
||||
original = self.entry.get("original") or ""
|
||||
if original.lower().startswith("apply_"):
|
||||
return ToolResult(
|
||||
status="error", tool=self.name, target_type="mcp",
|
||||
message=(f"Outil « {original} » bloqué côté client : une "
|
||||
"écriture croisée ne s'exécute pas par un appel "
|
||||
"d'outil — propose_* puis approbation dans "
|
||||
"l'application propriétaire (patron §7.5)."))
|
||||
try:
|
||||
res = await mcp_client.call_tool(self.entry["connector_id"],
|
||||
self.entry.get("original") or "", args)
|
||||
original, args)
|
||||
except ValueError as exc:
|
||||
return ToolResult(status="error", tool=self.name, message=str(exc))
|
||||
except Exception as exc: # noqa: BLE001 — réseau : erreur outil, pas run
|
||||
|
||||
Reference in New Issue
Block a user