fix(mcp): write bridge port to resolved data dir

This commit is contained in:
t8y2 2026-07-01 15:14:01 +08:00
parent 23ab48bfb7
commit a529344ac7
2 changed files with 40 additions and 5 deletions

View File

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager};
use tauri::{AppHandle, Emitter};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
@ -9,6 +10,7 @@ use super::connection::AppState;
use super::connection::ensure_connection_writable;
const BIND_ADDR: &str = "127.0.0.1:0";
const MCP_BRIDGE_PORT_FILE: &str = "mcp-bridge-port";
#[derive(Deserialize)]
struct OpenTableRequest {
@ -131,7 +133,7 @@ pub struct McpExecuteQueryEvent {
pub allow_dangerous: bool,
}
pub fn start(app_handle: AppHandle, state: Arc<AppState>) {
pub fn start(app_handle: AppHandle, state: Arc<AppState>, data_dir: PathBuf) {
tauri::async_runtime::spawn(async move {
let listener = match TcpListener::bind(BIND_ADDR).await {
Ok(l) => l,
@ -143,8 +145,9 @@ pub fn start(app_handle: AppHandle, state: Arc<AppState>) {
log::info!("MCP bridge listening on {BIND_ADDR}");
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
log::info!("MCP bridge assigned port {actual_port}");
if let Ok(dir) = app_handle.path().app_data_dir() {
let _ = std::fs::write(dir.join("mcp-bridge-port"), actual_port.to_string());
// Publish into DBX's resolved data dir so DBX_DATA_DIR and portable mode share the same discovery file.
if let Err(err) = write_port_file(&data_dir, actual_port) {
log::warn!("MCP bridge failed to write port file in {}: {err}", data_dir.display());
}
loop {
let (mut stream, _) = match listener.accept().await {
@ -202,6 +205,13 @@ pub fn start(app_handle: AppHandle, state: Arc<AppState>) {
});
}
fn write_port_file(data_dir: &Path, actual_port: u16) -> std::io::Result<PathBuf> {
std::fs::create_dir_all(data_dir)?;
let path = data_dir.join(MCP_BRIDGE_PORT_FILE);
std::fs::write(&path, actual_port.to_string())?;
Ok(path)
}
fn find_config_by_name<'a>(
configs: &'a [crate::models::connection::ConnectionConfig],
name: &str,
@ -209,6 +219,31 @@ fn find_config_by_name<'a>(
configs.iter().find(|c| c.name.eq_ignore_ascii_case(name))
}
#[cfg(test)]
mod tests {
use super::write_port_file;
#[test]
fn writes_bridge_port_file_to_resolved_data_dir() {
let root = std::env::temp_dir().join(format!(
"dbx-mcp-bridge-port-test-{}-{}",
std::process::id(),
uuid::Uuid::new_v4()
));
let default_data_dir = root.join("default-app-data");
let resolved_data_dir = root.join("resolved-data");
std::fs::create_dir_all(&default_data_dir).unwrap();
let port_file = write_port_file(&resolved_data_dir, 49152).unwrap();
assert_eq!(port_file, resolved_data_dir.join("mcp-bridge-port"));
assert_eq!(std::fs::read_to_string(port_file).unwrap(), "49152");
assert!(!default_data_dir.join("mcp-bridge-port").exists());
let _ = std::fs::remove_dir_all(root);
}
}
async fn respond(stream: &mut tokio::net::TcpStream, status: &str, body: &str) {
let resp = format!("HTTP/1.1 {status}\r\nContent-Length: {}\r\n\r\n{body}", body.len());
let _ = stream.write_all(resp.as_bytes()).await;

View File

@ -582,7 +582,7 @@ pub fn run() {
open_connection_deep_links(app.handle(), startup_links);
let app_handle = app.handle().clone();
commands::mcp_bridge::start(app_handle, state);
commands::mcp_bridge::start(app_handle, state, data_dir);
eprintln!("[STARTUP] setup complete in {:?} (total {:?})", setup_start.elapsed(), startup_begin.elapsed());
if let Some(decorations) = native_window_decorations_override(std::env::consts::OS) {