Skip to content

fix(cli): point packaged Linux installs at the system service (--system) - #1800

Merged
Dumbris merged 6 commits into
mainfrom
fix/linux-packaged-cli-system-flag
Oct 30, 2026
Merged

Dumbris merged 6 commits into
mainfrom
fix/linux-packaged-cli-system-flag

Conversation

@Dumbris

@Dumbris Dumbris commented Oct 29, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the CLI confusion on packaged Linux installs reported in discussion #1678. The deb/rpm service runs as mcpproxy with /etc/mcpproxy/mcp_config.json + /var/lib/mcpproxy. A plain mcpproxy upstream list silently created ~/.mcpproxy/mcp_config.json and reported an empty, unrelated instance, and nothing in the product pointed at the service.

  • No silent per-user config on packaged installs (Linux only). Detection: the service config exists (an unreadable /etc/mcpproxy, which is 2770 root:mcpproxy, still counts) and the shipped mcpproxy.service unit is present. When a management command has no --config/--data-dir and the user has no config of their own, it exits non-zero with the service config path and the exact sudo mcpproxy --system <same command> to run, plus the group alternative and mcpproxy serve for a separate per-user instance. serve, macOS, Windows, and users who already have ~/.mcpproxy behave as before. registry discovery keeps working on defaults but no longer creates the file.
  • Global --system flag. Shorthand for --config /etc/mcpproxy/mcp_config.json --data-dir /var/lib/mcpproxy. The service socket rejects every uid but mcpproxy's (SO_PEERCRED, root included), so in --system mode the socket is used only when it actually answers; otherwise the CLI goes over HTTP with the config's listen + api_key (so enable_socket: false works too). --system requires the running service: it never falls back to the CLI's standalone/offline paths, which would launch stdio upstreams or write the service's config/DB as root. The error names sudo systemctl start mcpproxy and the sudo -u mcpproxy mcpproxy --config … --data-dir … form for offline maintenance (e.g. db compact) or an HTTPS listener. An unreadable config gives a permission error naming sudo and usermod -aG mcpproxy. Refused together with --config/--data-dir, for serve, and off Linux.
  • Config saves keep a deliberate 0640 (internal/config). SaveConfig always wrote 0600; the service does that on first start when it persists its generated API key, which silently locked the mcpproxy group out of /etc/mcpproxy/mcp_config.json. A regular file that already grants group read and nothing else keeps 0640, but only when the replacement inode lands in the same group (checked on the temp file, which is created owner-only before the check). Everything else is still written 0600. Existing hosts restore group read once with sudo chmod 0640 (documented, and named in the --system permission error); no root-run postinstall repair, since /etc/mcpproxy is group-writable.
  • search-servers reads the config chosen by -c/--config/--system (it always used defaults). MCPP_CONFIG is honoured by the packaged-install guard and the instance warning.
  • doctor / status warn (stderr, so -o json stays clean) when mcpproxy.service is active and the CLI reads a different config — the case for anyone who already got a ~/.mcpproxy from older versions — and print the --system command.
  • status now reports (and --reset-key writes) the config file actually loaded, not <data-dir>/mcp_config.json, which was wrong under --config/--system.
  • Docs: new "Manage the packaged service from the CLI" section in docs/getting-started/installation.md; --system in docs/cli/command-reference.md and docs/cli-management-commands.md; migration-guide doctor invocations now use --system.

All host paths are injectable (packagedPaths, systemServiceActive); tests never touch /etc.

Test plan

  • New cmd/mcpproxy/packaged_install_test.go (written first, failing): detection matrix incl. unreadable config dir, every management loader refuses and creates nothing, user config still used, non-Linux unchanged, --system path wiring / conflicts / permission / serve refusal, hint command rewriting, different-instance warning, socket-refuses → HTTP fallback.
  • internal/config/save_mode_test.go: mode matrix, group-change fallback to 0600, temp file owner-only before the check (each seen failing first).
  • Existing cli_config_resolve_test.go (TestLoadersHonorGlobalConfigFlag and friends) green.
  • go test -race ./cmd/mcpproxy/ ./internal/config/... bare and -tags server; ./internal/runtime/... race green with the SaveConfig change.
  • golangci-lint v2 (.github/.golangci.yml) bare and --build-tags server: 0 issues on ./cmd/...; ./internal/config/... only the known main baseline (reflect.Ptr in zero_value_preservation_test.go, untouched).
  • ./scripts/test-api-e2e.sh (with ./mcpproxy-server built): 70 passed, 0 failed.
  • Cross-review: opencode github-copilot/gpt-6.1-sol, 5 rounds → CLEAN. Fixed along the way: root --reset-key making the config unreadable to the service, MCPP_CONFIG ignored, search-servers ignoring --system, symlink-following postinstall repair (dropped), 0640 kept across a group change, temp-file exposure window, standalone fallback as root, instance warning vs MCPP_CONFIG.
  • Not live-tested on a real deb host with systemd; Linux paths are simulated in tests.

Follow-up (not in this PR): the CLI's TCP fallback always uses http://, so a service with tls.enabled is only reachable via its socket (sudo -u mcpproxy …). Pre-existing for every CLI command.

Draft reply for discussion #1678 (not posted)

Thanks for the report, and sorry for the confusion. The two configs are intentional: the apt package runs the service as the mcpproxy user with /etc/mcpproxy and /var/lib/mcpproxy, while a plain mcpproxy command uses your own ~/.mcpproxy. The CLI gave no hint of that and even created ~/.mcpproxy/mcp_config.json, which made it look like the wrong config was in use.

The next release adds a --system flag that points any command at the service:

sudo mcpproxy --system upstream list
sudo mcpproxy --system status

To drop sudo, add yourself to the group once (sudo usermod -aG mcpproxy $USER, then log in again; if /etc/mcpproxy/mcp_config.json shows -rw-------, also sudo chmod 0640 it). On a packaged install the CLI also stops creating ~/.mcpproxy/mcp_config.json; it tells you to use --system instead. status and doctor warn when the service is running and you are looking at a different instance.

A user-level systemd unit is a fine setup too. If you go back to the packaged service, --system should cover what the long sudo -u mcpproxy … MCPPROXY_TRAY_ENDPOINT=… command did. Docs: https://docs.mcpproxy.app/getting-started/installation/#manage-the-packaged-service-from-the-cli

🤖 Generated with Claude Code

Dumbris and others added 5 commits October 29, 2026 11:43
On a deb/rpm install the service runs with /etc/mcpproxy + /var/lib/mcpproxy
as the mcpproxy user, but a plain CLI command silently created
~/.mcpproxy/mcp_config.json and reported an empty, unrelated instance
(discussion #1678).

- Linux-only detection of the packaged layout (service config + shipped
  systemd unit; an unreadable /etc/mcpproxy still counts). With no --config,
  --data-dir or user config, management commands no longer create a home
  config: they exit non-zero naming the service config and the exact
  `sudo mcpproxy --system ...` command. serve, macOS and Windows unchanged.
- Global --system flag: shorthand for the service's --config/--data-dir.
  The service socket refuses every uid but its own (root included), so in
  --system mode the socket is used only when it answers; otherwise the CLI
  uses HTTP with the config's listen + api_key (works with
  enable_socket:false). Unreadable config -> error naming sudo and the
  mcpproxy group. Refused with --config/--data-dir and for serve.
- doctor and status warn on stderr when mcpproxy.service is active and the
  CLI reads a different config, with the --system command to run.
- status reports and resets the key in the config file actually loaded
  (it used <data-dir>/mcp_config.json even under --config).
- Docs: "Manage the packaged service from the CLI" + --system in the CLI
  references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ONFIG

Cross-review round 1 (opencode gpt-6.1-sol):

- SaveConfig rewrote every config 0600. On a packaged install the service
  does that on first start (persisting its generated API key), which locked
  the mcpproxy group out of /etc/mcpproxy/mcp_config.json, and a save under
  `sudo mcpproxy --system ...` left a root-owned 0600 file the service could
  not read. A file that already grants group read and nothing else (0640)
  now keeps that mode; everything else is still written 0600.
  postinstall restores g+r on the shipped config for upgraded hosts.
- The packaged-install guard ignored MCPP_CONFIG, which config.Load honors.
- search-servers now reads the config chosen by -c/--config/--system
  (it always used defaults).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cross-review round 2 (opencode gpt-6.1-sol):

- A rename-based save creates a new inode whose group comes from the
  directory (setgid/BSD) or the process, not from the replaced file, so
  keeping 0640 could open a config to a group nobody chose. The temp file's
  group is now checked before the rename; on a mismatch the save writes
  0600 as before. Regular files only.
- Drop the postinstall chmod/chgrp repair: /etc/mcpproxy is group-writable,
  so a root-run repair would follow a planted symlink. Docs and the
  --system permission error give the one-time `sudo chmod 0640` instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…check

Cross-review round 3: with exactMode the temp file was created with the
final mode (0640) and written before its group was checked, so a member of
an unintended group could open it in that window. Create it owner-only and
apply the final mode with the post-check chmod.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstance warning

Cross-review round 4:

- --system now refuses to run unless the service answers (socket or HTTP).
  The CLI's standalone/offline fallbacks would otherwise run as the caller:
  launching the service's stdio upstreams as root, or writing root-owned
  config/database files the service cannot open (e.g. db compact). The
  error names systemctl start and the sudo -u mcpproxy form for offline
  maintenance or an HTTPS (tls.enabled) listener.
- The different-instance warning reads MCPP_CONFIG like the loader does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: ea8cdcc
Status: ✅  Deploy successful!
Preview URL: https://28827191.mcpproxy-docs.pages.dev
Branch Preview URL: https://fix-linux-packaged-cli-syste.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 79.42857% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/mcpproxy/packaged_install.go 86.17% 14 Missing and 3 partials ⚠️
cmd/mcpproxy/doctor_cmd.go 0.00% 5 Missing ⚠️
cmd/mcpproxy/status_cmd.go 0.00% 5 Missing ⚠️
cmd/mcpproxy/main.go 0.00% 3 Missing and 1 partial ⚠️
internal/config/loader.go 82.35% 1 Missing and 2 partials ⚠️
internal/config/file_group_unix.go 71.42% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

os.UserHomeDir reads USERPROFILE on Windows, so tests that only set HOME
resolved the runner's real home and failed on the Windows build jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📦 Build Artifacts

Workflow Run: View Run
Branch: fix/linux-packaged-cli-system-flag

Available Artifacts

  • archive-darwin-amd64 (32 MB)
  • archive-darwin-arm64 (29 MB)
  • archive-linux-amd64 (20 MB)
  • archive-linux-arm64 (18 MB)
  • archive-windows-amd64 (32 MB)
  • archive-windows-arm64 (28 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (28 MB)
  • installer-dmg-darwin-arm64 (25 MB)
  • smart-mcp-proxymcpproxy-goITR41P.dockerbuild (0 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 41039890419 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris
Dumbris merged commit 7f6772e into main Oct 30, 2026
59 of 60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants