import type { McpToolCatalogue } from "@/domain"; /** * 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; description?: string; tools: string[]; } type Tr = (french: string, english: string) => string; const fr: Tr = (french) => french; /** Domain groups, in display order (carnet #82 — Conception UX/F). */ export const MCP_TOOL_GROUPS: McpToolGroupDef[] = [ { id: "workspace", label: "Outils du projet", description: "Permet à l’agent d’explorer, rechercher et lire les fichiers du projet.", tools: [ "idea_workspace_map", "idea_workspace_list", "idea_workspace_read", "idea_workspace_search", ], }, { id: "crossProject", label: "IdeA inter-projets", tools: [ "idea_project_list", "idea_project_read_context", "idea_project_list_agents", "idea_project_ask_agent", "idea_project_cancel_agent", ], }, { 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_ask_agents", "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 = { idea_workspace_map: "Cartographier le projet", idea_workspace_list: "Lister les éléments du projet", idea_workspace_read: "Lire un élément du projet", idea_workspace_search: "Rechercher dans le projet", idea_project_list: "Lister les projets IdeA", idea_project_read_context: "Lire le contexte d'un autre projet", idea_project_list_agents: "Lister les agents d'un autre projet", idea_project_ask_agent: "Demander à un agent d'un autre projet", idea_project_cancel_agent: "Annuler une demande inter-projet", 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_ask_agents: "Solliciter plusieurs agents", 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", }; const MCP_TOOL_LABELS_EN: Record = { idea_workspace_map: "Map the project", idea_workspace_list: "List project items", idea_workspace_read: "Read a project item", idea_workspace_search: "Search the project", idea_project_list: "List IdeA projects", idea_project_read_context: "Read another project's context", idea_project_list_agents: "List agents in another project", idea_project_ask_agent: "Ask an agent in another project", idea_project_cancel_agent: "Cancel a cross-project request", idea_list_agents: "List agents", idea_context_read: "Read an agent's context", idea_memory_read: "Read project memory", idea_skill_read: "Read a skill", idea_workstate_read: "Read work state", idea_ticket_read: "Read a ticket", idea_ticket_list: "List tickets", idea_ticket_read_carnet: "Read ticket notes", idea_sprint_list: "List sprints", idea_ask_agent: "Delegate to an agent", idea_ask_agents: "Ask several agents", idea_launch_agent: "Launch an agent", idea_stop_agent: "Stop an agent", idea_update_context: "Update an agent's context", idea_context_propose: "Propose context", idea_memory_write: "Write project memory", idea_workstate_set: "Write work state", idea_create_skill: "Create a skill", idea_ticket_create: "Create a ticket", idea_ticket_update: "Update a ticket", idea_ticket_update_status: "Update ticket status", idea_ticket_update_priority: "Update ticket priority", idea_ticket_update_carnet: "Update ticket notes", idea_ticket_link: "Link tickets", idea_ticket_unlink: "Unlink tickets", idea_run_in_background: "Run a command in the background", }; export function mcpToolGroupLabel(group: McpToolGroupDef, tr: Tr = fr): string { const english: Record = { workspace: "Project tools", crossProject: "IdeA cross-project", readProject: "Project read", readTickets: "Ticket read", delegation: "Agent delegation", contextMemory: "Context and memory", tickets: "Tickets", work: "Work and execution", skills: "Skills", other: "Other" }; return tr(group.label, english[group.id] ?? group.label); } export function mcpToolGroupDescription( group: McpToolGroupDef, tr: Tr = fr, ): string | undefined { if (!group.description) return undefined; if (group.id === "workspace") { return tr( group.description, "Lets the agent explore, search, and read project files.", ); } return group.description; } /** Short human label for `tool`, falling back to the exact name if unmapped. */ export function mcpToolLabel(tool: string, tr: Tr = fr): string { const french = MCP_TOOL_LABELS[tool]; return french ? tr(french, MCP_TOOL_LABELS_EN[tool] ?? french) : 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; } /** * Builds the exhaustive presentation groups from the backend catalogue. * Local groups only choose placement/labels; every catalogued tool remains * configurable, with unmapped names collected under the visible fallback. */ export function mcpToolGroupsForCatalogue( catalogue: McpToolCatalogue, ): McpToolGroupDef[] { const catalogued = Array.from( new Set([...catalogue.readOnlyTools, ...catalogue.writeActionTools]), ); const cataloguedSet = new Set(catalogued); const mapped = new Set(); const groups = MCP_TOOL_GROUPS.flatMap((group) => { const tools = group.tools.filter((tool) => cataloguedSet.has(tool)); tools.forEach((tool) => mapped.add(tool)); return tools.length > 0 ? [{ ...group, tools }] : []; }); const otherTools = catalogued.filter((tool) => !mapped.has(tool)); return otherTools.length > 0 ? [...groups, { id: "other", label: "Autres", tools: otherTools }] : groups; }