From b9ac69f79adbdc4b393371fc9997535d5dba670d Mon Sep 17 00:00:00 2001 From: Guoyu Su Date: Wed, 15 Jul 2026 19:33:31 +0800 Subject: [PATCH] fix(mcp): require managed runtime --- src-tauri/src/commands/ai.rs | 5 +++-- src-tauri/src/commands/mcp.rs | 30 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs index b7beb31a9..8c4fd2318 100644 --- a/src-tauri/src/commands/ai.rs +++ b/src-tauri/src/commands/ai.rs @@ -89,16 +89,17 @@ pub async fn ai_agent_stream( allow_write_sql: Option, ) -> Result { let request = resolve_codex_cli_request(request); - let cancelled = dbx_core::ai::register_stream(&session_id).await; let parsed_db_type: DatabaseType = serde_json::from_str(&format!("\"{}\"", db_type)).map_err(|_| format!("Unknown database type: {db_type}"))?; let cli_mcp_server_command = if matches!(request.config.provider, AiProvider::CodexCli) { - super::mcp::resolve_mcp_server_command().await.map(|(program, args)| CliAgentCommandSpec { program, args }) + let (program, args) = super::mcp::resolve_mcp_server_command().await?; + Some(CliAgentCommandSpec { program, args }) } else { None }; + let cancelled = dbx_core::ai::register_stream(&session_id).await; let production_database = state .configs .read() diff --git a/src-tauri/src/commands/mcp.rs b/src-tauri/src/commands/mcp.rs index 306476719..d4744779a 100644 --- a/src-tauri/src/commands/mcp.rs +++ b/src-tauri/src/commands/mcp.rs @@ -201,8 +201,11 @@ async fn fetch_latest_mcp_version() -> Result { Ok(package.version) } -pub(crate) async fn resolve_mcp_server_command() -> Option<(String, Vec)> { - tauri::async_runtime::spawn_blocking(resolve_mcp_server_command_sync).await.ok().flatten() +pub(crate) async fn resolve_mcp_server_command() -> Result<(String, Vec), String> { + let command = tauri::async_runtime::spawn_blocking(resolve_mcp_server_command_sync) + .await + .map_err(|err| format!("Failed to resolve DBX MCP Server runtime: {err}"))?; + require_managed_mcp_command(command) } fn resolve_mcp_server_command_sync() -> Option<(String, Vec)> { @@ -225,6 +228,15 @@ fn resolve_managed_mcp_command( None } +fn require_managed_mcp_command(command: Option<(String, Vec)>) -> Result<(String, Vec), String> { + command.ok_or_else(|| { + format!( + "DBX MCP Server is unavailable: no compatible Node.js ({}) installation containing {} was found. Install MCP Server from DBX settings and try again.", + MCP_MIN_NODE_VERSION_REQUIREMENT, MCP_PACKAGE_NAME + ) + }) +} + fn resolve_node_runtime() -> Option { let mut seen = HashSet::new(); let mut fallback = None; @@ -799,8 +811,9 @@ mod tests { use super::{bash_login_script, canonical_runtime_path, NodeRuntimeCandidate}; use super::{ is_mcp_compatible_node_version, mcp_command_for_runtime, normalized_reported_path, npm_cli_candidates, - parse_node_version, prefer_runtime, prefixed_output_path, resolve_managed_mcp_command, - stdout_after_shell_marker, NodeRuntime, NodeVersion, SHELL_COMMAND_MARKER, + parse_node_version, prefer_runtime, prefixed_output_path, require_managed_mcp_command, + resolve_managed_mcp_command, stdout_after_shell_marker, NodeRuntime, NodeVersion, + MCP_MIN_NODE_VERSION_REQUIREMENT, MCP_PACKAGE_NAME, SHELL_COMMAND_MARKER, }; #[cfg(not(windows))] use super::{shell_command_script, shell_quote}; @@ -944,6 +957,15 @@ mod tests { assert!(command.is_none()); } + #[test] + fn desktop_launch_requires_a_managed_mcp_command() { + let error = require_managed_mcp_command(None).unwrap_err(); + + assert!(error.contains(MCP_MIN_NODE_VERSION_REQUIREMENT)); + assert!(error.contains(MCP_PACKAGE_NAME)); + assert!(error.contains("DBX settings")); + } + #[test] fn npm_cli_candidates_stay_with_the_selected_node_installation() { let candidates = npm_cli_candidates(PathBuf::from("/runtime/node-24/bin/node").as_path());