fix(mcp): expose batch tool permission
This commit is contained in:
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(() => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7046,6 +7046,7 @@ export default withEnglishFallback({
|
||||
mcpToolGetRoutineSource: "ルーチンソース",
|
||||
mcpToolGetSchemaContext: "Schema コンテキスト",
|
||||
mcpToolExecuteQuery: "SQL / Mongo コマンドを実行",
|
||||
mcpToolExecuteBatch: "SQL バッチを実行",
|
||||
mcpToolOpenSession: "クエリセッションを開く",
|
||||
mcpToolCloseSession: "クエリセッションを閉じる",
|
||||
mcpToolExecuteRedisCommand: "Redis コマンドを実行",
|
||||
|
||||
@@ -6792,6 +6792,7 @@ export default withEnglishFallback({
|
||||
mcpToolGetRoutineSource: "루틴 소스",
|
||||
mcpToolGetSchemaContext: "Schema 컨텍스트",
|
||||
mcpToolExecuteQuery: "SQL / Mongo 명령 실행",
|
||||
mcpToolExecuteBatch: "SQL 배치 실행",
|
||||
mcpToolOpenSession: "쿼리 세션 열기",
|
||||
mcpToolCloseSession: "쿼리 세션 닫기",
|
||||
mcpToolExecuteRedisCommand: "Redis 명령 실행",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7498,6 +7498,7 @@ export default withEnglishFallback({
|
||||
mcpToolGetRoutineSource: "存储过程源码",
|
||||
mcpToolGetSchemaContext: "Schema 上下文",
|
||||
mcpToolExecuteQuery: "执行 SQL / Mongo 命令",
|
||||
mcpToolExecuteBatch: "批量执行 SQL",
|
||||
mcpToolOpenSession: "打开查询会话",
|
||||
mcpToolCloseSession: "关闭查询会话",
|
||||
mcpToolExecuteRedisCommand: "执行 Redis 命令",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user