feat(mcp): add resource group scopes with permission inheritance
This commit is contained in:
31 files changed
+2398
-1047
No files matched your search
+13
-14
@@ -81,7 +81,7 @@ describe("MCP policy settings state", () => {
|
||||
it("guards the mutation entry point and wires the shared disabled state to policy controls", () => {
|
||||
expect(settingsDialogSource).toContain("if (mcpPolicyControlsDisabled.value) return;");
|
||||
expect(settingsDialogSource).toContain(':disabled="mcpPolicyControlsDisabled"');
|
||||
expect(settingsDialogSource).toContain('@update:allowed-connection-ids="onMcpAllowedConnectionIdsChange"');
|
||||
expect(settingsDialogSource).toContain('@update:scope="onMcpResourceScopeChange"');
|
||||
|
||||
const loadingStart = settingsDialogSource.indexOf("mcpPolicyLoading.value = true;");
|
||||
const policyLoad = settingsDialogSource.indexOf("await settingsStore.initMcpGlobalPolicy(true);");
|
||||
@@ -91,23 +91,22 @@ describe("MCP policy settings state", () => {
|
||||
expect(loadingEnd).toBeGreaterThan(policyLoad);
|
||||
});
|
||||
|
||||
it("keeps translated mode descriptions in one responsive layout track", () => {
|
||||
const descriptionStart = settingsDialogSource.indexOf("data-mcp-execution-mode-description");
|
||||
const descriptionEnd = settingsDialogSource.indexOf("settings.mcpCapabilityTitle", descriptionStart);
|
||||
const descriptionSource = settingsDialogSource.slice(descriptionStart, descriptionEnd);
|
||||
|
||||
expect(descriptionStart).toBeGreaterThan(-1);
|
||||
expect(descriptionEnd).toBeGreaterThan(descriptionStart);
|
||||
expect(descriptionSource).toContain('class="grid text-xs"');
|
||||
expect(descriptionSource.match(/col-start-1 row-start-1/g)).toHaveLength(3);
|
||||
expect(descriptionSource.match(/\? 'visible' : 'invisible'/g)).toHaveLength(3);
|
||||
it("renders one static mode hint in place of stacked per-mode description tracks", () => {
|
||||
expect(settingsDialogSource).not.toContain("data-mcp-execution-mode-description");
|
||||
const groupStart = settingsDialogSource.indexOf('role="radiogroup" aria-labelledby="mcp-execution-mode-label"');
|
||||
const hintIndex = settingsDialogSource.indexOf("settings.mcpPermissionGlobalDefaultHint");
|
||||
expect(groupStart).toBeGreaterThan(-1);
|
||||
expect(hintIndex).toBeGreaterThan(groupStart);
|
||||
});
|
||||
|
||||
it("keeps execution mode cards accessible as a keyboard radio group", () => {
|
||||
expect(settingsDialogSource).toContain('role="radiogroup" aria-labelledby="mcp-execution-mode-label"');
|
||||
expect(settingsDialogSource.match(/role="radio"/g)).toHaveLength(3);
|
||||
expect(settingsDialogSource).toContain(":aria-checked=\"mcpExecutionMode === 'safe_write'\"");
|
||||
expect(settingsDialogSource).toContain("onMcpExecutionModeKeydown($event, 'safe_write')");
|
||||
expect(settingsDialogSource).toContain('v-for="mode in mcpExecutionModeOptions"');
|
||||
expect(settingsDialogSource.match(/role="radio"/g)).toHaveLength(1);
|
||||
expect(settingsDialogSource).toContain(':aria-checked="mcpExecutionMode === mode"');
|
||||
expect(settingsDialogSource).toContain(':tabindex="mcpExecutionMode === mode ? 0 : -1"');
|
||||
expect(settingsDialogSource).toContain("onMcpExecutionModeKeydown($event, mode)");
|
||||
expect(settingsDialogSource).toContain('@keydown="onMcpExecutionModeKeydown($event, mode)"');
|
||||
});
|
||||
|
||||
it("keeps MCP client config tabs on a single scrollable row", () => {
|
||||
|
||||
@@ -250,12 +250,20 @@ export interface McpGlobalPolicy {
|
||||
readOnly: boolean;
|
||||
allowDangerousSql: boolean;
|
||||
allowedConnectionIds: string[] | null;
|
||||
allowedGroupIds: string[];
|
||||
allowedToolNames: string[] | null;
|
||||
connectionPolicies: McpConnectionPolicy[];
|
||||
groupPolicies: McpGroupPolicy[];
|
||||
configured: boolean;
|
||||
queryTimeoutSecs: number | null;
|
||||
}
|
||||
|
||||
export interface McpGroupPolicy {
|
||||
groupId: string;
|
||||
readOnly: boolean;
|
||||
allowDangerousSql: boolean;
|
||||
}
|
||||
|
||||
export interface McpConnectionPolicy {
|
||||
connectionId: string;
|
||||
readOnly: boolean;
|
||||
|
||||
@@ -337,6 +337,24 @@ export function buildConnectionGroupPathMap(layout: SidebarLayout): Map<string,
|
||||
return paths;
|
||||
}
|
||||
|
||||
/** Build root-to-leaf stable group-id paths for permission inheritance. */
|
||||
export function buildConnectionGroupIdPathMap(layout: SidebarLayout): Map<string, string[]> {
|
||||
const paths = new Map<string, string[]>();
|
||||
|
||||
const visit = (entries: SidebarOrderEntry[], path: string[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.type === "connection") {
|
||||
paths.set(entry.id, path);
|
||||
continue;
|
||||
}
|
||||
visit(entryChildren(entry), [...path, entry.id]);
|
||||
}
|
||||
};
|
||||
|
||||
visit(layout.order, []);
|
||||
return paths;
|
||||
}
|
||||
|
||||
export interface ConnectionGroupDestinationRow {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
Reference in new issue
Block a user