feat(settings): add MCP server status check

This commit is contained in:
t8y2 committed 2026-06-02 00:14:03 +08:00
1 parent 088eaccecc
commit 17504be027
11 files changed
+422 -1

No files matched your search

+1 -1
View File
@@ -688,7 +688,7 @@ function openGitHub() {
openUrl("https://github.com/t8y2/dbx");
}
function openMcpGuide() {
openUrl("https://github.com/t8y2/dbx/blob/main/docs/mcp-guide.md");
openUrl("https://dbxio.com/cn/docs/mcp");
}
function ensureQueryTab(): string {
@@ -3,14 +3,19 @@ import { ref, watch, shallowRef, computed, onMounted } from "vue";
import type { EditorView as EditorViewType } from "@codemirror/view";
import { useI18n } from "vue-i18n";
import {
AlertTriangle,
CheckCircle2,
CircleHelp,
Cloud,
Copy,
Download,
ExternalLink,
Loader2,
PackageSearch,
Pencil,
RefreshCw,
Settings,
Terminal,
Trash2,
Upload,
X,
@@ -41,9 +46,11 @@ import {
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { useTheme } from "@/composables/useTheme";
import { copyToClipboard } from "@/lib/clipboard";
import {
aiListModels,
aiTestConnection,
checkMcpServerStatus,
forgetWebdavSavedPassword,
listSystemFonts,
saveWebdavSavedPassword,
@@ -52,6 +59,7 @@ import {
webdavSyncTest,
webdavSyncUpload,
type AiModelInfo,
type McpServerStatus,
type WebDavConfig,
} from "@/lib/api";
import { eventToShortcut } from "@/lib/keyboardShortcuts";
@@ -425,6 +433,7 @@ type SettingsCategory =
| "snippets"
| "sync"
| "ai"
| "mcp"
| "security"
| "about";
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
@@ -436,6 +445,7 @@ const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[
{ value: "snippets", label: t("settings.snippetsTab") },
...(isWeb ? [] : [{ value: "sync" as const, label: t("settings.syncTab") }]),
{ value: "ai", label: t("settings.aiTab") },
...(isWeb ? [] : [{ value: "mcp" as const, label: t("settings.mcpTab") }]),
...(isWeb ? [{ value: "security" as const, label: t("settings.securityTab") }] : []),
{ value: "about", label: t("settings.aboutTab") },
]);
@@ -469,6 +479,66 @@ function openExternalUrl(url: string) {
}
}
// ---------- MCP Server ----------
const mcpStatus = ref<McpServerStatus | null>(null);
const mcpStatusLoading = ref(false);
const mcpStatusError = ref("");
const mcpCopied = ref<"" | "install" | "config">("");
const mcpRecommendedConfig = `{
"mcpServers": {
"dbx": {
"command": "dbx-mcp-server"
}
}
}`;
const mcpStatusTone = computed<"ok" | "warning" | "muted">(() => {
if (!mcpStatus.value) return "muted";
if (!mcpStatus.value.installed || mcpStatus.value.update_available || mcpStatus.value.error) return "warning";
return "ok";
});
const mcpStatusLabel = computed(() => {
if (mcpStatusLoading.value) return t("settings.mcpChecking");
if (mcpStatusError.value) return t("settings.mcpStatusError");
if (!mcpStatus.value) return t("settings.mcpStatusUnknown");
if (!mcpStatus.value.installed) return t("settings.mcpNotInstalled");
if (mcpStatus.value.update_available) return t("settings.mcpUpdateAvailable");
return t("settings.mcpReady");
});
const mcpCommand = computed(() => {
if (!mcpStatus.value) return "npm install -g @dbx-app/mcp-server@latest";
return mcpStatus.value.installed ? mcpStatus.value.update_command : mcpStatus.value.install_command;
});
async function refreshMcpStatus() {
if (mcpStatusLoading.value) return;
mcpStatusLoading.value = true;
mcpStatusError.value = "";
try {
mcpStatus.value = await checkMcpServerStatus();
} catch (e: any) {
mcpStatusError.value = e?.message || String(e);
} finally {
mcpStatusLoading.value = false;
}
}
async function copyMcpText(kind: "install" | "config", value: string) {
mcpCopied.value = kind;
try {
await copyToClipboard(value);
} catch {
mcpCopied.value = "";
return;
}
window.setTimeout(() => {
if (mcpCopied.value === kind) mcpCopied.value = "";
}, 1500);
}
// ---------- WebDAV Sync ----------
const webdavEndpoint = ref(localStorage.getItem("dbx-webdav-endpoint") || "");
const webdavUsername = ref(localStorage.getItem("dbx-webdav-username") || "");
@@ -617,6 +687,7 @@ watch(
webdavPassword.value = "";
await refreshWebDavPasswordStatus();
syncAiEditState();
if (!isWeb && activeSettingsTab.value === "mcp") void refreshMcpStatus();
}
},
{ immediate: true },
@@ -629,6 +700,10 @@ watch(webdavRememberPassword, (val) => {
localStorage.setItem("dbx-webdav-remember-password", String(val));
});
watch(activeSettingsTab, (tab) => {
if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus();
});
onMounted(() => {
void refreshWebDavPasswordStatus();
});
@@ -1849,6 +1924,123 @@ watch(
</div>
</section>
<section v-else-if="activeSettingsTab === 'mcp' && !isWeb" class="flex flex-col gap-5 py-2">
<div class="rounded-md border bg-muted/20 p-4">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 space-y-2">
<div class="flex items-center gap-2">
<PackageSearch class="h-4 w-4 text-muted-foreground" />
<Label class="text-base">{{ t("settings.mcpTitle") }}</Label>
</div>
<p class="text-sm text-muted-foreground">{{ t("settings.mcpDescription") }}</p>
</div>
<Badge
variant="outline"
class="shrink-0 rounded-md"
:class="
mcpStatusTone === 'ok'
? 'border-green-500/40 text-green-600 dark:text-green-400'
: mcpStatusTone === 'warning'
? 'border-amber-500/40 text-amber-600 dark:text-amber-400'
: 'text-muted-foreground'
"
>
<Loader2 v-if="mcpStatusLoading" class="mr-1 h-3 w-3 animate-spin" />
<CheckCircle2 v-else-if="mcpStatusTone === 'ok'" class="mr-1 h-3 w-3" />
<AlertTriangle v-else-if="mcpStatusTone === 'warning'" class="mr-1 h-3 w-3" />
{{ mcpStatusLabel }}
</Badge>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<div class="rounded-md border p-3">
<div class="text-xs font-medium uppercase text-muted-foreground">{{ t("settings.mcpCurrent") }}</div>
<div class="mt-2 font-mono text-sm">
{{ mcpStatus?.current_version ? `v${mcpStatus.current_version}` : t("settings.mcpVersionMissing") }}
</div>
</div>
<div class="rounded-md border p-3">
<div class="text-xs font-medium uppercase text-muted-foreground">{{ t("settings.mcpLatest") }}</div>
<div class="mt-2 font-mono text-sm">
{{ mcpStatus?.latest_version ? `v${mcpStatus.latest_version}` : t("settings.mcpVersionUnknown") }}
</div>
</div>
<div class="rounded-md border p-3">
<div class="text-xs font-medium uppercase text-muted-foreground">Node.js</div>
<div class="mt-2 font-mono text-sm">
{{ mcpStatus?.node_version || t("settings.mcpVersionUnknown") }}
</div>
</div>
<div class="rounded-md border p-3">
<div class="text-xs font-medium uppercase text-muted-foreground">npm</div>
<div class="mt-2 font-mono text-sm">
{{ mcpStatus?.npm_available ? t("settings.mcpAvailable") : t("settings.mcpUnavailable") }}
</div>
</div>
</div>
<div v-if="mcpStatus?.bin_path" class="space-y-2">
<Label>{{ t("settings.mcpBinPath") }}</Label>
<div class="rounded-md border bg-muted/20 px-3 py-2 font-mono text-xs text-muted-foreground">
{{ mcpStatus.bin_path }}
</div>
</div>
<div class="space-y-2">
<Label>{{
mcpStatus?.installed ? t("settings.mcpUpdateCommand") : t("settings.mcpInstallCommand")
}}</Label>
<div class="flex min-w-0 items-center gap-2">
<div class="min-w-0 flex-1 rounded-md border bg-background px-3 py-2 font-mono text-xs">
{{ mcpCommand }}
</div>
<Button
type="button"
variant="outline"
size="icon"
:title="t('common.copy')"
@click="copyMcpText('install', mcpCommand)"
>
<CheckCircle2 v-if="mcpCopied === 'install'" class="h-4 w-4 text-green-500" />
<Copy v-else class="h-4 w-4" />
</Button>
</div>
</div>
<div class="space-y-2">
<Label>{{ t("settings.mcpConfig") }}</Label>
<div class="relative rounded-md border bg-background p-3">
<pre
class="overflow-x-auto whitespace-pre text-xs leading-relaxed"
><code>{{ mcpRecommendedConfig }}</code></pre>
<Button
type="button"
variant="outline"
size="icon"
class="absolute right-2 top-2 h-7 w-7"
:title="t('common.copy')"
@click="copyMcpText('config', mcpRecommendedConfig)"
>
<CheckCircle2 v-if="mcpCopied === 'config'" class="h-3.5 w-3.5 text-green-500" />
<Copy v-else class="h-3.5 w-3.5" />
</Button>
</div>
</div>
<div
v-if="mcpStatus?.error || mcpStatusError"
class="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-300"
>
{{ mcpStatusError || mcpStatus?.error }}
</div>
<div class="flex items-center gap-2 text-xs text-muted-foreground">
<Terminal class="h-3.5 w-3.5" />
<span>{{ t("settings.mcpDetectionTiming") }} {{ t("settings.mcpNpmBoundary") }}</span>
</div>
</section>
<section v-else-if="activeSettingsTab === 'security' && isWeb" class="flex flex-col gap-5 py-2">
<div class="space-y-3">
<Label class="text-base">{{ t("auth.changePassword") }}</Label>
@@ -2082,6 +2274,25 @@ watch(
</Button>
</DialogFooter>
<DialogFooter
v-else-if="activeSettingsTab === 'mcp' && !isWeb"
class="mx-0 mb-0 shrink-0 rounded-none border-t border-border/60 bg-transparent px-0 pb-0 pt-3"
>
<Button variant="outline" @click="emit('update:open', false)">
{{ t("common.close") }}
</Button>
<div class="flex-1" />
<Button variant="outline" :disabled="mcpStatusLoading" @click="refreshMcpStatus">
<Loader2 v-if="mcpStatusLoading" class="mr-1 h-3 w-3 animate-spin" />
<RefreshCw v-else class="mr-1 h-3 w-3" />
{{ t("settings.mcpRefresh") }}
</Button>
<Button variant="outline" @click="openExternalUrl('https://dbxio.com/cn/docs/mcp')">
<ExternalLink class="mr-1 h-3 w-3" />
{{ t("settings.mcpGuide") }}
</Button>
</DialogFooter>
<DialogFooter
v-else-if="activeSettingsTab === 'security' && isWeb"
class="mx-0 mb-0 shrink-0 rounded-none border-t border-border/60 bg-transparent px-0 pb-0 pt-3"
+23
View File
@@ -1496,6 +1496,7 @@ export default {
snippetsTab: "Snippets",
syncTab: "Sync",
aiTab: "AI",
mcpTab: "MCP",
jdbcTab: "JDBC Drivers",
securityTab: "Security",
aboutTab: "About",
@@ -1651,6 +1652,28 @@ export default {
jdbcImportSuccess: "Imported {count} driver file(s)",
jdbcDeleteSuccess: "Driver removed",
jdbcNoDrivers: "No JDBC drivers imported yet.",
mcpTitle: "MCP Server",
mcpDescription: "Check the DBX MCP Server installation and version used by Claude Code, Cursor, and other agents.",
mcpChecking: "Checking",
mcpStatusUnknown: "Not checked",
mcpStatusError: "Check failed",
mcpReady: "Ready",
mcpNotInstalled: "Not installed",
mcpUpdateAvailable: "Update available",
mcpCurrent: "Current version",
mcpLatest: "Latest version",
mcpVersionMissing: "No global install detected",
mcpVersionUnknown: "Unknown",
mcpAvailable: "Available",
mcpUnavailable: "Unavailable",
mcpBinPath: "Command path",
mcpInstallCommand: "Install command",
mcpUpdateCommand: "Upgrade command",
mcpConfig: "Claude Code config",
mcpDetectionTiming: "DBX checks automatically when this page opens; use Check again to refresh.",
mcpNpmBoundary: "DBX only checks and explains MCP status; installation and upgrades still run through npm.",
mcpRefresh: "Check again",
mcpGuide: "MCP guide",
aboutDescription: "An open-source, lightweight database management tool.",
community: "Community",
qqGroup: "QQ Group",
+23
View File
@@ -1386,6 +1386,7 @@ export default {
snippetsTab: "Fragmentos",
syncTab: "Sync",
aiTab: "IA",
mcpTab: "MCP",
jdbcTab: "Drivers JDBC",
securityTab: "Seguridad",
aboutTab: "Acerca de",
@@ -1540,6 +1541,28 @@ export default {
jdbcImportSuccess: "Se importaron {count} archivo(s) de driver",
jdbcDeleteSuccess: "Driver eliminado",
jdbcNoDrivers: "Aún no se han importado drivers JDBC.",
mcpTitle: "MCP Server",
mcpDescription: "Comprueba la instalación y versión de DBX MCP Server para Claude Code, Cursor y otros agentes.",
mcpChecking: "Comprobando",
mcpStatusUnknown: "Sin comprobar",
mcpStatusError: "Error al comprobar",
mcpReady: "Listo",
mcpNotInstalled: "No instalado",
mcpUpdateAvailable: "Actualización disponible",
mcpCurrent: "Versión actual",
mcpLatest: "Última versión",
mcpVersionMissing: "No se detectó instalación global",
mcpVersionUnknown: "Desconocida",
mcpAvailable: "Disponible",
mcpUnavailable: "No disponible",
mcpBinPath: "Ruta del comando",
mcpInstallCommand: "Comando de instalación",
mcpUpdateCommand: "Comando de actualización",
mcpConfig: "Configuración de Claude Code",
mcpDetectionTiming: "DBX comprueba automáticamente al abrir esta página; usa Comprobar de nuevo para actualizar.",
mcpNpmBoundary: "DBX solo comprueba y explica el estado de MCP; la instalación y actualización siguen usando npm.",
mcpRefresh: "Comprobar de nuevo",
mcpGuide: "Guía MCP",
aboutDescription: "Una herramienta de administración de bases de datos liviana y de código abierto.",
community: "Comunidad",
qqGroup: "Grupo QQ",
+23
View File
@@ -1469,6 +1469,7 @@ export default {
snippetsTab: "代码片段",
syncTab: "同步",
aiTab: "AI",
mcpTab: "MCP",
jdbcTab: "JDBC 驱动",
securityTab: "安全",
aboutTab: "关于我们",
@@ -1612,6 +1613,28 @@ export default {
jdbcImportSuccess: "已导入 {count} 个驱动文件",
jdbcDeleteSuccess: "驱动已删除",
jdbcNoDrivers: "还没有导入 JDBC 驱动。",
mcpTitle: "MCP Server",
mcpDescription: "检查 Claude Code、Cursor 等编程助手使用的 DBX MCP Server 安装与版本状态。",
mcpChecking: "检查中",
mcpStatusUnknown: "未检查",
mcpStatusError: "检查失败",
mcpReady: "可用",
mcpNotInstalled: "未安装",
mcpUpdateAvailable: "有更新",
mcpCurrent: "当前版本",
mcpLatest: "最新版本",
mcpVersionMissing: "未检测到全局安装",
mcpVersionUnknown: "未知",
mcpAvailable: "可用",
mcpUnavailable: "不可用",
mcpBinPath: "命令路径",
mcpInstallCommand: "安装命令",
mcpUpdateCommand: "升级命令",
mcpConfig: "Claude Code 配置",
mcpDetectionTiming: "打开此页时会自动检测,点击重新检查可刷新。",
mcpNpmBoundary: "DBX 只检测和提示 MCP 状态;安装与升级仍由 npm 完成。",
mcpRefresh: "重新检查",
mcpGuide: "MCP 指南",
aboutDescription: "开源、轻量的数据库管理工具。",
community: "社区",
qqGroup: "QQ 群",
+2
View File
@@ -227,6 +227,7 @@ export const clearHistory = forward("clearHistory");
export const deleteHistoryEntry = forward("deleteHistoryEntry");
// Updates
export const checkMcpServerStatus = forward("checkMcpServerStatus");
export const checkForUpdates = forward("checkForUpdates");
export const getSystemProxyUrl = forward("getSystemProxyUrl");
export const getAppVersion = forward("getAppVersion");
@@ -256,6 +257,7 @@ export type {
WebDavPasswordStatus,
WebDavSyncSummary,
WebDavDownloadResult,
McpServerStatus,
UpdateInfo,
RedisDatabaseInfo,
RedisKeyInfo,
+15
View File
@@ -1381,6 +1381,21 @@ export async function checkForUpdates(): Promise<UpdateInfo> {
return get("/api/update/check");
}
export async function checkMcpServerStatus(): Promise<import("./tauri").McpServerStatus> {
return {
installed: false,
npm_available: false,
node_version: null,
current_version: null,
latest_version: null,
update_available: false,
bin_path: null,
install_command: "npm install -g @dbx-app/mcp-server@latest",
update_command: "npm install -g @dbx-app/mcp-server@latest",
error: "MCP Server status is only available in the desktop app.",
};
}
export async function getSystemProxyUrl(): Promise<string | null> {
return null;
}
+17
View File
@@ -915,6 +915,23 @@ export interface UpdateInfo {
release_notes: string;
}
export interface McpServerStatus {
installed: boolean;
npm_available: boolean;
node_version: string | null;
current_version: string | null;
latest_version: string | null;
update_available: boolean;
bin_path: string | null;
install_command: string;
update_command: string;
error: string | null;
}
export async function checkMcpServerStatus(): Promise<McpServerStatus> {
return invoke("check_mcp_server_status");
}
export async function checkForUpdates(): Promise<UpdateInfo> {
return invoke("check_for_updates");
}