Skip to content

Commit b504f09

Browse files
committed
feat: enhance MCP server activation with configurable retry attempts
1 parent 15f1133 commit b504f09

File tree

1 file changed

+63
-43
lines changed

1 file changed

+63
-43
lines changed

src-tauri/src/core/mcp.rs

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use serde_json::{Map, Value};
44
use std::fs;
55
use std::{collections::HashMap, env, sync::Arc, time::Duration};
66
use tauri::{AppHandle, Emitter, Runtime, State};
7-
use tokio::{process::Command, sync::Mutex, time::{timeout, sleep}};
7+
use tokio::{
8+
process::Command,
9+
sync::Mutex,
10+
time::{sleep, timeout},
11+
};
812

913
use super::{cmd::get_jan_data_folder_path, state::AppState};
1014

@@ -13,9 +17,6 @@ const DEFAULT_MCP_CONFIG: &str = r#"{"mcpServers":{"browsermcp":{"command":"npx"
1317
// Timeout for MCP tool calls (30 seconds)
1418
const MCP_TOOL_CALL_TIMEOUT: Duration = Duration::from_secs(30);
1519

16-
// Maximum retry attempts for MCP server loading
17-
const MAX_MCP_RETRY_ATTEMPTS: u32 = 3;
18-
1920
// Base backoff duration in milliseconds (exponential backoff will multiply this)
2021
const BASE_BACKOFF_MS: u64 = 1000;
2122

@@ -52,15 +53,19 @@ pub async fn run_mcp_commands<R: Runtime>(
5253
log::trace!("Server {name} is not active, skipping.");
5354
continue;
5455
}
55-
56+
5657
// Try to start the server with retry mechanism
57-
start_mcp_server(
58+
if let Err(e) = start_mcp_server(
5859
app.clone(),
5960
servers_state.clone(),
6061
name.clone(),
6162
config.clone(),
63+
None, // Use default max retry attempts (0)
6264
)
63-
.await;
65+
.await
66+
{
67+
log::error!("Failed to start MCP server {name}: {e}");
68+
}
6469
}
6570
}
6671

@@ -73,79 +78,94 @@ async fn start_mcp_server<R: Runtime>(
7378
servers_state: Arc<Mutex<HashMap<String, RunningService<RoleClient, ()>>>>,
7479
name: String,
7580
config: Value,
76-
) {
81+
max_retry_attempts: Option<u32>,
82+
) -> Result<(), String> {
83+
// Use default value of 0 if None is provided
84+
let max_retry_attempts = max_retry_attempts.unwrap_or(0);
85+
7786
// Initialize retry state locally for this server start attempt
7887
let mut retry_count = 0;
7988
let mut backoff_ms = BASE_BACKOFF_MS;
80-
89+
8190
loop {
8291
retry_count += 1;
83-
92+
8493
if retry_count > 1 {
8594
log::info!(
8695
"Starting MCP server {name} - retry attempt {} of {} (waiting {:.1}s)",
8796
retry_count,
88-
MAX_MCP_RETRY_ATTEMPTS,
97+
max_retry_attempts,
8998
backoff_ms as f64 / 1000.0
9099
);
91-
100+
92101
let _ = app.emit(
93102
"mcp-retry-attempt",
94103
serde_json::json!({
95104
"server": name,
96105
"attempt": retry_count,
97-
"max_attempts": MAX_MCP_RETRY_ATTEMPTS
98-
})
106+
"max_attempts": max_retry_attempts
107+
}),
99108
);
100-
109+
101110
sleep(Duration::from_millis(backoff_ms)).await;
102111
} else {
103112
log::info!("Starting MCP server {name} - initial attempt");
104113
}
105-
114+
106115
// Attempt to start the server
107-
match schedule_mcp_start_task(app.clone(), servers_state.clone(), name.clone(), config.clone()).await {
116+
match schedule_mcp_start_task(
117+
app.clone(),
118+
servers_state.clone(),
119+
name.clone(),
120+
config.clone(),
121+
)
122+
.await
123+
{
108124
Ok(_) => {
109125
log::info!("Server {name} activated successfully.");
110-
126+
111127
let _ = app.emit(
112128
"mcp-server-started",
113129
serde_json::json!({
114130
"server": name,
115131
"status": "success",
116132
"attempts": retry_count
117-
})
133+
}),
118134
);
119-
return;
135+
return Ok(());
120136
}
121137
Err(e) => {
122138
log::error!("Failed to activate server {name} (attempt {retry_count}): {e}");
123-
124-
if retry_count >= MAX_MCP_RETRY_ATTEMPTS {
139+
140+
// Check if we've exceeded the maximum retry attempts
141+
// retry_count starts at 1 for the first attempt, so retry_count - 1 is the number of retries done
142+
if retry_count > max_retry_attempts {
125143
log::error!(
126-
"Server {name} has exceeded maximum retry attempts ({}). Giving up.",
127-
MAX_MCP_RETRY_ATTEMPTS
144+
"Server {name} has exceeded maximum retry attempts ({max_retry_attempts}). Giving up."
128145
);
129146
let _ = app.emit(
130147
"mcp-max-retries-exceeded",
131-
format!("MCP server {name} failed after {MAX_MCP_RETRY_ATTEMPTS} attempts: {e}"),
132-
);
133-
return;
134-
} else {
135-
let _ = app.emit(
136-
"mcp-retry-scheduled",
137-
serde_json::json!({
138-
"server": name,
139-
"attempt": retry_count,
140-
"max_attempts": MAX_MCP_RETRY_ATTEMPTS,
141-
"error": e,
142-
"next_retry_in_ms": backoff_ms
143-
})
148+
format!(
149+
"MCP server {name} failed after {retry_count} attempts ({max_retry_attempts} retries): {e}"
150+
),
144151
);
145-
146-
// Update backoff duration for next attempt (exponential backoff)
147-
backoff_ms = (backoff_ms * 2).min(30000); // Cap at 30 seconds
152+
return Err(format!(
153+
"MCP server {name} failed after {retry_count} attempts ({max_retry_attempts} retries): {e}"
154+
));
148155
}
156+
let _ = app.emit(
157+
"mcp-retry-scheduled",
158+
serde_json::json!({
159+
"server": name,
160+
"attempt": retry_count,
161+
"max_attempts": max_retry_attempts,
162+
"error": e,
163+
"next_retry_in_ms": backoff_ms
164+
}),
165+
);
166+
167+
// Update backoff duration for next attempt (exponential backoff)
168+
backoff_ms = (backoff_ms * 2).min(30000); // Cap at 30 seconds
149169
}
150170
}
151171
}
@@ -160,8 +180,7 @@ pub async fn activate_mcp_server<R: Runtime>(
160180
) -> Result<(), String> {
161181
let servers: Arc<Mutex<HashMap<String, RunningService<RoleClient, ()>>>> =
162182
state.mcp_servers.clone();
163-
start_mcp_server(app, servers, name, config).await;
164-
Ok(())
183+
start_mcp_server(app, servers, name, config, None).await
165184
}
166185

167186
async fn schedule_mcp_start_task<R: Runtime>(
@@ -196,7 +215,8 @@ async fn schedule_mcp_start_task<R: Runtime>(
196215
cmd.arg("run");
197216
cmd.env("UV_CACHE_DIR", cache_dir.to_str().unwrap().to_string());
198217
}
199-
#[cfg(windows)] {
218+
#[cfg(windows)]
219+
{
200220
cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW: prevents shell window on Windows
201221
}
202222
let app_path_str = app_path.to_str().unwrap().to_string();

0 commit comments

Comments
 (0)