Skip to content

Commit f6dcf27

Browse files
committed
Add MCP server
- Expose a bunch of file exploring features so that we can access them from the command palette - Add 8 missing commands - Test it thoroughly - Add docs
1 parent 7b0ea13 commit f6dcf27

24 files changed

Lines changed: 3585 additions & 27 deletions

apps/desktop/src-tauri/Cargo.lock

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ env_logger = "0.11.8"
4444
log = "0.4"
4545
# HTTP client for license server validation
4646
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
47+
# MCP server
48+
axum = "0.8"
49+
tokio = { version = "1", features = ["rt-multi-thread", "net", "time", "sync", "macros"] }
50+
tower-http = { version = "0.6", features = ["cors"] }
4751
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
4852
tauri-plugin-window-state = "2"
4953

@@ -52,8 +56,6 @@ core-foundation = "0.10.1"
5256
core-services = "1.0.0"
5357
icns = "0.3.1"
5458
plist = "1.8.0"
55-
tokio = { version = "1.49.0", features = ["rt", "time"] }
56-
5759
urlencoding = "2.1.3"
5860
objc2 = { version = "0.6", features = ["std"] }
5961
objc2-foundation = { version = "0.3", features = [

apps/desktop/src-tauri/src/lib.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod icons;
4040
pub mod licensing;
4141
#[cfg(target_os = "macos")]
4242
mod macos_icons;
43+
mod mcp;
4344
mod menu;
4445
#[cfg(target_os = "macos")]
4546
mod network;
@@ -51,7 +52,8 @@ mod volumes;
5152

5253
use menu::{
5354
ABOUT_ID, COMMAND_PALETTE_ID, GO_BACK_ID, GO_FORWARD_ID, GO_PARENT_ID, MenuState, SHOW_HIDDEN_FILES_ID,
54-
SWITCH_PANE_ID, VIEW_MODE_BRIEF_ID, VIEW_MODE_FULL_ID, ViewMode,
55+
SORT_ASCENDING_ID, SORT_BY_CREATED_ID, SORT_BY_EXTENSION_ID, SORT_BY_MODIFIED_ID, SORT_BY_NAME_ID, SORT_BY_SIZE_ID,
56+
SORT_DESCENDING_ID, SWITCH_PANE_ID, VIEW_MODE_BRIEF_ID, VIEW_MODE_FULL_ID, ViewMode,
5557
};
5658
use tauri::{Emitter, Manager};
5759

@@ -136,6 +138,13 @@ pub fn run() {
136138
let _ = window.set_title(&title);
137139
}
138140

141+
// Initialize pane state store for MCP context tools
142+
app.manage(mcp::PaneStateStore::new());
143+
144+
// Start MCP server for AI agent integration
145+
let mcp_config = mcp::McpConfig::from_env();
146+
mcp::start_mcp_server(app.handle().clone(), mcp_config);
147+
139148
Ok(())
140149
})
141150
.on_menu_event(|app, event| {
@@ -191,6 +200,29 @@ pub fn run() {
191200
} else if id == SWITCH_PANE_ID {
192201
// Emit event to switch pane
193202
let _ = app.emit("switch-pane", ());
203+
} else if id == SORT_BY_NAME_ID
204+
|| id == SORT_BY_EXTENSION_ID
205+
|| id == SORT_BY_SIZE_ID
206+
|| id == SORT_BY_MODIFIED_ID
207+
|| id == SORT_BY_CREATED_ID
208+
{
209+
// Handle sort by column
210+
let column = match id {
211+
SORT_BY_NAME_ID => "name",
212+
SORT_BY_EXTENSION_ID => "extension",
213+
SORT_BY_SIZE_ID => "size",
214+
SORT_BY_MODIFIED_ID => "modified",
215+
SORT_BY_CREATED_ID => "created",
216+
_ => return,
217+
};
218+
let _ = app.emit("menu-sort", serde_json::json!({ "action": "sortBy", "value": column }));
219+
} else if id == SORT_ASCENDING_ID || id == SORT_DESCENDING_ID {
220+
// Handle sort order
221+
let order = if id == SORT_ASCENDING_ID { "asc" } else { "desc" };
222+
let _ = app.emit(
223+
"menu-sort",
224+
serde_json::json!({ "action": "sortOrder", "value": order }),
225+
);
194226
} else {
195227
// Handle file actions
196228
commands::ui::execute_menu_action(app, id);
@@ -221,6 +253,9 @@ pub fn run() {
221253
commands::ui::copy_to_clipboard,
222254
commands::ui::quick_look,
223255
commands::ui::get_info,
256+
mcp::pane_state::update_left_pane_state,
257+
mcp::pane_state::update_right_pane_state,
258+
mcp::pane_state::update_focused_pane,
224259
#[cfg(target_os = "macos")]
225260
commands::sync_status::get_sync_status,
226261
#[cfg(target_os = "macos")]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! MCP server configuration.
2+
3+
use std::env;
4+
5+
/// Configuration for the MCP server, read from environment variables.
6+
#[derive(Debug, Clone)]
7+
pub struct McpConfig {
8+
/// Whether the MCP server is enabled
9+
pub enabled: bool,
10+
/// Port to listen on
11+
pub port: u16,
12+
}
13+
14+
impl McpConfig {
15+
/// Load configuration from environment variables.
16+
pub fn from_env() -> Self {
17+
let enabled = env::var("CMDR_MCP_ENABLED")
18+
.map(|v| v == "true" || v == "1")
19+
.unwrap_or(cfg!(debug_assertions)); // Default: enabled in debug builds
20+
21+
let port = env::var("CMDR_MCP_PORT")
22+
.ok()
23+
.and_then(|v| v.parse().ok())
24+
.unwrap_or(9224);
25+
26+
Self { enabled, port }
27+
}
28+
}
29+
30+
impl Default for McpConfig {
31+
fn default() -> Self {
32+
Self::from_env()
33+
}
34+
}
35+
36+
#[cfg(test)]
37+
mod tests {
38+
use super::*;
39+
40+
#[test]
41+
fn test_default_values() {
42+
let config = McpConfig {
43+
enabled: true,
44+
port: 9224,
45+
};
46+
47+
assert_eq!(config.port, 9224);
48+
assert!(config.enabled);
49+
}
50+
51+
#[test]
52+
fn test_from_env_returns_config() {
53+
let config = McpConfig::from_env();
54+
assert!(config.port > 0);
55+
}
56+
57+
#[test]
58+
fn test_default_impl() {
59+
let config = McpConfig::default();
60+
assert!(config.port > 0);
61+
}
62+
}

0 commit comments

Comments
 (0)