Skip to content

Commit 9cfe80e

Browse files
fix: auto-spawn Warp-managed global MCP servers (warpdotdev#9886)
Closes warpdotdev#9879 ## Summary - Treat Warp-managed `~/.warp*/.mcp.json` detections that use the watcher-produced home-directory logical root as global Warp MCP servers. - Preserve the existing legacy `warp_data_dir()` root classification for compatibility. - Add a regression test covering the managed home-root path so global Warp MCP servers auto-spawn even when the third-party auto-spawn toggle is off. ## Validation - `cargo fmt` - `cargo check -p warp --lib` - `CARGO_BUILD_JOBS=1 cargo clippy -p warp --lib -- -D warnings` ## Validation notes - Targeted test commands were attempted, but `warp` lib-test compilation was killed by SIGKILL in the sandbox before tests ran. - Full workspace clippy was attempted. After installing missing `libclang-dev`, it failed on an unrelated missing generated `stable_config.json` for the `stable` test binary.
1 parent 73406f5 commit 9cfe80e

2 files changed

Lines changed: 40 additions & 25 deletions

File tree

app/src/ai/mcp/file_based_manager.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
ParsedTemplatableMCPServerResult,
1515
},
1616
settings::{ai::AISettings, AISettingsChangedEvent},
17-
warp_managed_paths_watcher::warp_data_dir,
17+
warp_managed_paths_watcher::warp_managed_mcp_config_path,
1818
};
1919

2020
/// Singleton model to manage file-based MCP servers.
@@ -235,15 +235,14 @@ impl FileBasedMCPManager {
235235
/// config location.
236236
///
237237
/// "Global" means the installation was detected outside of a user repository:
238-
/// - For `MCPProvider::Warp`: `warp_data_dir()` (i.e. `~/.warp/.mcp.json`).
238+
/// - For `MCPProvider::Warp`: the logical root for `~/.warp*/.mcp.json`.
239239
/// - For any other provider: the user's home directory (e.g. `~/.claude.json`).
240240
///
241241
/// Project-scoped installations (those detected inside a repo) are not considered
242242
/// global, even if they also happen to be referenced from a global location (in which
243243
/// case this returns `true` due to the global reference).
244244
fn is_global_server(&self, hash: u64) -> bool {
245245
let home_dir = dirs::home_dir();
246-
let warp_root = warp_data_dir();
247246
self.file_based_servers_by_root
248247
.iter()
249248
.any(|(root_path, provider_map)| {
@@ -252,7 +251,7 @@ impl FileBasedMCPManager {
252251
return false;
253252
}
254253
match provider {
255-
MCPProvider::Warp => root_path == &warp_root,
254+
MCPProvider::Warp => Self::is_global_warp_root(root_path),
256255
MCPProvider::Claude | MCPProvider::Codex | MCPProvider::Agents => {
257256
home_dir.as_ref().is_some_and(|home| root_path == home)
258257
}
@@ -264,11 +263,18 @@ impl FileBasedMCPManager {
264263
/// Returns `true` if the server identified by `hash` is referenced from the global
265264
/// Warp config (`~/.warp/.mcp.json`). Global Warp servers always auto-spawn.
266265
fn is_global_warp_server(&self, hash: u64) -> bool {
267-
let warp_root = warp_data_dir();
268266
self.file_based_servers_by_root
269-
.get(&warp_root)
270-
.and_then(|provider_map| provider_map.get(&MCPProvider::Warp))
271-
.is_some_and(|hashes| hashes.contains(&hash))
267+
.iter()
268+
.any(|(root_path, provider_map)| {
269+
Self::is_global_warp_root(root_path)
270+
&& provider_map
271+
.get(&MCPProvider::Warp)
272+
.is_some_and(|hashes| hashes.contains(&hash))
273+
})
274+
}
275+
276+
fn is_global_warp_root(root_path: &Path) -> bool {
277+
warp_managed_mcp_config_path().is_some_and(|path| root_path == path.root_path.as_path())
272278
}
273279

274280
fn spawn_file_based_servers(
@@ -409,9 +415,9 @@ impl FileBasedMCPManager {
409415
/// when its config does not specify `working_directory`.
410416
///
411417
/// The spawn root is the directory the config was discovered in, with one
412-
/// exception: global Warp installs are discovered in `~/.warp/` (Warp's data
413-
/// dir) which isn't a useful cwd for spawned processes, so they are remapped
414-
/// to the home directory instead.
418+
/// exception: global Warp installs are discovered in `~/.warp*/`, which
419+
/// isn't a useful cwd for spawned processes, so they are remapped to the
420+
/// home directory instead.
415421
/// - Project-scoped installations: the repo root.
416422
/// - Global installations (`~/.warp/.mcp.json`, `~/.claude.json`, etc.): the
417423
/// home directory.
@@ -429,10 +435,11 @@ impl FileBasedMCPManager {
429435
.sorted()
430436
.next()?;
431437

432-
// Global Warp installs live under `~/.warp/`, which is internal Warp state
433-
// rather than a meaningful working directory. Map them to the home dir so
434-
// all global installs (Warp and third-party) share a consistent cwd.
435-
if discovery_root == warp_data_dir() {
438+
// Global Warp installs live under `~/.warp*/`, which is internal Warp
439+
// state rather than a meaningful working directory. Map them to the
440+
// home dir so all global installs (Warp and third-party) share a
441+
// consistent cwd.
442+
if self.is_global_warp_server(hash) {
436443
return dirs::home_dir().or(Some(discovery_root));
437444
}
438445
Some(discovery_root)

app/src/ai/mcp/file_based_manager_tests.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::ai::mcp::FileMCPWatcher;
33
use crate::ai::mcp::ParsedTemplatableMCPServerResult;
44
use crate::auth::AuthStateProvider;
55
use crate::settings::{AISettings, FocusedTerminalInfo};
6-
use crate::warp_managed_paths_watcher::{warp_data_dir, WarpManagedPathsWatcher};
6+
use crate::warp_managed_paths_watcher::{warp_managed_mcp_config_path, WarpManagedPathsWatcher};
77
use crate::workspaces::user_workspaces::UserWorkspaces;
88
use repo_metadata::{
99
repositories::DetectedRepositories, watcher::DirectoryWatcher, RepoMetadataModel,
@@ -255,28 +255,37 @@ fn test_update_file_based_servers_removes_unreferenced_servers() {
255255
});
256256
}
257257

258-
/// A globally-scoped Warp installation always auto-spawns, regardless of the
259-
/// `file_based_mcp_enabled` toggle.
258+
/// A Warp-global installation detected from the managed `~/.warp*/.mcp.json`
259+
/// watcher uses the home directory as its logical root and still always
260+
/// auto-spawns.
260261
#[test]
261-
fn test_global_warp_server_always_spawns() {
262+
fn test_global_warp_server_from_managed_home_root_always_spawns() {
262263
let _flag_guard = FeatureFlag::FileBasedMcp.override_enabled(true);
263-
let warp_root = warp_data_dir();
264+
let Some(warp_mcp_config_path) = warp_managed_mcp_config_path() else {
265+
return;
266+
};
264267
let parsed = parse_mcp_json(r#"{"global-warp": {"command": "npx", "args": ["warp"]}}"#);
265268

266269
App::test((), |mut app| async move {
267270
let manager = setup_app(&mut app);
268271
let events = subscribe_events(&mut app, &manager);
269272

270-
// Toggle is off by default; global Warp server should still spawn.
273+
// Toggle is off by default; the watcher-produced Warp root should still
274+
// be classified as the global Warp config and auto-spawn.
271275
manager.update(&mut app, |m, ctx| {
272-
m.apply_parsed_servers(warp_root.clone(), MCPProvider::Warp, parsed, ctx);
276+
m.apply_parsed_servers(
277+
warp_mcp_config_path.root_path.clone(),
278+
MCPProvider::Warp,
279+
parsed,
280+
ctx,
281+
);
273282
});
274283

275284
events.update(&mut app, |e, _| {
276285
assert_eq!(
277286
e.spawned_uuids.len(),
278287
1,
279-
"Global Warp server should auto-spawn regardless of toggle"
288+
"Managed Warp MCP config should auto-spawn regardless of toggle"
280289
);
281290
});
282291

@@ -287,13 +296,12 @@ fn test_global_warp_server_always_spawns() {
287296
events.update(&mut app, |e, _| {
288297
assert!(
289298
e.despawned_uuids.is_empty(),
290-
"Global Warp server should never be despawned by toggle changes, got: {:?}",
299+
"Managed Warp MCP config should never be despawned by toggle changes, got: {:?}",
291300
e.despawned_uuids
292301
);
293302
});
294303
});
295304
}
296-
297305
/// A globally-scoped non-Warp installation only auto-spawns when the toggle is on.
298306
#[test]
299307
fn test_global_non_warp_server_respects_toggle() {

0 commit comments

Comments
 (0)