fix(mcp): expose batch tool permission

This commit is contained in:
Eve authored and GitHub committed 2026-09-08 22:34:57 +08:00
1 parent f102c5d2d5
commit dcbe9701ef
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) {