diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b2a73b..bc376b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,15 +119,15 @@ let h2 = spawn_mux_server(MuxConfig::new("/tmp/b.sock", "server-b")).await?; ## [0.2.0] - 2025-11-24 ### Added -- Optional tray icon (`--tray`) showing live server status, client and pending counts, and restart reasons. ([5eefde4](https://github.com/Loctree/rmcp-mux/commit/5eefde4)) -- Config file support (JSON/YAML/TOML) with auto-detection and CLI overrides. ([5eefde4](https://github.com/Loctree/rmcp-mux/commit/5eefde4)) -- `rmcp_mux_proxy` helper binary plus launchd template and installer tweaks for easier setup. ([04e5402](https://github.com/Loctree/rmcp-mux/commit/04e5402)) -- GitHub Actions CI workflow for formatting, linting, testing, and coverage, including an async proxy forwarding test. ([ad2b9aa](https://github.com/Loctree/rmcp-mux/commit/ad2b9aa)) -- Mux hooks, Semgrep rules, and expanded README documentation. ([e80083c](https://github.com/Loctree/rmcp-mux/commit/e80083c)) +- Optional tray icon (`--tray`) showing live server status, client and pending counts, and restart reasons. ([5eefde4](https://github.com/vetcoders/rmcp-mux/commit/5eefde4)) +- Config file support (JSON/YAML/TOML) with auto-detection and CLI overrides. ([5eefde4](https://github.com/vetcoders/rmcp-mux/commit/5eefde4)) +- `rmcp_mux_proxy` helper binary plus launchd template and installer tweaks for easier setup. ([04e5402](https://github.com/vetcoders/rmcp-mux/commit/04e5402)) +- GitHub Actions CI workflow for formatting, linting, testing, and coverage, including an async proxy forwarding test. ([ad2b9aa](https://github.com/vetcoders/rmcp-mux/commit/ad2b9aa)) +- Mux hooks, Semgrep rules, and expanded README documentation. ([e80083c](https://github.com/vetcoders/rmcp-mux/commit/e80083c)) - `health` subcommand to resolve config and assert socket reachability, plus unit tests for healthy/missing sockets. ### Changed -- Refactored mux state management and tray functionality into dedicated `state` and `tray` modules, with tray dependencies gated behind an optional `tray` feature; CI updated to run with `--no-default-features`. ([0d60764](https://github.com/Loctree/rmcp-mux/commit/0d60764), [ad2b9aa](https://github.com/Loctree/rmcp-mux/commit/ad2b9aa)) +- Refactored mux state management and tray functionality into dedicated `state` and `tray` modules, with tray dependencies gated behind an optional `tray` feature; CI updated to run with `--no-default-features`. ([0d60764](https://github.com/vetcoders/rmcp-mux/commit/0d60764), [ad2b9aa](https://github.com/vetcoders/rmcp-mux/commit/ad2b9aa)) ## [0.1.5] - 2025-11-20 diff --git a/Cargo.toml b/Cargo.toml index f0faa3e..dfee91e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.4.0" edition = "2024" description = "MCP server multiplexer - single server, multiple clients via Unix sockets" license = "MIT" -repository = "https://github.com/Loctree/rmcp-mux" +repository = "https://github.com/vetcoders/rmcp-mux" keywords = ["mcp", "multiplexer", "rpc", "unix-socket"] categories = ["network-programming", "development-tools"] readme = "README.md" diff --git a/README.md b/README.md index aaf85a6..99026f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rmcp-mux – MCP Server Multiplexer -[![CI](https://github.com/Loctree/rmcp-mux/actions/workflows/ci.yml/badge.svg)](https://github.com/Loctree/rmcp-mux/actions/workflows/ci.yml) +[![CI](https://github.com/vetcoders/rmcp-mux/actions/workflows/ci.yml/badge.svg)](https://github.com/vetcoders/rmcp-mux/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/rmcp-mux.svg)](https://crates.io/crates/rmcp-mux) [![Version](https://img.shields.io/badge/version-0.3.4-blue.svg)](Cargo.toml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) @@ -79,7 +79,7 @@ cargo build --release ### One-liner (curl | sh) ```bash -curl -fsSL https://raw.githubusercontent.com/Loctree/rmcp-mux/main/tools/install.sh | sh +curl -fsSL https://raw.githubusercontent.com/vetcoders/rmcp-mux/main/tools/install.sh | sh ``` **Environment overrides:** @@ -119,11 +119,11 @@ cmd = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/docs"] lazy_start = true -[servers.rmcp-memex] -socket = "~/.rmcp-servers/sockets/rmcp-memex.sock" -cmd = "/path/to/rmcp-memex" -args = ["serve", "--config", "config.toml", "--db-path", "~/.ai-memories/lancedb"] -env = { SLED_PATH = "~/.rmcp-servers/sled/memex" } +[servers.custom-db] +socket = "~/mcp-sockets/custom-db.sock" +cmd = "/path/to/custom-mcp-server" +args = ["serve", "--config", "config.toml", "--db-path", "~/.local/share/custom-db"] +env = { DB_PATH = "~/.local/share/custom-db/store" } lazy_start = false ``` @@ -152,9 +152,9 @@ MCP hosts expecting STDIO communication connect through `rmcp-mux-proxy`: ```json { "mcpServers": { - "rmcp-memex": { + "memory": { "command": "rmcp-mux-proxy", - "args": ["--socket", "~/.rmcp-servers/sockets/rmcp-memex.sock"] + "args": ["--socket", "~/.rmcp-servers/sockets/memory.sock"] }, "loctree": { "command": "rmcp-mux-proxy", diff --git a/docs/dev/guidelines.md b/docs/dev/guidelines.md index ed8f1e2..30fe4ac 100644 --- a/docs/dev/guidelines.md +++ b/docs/dev/guidelines.md @@ -68,8 +68,7 @@ error messages or tests. ### 1.3. Building the binaries -All builds are driven through Cargo. From the project root (`/Users/maciejgad/.rmcp_servers/rmcp-mux` in the current -setup): +All builds are driven through Cargo. From the project root (`~/rmcp-mux`, for example): #### 1.3.1. Debug build (fast, for development) diff --git a/src/runtime/heartbeat.rs b/src/runtime/heartbeat.rs index 29ecdf0..a98faff 100644 --- a/src/runtime/heartbeat.rs +++ b/src/runtime/heartbeat.rs @@ -4,8 +4,7 @@ //! It periodically sends lightweight ping probes and tracks response times, //! triggering server restarts when the backend becomes unresponsive. //! -//! Created by M&K (c)2025 The LibraxisAI Team -//! Co-Authored-By: [Maciej](void@div0.space) & [Klaudiusz](the1st@whoai.am) +//! Created by Vetcoders (c)2025 use std::sync::Arc; use std::time::{Duration, Instant}; diff --git a/src/state.rs b/src/state.rs index 4432c73..57f678e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -409,6 +409,9 @@ pub async fn reset_state( fn compute_health_status(st: &MuxState, active_clients: usize) -> HealthStatus { match &st.server_status { ServerStatus::Running => { + // Explicit guard-then-divide reads clearer than checked_div here: + // we want a plain 0 default, not an Option. Behaviour unchanged. + #[allow(clippy::manual_checked_ops)] let load_pct = if st.max_active_clients > 0 { (active_clients * 100) / st.max_active_clients } else { diff --git a/src/wizard/keys.rs b/src/wizard/keys.rs index 23113df..b6ccbfe 100644 --- a/src/wizard/keys.rs +++ b/src/wizard/keys.rs @@ -45,94 +45,82 @@ pub fn handle_key(app: &mut AppState, key: KeyEvent) -> Result { } // Tab to switch panels - KeyCode::Tab => { - if app.editing.is_none() { - app.active_panel = match app.active_panel { - Panel::ServiceList => Panel::Editor, - Panel::Editor => Panel::ServiceList, - Panel::ConfirmDialog => Panel::ConfirmDialog, - }; - update_message(app); - } + KeyCode::Tab if app.editing.is_none() => { + app.active_panel = match app.active_panel { + Panel::ServiceList => Panel::Editor, + Panel::Editor => Panel::ServiceList, + Panel::ConfirmDialog => Panel::ConfirmDialog, + }; + update_message(app); } - KeyCode::BackTab => { - if app.editing.is_none() { - app.active_panel = match app.active_panel { - Panel::ServiceList => Panel::Editor, - Panel::Editor => Panel::ServiceList, - Panel::ConfirmDialog => Panel::ConfirmDialog, - }; - update_message(app); - } + KeyCode::BackTab if app.editing.is_none() => { + app.active_panel = match app.active_panel { + Panel::ServiceList => Panel::Editor, + Panel::Editor => Panel::ServiceList, + Panel::ConfirmDialog => Panel::ConfirmDialog, + }; + update_message(app); } // Navigation - KeyCode::Up => { - if app.editing.is_none() { - match (app.wizard_step, app.active_panel) { - (WizardStep::ServerSelection, Panel::ServiceList) => { - if app.selected_service > 0 { - sync_form_to_service(app); - app.selected_service -= 1; - load_service_to_form(app); - } - } - (WizardStep::ServerSelection, Panel::Editor) => { - app.current_field = previous_field(app.current_field); - } - (WizardStep::ClientSelection, Panel::ServiceList) => { - if app.selected_client > 0 { - app.selected_client -= 1; - } - } - (WizardStep::Confirmation, _) => { - // Navigate through save options - app.confirm_choice = previous_confirm_choice(app.confirm_choice); - } - (WizardStep::HealthCheck, _) => { - // Toggle between Ok and TryAgain - app.health_choice = match app.health_choice { - HealthCheckChoice::Ok => HealthCheckChoice::TryAgain, - HealthCheckChoice::TryAgain => HealthCheckChoice::Ok, - }; - } - _ => {} + KeyCode::Up if app.editing.is_none() => { + match (app.wizard_step, app.active_panel) { + (WizardStep::ServerSelection, Panel::ServiceList) if app.selected_service > 0 => { + sync_form_to_service(app); + app.selected_service -= 1; + load_service_to_form(app); + } + (WizardStep::ServerSelection, Panel::Editor) => { + app.current_field = previous_field(app.current_field); + } + (WizardStep::ClientSelection, Panel::ServiceList) if app.selected_client > 0 => { + app.selected_client -= 1; } + (WizardStep::Confirmation, _) => { + // Navigate through save options + app.confirm_choice = previous_confirm_choice(app.confirm_choice); + } + (WizardStep::HealthCheck, _) => { + // Toggle between Ok and TryAgain + app.health_choice = match app.health_choice { + HealthCheckChoice::Ok => HealthCheckChoice::TryAgain, + HealthCheckChoice::TryAgain => HealthCheckChoice::Ok, + }; + } + _ => {} } } - KeyCode::Down => { - if app.editing.is_none() { - match (app.wizard_step, app.active_panel) { - (WizardStep::ServerSelection, Panel::ServiceList) => { - if app.selected_service < app.services.len().saturating_sub(1) { - sync_form_to_service(app); - app.selected_service += 1; - load_service_to_form(app); - } - } - (WizardStep::ServerSelection, Panel::Editor) => { - app.current_field = next_field(app.current_field); - } - (WizardStep::ClientSelection, Panel::ServiceList) => { - if app.selected_client < app.clients.len().saturating_sub(1) { - app.selected_client += 1; - } - } - (WizardStep::Confirmation, _) => { - // Navigate through save options - app.confirm_choice = next_confirm_choice(app.confirm_choice); - } - (WizardStep::HealthCheck, _) => { - // Toggle between Ok and TryAgain - app.health_choice = match app.health_choice { - HealthCheckChoice::Ok => HealthCheckChoice::TryAgain, - HealthCheckChoice::TryAgain => HealthCheckChoice::Ok, - }; - } - _ => {} + KeyCode::Down if app.editing.is_none() => { + match (app.wizard_step, app.active_panel) { + (WizardStep::ServerSelection, Panel::ServiceList) + if app.selected_service < app.services.len().saturating_sub(1) => + { + sync_form_to_service(app); + app.selected_service += 1; + load_service_to_form(app); + } + (WizardStep::ServerSelection, Panel::Editor) => { + app.current_field = next_field(app.current_field); + } + (WizardStep::ClientSelection, Panel::ServiceList) + if app.selected_client < app.clients.len().saturating_sub(1) => + { + app.selected_client += 1; + } + (WizardStep::Confirmation, _) => { + // Navigate through save options + app.confirm_choice = next_confirm_choice(app.confirm_choice); + } + (WizardStep::HealthCheck, _) => { + // Toggle between Ok and TryAgain + app.health_choice = match app.health_choice { + HealthCheckChoice::Ok => HealthCheckChoice::TryAgain, + HealthCheckChoice::TryAgain => HealthCheckChoice::Ok, + }; } + _ => {} } } @@ -153,14 +141,13 @@ pub fn handle_key(app: &mut AppState, key: KeyEvent) -> Result { app.message = "Editing... Esc to finish".into(); } } - (WizardStep::ClientSelection, Panel::ServiceList) => { + (WizardStep::ClientSelection, Panel::ServiceList) // Toggle client selection on Enter as well - if app.selected_client < app.clients.len() { + if app.selected_client < app.clients.len() => { app.clients[app.selected_client].selected = !app.clients[app.selected_client].selected; update_step2_message(app); } - } (WizardStep::Confirmation, _) => { // Execute the selected action return execute_confirm_choice(app); @@ -176,9 +163,9 @@ pub fn handle_key(app: &mut AppState, key: KeyEvent) -> Result { // Space toggles selection (in ServiceList for both steps) or tray (in Editor) KeyCode::Char(' ') => { match (app.wizard_step, app.active_panel) { - (WizardStep::ServerSelection, Panel::ServiceList) => { + (WizardStep::ServerSelection, Panel::ServiceList) // Toggle selection for current server - if app.selected_service < app.services.len() { + if app.selected_service < app.services.len() => { app.services[app.selected_service].selected = !app.services[app.selected_service].selected; let selected_count = app.services.iter().filter(|s| s.selected).count(); @@ -187,31 +174,26 @@ pub fn handle_key(app: &mut AppState, key: KeyEvent) -> Result { selected_count ); } - } - (WizardStep::ServerSelection, Panel::Editor) => { - if app.current_field == Field::Tray { + (WizardStep::ServerSelection, Panel::Editor) + if app.current_field == Field::Tray => { app.form.tray = !app.form.tray; app.form.dirty = true; } - } - (WizardStep::ClientSelection, Panel::ServiceList) => { + (WizardStep::ClientSelection, Panel::ServiceList) // Toggle selection for current client - if app.selected_client < app.clients.len() { + if app.selected_client < app.clients.len() => { app.clients[app.selected_client].selected = !app.clients[app.selected_client].selected; update_step2_message(app); } - } _ => {} } } // Escape - KeyCode::Esc => { - if app.editing.is_some() { - app.editing = None; - update_message(app); - } + KeyCode::Esc if app.editing.is_some() => { + app.editing = None; + update_message(app); } // Next step with 'n' key diff --git a/tools/install.sh b/tools/install.sh index 7631a8b..4795f7e 100644 --- a/tools/install.sh +++ b/tools/install.sh @@ -4,7 +4,7 @@ umask 022 # rmcp-mux install script # Usage: -# curl -fsSL https://raw.githubusercontent.com/Loctree/rmcp-mux/main/tools/install.sh | sh +# curl -fsSL https://raw.githubusercontent.com/vetcoders/rmcp-mux/main/tools/install.sh | sh # Env overrides: # INSTALL_DIR where to place the runnable `rmcp-mux` wrapper (default: $HOME/.local/bin) # CARGO_HOME override cargo home (default: ~/.cargo) @@ -14,7 +14,7 @@ umask 022 INSTALL_DIR=${INSTALL_DIR:-"$HOME/.local/bin"} CARGO_HOME=${CARGO_HOME:-"$HOME/.cargo"} CARGO_BIN="$CARGO_HOME/bin" -REPO_URL="https://github.com/LibraxisAI/rmcp-mux" +REPO_URL="https://github.com/vetcoders/rmcp-mux" # Allow pinning a branch/tag/commit; defaults to main. MUX_REF=${MUX_REF:-"main"}