Skip to content

feat: clipboard sharing and import for mcp configurations#89

Merged
doomspork merged 5 commits intomainfrom
doomspork/sharing-export-import
Feb 13, 2026
Merged

feat: clipboard sharing and import for mcp configurations#89
doomspork merged 5 commits intomainfrom
doomspork/sharing-export-import

Conversation

@doomspork
Copy link
Copy Markdown
Member

Summary

Add clipboard-based sharing and import capabilities for MCP server configurations with sensitive data redaction support.

Features:

  • Copy to clipboard: Export all MCP servers from any scope (project/global) as shareable .mcp.json-format JSON
  • Import from JSON: Paste JSON to import servers with conflict resolution (rename/overwrite/skip)
  • Sensitive data handling: Automatic redaction with <YOUR_KEY_NAME> placeholders (default) or raw export
  • Export .mcp.json: Generate .mcp.json files directly in projects
  • Global MCP management: Enable full CRUD + sharing for global MCP servers (previously read-only)

Architecture

graph TB
    subgraph Services["Services"]
        MCPSharingService["MCPSharingService<br/>(actor)"]
    end
    
    subgraph ViewModels["ViewModels"]
        PasteVM["MCPPasteViewModel<br/>(@MainActor)"]
    end
    
    subgraph Views["Views"]
        ProjectDetail["ProjectDetailView"]
        GlobalSettings["GlobalSettingsDetailView"]
        MCPServersTab["MCPServersTabView"]
        PasteSheet["MCPPasteSheet"]
    end
    
    MCPSharingService -->|serialize/parse| PasteVM
    MCPSharingService -->|write/read| ProjectDetail
    MCPSharingService -->|write/read| GlobalSettings
    ProjectDetail -->|callbacks| MCPServersTab
    GlobalSettings -->|callbacks| MCPServersTab
    PasteVM -->|UI state| PasteSheet
Loading

Test Coverage

  • MCPSharingServiceTests (22 tests): Serialization, redaction, parsing, sensitive data detection, bulk import
  • MCPPasteViewModelTests (7 tests): Initialization, state, destination loading, import flow
  • All 172 existing tests continue to pass
  • Zero build warnings, strict Swift 6 concurrency

Implementation Details

MCPSharingService (actor):

  • nonisolated func serializeToJSON() → MCPConfig JSON with optional redaction
  • func parseServersFromJSON() → Supports MCPConfig, flat dict, single named/unnamed formats
  • @MainActor func writeToClipboard()/readFromClipboard() → NSPasteboard operations
  • func detectSensitiveData() → Pattern-based detection for tokens, keys, secrets, etc.
  • func importServers() → Bulk import with conflict strategies (rename/overwrite/skip)

UI Changes:

  • Project view: "Copy All" button + "Import from JSON" (Cmd+Shift+V) + "Export .mcp.json" menu
  • Global settings: Full MCP CRUD + sharing (add/edit/delete/copy/copyAll/paste)
  • Sensitive data alert: Choose redaction or raw export before copying

Files Added (5):

  • MCPSharingService.swift (423 LOC) — Core service
  • MCPPasteViewModel.swift (158 LOC) — Paste/import flow
  • MCPPasteSheet.swift (300 LOC) — Import dialog UI
  • MCPSharingServiceTests.swift (362 LOC)
  • MCPPasteViewModelTests.swift (106 LOC)

Files Modified (5):

  • ConfigTabViews.swift — Added onCopyAll/onPasteServers callbacks
  • ProjectDetailView.swift — Wired sharing features, export, header menu
  • GlobalSettingsDetailView.swift — Full MCP management
  • AppCommands.swift — Cmd+Shift+V keyboard shortcut
  • FocusedValues.swiftpasteMCPServersAction focused value

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings February 6, 2026 20:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds clipboard-based sharing and JSON import for MCP server configurations (with optional sensitive-data redaction), and extends the UI to support “copy all”/paste/import flows in both project and global scopes.

Changes:

  • Introduces MCPSharingService for MCP server JSON serialization/parsing, sensitive-data detection/redaction, clipboard I/O, and bulk import with conflict strategies.
  • Adds paste/import UI flow (MCPPasteViewModel, MCPPasteSheet) and wires it into Project and Global Settings MCP server management.
  • Adds command + focused-value plumbing for “Import MCP Servers from JSON…” plus new test suites for the service and view model.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Fig/Sources/Services/MCPSharingService.swift New actor/service implementing JSON share/import, redaction, clipboard, bulk import
Fig/Sources/ViewModels/MCPPasteViewModel.swift New view model for paste/import state, parsing, and executing imports
Fig/Sources/Views/MCPPasteSheet.swift New sheet UI for pasting JSON, previewing servers, selecting destination/strategy, and importing
Fig/Sources/Views/ProjectDetailView.swift Wires project UI for copy-all, paste/import sheet, sensitive-data alert, and .mcp.json export
Fig/Sources/Views/GlobalSettingsDetailView.swift Enables global MCP CRUD + copy/paste/import flows and related UI state/sheets/alerts
Fig/Sources/Views/ConfigTabViews.swift Adds “Import from JSON” + “Copy All as JSON” toolbar actions to MCP servers tab
Fig/Sources/App/FocusedValues.swift Adds focused action for paste/import MCP servers
Fig/Sources/App/AppCommands.swift Adds Cmd+Shift+V command for importing MCP servers from JSON
Fig/Tests/MCPSharingServiceTests.swift New test coverage for serialization, parsing, redaction, sensitive detection, bulk import result formatting
Fig/Tests/MCPPasteViewModelTests.swift New test coverage for paste VM initial state, destinations, and parsing-derived state

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +225 to +228
switch strategy {
case .skip, .prompt:
skipped.append(name)
continue
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

importServers(..., strategy:) treats .prompt the same as .skip, which is inconsistent with ConflictStrategy.prompt (“Ask for each”) and can surprise callers. Either handle .prompt explicitly (e.g., throw/return a conflict result) or constrain this API to strategies that are actually supported for bulk import.

Copilot uses AI. Check for mistakes.
Comment on lines +188 to +191
let json = try serializeToJSON(servers: servers, redactSensitive: redactSensitive)
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(json, forType: .string)
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writeToClipboard ignores the return value of NSPasteboard.general.setString(...). If the pasteboard write fails, the UI will still report success. Consider checking the Bool return and throwing a dedicated error when the pasteboard can’t be updated.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +143
/// Detects sensitive environment variables and headers across multiple servers.
func detectSensitiveData(servers: [String: MCPServer]) -> [SensitiveEnvWarning] {
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detectSensitiveData returns [SensitiveEnvWarning] but it also reports HTTP header keys. The type name suggests env-only, which makes the API misleading for future callers. Consider introducing a more general warning type (or renaming) that covers both env vars and headers.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
// Allow async parsing to complete
try await Task.sleep(for: .milliseconds(100))

Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test relies on Task.sleep(100ms) to wait for async parsing, which can be flaky on slow/loaded CI runners. Prefer a deterministic approach (e.g., expose an async parseNow() API, inject a controllable scheduler, or poll/yield until parsedServers is set with a reasonable timeout).

Suggested change
// Allow async parsing to complete
try await Task.sleep(for: .milliseconds(100))
// Wait deterministically for async parsing to complete, with a timeout.
let timeout: UInt64 = 1_000_000_000 // 1 second in nanoseconds
let pollInterval: UInt64 = 10_000_000 // 10 ms in nanoseconds
var elapsed: UInt64 = 0
while vm.parsedServers == nil && elapsed < timeout {
try await Task.sleep(nanoseconds: pollInterval)
elapsed += pollInterval
}
#expect(vm.parsedServers != nil)

Copilot uses AI. Check for mistakes.
Comment on lines +167 to +168
"The MCP configuration contains environment variables that may contain "
+ "secrets (API keys, tokens, etc.). Choose how to copy."
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sensitive-data warning text only mentions environment variables, but the sensitive-data detection/redaction logic also treats HTTP headers (e.g., Authorization) as sensitive. Consider updating the alert copy to mention headers as well so users understand what triggered the warning.

Suggested change
"The MCP configuration contains environment variables that may contain "
+ "secrets (API keys, tokens, etc.). Choose how to copy."
"The MCP configuration contains environment variables or HTTP headers "
+ "(for example Authorization) that may contain secrets (API keys, tokens, etc.). "
+ "Choose how to copy."

Copilot uses AI. Check for mistakes.
}
} message: { _ in
Text(
"The MCP configuration contains environment variables that may contain "
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sensitive-data warning text only mentions environment variables, but the sensitive-data detection/redaction logic also flags HTTP headers. Consider updating the alert message to include headers so the warning matches the behavior.

Suggested change
"The MCP configuration contains environment variables that may contain "
"The MCP configuration contains environment variables and HTTP headers that may contain "

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +156
Task {
do {
parsedServers = try await sharingService.parseServersFromJSON(trimmed)
parseError = nil
} catch {
parsedServers = nil
parseError = error.localizedDescription
}
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseJSON() spawns a new Task on every jsonText change, but previous parse tasks are never cancelled/invalidated. This can lead to out-of-order completion where an older parse finishes after a newer edit and overwrites parsedServers/parseError with stale results. Consider keeping a Task reference (or a monotonically increasing parse token) and cancelling/ignoring prior work before applying results.

Copilot uses AI. Check for mistakes.
- New MCPSharingService actor for serialization, parsing, redaction, and bulk import of MCP servers
- MCPPasteViewModel and MCPPasteSheet for importing servers from pasted JSON
- Support for multiple JSON formats: MCPConfig, flat dict, single named/unnamed servers
- Sensitive data redaction with <YOUR_KEY_NAME> placeholders (default) and raw export option
- "Copy All" toolbar button in project and global MCP server tabs
- "Paste from Clipboard" / "Import from JSON" via new sheet dialog (Cmd+Shift+V)
- Export .mcp.json file option in project header More menu
- Full MCP management in global settings (previously read-only): add, edit, delete, copy, paste
- 172 tests passing across 49 suites with full test coverage
- Zero build warnings, strict Swift 6 concurrency compliance
Servers without env vars (stdio) or headers (http) showed a useless
expand chevron that only revealed an empty divider line.
- use sheet(item:) instead of sheet(isPresented:) for MCPPasteSheet to
  prevent empty sheet rendering when view model is not yet available
- make MCPPasteViewModel conform to Identifiable for sheet(item:) binding
- remove source filter from project copy-all so all visible servers are
  copied to clipboard, not just project-scoped ones
- add proper error handling in MCPServerCard.copyToClipboard() instead
  of silently swallowing encoding failures with try?
add sidebar toolbar menu with group-by-parent-directory toggle and
remove missing projects action. projects can now be grouped under
collapsible disclosure groups by their parent directory, making
worktree-heavy setups easier to navigate. missing project detection
enables one-click cleanup of stale entries.
@doomspork doomspork force-pushed the doomspork/sharing-export-import branch from 05cbd74 to af6e333 Compare February 13, 2026 18:46
@doomspork doomspork merged commit c4991c3 into main Feb 13, 2026
@doomspork doomspork deleted the doomspork/sharing-export-import branch February 13, 2026 18:46
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