Repository navigation
Robustness fixes: config data loss, unmounting, hung backends, CLI and mbro bugs - #21
Conversation
- A config.json that fails to parse is no longer overwritten by the next save; the save is refused and the file left as-is. An empty file is still treated as new. - Server entries that fail validation, and top-level settings like self_prefix, are written back unchanged instead of being dropped. - The reloader now reads the file with the same loader as ConfigManager, so its cached config includes kits and settings. With auto-reload on (the default), the first save from a running server used to erase the kits section. - ServerConfig, KitConfig, AuthConfig and BearerAuthConfig are plain models. As BaseSettings with no env prefix they read unrelated env vars like PREFIX (set by Termux), URI, ENV and KEY_PATH, which made valid servers fail to load and then get dropped on save. - Config and auth.json writes go through a temp file and rename, keeping the existing file's mode and following symlinks. - The watchdog handler also reacts to files created or renamed over the config, which is how many editors and tools save. - A disabled server without a command or uri no longer blocks every subsequent reload.
Auth: - `magg auth init` creates missing parent dirs (e.g. no ~/.ssh) and reports "key already exists" as an error instead of a traceback. - The private key file is created 0600 rather than chmod'ed after write. - `--key-path` is saved to auth.json; previously only a custom issuer or audience was, so auth status/token couldn't find the new key. - `auth private-key --export` quotes the key so the output can be eval'd. - `auth status` no longer prints the MAGG_PRIVATE_KEY line twice. Search: - A GitHub repo with a null description, or an npm package with a null author, no longer throws away all results from that source. - Sources are searched concurrently, with a 15s session timeout instead of aiohttp's 300s default, and proxy env vars are honored. - The ranking bonus for Glama results now matches their source name.
- Kits in a kit.d next to the config file are found even when the config is set with MAGG_CONFIG_PATH or --config, as the docs described. - MAGG_RELOAD_USE_WATCHDOG was documented but ignored; false now forces polling, which helps where file notifications don't arrive (network filesystems, Docker Desktop bind mounts). - Docs: --env-pass/--env-set are global options (magg --env-pass serve); kit discovery uses MAGG_PATH, not the nonexistent MAGG_KITD_PATH; an omitted prefix means no prefix, not the server name; MAGG_PREFIX_SEP only applies to Magg's own tools; magg_check's timeout default is 2.5; the kit example is valid JSON; document MAGG_BACKEND_INIT_TIMEOUT and how reload treats invalid files.
A bad value such as MAGG_AUTO_RELOAD=maybe or MAGG_SELF_PREFIX=my_prefix used to print a pydantic traceback on every command, including --version. The CLI now prints one line per invalid field naming the environment variable to fix, and exits 1. python -m magg goes through the same path.
- call/prompt keep a JSON object argument as raw text (quoted or not), and scripts join JSON objects that span lines until braces balance - key=value values are decoded as JSON with a raw-string fallback, keeping string-typed schema params as strings (no more 007 -> 7 or ValueError on 10-20); prompt accepts key=value - Non-interactive runs (-n, -X, -) exit 1 when any command fails; per-command errors are contained and later commands keep running, with --fail-fast to stop at the first failure; quit stops scripts - status takes args and checks for a connection; None descriptions no longer crash tools/prompts/search filtering or JSON search output - script run/dump with a missing script report an error instead of exiting; resource/prompt tracebacks only print with -v - Connection errors carry the real reason (including duplicate names); connects time out after --timeout seconds (default 30) - Only bare URLs get /mcp/ appended; /mcp and /sse paths are kept - mbro - runs non-interactively; --repl without arepl returns cleanly - help lists status and script instead of the nonexistent refresh; unknown tools get "Did you mean" suggestions - Remove unused JsonArgParser
Replace nonexistent --connect/--call-tool flags and `mbro scripts`, document `disconnect <name>`, script subcommands, script discovery paths, JSON/key=value value handling, exit status, --fail-fast, --timeout, URL defaults, and fix the REPL example.
…y, auth - Unmount on fastmcp 3: track the provider each mount adds and remove it from mcp.providers, so disable/remove/update actually drop or replace a backend's tools (previously a no-op that kept stale providers). - Hung backends: pass backend_init_timeout to backend clients and resolve Magg's own tools/resources/prompts locally first, so one unresponsive backend no longer blocks list_tools or Magg's tools. - Suppress backend stderr via the stdio transport's log_file; drop the stdio_client monkeypatch that fastmcp bypassed. - Per-server env now overrides --env-pass/--env-set. - magg_add_server: accept uri (AnyUrl -> str), require command or uri, report the mount failure cause, roll back the mount if saving fails. - magg_check: use MountedServer.client (it crashed with any mount) and iterate over a snapshot of mounted servers. - Read-only mode is checked up front in add/remove/enable/disable, kit load/unload, and non-report check actions. - Refuse to serve HTTP/hybrid when a configured private key can't be loaded instead of silently running without auth. - Runner: use loop signal handlers so SIGTERM wakes an idle stdio loop, bound the wait on the cancelled server task, exit via the signal once cleanup is done, force exit on a second signal, and restore SIG_DFL. - Proxy tool: path is required for 'info' (fix description).
…parsing - magg_check's remount and unmount actions only affect the running process, so they stay available in read-only mode; disable still refuses since it writes the config. - On Python 3.12.3 (Ubuntu 24.04's system Python), argparse treated `magg server update --env` as an ambiguous abbreviation of the global --env-pass/--env-set options. Disable abbreviations on the top-level parser. - magg_add_server's prefix description matches actual behavior: no prefix by default.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
mbro --timeout 0 is documented as disabling timeouts but currently passes init_timeout=0 through to fastmcp, which likely causes immediate timeouts instead of disabling them.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
This PR is a broad robustness sweep across Magg’s config persistence/reload, server mounting lifecycle, CLI UX, and the mbro client, with substantial regression test additions to prevent data loss and hangs.
Changes:
- Harden config load/save/reload to avoid silent data loss (atomic writes, preserve invalid entries, watchdog rename/create handling, env isolation, kit discovery).
- Fix server lifecycle issues (provider-based unmounting, hung-backend init timeout, signal handling, fail-closed HTTP auth).
- Improve CLI/mbro ergonomics and resilience (better error reporting, concurrent search, safer auth init/export, improved mbro scripting/argument parsing and exit codes).
| File | Description |
|---|---|
| test/magg/test_server_robustness.py | New end-to-end robustness tests for unmounting, hung backends, read-only mode, auth fail-closed, and signals. |
| test/magg/test_mbro_commands.py | New tests covering mbro argument parsing, error handling, batch execution, and exit codes. |
| test/magg/test_mbro_cli_overhaul.py | Adjust tests to validate the new run_commands() batching behavior. |
| test/magg/test_kit_tools.py | Update kit tool tests to account for read-only gating. |
| test/magg/test_config_safety.py | New tests ensuring config persistence never loses user-written data and reload edge cases behave safely. |
| test/magg/test_config_reload.py | Update tests to reflect MountedServer being a dataclass (not dict-like). |
| test/magg/test_cli_fixes.py | New CLI tests for auth init/export, search robustness, and invalid env var handling. |
| readme.md | Document new/clarified env vars and backend init timeout behavior. |
| magg/util/stdio_patch.py | Remove ineffective stderr monkeypatch approach. |
| magg/settings.py | Implement atomic JSON writes, add config schema hardening, add new settings (reload_use_watchdog, backend_init_timeout), and prevent env leakage in nested models. |
| magg/server/server.py | Add read-only upfront checks, stricter add_server validation, improved mount failure messages, and safer URI handling. |
| magg/server/runner.py | Improve signal handling with loop-based handlers, escalation on repeat signals, and bounded shutdown behavior. |
| magg/server/manager.py | Rework mount/unmount to track and remove providers correctly; add init timeout and proper stderr suppression; enforce fail-closed HTTP auth. |
| magg/server/defaults.py | Update proxy tool parameter docs to match actual semantics. |
| magg/reload.py | Use ConfigManager for consistent config loading, handle rename/create events, and honor MAGG_RELOAD_USE_WATCHDOG. |
| magg/proxy/server.py | Resolve local components before querying backend providers to avoid hung backend stalling lookups. |
| magg/proxy/mixin.py | Align parameter descriptions with updated proxy behavior. |
| magg/mbro/scripts.py | Run scripts via run_commands() and avoid exit(1) from deep command handlers. |
| magg/mbro/parser.py | Replace JSON arg parsing with brace-aware multiline/JSON-preserving parsing. |
| magg/mbro/formatter.py | Track error count for batch exit codes; harden formatting for missing descriptions; expand help text. |
| magg/mbro/command.py | Improve connection/status handling, schema-aware arg parsing, better null-safe filtering, and tool name suggestions. |
| magg/mbro/client.py | Add URL normalization, connect timeout support, and improve connect error reporting. |
| magg/mbro/cli.py | Add batch execution (run_commands), fail-fast mode, connect timeout option, and non-interactive exit code behavior. |
| magg/kit.py | Prevent env leakage by switching models and improve kit discovery when config path is outside MAGG_PATH. |
| magg/discovery/search.py | Add bounded concurrent searching and null-safe parsing of upstream results. |
| magg/discovery/metadata.py | Add timeout to GitHub metadata fetch to avoid long stalls. |
| magg/cli.py | Disable argparse abbreviations, improve ValidationError reporting, and harden auth init/export flows. |
| magg/auth.py | Ensure key directory parents are created, enforce 0600-at-creation semantics, and improve failure messages. |
| magg/__main__.py | Remove eager process.setup() at import-time; delegate to CLI main(). |
| docs/mbro.md | Update mbro docs to match new commands, scripting, JSON/key=value parsing, exit codes, and timeouts. |
| docs/kits.md | Correct kit discovery documentation and fix JSON example validity. |
| docs/index.md | Refresh docs for changed defaults/options, env var behavior, and prefix semantics. |
| docs/config-reload.md | Clarify watchdog control and document safety behavior for invalid JSON / invalid server entries. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Backend stderr: assert what actually reaches stderr (via capfd) with suppression on and off, instead of checking a transport attribute. - Reload: rename a real file over the watched config and check the reload fires; check Magg's own atomic save doesn't trigger a reload while the next outside edit still does; check polling mode without watchdog by observing a detected change. - Placeholder and unparseable-file reload tests go through reload_config() rather than private helpers. - Hung backend test fails with a timeout instead of hanging on regression, and no longer inspects private client attributes. - --key-path test checks that `auth token` works afterwards, which was the user-facing failure. - Search concurrency is proven with a barrier instead of timing. - Settings: a bad value no longer skips later valid settings. - Remove tests that couldn't fail: a tool description string check, and a timeout-0 check that fastmcp already guarantees.
fastmcp treats an init_timeout of 0 as disabled, so passing it through already matches the documented behavior. The test fails if 0 ever turns into an immediate timeout.
|
Re: the Copilot finding on a5c2849 adds a test that connects to |
67f221c to
a5c2849
Compare
scripts/bump_version.py bumps the version with `uv version` (which updates pyproject.toml and re-locks uv.lock) and copies it into server.json. With --check it only verifies that server.json matches pyproject.toml, for CI to run instead of the tests. The server.json tests in test_registry_search.py checked release metadata rather than code behavior, so they're removed.
Added version consistency check for server.json and pyproject.toml.

A sweep of bugs found by exercising Magg, the
maggCLI and mbro. Every fix was reproduced first and has a regression test. Tests go from 244 to 334 and pass on Python 3.12 and 3.13. About 1,150 of the added lines are tests.Bumps the version to 1.3.0, so merging publishes a release.
The commits are split by area and can be reviewed one at a time.
Config data loss (
0b961e4,d432c19)Each of these silently deleted user config while reporting success:
serversonly, so the first save from a running server erasedkits. It now uses the same loader asConfigManager.self_prefix, were dropped on save. They are now written back unchanged.ServerConfig,KitConfig,AuthConfigandBearerAuthConfigwereBaseSettingswithout an env prefix, so they read env vars likePREFIX,URI,ENVandKEY_PATH. Termux setsPREFIX=/usr, which made every server without an explicit prefix invalid, and the next save deleted them. They are plain models now.kit.dnext to a config given with--configorMAGG_CONFIG_PATHis now searched, as the docs said.MAGG_RELOAD_USE_WATCHDOGwas documented but ignored. It's implemented now;falseforces polling.Server core (
4a88192,3c7036f)Unmounting did nothing on fastmcp 3. The old code looked for
_tool_manager._mounted_servers, which no longer exists. As a result:Mounting now keeps the provider it adds and removes it from
mcp.providerson unmount.magg_checkfailed whenever any server was mounted, because it calledserver_info.get("client")on a dataclass. This is the same fix as Fix Docker build errors and MountedServer attribute access #2.magg_add_serverwith aurialways failed (anAnyUrlpassed into astrfield). It now also requirescommandoruri.One hung backend froze everything, including Magg's own tools.
init_timeout:MAGG_BACKEND_INIT_TIMEOUT, default 30s, 0 to disable.ProxyFastMCPresolves Magg's own components before asking backends.Backend stderr suppression was never applied: fastmcp imports
stdio_clientdirectly, so the patch had no effect. It now uses the transport'slog_file, andstdio_patch.pyis deleted.Signal handling:
signal.signalplusEvent.setdoesn't wake an idle loop. Handlers now go throughloop.add_signal_handler.Read-only mode is checked before mounting or changing anything. Previously
add_servermounted first and only then failed to save.A server's own
envnow overrides--env-passand--env-set; before, it was the other way around.HTTP and hybrid mode refuse to start if a configured private key can't be loaded. Previously Magg logged a warning and served without auth. Stdio only warns.
CLI (
0609c98,6d1a0d7,3c7036f)auth initfailed when~/.sshwas missing, and printed a traceback when a key already existed.auth init --key-pathwasn't saved.auth private-key --exportoutput couldn't be eval'd.MAGG_*value prints one line naming the env var instead of a traceback on every command.magg server update --envas an ambiguous abbreviation of--env-pass/--env-set. Abbreviations are now disabled on the top-level parser.mbro (
32ff7fc,44feea7)statusalways crashed.key=valuevalues are parsed as JSON where possible, and parameters the tool's schema declares as strings are left as strings.value=10-20used to crash, and007became7.-n,-xand stdin mode now exit 1, and one failing command no longer stops the rest. There's a new--fail-fastflag.http://host/mcpwas rewritten to/mcp/mcp/.--timeout, default 30s, 0 to disable).Tests
The new tests check behavior rather than internals where possible:
Each regression test fails against the pre-fix code, except a few that guard against regressions from the refactor itself (mode and symlink preservation on save, unparseable files during reload).
Behavior changes
magg_add_serverrequirescommandoruri.npxdownload) now times out. RaiseMAGG_BACKEND_INIT_TIMEOUTif that's a problem.--conf).Docs
--env-pass/--env-setare global options, so they go beforeserve(magg --env-pass serve).MAGG_KITD_PATHwith how discovery actually works throughMAGG_PATH.MAGG_PREFIX_SEPonly applies to Magg's own tools.magg_checktimeout default is 2.5.Known gaps (not in this PR)
ProxyClient.Supersedes #2.