feat(frontend): surface MCP tool permissions per agent — Permissions panel (#82 lot UX/F)

Adds the "Tools MCP IdeA" tab to the project Permissions panel, alongside
the existing "Système" (file/command) tab. Lets the user grant/revoke MCP
tool capabilities per agent or project-wide default, grouped by domain
(Lecture projet, Lecture tickets, Délégation agents, Contexte et mémoire,
Tickets, Travail et exécution, Skills) instead of a flat 25-checkbox list,
per the UX conception in carnet #82.

- domain/ports/adapters (Tauri, HTTP, mock): wire get_mcp_tool_permissions,
  update_project_mcp_tool_permissions, update_agent_mcp_tool_permissions
  (already merged backend API, #82 lots B1-B4) onto PermissionGateway.
- useMcpToolPermissions: view-model owning the durable MCP tool policy
  document, distinct from the file/command permissions in usePermissions.
- McpToolPermissionsPanel: target selector (Défaut projet + agents with
  Hérité/Override badges) and grouped editor — inherited agents are
  read-only until "Créer un override" (prefilled with the effective
  allowlist), per-row Ajouté/Retiré diffing against the project default,
  inline confirmation before granting a write tool at project-default
  level, and unsaved-draft protection on target change.
- mcpToolGroups.ts: presentational-only domain grouping and short French
  labels — the read/write classification itself always comes from the
  backend-provided catalogue, never hardcoded here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 23:36:02 +02:00
parent 2db37a50fa
commit c3078875f4
10 changed files with 1381 additions and 2 deletions

View File

@ -0,0 +1,111 @@
/**
* Presentational grouping of the MCP tool catalogue (ticket #82).
*
* Purely a frontend display concern: which domain a tool "belongs to" for the
* grouped/collapsible editor, and its short French label. The read/write
* classification stays backend-canonical — always read from the
* {@link McpToolCatalogue} the API returns, never re-derived here.
*/
export interface McpToolGroupDef {
id: string;
label: string;
tools: string[];
}
/** Domain groups, in display order (carnet #82 — Conception UX/F). */
export const MCP_TOOL_GROUPS: McpToolGroupDef[] = [
{
id: "readProject",
label: "Lecture projet",
tools: [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
],
},
{
id: "readTickets",
label: "Lecture tickets",
tools: [
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
],
},
{
id: "delegation",
label: "Délégation agents",
tools: ["idea_ask_agent", "idea_launch_agent", "idea_stop_agent"],
},
{
id: "contextMemory",
label: "Contexte et mémoire",
tools: ["idea_update_context", "idea_context_propose", "idea_memory_write"],
},
{
id: "tickets",
label: "Tickets",
tools: [
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
],
},
{
id: "work",
label: "Travail et exécution",
tools: ["idea_run_in_background", "idea_workstate_set"],
},
{
id: "skills",
label: "Skills",
tools: ["idea_create_skill"],
},
];
/** Short, human French label per exact MCP tool name. */
export const MCP_TOOL_LABELS: Record<string, string> = {
idea_list_agents: "Lister les agents",
idea_context_read: "Lire le contexte d'un agent",
idea_memory_read: "Lire la mémoire projet",
idea_skill_read: "Lire un skill",
idea_workstate_read: "Lire l'état de travail",
idea_ticket_read: "Lire un ticket",
idea_ticket_list: "Lister les tickets",
idea_ticket_read_carnet: "Lire le carnet d'un ticket",
idea_sprint_list: "Lister les sprints",
idea_ask_agent: "Déléguer à un agent",
idea_launch_agent: "Lancer un agent",
idea_stop_agent: "Arrêter un agent",
idea_update_context: "Modifier le contexte d'un agent",
idea_context_propose: "Proposer un contexte",
idea_memory_write: "Écrire la mémoire projet",
idea_workstate_set: "Écrire l'état de travail",
idea_create_skill: "Créer un skill",
idea_ticket_create: "Créer un ticket",
idea_ticket_update: "Modifier un ticket",
idea_ticket_update_status: "Modifier le statut d'un ticket",
idea_ticket_update_priority: "Modifier la priorité d'un ticket",
idea_ticket_update_carnet: "Modifier le carnet d'un ticket",
idea_ticket_link: "Lier des tickets",
idea_ticket_unlink: "Délier des tickets",
idea_run_in_background: "Exécuter une commande en arrière-plan",
};
/** Short human label for `tool`, falling back to the exact name if unmapped. */
export function mcpToolLabel(tool: string): string {
return MCP_TOOL_LABELS[tool] ?? tool;
}
/** The group a tool belongs to, or `undefined` if not locally mapped. */
export function mcpToolGroupId(tool: string): string | undefined {
return MCP_TOOL_GROUPS.find((group) => group.tools.includes(tool))?.id;
}