feat(web): surface connect backup path in wizard and Connect modal (Spec 078 US2)#799
Merged
Conversation
…ct modal First slice of specs/078-connect-trust-preview (backup visibility, spec US2 / FR-005+FR-006): the backend has always written a timestamped backup before modifying a client config and returned it as ConnectResult.backup_path (internal/connect/backup.go, connect.go), and the CLI prints it — but the Web UI dropped it. Now: - ConnectModal.vue: after a successful connect OR disconnect, the result area shows "A backup of your previous config was saved to <path>" with a one-click "Copy path" button (same navigator.clipboard pattern as the existing copy-tccutil affordance). A success with no backup_path renders the explicit "No prior config file existed, so no backup was needed." message instead of a blank path (FR-006 no-prior-file case, e.g. bridge clients whose config is created by connect). - OnboardingWizard.vue: ClientRow now renders the same backup line (path + Copy path, or the no-prior-file message) inside the client's row after a connect performed in the wizard session. TDD: added 3 failing ConnectModal cases (backup path + copy, no-prior-file, disconnect backup) to tests/unit/connect-modal.spec.ts and a new tests/unit/onboarding-wizard-backup-path.spec.ts (2 cases) before implementing; full frontend suite passes (32 files, 235 tests). Built via make build so the Go binary embeds the updated web/frontend/dist. Assumptions (zero-interruption policy): - The TS contract already carries backup_path?: string on ConnectResult (frontend/src/types/api.ts) — no contract change needed. - An empty-string backup_path on success is treated the same as absent (backend omits it when no prior file existed; never render a blank path). - Preview/undo/TCC copy (spec US1/US3/US4) intentionally NOT built here. - No tasks.md exists under specs/078-connect-trust-preview/ — nothing to check off; spec.md left untouched. - npm-ci metadata churn in frontend/package-lock.json was reverted, not committed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent Connect All backups
Codex review of the Spec 078 US2 slice found two majors and a minor; all
three verified against the code and fixed:
1. OnboardingWizard ClientRow rendered 'Not installed' for EVERY
exists=false client, including bridge clients (Claude Desktop), which
the backend explicitly marks connectable without a config file
(internal/connect/connect.go:48; connect creates the file, returning an
empty backup_path — connect_test.go:253). That made the wizard's
"no prior file to back up" branch (FR-006 / US2 independent test)
unreachable. ClientRow now gates on !exists && !bridge, matching
ConnectModal's connectableClients predicate; non-bridge missing clients
still show 'Not installed'.
2. ConnectModal connectAll() let each connect() overwrite
resultBackupPath, so a bulk run over N modified configs displayed only
the Nth backup (SC-005 requires 100%). connectAll now accumulates a
per-client bulkBackups list ({name, path|no-prior-file} per successful
connect, each with its own Copy affordance) and suppresses the single-
result line for bulk runs. The loop also iterates a snapshot of
connectableClients — connect() refetches the listing mid-loop, which
mutated the computed while it was being iterated. Single connects
(connectSingle) and disconnects clear the bulk list.
3. Minor: the wizard's open watcher reset connectMessage but not
connectBackups/copiedBackupClient, so backup rows from a previous
wizard session replayed on reopen. Both are now session-scoped.
TDD: 4 new failing cases first — wizard bridge-no-config connect,
wizard non-bridge stays 'Not installed', wizard reopen clears backup rows,
ConnectModal Connect All shows all three per-client outcomes (two paths +
one no-prior-file) with per-row copy. Full frontend suite: 32 files,
239 tests pass; vue-tsc clean; make build rebuilds the embedded dist;
golangci-lint v2 + go test -race ./internal/connect/... clean (no Go
changes in this commit).
npm-ci metadata churn in frontend/package-lock.json reverted again, not
committed (same as the base commit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying mcpproxy-docs with
|
| Latest commit: |
543cb98
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e471c054.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://feat-078-backup-visibility.mcpproxy-docs.pages.dev |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 28586067827 --repo smart-mcp-proxy/mcpproxy-go
|
Dumbris
added a commit
that referenced
this pull request
Jul 2, 2026
…t modal
Second slice of specs/078-connect-trust-preview US1 (FR-001,003,004): the
Connect action in BOTH the standalone Connect modal and the onboarding
wizard's ClientRow now fetches the preview first and shows an inline panel
before any file is written:
- target config path and, in a monospace additive ("+ will be added")
block, the exact entry that will be written (pretty JSON, or TOML for
Codex) with the API key masked;
- plain-language trust copy ("Only this entry is added — everything else in
the file stays untouched. A timestamped backup is created first.");
- a masked-key line when contains_api_key so the user knows a credential is
written;
- an overwrite warning when an entry with the same name already exists, in
which case confirming implies force=true;
- graceful copy for the bridge/no-prior-file (access_state=absent) and
unparseable (malformed) cases.
Confirm proceeds with the connect (reusing the existing #799 post-connect
backup-path line, untouched); Cancel dismisses the panel and writes nothing.
A non-denial preview fetch error is surfaced inline; in the modal a denied
read still resolves the Spec 075 access banner via checkAccess. Wizard
connect-step telemetry (markConnectCompleted/Skipped) is unchanged.
- types/api.ts: ConnectPreview; services/api.ts: getConnectPreview
- ConnectModal.vue + OnboardingWizard.vue ClientRow: preview panel + flow
- The existing #799 connect specs are updated to drive click → preview →
confirm (Connect no longer writes on the first click); new
connect-preview.spec.ts covers preview render, cancel-writes-nothing,
confirm-proceeds, masked key, and overwrite→force for both surfaces.
Full frontend suite: 33 files, 244 tests pass; vue-tsc clean; make build
embeds the updated dist. npm-ci lockfile churn reverted (as in #799).
Related #793
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dumbris
added a commit
that referenced
this pull request
Jul 2, 2026
… the API key into client configs (Spec 078 US1) (#802) * feat(connect): preview endpoint sharing buildServerEntry with the write path First slice of specs/078-connect-trust-preview US1 (see spec FR-001..004, 012, 013). Adds GET /api/v1/connect/{client}/preview returning the exact change a subsequent connect would make — target config_path, server_key, entry name, and the exact entry contents — WITHOUT modifying the file or creating a backup. Preview==write guarantee: buildServerEntry is now the single construction path for every client, including Codex/TOML (connectTOML previously built its {url} entry inline). Preview derives its entry from the same function the write uses, so what is previewed equals what is written (FR-002); a table-driven test connects each supported client and asserts the written file entry deep-equals the shared constructor output across JSON and TOML. API-key honesty (FR-004): the embedded apikey is masked in the payload (entry, entry_text) while contains_api_key flags that a credential is written; the real key never appears in the preview (verified by test that marshals the whole payload and asserts the secret is absent). Spec 075 access states (FR-012): the on-demand read used to classify create-vs-overwrite degrades to access_state=absent|malformed and a denial returns the same typed AccessError (403 + remediation) as connect/disconnect. - internal/connect/preview.go: Service.Preview, maskURLAPIKey, entry render - internal/connect/clients.go: buildServerEntry gains the codex case - internal/connect/connect.go: connectTOML uses buildServerEntry - internal/httpapi: handler + route + tests (masked, no side effects, 403) - oas: regenerated for the new endpoint (make swagger) Tests: preview-equals-write (JSON+TOML), masking, entry_exists (create vs overwrite), no-side-effects (no write, no backup, mtime unchanged), absent/ malformed/denied degradation. go test -race ./internal/connect/... and ./internal/httpapi/... pass; golangci-lint v2 clean. Related #793 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): preview the exact change before writing in wizard + Connect modal Second slice of specs/078-connect-trust-preview US1 (FR-001,003,004): the Connect action in BOTH the standalone Connect modal and the onboarding wizard's ClientRow now fetches the preview first and shows an inline panel before any file is written: - target config path and, in a monospace additive ("+ will be added") block, the exact entry that will be written (pretty JSON, or TOML for Codex) with the API key masked; - plain-language trust copy ("Only this entry is added — everything else in the file stays untouched. A timestamped backup is created first."); - a masked-key line when contains_api_key so the user knows a credential is written; - an overwrite warning when an entry with the same name already exists, in which case confirming implies force=true; - graceful copy for the bridge/no-prior-file (access_state=absent) and unparseable (malformed) cases. Confirm proceeds with the connect (reusing the existing #799 post-connect backup-path line, untouched); Cancel dismisses the panel and writes nothing. A non-denial preview fetch error is surfaced inline; in the modal a denied read still resolves the Spec 075 access banner via checkAccess. Wizard connect-step telemetry (markConnectCompleted/Skipped) is unchanged. - types/api.ts: ConnectPreview; services/api.ts: getConnectPreview - ConnectModal.vue + OnboardingWizard.vue ClientRow: preview panel + flow - The existing #799 connect specs are updated to drive click → preview → confirm (Connect no longer writes on the first click); new connect-preview.spec.ts covers preview render, cancel-writes-nothing, confirm-proceeds, masked key, and overwrite→force for both surfaces. Full frontend suite: 33 files, 244 tests pass; vue-tsc clean; make build embeds the updated dist. npm-ci lockfile churn reverted (as in #799). Related #793 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(connect): stop leaking the API key into client configs; auth-aware carrier Security fix folded into Spec 078. Connect (and the new preview) previously appended ?apikey=<REST-admin key> to EVERY client entry via mcpURL(), but /mcp does not require auth by default (config.RequireMCPAuth defaults false; the admin context is granted unauthenticated). So connect leaked the REST-admin API key in plaintext into other apps' config files where it served no purpose. New behavior, threaded from config.RequireMCPAuth into connect.Service the same way listenAddr/apiKey are (server wiring + both CLI sites), via WithRequireMCPAuth: - require_mcp_auth=false (default): the entry carries NO credential — a clean http://host:port/mcp URL, no query, no headers. Existing configs keep working because /mcp accepts unauthenticated requests when protection is off. - require_mcp_auth=true: the credential is written via the carrier each client's real config schema supports (verified against vendor docs): * X-API-Key header — claude-code, vscode, cursor, windsurf, gemini, opencode (all support a "headers" object on their remote entry); * mcp-remote --header "X-API-Key:<key>" bridge arg — claude-desktop (no space after the colon: Claude Desktop/Cursor don't escape spaces in args and mangle the header — geelen/mcp-remote README; our keys are space-free); * ?apikey= query fallback — codex only, whose TOML HTTP transport takes header values indirectly via env-var names (http_headers / bearer_token_env_var) and cannot express a literal header value. Server-side ExtractToken accepts X-API-Key, Authorization: Bearer, and ?apikey= (in that order), so header and query authenticate identically. buildServerEntry now takes serverEntryParams{baseURL, credential}; mcpURL() is replaced by the credential-free baseURL() anchor. Entry MATCHING (findEntry*/findEquivalentJSONServerName/entryPointsToBridge) already keys on baseURL with an optional ?query, so it recognizes BOTH legacy ?apikey= entries and new clean entries — reconnect adopts/updates in place (no duplicate) and disconnect still finds legacy entries; a force-reconnect over a legacy entry upgrades it and drops the leaked key. Preview reflects reality: contains_api_key is true only when require_mcp_auth is on; the credential is masked in whichever carrier it uses (header value, bridge arg, or query) and the real key never appears in the preview payload. Tests: matrix over require_mcp_auth on/off × all 8 clients for both write and preview (preview==write shape, auth-off writes contain NO apikey/headers at all); per-client carrier assertions; legacy ?apikey= migration (recognized, upgraded in place, no duplicate, disconnect still works). go test -race ./internal/connect/... ./internal/httpapi/... pass; server/config/management pass; golangci-lint v2 clean. Related #793 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): carrier-agnostic credential copy in the connect preview Follows the connect security fix: the preview panel's masked-credential line now reads "This entry includes your API key (shown masked)…" instead of "The URL includes…", since with require_mcp_auth on the credential is carried in an X-API-Key header (or the mcp-remote --header bridge arg) for most clients and only falls back to the ?apikey= URL query for Codex. The line already renders only when contains_api_key is true, which the backend now sets only when require_mcp_auth is on — so the default (keyless) connect shows no credential line at all. Adds a frontend test for the default keyless case: contains_api_key=false ⇒ no credential line, and the previewed entry shows neither an apikey query nor a mask. Full frontend suite: 33 files, 245 tests pass; vue-tsc clean. Related #793 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(connect): live-config carrier + honest preview for edge cases Codex review of the Spec 078 US1 connect-preview branch surfaced one blocker and three preview/write-divergence gaps; this addresses them: - blocker: the HTTP connect service snapshotted require_mcp_auth (and listen/api_key) once at startup and was never rebuilt, while the /mcp auth middleware honors require_mcp_auth live per-request. Toggling auth off at runtime (wizard toggle or hot-reload) left the service embedding the real API key into client configs — the exact leak this branch closes. Add a live config-provider seam (WithConfigProvider) wired to runtime.Config() so preview and write always match the currently-enforced auth state. - OpenCode preview divergence: previewEntryExists checked only the exact server name, but OpenCode's write path adopts/normalizes an equivalent entry (incl. the legacy ?apikey= shape) under a different key. Preview now mirrors that via findEquivalentJSONServerName so entry_exists reflects the overwrite. - preview endpoint ignored server_name and always previewed "mcpproxy" while POST connect accepts a custom name. Honor an optional ?server_name= query so a caller previewing then connecting under a custom name does not diverge. - malformed config: the write parses the same bytes and would fail, but the preview copy promised it "still writes only the entry" and left Connect enabled. Make the copy honest and disable Connect for malformed configs. Tests: live-toggle credential (auth off->on without rebuild), OpenCode equivalent-entry preview, server_name honoring, and malformed confirm-disabled. go test -race ./internal/connect/... ./internal/httpapi/... green; frontend 246 tests + vue-tsc clean; golangci-lint v2 ./... 0 issues; swagger regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): make the first connect click explicitly commitment-free The preview only appeared AFTER clicking "Connect" — the exact button hesitant users avoid, fearing the click immediately modifies their config. Rename the row action to "Review & connect" and say up front in the modal subtitle and wizard intro that nothing is written until the confirm step, so the safety of the first click is visible before it happens (Spec 078 US1 trust copy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(web): align setup.md and test prose with the Review & connect flow Codex review follow-up: docs/setup.md described the Claude Desktop bridge registration as one-click "Connect"; it is now review-then- confirm. Test comments updated to match the renamed row action. Spec 046/075 texts intentionally left untouched — they are point-in- time records (046 FR-005 already required the diff preview this branch implements). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Dumbris
added a commit
that referenced
this pull request
Jul 2, 2026
…ne, connect-trust/upgrade-nudge progress (#803) check-github now passes with 0 errors: scanner-simplification epic complete (#786/#792/#793/#794 incl. deep-scan trust fixes + docs sweep); connect-trust US1 preview (#802) + backup visibility (#799) done; upgrade-nudge status/log slice (#798) split out as done with the banner+config remainder tracked separately; telemetry machine_id client (#796) and hygiene check-github (#800) done. Remaining warnings are the known windows-tray no-PR-evidence items. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Part of Spec 078 (Connect Trust Preview) — this PR implements the backup-visibility slice (User Story 2: "Always-created backup is visible and understood", FR-005/FR-006). The backend already creates a timestamped backup before modifying a client config and returns it as
ConnectResult.backup_path; the Web UI silently dropped it. This slice surfaces it.This is a deliberate FIRST SLICE. It does not include:
backup_pathwas already in the API contract (frontend/src/types/api.tsneeded no change)Implementation
ConnectModal.vue: after a successful connect or disconnect, the result area shows "A backup of your previous config was saved to<path>" with a one-click Copy path button (reuses the existingnavigator.clipboardpattern). A success with nobackup_pathrenders an explicit "No prior config file existed, so no backup was needed." instead of nothing.OnboardingWizard.vue: the client row renders the same backup line (path + Copy path, or the no-prior-file message) after a connect performed in the wizard session — including per-client results from Connect All and connects where no config file existed. State tracked per-client inconnectBackups; newbackupPath/copiedBackupprops +copy-backupemit on the functionalClientRow.data-test="connect-backup-path" / "connect-no-backup" / "connect-copy-backup"(modal);"client-backup-<id>" / "client-copy-backup-<id>"(wizard).frontend/tests/unit/connect-modal.spec.ts,frontend/tests/unit/onboarding-wizard-backup-path.spec.ts.Codex review
Reviewed by codex; findings applied in
543cb98c:connectBackups) — fixed.No findings rebutted.
Verification
All steps run in the worktree at HEAD
543cb98c(on top of3b339516):make buildexit 0 — frontend built and embedded,go buildOK (v0.47.0-rc.1)./readyzREADY):POST /api/v1/connect/claude-codewith a pre-existing~/.claude.json→ success with a real timestampedbackup_path; connect with no prior file → success withoutbackup_path(drives the explicit no-backup message).Part of specs/078-connect-trust-preview
🤖 Generated with Claude Code