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:
@ -32,9 +32,11 @@ import type {
|
||||
MemoryIndexEntry,
|
||||
MemoryLink,
|
||||
MemoryType,
|
||||
McpToolPolicy,
|
||||
ModelServerCommandPreview,
|
||||
PermissionSet,
|
||||
Project,
|
||||
ProjectMcpToolPermissions,
|
||||
ProjectPermissions,
|
||||
ProjectWorkState,
|
||||
ProfileAvailability,
|
||||
@ -344,6 +346,26 @@ export class HttpPermissionGateway implements PermissionGateway {
|
||||
request: { projectId, agentId },
|
||||
});
|
||||
}
|
||||
getMcpToolPermissions(projectId: string): Promise<ProjectMcpToolPermissions> {
|
||||
return this.http.invoke<ProjectMcpToolPermissions>("get_mcp_tool_permissions", { projectId });
|
||||
}
|
||||
updateProjectMcpToolPermissions(
|
||||
projectId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
return this.http.invoke<ProjectMcpToolPermissions>("update_project_mcp_tool_permissions", {
|
||||
request: { projectId, policy },
|
||||
});
|
||||
}
|
||||
updateAgentMcpToolPermissions(
|
||||
projectId: string,
|
||||
agentId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
return this.http.invoke<ProjectMcpToolPermissions>("update_agent_mcp_tool_permissions", {
|
||||
request: { projectId, agentId, policy },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpWorkStateGateway implements WorkStateGateway {
|
||||
|
||||
@ -31,11 +31,14 @@ import type {
|
||||
MemoryIndexEntry,
|
||||
MemoryLink,
|
||||
MemoryType,
|
||||
McpToolCatalogue,
|
||||
McpToolPolicy,
|
||||
EffectivePermissions,
|
||||
PairedDevice,
|
||||
PairingCode,
|
||||
PermissionSet,
|
||||
Project,
|
||||
ProjectMcpToolPermissions,
|
||||
ProjectPermissions,
|
||||
ProjectWorkState,
|
||||
ProfileAvailability,
|
||||
@ -2151,6 +2154,79 @@ export class MockPermissionGateway implements PermissionGateway {
|
||||
fallback: mostRestrictive(project?.fallback, agent?.fallback),
|
||||
};
|
||||
}
|
||||
|
||||
// ── MCP tool permissions (ticket #82) — mirrors the backend catalogue in
|
||||
// `crates/infrastructure/src/orchestrator/mcp/tools.rs`, a separate durable
|
||||
// document from the file/command permissions above. ─────────────────────
|
||||
private mcpCatalogue: McpToolCatalogue = {
|
||||
readOnlyTools: [
|
||||
"idea_list_agents",
|
||||
"idea_context_read",
|
||||
"idea_memory_read",
|
||||
"idea_skill_read",
|
||||
"idea_workstate_read",
|
||||
"idea_ticket_read",
|
||||
"idea_ticket_list",
|
||||
"idea_ticket_read_carnet",
|
||||
"idea_sprint_list",
|
||||
],
|
||||
writeActionTools: [
|
||||
"idea_ask_agent",
|
||||
"idea_run_in_background",
|
||||
"idea_launch_agent",
|
||||
"idea_stop_agent",
|
||||
"idea_update_context",
|
||||
"idea_context_propose",
|
||||
"idea_memory_write",
|
||||
"idea_workstate_set",
|
||||
"idea_create_skill",
|
||||
"idea_ticket_create",
|
||||
"idea_ticket_update",
|
||||
"idea_ticket_update_status",
|
||||
"idea_ticket_update_priority",
|
||||
"idea_ticket_update_carnet",
|
||||
"idea_ticket_link",
|
||||
"idea_ticket_unlink",
|
||||
],
|
||||
};
|
||||
|
||||
private mcpDocs = new Map<string, ProjectMcpToolPermissions>();
|
||||
|
||||
private mcpDoc(projectId: string): ProjectMcpToolPermissions {
|
||||
if (!this.mcpDocs.has(projectId)) {
|
||||
this.mcpDocs.set(projectId, {
|
||||
version: 1,
|
||||
catalogue: this.mcpCatalogue,
|
||||
projectDefault: null,
|
||||
agents: [],
|
||||
});
|
||||
}
|
||||
return this.mcpDocs.get(projectId)!;
|
||||
}
|
||||
|
||||
async getMcpToolPermissions(projectId: string): Promise<ProjectMcpToolPermissions> {
|
||||
return structuredClone(this.mcpDoc(projectId));
|
||||
}
|
||||
|
||||
async updateProjectMcpToolPermissions(
|
||||
projectId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
const doc = this.mcpDoc(projectId);
|
||||
doc.projectDefault = policy ? structuredClone(policy) : null;
|
||||
return structuredClone(doc);
|
||||
}
|
||||
|
||||
async updateAgentMcpToolPermissions(
|
||||
projectId: string,
|
||||
agentId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
const doc = this.mcpDoc(projectId);
|
||||
doc.agents = doc.agents.filter((entry) => entry.agentId !== agentId);
|
||||
if (policy) doc.agents.push({ agentId, policy: structuredClone(policy) });
|
||||
return structuredClone(doc);
|
||||
}
|
||||
}
|
||||
|
||||
export class MockWorkStateGateway implements WorkStateGateway {
|
||||
|
||||
@ -2,7 +2,9 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import type {
|
||||
EffectivePermissions,
|
||||
McpToolPolicy,
|
||||
PermissionSet,
|
||||
ProjectMcpToolPermissions,
|
||||
ProjectPermissions,
|
||||
} from "@/domain";
|
||||
import type { PermissionGateway } from "@/ports";
|
||||
@ -40,4 +42,31 @@ export class TauriPermissionGateway implements PermissionGateway {
|
||||
request: { projectId, agentId },
|
||||
});
|
||||
}
|
||||
|
||||
getMcpToolPermissions(projectId: string): Promise<ProjectMcpToolPermissions> {
|
||||
return invoke<ProjectMcpToolPermissions>("get_mcp_tool_permissions", {
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
|
||||
updateProjectMcpToolPermissions(
|
||||
projectId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
return invoke<ProjectMcpToolPermissions>(
|
||||
"update_project_mcp_tool_permissions",
|
||||
{ request: { projectId, policy } },
|
||||
);
|
||||
}
|
||||
|
||||
updateAgentMcpToolPermissions(
|
||||
projectId: string,
|
||||
agentId: string,
|
||||
policy: McpToolPolicy | null,
|
||||
): Promise<ProjectMcpToolPermissions> {
|
||||
return invoke<ProjectMcpToolPermissions>(
|
||||
"update_agent_mcp_tool_permissions",
|
||||
{ request: { projectId, agentId, policy } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user