feat(040): Add/Edit Server UX improvements for macOS tray app#359
Merged
Conversation
Backend:
- New `internal/connect/` package: register MCPProxy in AI client configs
(Claude Code, Cursor, VS Code, Windsurf, Codex, Gemini) with backup
- REST API: GET/POST/DELETE /api/v1/connect/{client}
- CLI: `mcpproxy connect <client>`, `mcpproxy connect --list`, `mcpproxy disconnect`
- 32 unit tests covering all client formats (JSON + TOML)
Web Dashboard:
- Hub visualization: MCPProxy logo center, AI Agents left, Upstream Servers right
- Fat green connection lines with animated dots (20s interval)
- Connect modal with per-client connect/disconnect
- Security status: Docker isolation + quarantine protection badges
- Dynamic token savings from API, Activity Log link
- Fix: isServerConnected uses actual connected field, not health level
- Fix: "Connecting" servers no longer trigger attention banner (healthy, not degraded)
macOS App:
- Dashboard rewritten to match web UI hub layout
- Connect Clients sheet with live API data
- Real MCP sessions from /api/v1/sessions (deduplicated by client)
- Docker/quarantine status fetched from API
- Tab switching: Import, Activity Log, Add Server all navigate correctly
- App icon used as logo instead of SF Symbol shield
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security: - fix(quarantine): new tools from trusted servers now require approval instead of auto-approve. Prevents injection via new tool additions on compromised servers. Only skip_quarantine=true opts out. Rug pull detection for changed tools unchanged. Added test. macOS tray app: - fix(tray): fetch API key from core via socket /api/v1/info instead of generating ephemeral key. Config file key and runtime key now match. - fix(tray): secrets delete button now shows confirmation dialog - fix(tray): token revoke button now shows confirmation dialog - feat(tray): add Set/Update secret value sheet for missing keyring secrets - fix(tray): Settings scene shows placeholder instead of blank EmptyView - fix(tray): Activity Log Details column min-width prevents single-char wrap - fix(tray): reduced fixed column widths for better table proportions Web UI: - fix(frontend): remove CSS transition deadlock that blocked SPA navigation - fix(frontend): add scrollBehavior to reset scroll on route change QA report: docs/qa/mcpproxy-qa-report-2026-03-29.html (121 tests, 92.6% pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… config, validation
## Backend (Go)
- Add PATCH /api/v1/servers/{name} for partial server config updates
- Fix command/args/url/working_dir missing from /api/v1/servers response
- Fallback to storage when stateview Config is nil
- UpdateServer on ServerController interface + implementation
## macOS Tray App (Swift)
- AddServerView: sheet 560x560, pinned submit, simplified protocols, inline validation
- AddServerView: phased connection test with Save Anyway/Retry on failure
- ServerDetailView: editable Config tab with Edit/Save/Cancel + toggles
- ServerDetailView: log auto-refresh (3s), last-error banner, color-coded lines
- ServersView: tooltip on unhealthy status, empty state onboarding
- DashboardView: accessibility labels, removed placeholder
- MCPProxyApp: Cmd+N from main window
- APIClient: updateServer() PATCH method
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cmd+N now switches to Servers tab before opening Add Server sheet (sheet is hosted by ServersView, not Dashboard) - Fixed SwiftUI @State preservation bug: replaced onAppear tab init with explicit State(initialValue:) + .id() force recreation - Manual tab correctly selected for Cmd+N, Import tab for Dashboard button Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eout When addServer fails (timeout or error), now: - Fetches last 10 server log lines, finds ERROR/failed lines - Shows the actual error from logs in the failure message - Multi-line error text with up to 8 lines, selectable - "Save Anyway" still available for timeout cases - Retry button resets phase before resubmitting - Helpful hint for timeout: "config may be saved, check command/URL" This gives users actionable feedback (e.g., "npx: command not found", "package @company/echo not found") instead of "The request timed out." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SocketURLProtocol has intermittent issues with POST requests over Unix sockets that cause 30s timeouts. GET requests work fine. Fix: When addServer() via socket times out, automatically retry via a TCP APIClient. Gets the API key from core via socket GET (which works), then POSTs via TCP with the API key. This transparent fallback means Add Server now succeeds even when the socket POST path is slow. Also increased error text lineLimit from 2 to 8 for better visibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
URLSession converts request.httpBody to request.httpBodyStream internally when passing through URLProtocol subclasses. The SocketURLProtocol was only checking request.httpBody (always nil for POST), causing the POST body to be empty. The Go server received a POST with no body, returned 400 "name required", but the empty-body request itself went through fine. The REAL issue: without Content-Length header (because body appeared empty), the server waited for the body, causing the 30s socket read timeout. Fix: In buildHTTPRequest(), check request.httpBodyStream when httpBody is nil. Read the full stream into Data before building the HTTP request. This is the standard pattern for URLProtocol subclasses handling POST. The TCP fallback is kept as defense-in-depth but should no longer trigger. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SocketTransport: - Read httpBodyStream when httpBody is nil (URLSession converts body to stream for URLProtocol subclasses) - root cause of POST timeouts - Robust stream reading with error handling and EOF detection - Content-Length header correction after stream read - NSLog debug statements for diagnosing POST body issues AddServerView: - Quarantined servers treated as success (dialog closes, not error) - SubmitPhase.success now carries quarantined flag - Orange shield icon + "quarantined for security review" message - TCP fallback kept as defense-in-depth for socket issues ServersView: - Default tab changed from .importConfig to .manual - Cmd+N now correctly opens Manual tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…default ON Backend: - AddServer() now grants 1-hour inspection exemption for quarantined servers, so they connect and discover tools for user review - Tools remain hidden from retrieve_tools (LLM) and blocked from call_tool — only visible via inspect endpoints for human review - Security model verified: quarantine + exemption = connect for inspection only, not for LLM access macOS tray app: - Docker Isolation toggle defaults to ON (was OFF) - Added help hints for all option toggles: - Enabled: "When disabled, server will not connect" - Docker Isolation: "Runs in isolated container, prevents filesystem access" - Quarantined: "Tools must be reviewed before AI agents can use them" - Options sent in API request: docker_isolation, quarantined, enabled - ServerDetailView edit mode also has toggle hints Verified: add quarantined server → connects → 13 tools discovered → tools hidden from LLM → tools visible for review → call_tool blocked Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace single 3s check with poll loop (3 attempts, 3s each) - "Connecting..." no longer shown as red error — keeps polling - Connected → success. Quarantined → success. Error → show actual error - Still connecting after 9s → treat as success (server list shows live status) - 409 "already exists" on retry → recognize previous save succeeded, skip to connection status check instead of showing error - Both socket and TCP fallback paths handle 409 gracefully Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user edits form fields after a failure (fixes name, command, args) and clicks Retry, the old server is now deleted first via DELETE API, then re-added with the current field values. This ensures: - Updated params are applied (not stuck with old values) - No 409 "already exists" error on retry - User can change server name, command, args between attempts - lastSavedServerName tracks what was saved for cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…vigation
1. approveTools sends {"approve_all":true} body (was empty → EOF error)
2. Duplicate server name shows error on first attempt (409 only treated
as success if lastSavedServerName matches, i.e. our previous save)
3. "Approve Server" button in ServerDetailView header when quarantined
— approves all tools + unquarantines in one click
4. Tray "Needs Attention" items open macOS app server detail view
instead of Web UI (added .showServerDetail notification)
5. Quarantine logic verified: new tools=pending, changed tools=blocked,
dynamic additions require approval (enforceNewTools)
E2E verified: add → connect → 13 tools → approve all → unquarantine →
connected + not quarantined + 13 tools + "Connected (13 tools)"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ionAction
The tray menu "Needs Attention" item handler called showMainWindow()
inside a Task{} block which runs on a cooperative thread pool. NSWindow
creation requires the main thread — calling it from background thread
caused SIGABRT (abort trap 6).
Fix: wrap showMainWindow() and notification posts in await MainActor.run{}
for both "approve" and "default" cases in handleAttentionAction.
Also fixed missing closing brace for Task{} block.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…button SocketTransport: - Race condition: stopLoading() and readResponse() both tried to close socketFD from different threads → EXC_GUARD crash when fd was reused - Fix: thread-safe closeSocket() with NSLock, atomically reads and clears socketFD so only one thread can close it ServerDetailView: - Added NSLog debug statements to loadTools() for diagnosing empty state - Added "Reload" button to empty tools state so users can manually retry - Tools tab still shows "2 tool(s) need approval" banner from server model Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ServerDetailView: - Server data is now @State (mutable) instead of let (immutable) - refreshServer() fetches updated server status from API after mutations - Approve Server button: approve tools + unquarantine + refresh → UI immediately shows the new state (quarantined badge disappears) - New "Quarantine" button visible when server is NOT quarantined, allowing users to re-quarantine a previously approved server - Enable/Disable/Restart actions also refresh the server state - Added Reload button on empty tools state for manual retry E2E verified: add → connect → approve → unquarantine (UI updates) → re-quarantine (UI updates) → cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deploying mcpproxy-docs with
|
| Latest commit: |
1172ef7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://cae1294a.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://040-server-ux.mcpproxy-docs.pages.dev |
Co-Authored-By: Claude Opus 4.6 (1M context) <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.
Summary
Test plan
Generated with Claude Code