Guidance for coding agents working inside the mcp-mux repo — the McpMux desktop app and local gateway. Complements README.md and CONTRIBUTING.md; when anything here conflicts with an explicit user instruction in the current session, the user wins.
McpMux is a Tauri 2 desktop app (Rust + React 19) with a local Axum HTTP gateway on localhost:45818. It lets users configure MCP servers once and connect every AI client (Cursor, Claude Desktop, VS Code, Windsurf) through a single endpoint, with credentials encrypted in the OS keychain instead of plain-text JSON files.
A more detailed map of the workspace lives in CLAUDE.md at the repo root — read it for the crate layout, frontend architecture, and cross-project context. This file captures the minimum an agent needs to make safe, useful changes here.
mcp-mux/
├── apps/desktop/ # Tauri shell — React frontend (src/) + Rust Tauri commands (src-tauri/)
├── crates/
│ ├── mcpmux-core/ # Domain entities, repository traits, service layer, EventBus
│ ├── mcpmux-gateway/ # Axum gateway — routing, OAuth refresh, FeatureSet filtering
│ ├── mcpmux-storage/ # SQLite + AES-256-GCM field encryption + OS keychain
│ └── mcpmux-mcp/ # MCP protocol client wrapper (rmcp SDK)
├── packages/ui/ # Shared UI components (`@mcpmux/ui`)
├── schemas/ # JSON Schemas surfaced in the Monaco editor
└── tests/ # Rust integration, TS unit (vitest), desktop E2E (WDIO), web E2E (playwright)
Run everything from mcp-mux/:
| Command | What it does |
|---|---|
pnpm setup |
First-time dev environment setup (PowerShell on Windows). |
pnpm dev |
Tauri desktop dev mode (Rust + React hot-reload). |
pnpm dev:web |
Web UI only via Vite — no Rust, no Tauri shell. |
pnpm build |
Production Tauri build for the current platform. |
pnpm validate |
Full correctness gate — runs the items below in sequence. |
pnpm lint |
ESLint (recursive) + cargo clippy --workspace -- -D warnings. |
pnpm lint:fix |
Auto-fix lint issues. |
pnpm format |
prettier --write . + cargo fmt --all. |
pnpm format:check |
Formatting check (no writes). |
pnpm typecheck |
Recursive TypeScript typecheck. |
Before claiming a change is done, run pnpm validate (or the relevant subset) — it mirrors what CI enforces.
| Command | Scope |
|---|---|
pnpm test |
Rust + TypeScript, everything. |
pnpm test:rust |
cargo nextest run --workspace. |
pnpm test:rust:unit |
cargo nextest run --workspace --lib. |
pnpm test:rust:int |
cargo nextest run -p tests — integration crate in tests/rust. |
pnpm test:rust:doc |
cargo test --workspace --doc. |
pnpm test:ts |
Vitest run (tests/ts/vitest.config.ts). |
pnpm test:ts:watch |
Vitest watch. |
pnpm test:e2e |
Desktop E2E via WebDriver IO — requires MCPMUX_REGISTRY_URL. |
pnpm test:e2e:file -- tests/e2e/specs/foo.ts |
One WDIO spec file. |
pnpm test:e2e:grep -- "test name" |
WDIO tests matching a name. |
pnpm test:e2e:web |
Playwright on the web UI. |
pnpm test:coverage |
cargo llvm-cov + Vitest coverage. |
Prefer narrow commands over pnpm test while iterating — the full suite is slow.
- Rust: 100-char max width, 4-space indent. Clippy runs with
avoid-breaking-exported-api = false; all warnings are denied in CI. - TypeScript / JSX: Prettier — single quotes, 2-space indent, 100-char width, trailing commas (es5), Tailwind CSS plugin for class ordering.
- Path aliases:
@/→apps/desktop/src/;@mcpmux/ui→packages/ui. - No emojis in code or commits unless the user explicitly asks for them.
- Comments: only when the why is non-obvious. Identifiers should explain the what.
- Commits must be signed off (DCO):
git commit -s -m "...". CI rejects unsigned commits. - Prefer conventional-style subjects — releases use release-please for semantic versioning.
- PRs follow
.github/pull_request_template.md: describe the change, how you tested, and check thepnpm test/pnpm lint/pnpm typecheckboxes. - Don't bypass hooks (
--no-verify) or DCO signing unless explicitly told to.
Anything that spawns a child process (stdio MCP servers, installers, etc.) must go through mcpmux_gateway::pool::transport::configure_child_process_platform(). That helper applies:
- Windows:
CREATE_NO_WINDOW(0x08000000) — release builds usewindows_subsystem = "windows", so without this the OS briefly flashes a console window when a child starts. - Unix:
process_group(0)— stops SIGINT/SIGTSTP from the parent terminal from tearing down the child.
tokio::process::Command already exposes creation_flags() (Windows) and process_group() (Unix). Do not import std::os::*::process::CommandExt — those traits are unused with Tokio's Command and trigger clippy.
- The pre-commit hook runs
cargo clippy --workspace -- -D warningson your dev machine. #[cfg(unix)]only compiles on Unix;#[cfg(windows)]only on Windows. CI is Linux, so Windows-gated code is not linted in CI, and Unix-gated code is not linted on a Windows dev box.- When you touch platform-conditional code, check the other platform compiles before pushing — CI won't catch a Windows-only clippy regression.
- Never log tokens, API keys, headers with auth material, or raw OAuth responses. Use the existing sanitised-log helpers in
mcpmux-gateway. - Credentials encrypt at rest via AES-256-GCM in SQLite plus DPAPI (Windows) / OS keychain (macOS, Linux). Don't add new code paths that persist secrets any other way.
- Secrets should be wiped from memory after use via
zeroize. - The gateway binds to
127.0.0.1. Don't bind to0.0.0.0or expose it on the network.
- Entry point:
apps/desktop/src/main.tsx→App.tsx. - Global state: a single Zustand store at
src/stores/appStore.ts. - Key hooks:
useServerManager(server CRUD),useSpaces(workspace switching),useDomainEvents(Rust-side EventBus listener),useDataSync. - UI: React 19, Tailwind CSS, Lucide icons, Monaco Editor for JSON config surfaces.
- Open external URLs through
openExternalinapps/desktop/src/lib/contribute.ts— it routes through the Tauri opener plugin so links open in the user's default browser, not the webview. - For UI changes, launch
pnpm devand exercise the feature in the running app before reporting done — typecheck and tests verify correctness, not UX regressions.
- Cross-layer communication goes through the
EventBusinmcpmux-core. Prefer emitting a domain event over reaching across module boundaries directly. - Storage is behind repository traits — don't call SQLx or SQLite APIs directly from gateway or app code; add or use a repo method.
- Services are wired up via the
ApplicationServicesbuilders inmcpmux-core. New services should follow the same DI pattern.
The full MCP spec is vendored at ../modelcontextprotocol/docs/specification/. Default to the latest stable version (2025-11-25) and read the relevant section before implementing or modifying protocol behaviour (transports, lifecycle, capability negotiation, OAuth flows, tools / resources / prompts). For features targeting a specific protocol version, use that version's folder.
Server catalog entries live in the separate mcp-servers repo — not here. If a task involves adding, editing, or fixing a server definition, switch to that repo and follow its AGENTS.md.
- Don't add backwards-compatibility shims, deprecated aliases, or
// removedplaceholder comments when removing code — delete it cleanly. - Don't introduce new fallbacks or input validation for states that are already framework-guaranteed. Trust internal invariants; validate only at the boundary (user input, external APIs).
- Don't edit generated files:
CHANGELOG.md, release-please manifests,bundle/*.jsonin sibling repos,packages/ui/dist. - Don't commit screenshots, videos, or large binaries to the repo — link out instead.