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

@@ -181,7 +181,7 @@ import { EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE, parseTableColumnTemplateFields,
import { DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES, normalizeSqlVariableSyntaxOverrides, SQL_VARIABLE_SYNTAX_DATABASE_TYPES, SQL_VARIABLE_SYNTAX_KEYS, SQL_VARIABLE_SYNTAX_TOKENS, type SqlVariableSyntaxOverrides, type SqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
import { buildMcpCherryStudioConfig, buildMcpCodexConfig, buildMcpDeepSeekHarnessConfig, buildMcpJsonConfig, buildMcpOpenCodeConfig, buildMcpPiConfig, buildMcpQoderConfig, buildMcpTraeConfig, buildMcpVsCodeConfig, mcpWebBackendUrl, type McpLaunchConfig } from "@/lib/mcp/mcpConfigTemplates";
import { beginMcpStatusRequest, mcpUpdateAvailability } from "@/lib/mcp/mcpUpdateStatus";
import { isMcpPolicyMutationBlocked, MCP_CAPABILITY_ROWS, MCP_EXECUTION_MODE_COLUMNS, mcpExecutionModeFromPolicy, mcpPolicyFieldsForExecutionMode, type McpExecutionMode } from "@/lib/mcp/mcpPolicySelection";
import { isMcpPolicyMutationBlocked, MCP_CAPABILITY_ROWS, MCP_EXECUTION_MODE_COLUMNS, MCP_TOOL_OPTIONS, mcpExecutionModeFromPolicy, mcpPolicyFieldsForExecutionMode, toggleMcpAllowedToolName, type McpExecutionMode } from "@/lib/mcp/mcpPolicySelection";
import { isMacOS, isWindows } from "@/lib/backend/platform";
import { combineDataTypeForDatabase, dataTypeLengthInputValue, getDataTypeOptions, getDefaultLengthForType, isDataTypeLengthDisabled, splitDataType } from "@/lib/table/tableStructureEditorState";
import { useToast } from "@/composables/useToast";
@@ -2682,25 +2682,7 @@ function onMcpResourceScopeChange(scope: { allowedGroupIds: string[]; allowedCon
type McpConnectionExecutionMode = "read_only" | "safe_write" | "high_risk_write";
const mcpToolOptions = [
{ 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_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;
const mcpToolOptions = MCP_TOOL_OPTIONS;
const mcpAllowedToolNames = computed(() => settingsStore.mcpGlobalPolicy.allowedToolNames);
@@ -2709,9 +2691,7 @@ function mcpToolAllowed(name: string): boolean {
}
function onMcpToolAllowedChange(name: string, allowed: boolean) {
const current = mcpAllowedToolNames.value ?? mcpToolOptions.map((tool) => tool.name);
const next = allowed ? [...new Set([...current, name])] : current.filter((tool) => tool !== name);
void saveMcpPolicy({ allowedToolNames: next });
void saveMcpPolicy({ allowedToolNames: toggleMcpAllowedToolName(mcpAllowedToolNames.value, name, allowed) });
}
const mcpConnectionPolicyConnections = computed(() => {
+1
View File
@@ -7513,6 +7513,7 @@ export default {
mcpToolGetRoutineSource: "Routine source",
mcpToolGetSchemaContext: "Schema context",
mcpToolExecuteQuery: "Execute SQL / Mongo commands",
mcpToolExecuteBatch: "Execute SQL batch",
mcpToolOpenSession: "Open query session",
mcpToolCloseSession: "Close query session",
mcpToolExecuteRedisCommand: "Execute Redis command",
+1
View File
@@ -7039,6 +7039,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "Código de rutina",
mcpToolGetSchemaContext: "Contexto de Schema",
mcpToolExecuteQuery: "Ejecutar SQL / comandos Mongo",
mcpToolExecuteBatch: "Ejecutar lote SQL",
mcpToolOpenSession: "Abrir sesión de consulta",
mcpToolCloseSession: "Cerrar sesión de consulta",
mcpToolExecuteRedisCommand: "Ejecutar comando Redis",
+1
View File
@@ -7039,6 +7039,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "Sorgente routine",
mcpToolGetSchemaContext: "Contesto Schema",
mcpToolExecuteQuery: "Esegui SQL / comandi Mongo",
mcpToolExecuteBatch: "Esegui batch SQL",
mcpToolOpenSession: "Apri sessione di query",
mcpToolCloseSession: "Chiudi sessione di query",
mcpToolExecuteRedisCommand: "Esegui comando Redis",
+1
View File
@@ -7046,6 +7046,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "ルーチンソース",
mcpToolGetSchemaContext: "Schema コンテキスト",
mcpToolExecuteQuery: "SQL / Mongo コマンドを実行",
mcpToolExecuteBatch: "SQL バッチを実行",
mcpToolOpenSession: "クエリセッションを開く",
mcpToolCloseSession: "クエリセッションを閉じる",
mcpToolExecuteRedisCommand: "Redis コマンドを実行",
+1
View File
@@ -6792,6 +6792,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "루틴 소스",
mcpToolGetSchemaContext: "Schema 컨텍스트",
mcpToolExecuteQuery: "SQL / Mongo 명령 실행",
mcpToolExecuteBatch: "SQL 배치 실행",
mcpToolOpenSession: "쿼리 세션 열기",
mcpToolCloseSession: "쿼리 세션 닫기",
mcpToolExecuteRedisCommand: "Redis 명령 실행",
+1
View File
@@ -7041,6 +7041,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "Código da rotina",
mcpToolGetSchemaContext: "Contexto do Schema",
mcpToolExecuteQuery: "Executar SQL / comandos Mongo",
mcpToolExecuteBatch: "Executar lote SQL",
mcpToolOpenSession: "Abrir sessão de consulta",
mcpToolCloseSession: "Fechar sessão de consulta",
mcpToolExecuteRedisCommand: "Executar comando Redis",
+1
View File
@@ -7398,6 +7398,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "Yordam kaynağı",
mcpToolGetSchemaContext: "Şema bağlamı",
mcpToolExecuteQuery: "SQL / Mongo komutlarını çalıştır",
mcpToolExecuteBatch: "SQL toplu işlemini çalıştır",
mcpToolOpenSession: "Sorgu oturumu aç",
mcpToolCloseSession: "Sorgu oturumunu kapat",
mcpToolExecuteRedisCommand: "Redis komutunu çalıştır",
+1
View File
@@ -7498,6 +7498,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "存储过程源码",
mcpToolGetSchemaContext: "Schema 上下文",
mcpToolExecuteQuery: "执行 SQL / Mongo 命令",
mcpToolExecuteBatch: "批量执行 SQL",
mcpToolOpenSession: "打开查询会话",
mcpToolCloseSession: "关闭查询会话",
mcpToolExecuteRedisCommand: "执行 Redis 命令",
+1
View File
@@ -6400,6 +6400,7 @@ export default withEnglishFallback({
mcpToolGetRoutineSource: "預存程序原始碼",
mcpToolGetSchemaContext: "Schema 上下文",
mcpToolExecuteQuery: "執行 SQL / Mongo 命令",
mcpToolExecuteBatch: "批次執行 SQL",
mcpToolOpenSession: "開啟查詢工作階段",
mcpToolCloseSession: "關閉查詢工作階段",
mcpToolExecuteRedisCommand: "執行 Redis 命令",
@@ -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) {