feat(mcp,ui): add per-client global install status and update-safe MCP launcher
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
This change adds per-client global install status for Claude Code and Copilot in Settings, letting users see, reinstall, or remove each client entry, and switches all MCP configuration and command-line entrypoints to use an update-safe gitdesktop-mcp launcher. The new launcher solves update-file-lock and upgrade-kill issues on Windows by running MCP from a managed copy outside the install directory, ensuring updates are always safe and live status is never stale.
src-tauri/src/mcp_launcher.rs providing a managed gitdesktop-mcp launcher copy, with atomic copy & version tracking, only on Windows release builds (env override for dev/tests).src-tauri/src/lib.rs to refresh the managed launcher after each app update.gitdesktop-mcp (not gitdesktop), switching config emission logic in all clusters.src-tauri/src/mcp.rs:mcp_global_status and mcp_global_remove Tauri commands to enumerate/read and remove per-client user-config MCP installs via the relevant CLI.src/features/settings/mcp/GitDesktopAsServer.tsx, src/lib/git/api.ts):README.md, src/features/help/content.ts) to refer to the new MCP launcher name, global install management flow, and launcher path expectations.src-tauri/src/path_launcher.rs to operate on gitdesktop-mcp consistently:gitdesktop-mcp into ~/.local/bin (auto-migrates previous gitdesktop symlinks).src/lib/git/api.ts, src/features/settings/mcp/GitDesktopAsServer.tsx): new launcher path accessors and copy/install flows, migrated everywhere relevant.changelog.d/added-mcp-global-install-status.md covering the new global install status view and management.changelog.d/fixed-mcp-installer-file-lock.md documenting the fix for file locks and update safety.src-tauri/src/fsops.rs (the previous app_exe_path).src-tauri/src/local_prs.rs as needed for cross-module coordination.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
970314eView logs
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
sonnet· automatedThis PR replaces the direct-app-binary MCP invocation with a managed
gitdesktop-mcpcopy on Windows, adds per-client global install status rows (Claude Code / Copilot) with Reinstall/Remove controls, and migrates the PATH launcher to point at the new copy. The architecture is sound — the marker-based staleness check, atomic copy recipe, and zero-write status probe are all well-designed. A few real issues below; nothing is a hard blocker, but finding [#1] will silently break the documented Linux test path.Correctness
should-fix —
mcp_launcher.rs,copy_into_place:std::fs::copyon Linux does not preserve file permissions (mode bits). On macOS it does (viacopyfile); on Linux the destination is created with umask-restricted bits (typically0o644), so the managed copy is not executable. Management is inactive on Linux in production (Windows-only), but theGD_MCP_LAUNCHER_DIRenv override — which the module's own doc calls "how dev/live validation exercises the machinery" — makes management active on any platform. A developer validating this on Linux would get a copy thatensure()reports as fresh,mcp_launcher_pathreturns as a valid path, butgitdesktop-mcp mcp …fails withEACCES.Fix: set the execute bit after the temp-copy step, before the rename, on Unix:
should-fix —
mcp.rs,norm_launcher_path: the function unconditionally applies.to_ascii_lowercase()and replaces/with\\, makingclassify_global_entryperform a case-insensitive path comparison on every platform. On macOS (case-insensitive FS) this is correct. On Linux (case-sensitive FS), two genuinely distinct paths like/home/Alice/bin/gitdesktop-mcpand/home/alice/bin/gitdesktop-mcpwould incorrectly compare equal, producing a falsecurrent: true. Low probability in practice (the app writes the exactcurrent_exe()path), but becomes relevant for any manually-authored config or multi-user scenario.Fix: guard the lowercase behind the same platforms that actually have case-insensitive paths:
Nits
nit —
mcp.rs,mcp_global_remove:_app: tauri::AppHandleis declared in the Tauri command signature and prefixed with_to suppress the unused warning, but it genuinely isn't used. Tauri only injectsAppHandlewhen the handler declares it; dropping it shrinks the noise.nit —
GitDesktopAsServer.tsx,globalRow:key={client}on the outermost<div>returned from a plain function call has no effect — Reactkeyis only meaningful when elements are part of an array (.map()). SinceglobalRow("claude")andglobalRow("copilot")are rendered as two distinct JSX children, not an array, React silently ignores the prop. Remove it.Posted by GitDesktop — AI output, verify before acting on it.
Related
Tickets:
#1Originally posted by: theBGuy
🤖 GitDesktop AI security audit ·
sonnet· automatedNo security issues found in these changes.
The key risk surfaces were all guarded:
mcp_global_remove'sclientparameter is validated against an explicit allowlist ("claude"|"copilot") before dispatching;run_client_cliusesCommand::new()+.args()(never a shell) so the frontend-suppliedcommand/argsinmcp_global_installcannot cause command injection; all file-operation paths inmcp_launcher.rsderive exclusively from the bundle identifier anddirs::data_local_dir()(no user-controlled input); the Windows registry PATH write uses the same app-controlled path;read_global_configreads only two hardcoded home-relative paths with a 16 MB size guard; and thecommandfield from~/.claude.jsonsurfaced bymcp_global_statusis used only for a boolean path-normalization comparison server-side — it is never rendered into the DOM (the UI shows only theinstalled/currentbooleans), so React's default escaping is not even needed.Copilot's four findings are all documentation/copy inconsistencies (stale command names, comment inaccuracy, outdated UI description) — none introduce exploitable vulnerabilities.
The prior reviewer's Linux-
chmodfinding (non-executable managed copy underGD_MCP_LAUNCHER_DIR) is a correctness issue that's still present incopy_into_place, but it has no security impact: the worst case isEACCESon exec (a reliability failure, not a vulnerability), and it only affects the dev-override path on Linux.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
sonnet· automatedAll four previous-review nits and the two Copilot-flagged copy/comment issues are resolved in this round. One genuine should-fix from the first review is still present.
Resolved since last review
norm_launcher_pathLinux case-sensitivity — the#[cfg(any(windows, target_os = "macos"))]gate is in the diff and the new platform-split tests confirm it.mcp_global_removeunused_app— signature is nowclient: Stringonly.key={client}on non-array JSX — removed from<div className="space-y-1">."gitdesktop"→"gitdesktop-mcp"— fixed inpath_launcher.rs:312.GitDesktopAsServer.tsx:87.gitdesktop mcp ...command — removed; replaced with a platform-split description that avoids the old invocation form entirely.Correctness
should-fix —
mcp_launcher.rs,copy_into_place: the Linux execute-bit problem from the previous review is still unaddressed. The "changes since last review" diff contains no edits tomcp_launcher.rs.std::fs::copyon Linux does not preserve mode bits; the destination is created with umask-restricted permissions (0o644) and is therefore not executable. TheGD_MCP_LAUNCHER_DIRenv override explicitly enables the managed-copy path on any OS, so a developer live-validating this on Linux gets a copy thatensure()accepts as fresh but thatexecrejects withEACCES.Fix (after the temp-file write, before
fs::rename):Nits
nit —
src/lib/git/api.ts,mcpGlobalStatusJSDoc: the new comment says"via each CLI"— that described the earlier CLI-spawn design. The currentmcp_global_statusbackend reads each client's config file directly (no process spawn). The TSX comment was corrected; theapi.tsJSDoc line wasn't. Change"via each CLI"to"by reading each client's config file directly"for accuracy.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
sonnet· automatedThe diff addresses all four findings from the previous round (Linux case-fold guard in
norm_launcher_path, dropped_appfrommcp_global_remove, removed no-opkeyprop, and thegitdesktop→gitdesktop-mcpnote text). One prior should-fix is still open; one new nit surfaced.Resolved since last review
norm_launcher_pathLinux case-sensitivity —#[cfg(any(windows, target_os = "macos"))]guard is present and the test is correctly split._app: tauri::AppHandleunused inmcp_global_remove— parameter dropped cleanly.key={client}on non-array<div>— removed from theglobalRowreturn.useQuery, help content, "one-click button" wording).Correctness
should-fix —
mcp_launcher.rs,copy_into_place(unaddressed from prior review):std::fs::copyon Linux creates the destination with umask-restricted bits (typically0o644), so the managed copy is not executable. The "changes since last review" diff doesn't touchmcp_launcher.rs, meaning the fix suggested in the previous round was never applied. In production this only matters under theGD_MCP_LAUNCHER_DIRoverride (management is Windows-only at runtime), but that override is the documented way to exercise the copy machinery on Linux/macOS dev machines — a developer following the module's own instructions will silently get anEACCESwhen the CLI tries to run the copy.Fix (after the temp-write, before
rename):Nit
nit —
src/lib/git/api.ts,mcpGlobalStatusdoc comment: the JSDoc still says "via each CLI" (/** Read-only probe … via each CLI. …*/), but the implementation (and the updatedGitDesktopAsServer.tsxcomment) now correctly describes it as a direct config-file read — no CLI spawn. Low stakes, but misleads the next developer debugging a spawn timeout.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
sonnet· automatedThe PR adds per-client (Claude Code / Copilot) global install status rows with Install/Reinstall/Remove controls, and replaces the direct-binary MCP invocation with a managed
gitdesktop-mcpcopy on Windows. The architecture is sound and the code is well-structured.Resolved since last review
mcp_launcher.rscopy_into_place— Linux execute-bit finding — Closed, and this finding was never valid. The previous rounds incorrectly claimedstd::fs::copyon Linux creates the destination with umask-restricted bits. Rust's stdlib docs and implementation are explicit:fs::copycopies the source's permission bits on all Unix platforms (it callsset_permissionson the destination after the data copy). The new comment added in this round states this accurately. There is nothing to fix here, and there never was.api.tsmcpGlobalStatusJSDoc — corrected from "via each CLI" to "by reading each client's config file directly (no CLI spawn)". Accurate.Correctness
nit —
path_launcher.rs,path_launcher_install,_versionon non-Windows: the underscore-prefix suppresses the "unused variable" lint, which is correct, but the way the cfg blocks are structured means_versionis allocated and.to_string()-copied even on macOS/Linux whereinstall_impl()never sees it. This is harmless (it's just a string alloc), but restructuring with#[cfg(windows)] let version = …would be cleaner and self-documenting.The overall implementation — marker-based staleness check, same-volume atomic rename, zero-write status probe via
resolved_launcher_path(), and UI gating onlauncherDisabledReasonbefore any path-embedding emission — is all well-designed. No new real issues.Posted by GitDesktop — AI output, verify before acting on it.
Ticket changed by: theBGuy