diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index a8a3de7642..f84a57d13b 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -65,6 +65,7 @@ import { SHORTCUT_DEFINITIONS, findShortcutConflict, normalizeShortcutSettings, import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebarTableNameDisplay"; import { normalizeSqlFormatterSettings, type SqlFormatterSettings } from "@/lib/sqlFormatterConfig"; import { EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE, parseTableColumnTemplateFields, TABLE_COLUMN_TEMPLATE_DATABASE_TYPES } from "@/lib/tableColumnTemplates"; +import { buildMcpCodexConfig, buildMcpJsonConfig, buildMcpOpenCodeConfig, buildMcpVsCodeConfig, type McpEnvEntry } from "@/lib/mcpConfigTemplates"; import { combineDataTypeForDatabase, getDataTypeOptions, getDefaultLengthForType, isDataTypeLengthDisabled, splitDataType } from "@/lib/tableStructureEditorState"; import type { DatabaseType, SqlSnippet } from "@/types/database"; import { uuid } from "@/lib/utils"; @@ -1052,19 +1053,22 @@ async function exportDebugLogs() { } // ---------- MCP Server ---------- +type McpConfigTab = "claude" | "cursor" | "trae" | "vscode" | "windsurf" | "codex" | "opencode"; +type McpCopyKind = "install" | `${McpConfigTab}-config`; + const mcpStatus = ref(null); const mcpStatusLoading = ref(false); const mcpStatusError = ref(""); -const mcpCopied = ref<"" | "install" | "claude-config" | "codex-config" | "opencode-config">(""); -const mcpConfigTab = ref<"claude" | "codex" | "opencode">("claude"); +const mcpCopied = ref<"" | McpCopyKind>(""); +const mcpConfigTab = ref("claude"); const mcpReadonlyMode = ref(false); const mcpAllowDangerous = ref(false); const mcpInstalling = ref(false); const mcpInstallMessage = ref(""); const mcpInstallError = ref(false); -const mcpEnvEntries = computed(() => { - const entries: Array<[string, string]> = []; +const mcpEnvEntries = computed(() => { + const entries: McpEnvEntry[] = []; if (mcpReadonlyMode.value) { entries.push(["DBX_MCP_ALLOW_WRITES", "0"]); } @@ -1074,48 +1078,13 @@ const mcpEnvEntries = computed(() => { return entries; }); -const mcpClaudeRecommendedConfig = computed(() => { - const config: Record = { - mcpServers: { - dbx: { - command: "dbx-mcp-server", - } as Record, - }, - }; - if (mcpEnvEntries.value.length > 0) { - const env = Object.fromEntries(mcpEnvEntries.value); - ((config.mcpServers as Record).dbx as Record).env = env; - } - return JSON.stringify(config, null, 2); -}); +const mcpJsonRecommendedConfig = computed(() => buildMcpJsonConfig(mcpEnvEntries.value)); -const mcpCodexRecommendedConfig = computed(() => { - const lines = ["[mcp_servers.dbx]", 'command = "dbx-mcp-server"']; - if (mcpEnvEntries.value.length > 0) { - lines.push(""); - lines.push("[mcp_servers.dbx.env]"); - for (const [key, value] of mcpEnvEntries.value) { - lines.push(`${key} = "${value}"`); - } - } - return lines.join("\n"); -}); +const mcpVsCodeRecommendedConfig = computed(() => buildMcpVsCodeConfig(mcpEnvEntries.value)); -const mcpOpenCodeRecommendedConfig = computed(() => { - const config: Record = { - mcp: { - dbx: { - type: "local", - command: ["dbx-mcp-server"], - } as Record, - }, - }; - if (mcpEnvEntries.value.length > 0) { - const env = Object.fromEntries(mcpEnvEntries.value); - ((config.mcp as Record).dbx as Record).environment = env; - } - return JSON.stringify(config, null, 2); -}); +const mcpCodexRecommendedConfig = computed(() => buildMcpCodexConfig(mcpEnvEntries.value)); + +const mcpOpenCodeRecommendedConfig = computed(() => buildMcpOpenCodeConfig(mcpEnvEntries.value)); const mcpStatusTone = computed<"ok" | "warning" | "muted">(() => { if (!mcpStatus.value) return "muted"; @@ -1154,7 +1123,7 @@ async function refreshMcpStatus() { } } -async function copyMcpText(kind: "install" | "claude-config" | "codex-config" | "opencode-config", value: string) { +async function copyMcpText(kind: McpCopyKind, value: string) { mcpCopied.value = kind; try { await copyToClipboard(value); @@ -3225,22 +3194,86 @@ watch(
- + Claude Code + Cursor + TRAE + VS Code + Windsurf Codex OpenCode
-
{{ mcpClaudeRecommendedConfig }}
-
+ +
+
+ {{ t("settings.mcpCursorConfigPath") }} +
+
+
{{ mcpJsonRecommendedConfig }}
+ +
+
+
+ + +
+
+ {{ t("settings.mcpTraeConfigPath") }} +
+
+
{{ mcpJsonRecommendedConfig }}
+ +
+
+
+ + +
+
+ {{ t("settings.mcpVsCodeConfigPath") }} +
+
+
{{ mcpVsCodeRecommendedConfig }}
+ +
+
+
+ + +
+
+ {{ t("settings.mcpWindsurfConfigPath") }} +
+
+
{{ mcpJsonRecommendedConfig }}
+ +
+
+
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 7f91378eff..ec876a59f1 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2730,6 +2730,10 @@ export default { mcpConfig: "MCP config", mcpCodexConfig: "Codex config", mcpCodexConfigPath: "Codex can use ~/.codex/config.toml or a project-level .codex/config.toml.", + mcpCursorConfigPath: "Cursor can use .cursor/mcp.json in the project or ~/.cursor/mcp.json globally.", + mcpTraeConfigPath: "TRAE: Settings > MCP > Add > Add Manually, then paste the JSON configuration.", + mcpVsCodeConfigPath: "VS Code/Copilot can use .vscode/mcp.json in the workspace or mcp.json in the user profile.", + mcpWindsurfConfigPath: "Windsurf can use ~/.codeium/windsurf/mcp_config.json.", mcpOpenCodeConfigPath: "~/.config/opencode/opencode.json globally, or opencode.json at the project level.", mcpReadonlyMode: "Read-only mode", mcpReadonlyModeDescription: "Adds DBX_MCP_ALLOW_WRITES=0 to the sample config so the MCP session stays query-only.", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index e2f93893a0..5672a5479b 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2297,6 +2297,10 @@ export default { mcpConfig: "Configuración de MCP", mcpCodexConfig: "Configuración de Codex", mcpCodexConfigPath: "Codex puede usar ~/.codex/config.toml o .codex/config.toml dentro del proyecto.", + mcpCursorConfigPath: "Cursor puede usar .cursor/mcp.json en el proyecto o ~/.cursor/mcp.json globalmente.", + mcpTraeConfigPath: "TRAE: Configuración > MCP > Agregar > Agregar manualmente; luego pega la configuración JSON.", + mcpVsCodeConfigPath: "VS Code/Copilot puede usar .vscode/mcp.json en el espacio de trabajo o mcp.json en el perfil de usuario.", + mcpWindsurfConfigPath: "Windsurf puede usar ~/.codeium/windsurf/mcp_config.json.", mcpOpenCodeConfigPath: "~/.config/opencode/opencode.json global, o opencode.json a nivel de proyecto.", mcpReadonlyMode: "Modo solo lectura", mcpReadonlyModeDescription: "Añade DBX_MCP_ALLOW_WRITES=0 al ejemplo de configuración para que la sesión MCP solo permita consultas.", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index f72da70480..62a6cd7fef 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2365,6 +2365,10 @@ export default { mcpConfig: "Configurazione MCP", mcpCodexConfig: "Configurazione Codex", mcpCodexConfigPath: "Codex può utilizzare ~/.codex/config.toml o un .codex/config.toml a livello di progetto.", + mcpCursorConfigPath: "Cursor può usare .cursor/mcp.json nel progetto o ~/.cursor/mcp.json globalmente.", + mcpTraeConfigPath: "TRAE: Impostazioni > MCP > Aggiungi > Aggiungi manualmente, poi incolla la configurazione JSON.", + mcpVsCodeConfigPath: "VS Code/Copilot può usare .vscode/mcp.json nell'area di lavoro o mcp.json nel profilo utente.", + mcpWindsurfConfigPath: "Windsurf può usare ~/.codeium/windsurf/mcp_config.json.", mcpOpenCodeConfigPath: "~/.config/opencode/opencode.json globalmente, o opencode.json a livello di progetto.", mcpReadonlyMode: "Modalità sola lettura", mcpReadonlyModeDescription: "Aggiunge DBX_MCP_ALLOW_WRITES=0 alla configurazione di esempio in modo che la sessione MCP rimanga di sola query.", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 497876689d..bf9c0ca356 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2618,6 +2618,10 @@ export default { mcpConfig: "MCP設定", mcpCodexConfig: "Codex設定", mcpCodexConfigPath: "Codexは ~/.codex/config.toml またはプロジェクトレベルの .codex/config.toml を使用できます。", + mcpCursorConfigPath: "Cursorはプロジェクトの .cursor/mcp.json またはグローバル ~/.cursor/mcp.json を使用できます。", + mcpTraeConfigPath: "TRAE: 設定 > MCP > 追加 > 手動で追加 からJSON設定を貼り付けます。", + mcpVsCodeConfigPath: "VS Code/Copilotはワークスペースの .vscode/mcp.json またはユーザープロファイルの mcp.json を使用できます。", + mcpWindsurfConfigPath: "Windsurfは ~/.codeium/windsurf/mcp_config.json を使用できます。", mcpOpenCodeConfigPath: "グローバル ~/.config/opencode/opencode.json、プロジェクト opencode.json。", mcpReadonlyMode: "読み取り専用モード", mcpReadonlyModeDescription: "サンプル設定にDBX_MCP_ALLOW_WRITES=0を追加し、MCPセッションをクエリのみに制限します。", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index d7940bde4f..be518bf782 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2376,6 +2376,10 @@ export default { mcpConfig: "Configuração do MCP", mcpCodexConfig: "Configuração do Codex", mcpCodexConfigPath: "O Codex pode usar ~/.codex/config.toml ou um .codex/config.toml no nível do projeto.", + mcpCursorConfigPath: "O Cursor pode usar .cursor/mcp.json no projeto ou ~/.cursor/mcp.json globalmente.", + mcpTraeConfigPath: "TRAE: Configurações > MCP > Adicionar > Adicionar manualmente; depois cole a configuração JSON.", + mcpVsCodeConfigPath: "O VS Code/Copilot pode usar .vscode/mcp.json no workspace ou mcp.json no perfil do usuário.", + mcpWindsurfConfigPath: "O Windsurf pode usar ~/.codeium/windsurf/mcp_config.json.", mcpOpenCodeConfigPath: "~/.config/opencode/opencode.json globalmente, ou opencode.json no nível do projeto.", mcpReadonlyMode: "Modo somente leitura", mcpReadonlyModeDescription: "Adiciona DBX_MCP_ALLOW_WRITES=0 à configuração de exemplo para que a sessão MCP permaneça apenas para consultas.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index d543e19bf3..fda9ad1080 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2754,6 +2754,10 @@ export default { mcpConfig: "MCP 配置", mcpCodexConfig: "Codex 配置", mcpCodexConfigPath: "Codex 可放在 ~/.codex/config.toml 或项目级 .codex/config.toml。", + mcpCursorConfigPath: "Cursor 可放在项目级 .cursor/mcp.json 或全局 ~/.cursor/mcp.json。", + mcpTraeConfigPath: "TRAE:设置 > MCP > 添加 > 手动添加,然后粘贴 JSON 配置。", + mcpVsCodeConfigPath: "VS Code/Copilot 可放在工作区 .vscode/mcp.json 或用户配置文件 mcp.json。", + mcpWindsurfConfigPath: "Windsurf 可放在 ~/.codeium/windsurf/mcp_config.json。", mcpOpenCodeConfigPath: "全局 ~/.config/opencode/opencode.json,项目级 opencode.json。", mcpReadonlyMode: "只读模式", mcpReadonlyModeDescription: "开启后会为示例配置附加 DBX_MCP_ALLOW_WRITES=0,MCP 会话只允许查询。", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index d519334a98..85d57240ae 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2433,6 +2433,10 @@ export default { mcpConfig: "MCP 設定", mcpCodexConfig: "Codex 設定", mcpCodexConfigPath: "Codex 可放在 ~/.codex/config.toml 或專案級 .codex/config.toml。", + mcpCursorConfigPath: "Cursor 可放在專案級 .cursor/mcp.json 或全域 ~/.cursor/mcp.json。", + mcpTraeConfigPath: "TRAE:設定 > MCP > 新增 > 手動新增,然後貼上 JSON 設定。", + mcpVsCodeConfigPath: "VS Code/Copilot 可放在工作區 .vscode/mcp.json 或使用者設定檔 mcp.json。", + mcpWindsurfConfigPath: "Windsurf 可放在 ~/.codeium/windsurf/mcp_config.json。", mcpOpenCodeConfigPath: "全域 ~/.config/opencode/opencode.json,專案級 opencode.json。", mcpReadonlyMode: "唯讀模式", mcpReadonlyModeDescription: "開啟後會為示例配置附加 DBX_MCP_ALLOW_WRITES=0,MCP 會話只允許查詢。", diff --git a/apps/desktop/src/lib/__tests__/mcpConfigTemplates.spec.ts b/apps/desktop/src/lib/__tests__/mcpConfigTemplates.spec.ts new file mode 100644 index 0000000000..5f95b04b75 --- /dev/null +++ b/apps/desktop/src/lib/__tests__/mcpConfigTemplates.spec.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { buildMcpCodexConfig, buildMcpJsonConfig, buildMcpOpenCodeConfig, buildMcpVsCodeConfig, type McpEnvEntry } from "@/lib/mcpConfigTemplates"; + +describe("MCP config templates", () => { + it("builds the standard mcpServers JSON used by Claude, Cursor, TRAE, and Windsurf", () => { + const config = JSON.parse(buildMcpJsonConfig()); + + expect(config).toEqual({ + mcpServers: { + dbx: { + command: "dbx-mcp-server", + }, + }, + }); + }); + + it("adds DBX MCP env entries to standard JSON configs", () => { + const env: McpEnvEntry[] = [ + ["DBX_MCP_ALLOW_WRITES", "0"], + ["DBX_MCP_ALLOW_DANGEROUS_SQL", "1"], + ]; + const config = JSON.parse(buildMcpJsonConfig(env)); + + expect(config.mcpServers.dbx.env).toEqual({ + DBX_MCP_ALLOW_WRITES: "0", + DBX_MCP_ALLOW_DANGEROUS_SQL: "1", + }); + }); + + it("builds VS Code MCP config with the servers root", () => { + const config = JSON.parse(buildMcpVsCodeConfig([["DBX_MCP_ALLOW_WRITES", "0"]])); + + expect(config).toEqual({ + servers: { + dbx: { + type: "stdio", + command: "dbx-mcp-server", + env: { + DBX_MCP_ALLOW_WRITES: "0", + }, + }, + }, + }); + }); + + it("builds Codex TOML config with env entries", () => { + expect(buildMcpCodexConfig([["DBX_MCP_ALLOW_WRITES", "0"]])).toBe(["[mcp_servers.dbx]", 'command = "dbx-mcp-server"', "", "[mcp_servers.dbx.env]", 'DBX_MCP_ALLOW_WRITES = "0"'].join("\n")); + }); + + it("builds OpenCode config using environment entries", () => { + const config = JSON.parse(buildMcpOpenCodeConfig([["DBX_MCP_ALLOW_DANGEROUS_SQL", "1"]])); + + expect(config).toEqual({ + mcp: { + dbx: { + type: "local", + command: ["dbx-mcp-server"], + environment: { + DBX_MCP_ALLOW_DANGEROUS_SQL: "1", + }, + }, + }, + }); + }); +}); diff --git a/apps/desktop/src/lib/mcpConfigTemplates.ts b/apps/desktop/src/lib/mcpConfigTemplates.ts new file mode 100644 index 0000000000..607a8dad21 --- /dev/null +++ b/apps/desktop/src/lib/mcpConfigTemplates.ts @@ -0,0 +1,57 @@ +export type McpEnvEntry = readonly [key: string, value: string]; + +function envObject(envEntries: readonly McpEnvEntry[]): Record { + return Object.fromEntries(envEntries); +} + +export function buildMcpJsonConfig(envEntries: readonly McpEnvEntry[] = []): string { + const dbx: Record = { + command: "dbx-mcp-server", + }; + + if (envEntries.length > 0) { + dbx.env = envObject(envEntries); + } + + return JSON.stringify({ mcpServers: { dbx } }, null, 2); +} + +export function buildMcpVsCodeConfig(envEntries: readonly McpEnvEntry[] = []): string { + const dbx: Record = { + type: "stdio", + command: "dbx-mcp-server", + }; + + if (envEntries.length > 0) { + dbx.env = envObject(envEntries); + } + + return JSON.stringify({ servers: { dbx } }, null, 2); +} + +export function buildMcpCodexConfig(envEntries: readonly McpEnvEntry[] = []): string { + const lines = ["[mcp_servers.dbx]", 'command = "dbx-mcp-server"']; + + if (envEntries.length > 0) { + lines.push(""); + lines.push("[mcp_servers.dbx.env]"); + for (const [key, value] of envEntries) { + lines.push(`${key} = ${JSON.stringify(value)}`); + } + } + + return lines.join("\n"); +} + +export function buildMcpOpenCodeConfig(envEntries: readonly McpEnvEntry[] = []): string { + const dbx: Record = { + type: "local", + command: ["dbx-mcp-server"], + }; + + if (envEntries.length > 0) { + dbx.environment = envObject(envEntries); + } + + return JSON.stringify({ mcp: { dbx } }, null, 2); +}