fix(mcp): expose batch tool permission
This commit is contained in:
12 files changed
+77
-24
No files matched your search
@@ -1,10 +1,23 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { groupMcpScopeConnections, isMcpPolicyMutationBlocked, matchesMcpSearchQuery, MCP_CAPABILITY_ROWS, MCP_EXECUTION_MODE_COLUMNS, mcpExecutionModeFromPolicy, mcpPolicyFieldsForExecutionMode, toggleMcpAllowedConnectionId, updateMcpAllowedConnectionIds } from "@/lib/mcp/mcpPolicySelection";
|
||||
import {
|
||||
groupMcpScopeConnections,
|
||||
isMcpPolicyMutationBlocked,
|
||||
matchesMcpSearchQuery,
|
||||
MCP_CAPABILITY_ROWS,
|
||||
MCP_EXECUTION_MODE_COLUMNS,
|
||||
MCP_TOOL_OPTIONS,
|
||||
mcpExecutionModeFromPolicy,
|
||||
mcpPolicyFieldsForExecutionMode,
|
||||
toggleMcpAllowedConnectionId,
|
||||
toggleMcpAllowedToolName,
|
||||
updateMcpAllowedConnectionIds,
|
||||
} from "@/lib/mcp/mcpPolicySelection";
|
||||
|
||||
const settingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
const scopePickerSource = readFileSync(new URL("../../../components/settings/McpConnectionScopePicker.vue", import.meta.url), "utf8");
|
||||
const mcpServerSource = readFileSync(new URL("../../../../../../crates/dbx-mcp/src/server.rs", import.meta.url), "utf8");
|
||||
|
||||
describe("MCP execution permission selection", () => {
|
||||
it("maps the persisted policy to the three UI modes", () => {
|
||||
@@ -70,6 +83,29 @@ describe("MCP policy connection selection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP tool permission selection", () => {
|
||||
it("lists every tool registered by the MCP server", () => {
|
||||
const registeredToolNames = [...mcpServerSource.matchAll(/name\s*=\s*"(dbx_[^"]+)"/g)].map((match) => match[1]).sort();
|
||||
|
||||
expect(MCP_TOOL_OPTIONS.map((tool) => tool.name).sort()).toEqual(registeredToolNames);
|
||||
});
|
||||
|
||||
it("keeps batch execution allowed when allow-all becomes an explicit allowlist", () => {
|
||||
const next = toggleMcpAllowedToolName(null, "dbx_send_message", false);
|
||||
|
||||
expect(next).toContain("dbx_execute_batch");
|
||||
expect(next).not.toContain("dbx_send_message");
|
||||
});
|
||||
|
||||
it("lets batch execution be enabled and disabled independently", () => {
|
||||
expect(toggleMcpAllowedToolName(["dbx_execute_query"], "dbx_execute_batch", true)).toEqual(["dbx_execute_query", "dbx_execute_batch"]);
|
||||
expect(toggleMcpAllowedToolName(["dbx_execute_query", "dbx_execute_batch"], "dbx_execute_batch", false)).toEqual(["dbx_execute_query"]);
|
||||
expect(MCP_TOOL_OPTIONS.find((tool) => tool.name === "dbx_execute_batch")?.labelKey).toBe("settings.mcpToolExecuteBatch");
|
||||
expect(settingsDialogSource).toContain("const mcpToolOptions = MCP_TOOL_OPTIONS;");
|
||||
expect(settingsDialogSource).toContain("toggleMcpAllowedToolName(mcpAllowedToolNames.value, name, allowed)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP policy settings state", () => {
|
||||
it("blocks mutations while loading, saving, or displaying a load error", () => {
|
||||
expect(isMcpPolicyMutationBlocked({ loading: true, saving: false, loadError: "" })).toBe(true);
|
||||
|
||||
@@ -21,6 +21,27 @@ export const MCP_CAPABILITY_ROWS: readonly McpCapabilityRow[] = [
|
||||
{ labelKey: "settings.mcpCapabilityConnectionManagement", read_only: false, safe_write: true, high_risk_write: true },
|
||||
];
|
||||
|
||||
export const MCP_TOOL_OPTIONS = [
|
||||
{ name: "dbx_list_connections", labelKey: "settings.mcpToolListConnections" },
|
||||
{ name: "dbx_list_databases", labelKey: "settings.mcpToolListDatabases" },
|
||||
{ name: "dbx_list_tables", labelKey: "settings.mcpToolListTables" },
|
||||
{ name: "dbx_describe_table", labelKey: "settings.mcpToolDescribeTable" },
|
||||
{ name: "dbx_list_routines", labelKey: "settings.mcpToolListRoutines" },
|
||||
{ name: "dbx_get_routine_source", labelKey: "settings.mcpToolGetRoutineSource" },
|
||||
{ name: "dbx_get_schema_context", labelKey: "settings.mcpToolGetSchemaContext" },
|
||||
{ name: "dbx_execute_query", labelKey: "settings.mcpToolExecuteQuery" },
|
||||
{ name: "dbx_execute_batch", labelKey: "settings.mcpToolExecuteBatch" },
|
||||
{ name: "dbx_open_session", labelKey: "settings.mcpToolOpenSession" },
|
||||
{ name: "dbx_close_session", labelKey: "settings.mcpToolCloseSession" },
|
||||
{ name: "dbx_execute_redis_command", labelKey: "settings.mcpToolExecuteRedisCommand" },
|
||||
{ name: "dbx_send_message", labelKey: "settings.mcpToolSendMessage" },
|
||||
{ name: "dbx_add_connection", labelKey: "settings.mcpToolAddConnection" },
|
||||
{ name: "dbx_duplicate_connection", labelKey: "settings.mcpToolDuplicateConnection" },
|
||||
{ name: "dbx_remove_connection", labelKey: "settings.mcpToolRemoveConnection" },
|
||||
{ name: "dbx_open_table", labelKey: "settings.mcpToolOpenTable" },
|
||||
{ name: "dbx_execute_and_show", labelKey: "settings.mcpToolExecuteAndShow" },
|
||||
] as const;
|
||||
|
||||
export interface McpExecutionPolicyFields {
|
||||
readOnly: boolean;
|
||||
allowDangerousSql: boolean;
|
||||
@@ -90,6 +111,13 @@ export function toggleMcpAllowedConnectionId(current: readonly string[] | null,
|
||||
return updateMcpAllowedConnectionIds(current, availableConnectionIds, [connectionId], allowed);
|
||||
}
|
||||
|
||||
export function toggleMcpAllowedToolName(current: readonly string[] | null, toolName: string, allowed: boolean): string[] {
|
||||
const selected = new Set(current === null ? MCP_TOOL_OPTIONS.map((tool) => tool.name) : current);
|
||||
if (allowed) selected.add(toolName);
|
||||
else selected.delete(toolName);
|
||||
return [...selected];
|
||||
}
|
||||
|
||||
export function updateMcpAllowedConnectionIds(current: readonly string[] | null, availableConnectionIds: readonly string[], connectionIds: readonly string[], allowed: boolean): string[] {
|
||||
const selected = new Set(current === null ? availableConnectionIds : current);
|
||||
for (const connectionId of connectionIds) {
|
||||
|
||||
Reference in new issue
Block a user