Skip to content

Latest commit

 

History

History
956 lines (660 loc) · 36.4 KB

File metadata and controls

956 lines (660 loc) · 36.4 KB

mcp-wire — Implementation Plan

Overview

mcp-wire is a CLI tool written in Go that lets users install and configure MCP (Model Context Protocol) servers across multiple AI coding CLI tools (Claude Code, Codex, Gemini CLI, OpenCode, etc.) from a single interface.

The architecture has two dimensions:

  • Services: what to install (e.g., Sentry, Jira, Stripe). Defined as YAML files — no Go code needed to add one.
  • Targets: where to install (e.g., Claude Code, Codex). Each target is a Go implementation that knows how to read/write that tool's config file.

The CLI combines the two: the user picks a service, the tool resolves credentials, and writes the config into one or more targets.

Interaction modes:

  • Guided mode (default): running mcp-wire with no subcommand starts an interactive menu/wizard for common workflows.
  • Explicit mode (advanced/CI): command-based syntax (for example mcp-wire install <service> --target <slug> --no-prompt) remains fully supported for scripting.

UX principle:

  • Optimize for users who do not remember command syntax.
  • At the end of guided flows, print the equivalent explicit command for reproducibility.

Project Structure

mcp-wire/
├── cmd/
│   └── root.go                  # Cobra root command
│   └── install.go               # install command
│   └── uninstall.go             # uninstall command
├── internal/
│   ├── service/
│   │   ├── service.go           # Service struct, YAML parsing
│   │   └── registry.go          # Discovers and lists available service definitions
│   ├── target/
│   │   ├── target.go            # Target interface definition
│   │   ├── registry.go          # Target discovery (which are installed on this machine)
│   │   ├── claude.go            # Claude Code target implementation
│   │   └── codex.go             # Codex target implementation
│   ├── credential/
│   │   ├── resolver.go          # Credential resolution chain
│   │   ├── env.go               # Environment variable source
│   │   └── file.go              # File-based credential store
│   └── config/
│       └── config.go            # mcp-wire's own config (paths, defaults)
├── services/                    # Community-contributed service definitions
│   ├── sentry.yaml
│   ├── jira.yaml
│   └── context7.yaml
├── main.go                      # Entrypoint, calls cmd/root.go
├── go.mod
├── go.sum
└── README.md

Phase 1: Core Data Model ✅

1.1 — Service Definition (YAML schema) ✅

Create internal/service/service.go with the struct that maps to service YAML files.

type Service struct {
    Name        string    `yaml:"name"`
    Description string    `yaml:"description"`
    Transport   string    `yaml:"transport"` // "sse" or "stdio"
    URL         string    `yaml:"url,omitempty"`         // for SSE transport
    Command     string    `yaml:"command,omitempty"`     // for stdio transport
    Args        []string  `yaml:"args,omitempty"`        // for stdio transport
    Env         []EnvVar  `yaml:"env,omitempty"`
}

type EnvVar struct {
    Name        string `yaml:"name"`
    Description string `yaml:"description"`
    Required    bool   `yaml:"required"`
    SetupURL    string `yaml:"setup_url,omitempty"`
    SetupHint   string `yaml:"setup_hint,omitempty"`
}

A service YAML file looks like this:

name: sentry
description: "Sentry error tracking MCP"
transport: sse
url: "https://mcp.sentry.dev/sse"
env:
  - name: SENTRY_AUTH_TOKEN
    description: "Sentry authentication token"
    required: true
    setup_url: "https://sentry.io/settings/account/api/auth-tokens/"
    setup_hint: "Create a token with project:read and event:read scopes"
name: context7
description: "Context7 documentation lookup MCP"
transport: sse
url: "https://mcp.context7.com/mcp"
env: []
name: filesystem
description: "Local filesystem access MCP"
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
env: []

1.2 — Service Registry ✅

Create internal/service/registry.go.

This module discovers and loads all .yaml files from the services/ directory. It should:

  1. Accept a base path (default: services/ relative to the binary, but also support ~/.config/mcp-wire/services/ for user-added definitions).
  2. Read each .yaml file and unmarshal into a Service struct.
  3. Validate required fields (name, transport, and either url or command depending on transport).
  4. Return a map of name → Service.
  5. If two files define the same name, the user-local one takes precedence.

Key function signatures:

func LoadServices(paths ...string) (map[string]Service, error)
func ValidateService(s Service) error

1.3 — Target Interface ✅

Create internal/target/target.go.

type Target interface {
    // Name returns the human-readable name (e.g., "Claude Code")
    Name() string

    // Slug returns the CLI-friendly identifier (e.g., "claude")
    Slug() string

    // IsInstalled checks if this CLI tool is present on the system
    IsInstalled() bool

    // Install writes the service config into this target's config file.
    // resolvedEnv contains env var names mapped to their resolved values.
    Install(svc service.Service, resolvedEnv map[string]string) error

    // Uninstall removes the service config from this target's config file.
    Uninstall(serviceName string) error

    // List returns the names of currently configured MCP services in this target.
    List() ([]string, error)
}

1.4 — Target Registry ✅

Create internal/target/registry.go.

A simple slice of all known targets. On startup, iterate and call IsInstalled() to determine which are available.

func AllTargets() []Target
func InstalledTargets() []Target
func FindTarget(slug string) (Target, bool)

Phase 2: Target Implementations ✅

Each target follows the same pattern: locate the config file, read it as JSON (preserving unknown keys), add/remove the MCP entry, write it back.

Critical rule: always preserve unknown keys. Use map[string]any or json.RawMessage when reading config files. Never deserialize into a strict struct that would drop fields the user set manually.

2.1 — Claude Code Target ✅

Create internal/target/claude.go.

Config file location: ~/.claude/settings.json (global) or .claude/settings.json (project-local). Start with global only.

Config structure (relevant portion):

{
  "mcpServers": {
    "sentry": {
      "type": "sse",
      "url": "https://mcp.sentry.dev/sse",
      "env": {
        "SENTRY_AUTH_TOKEN": "the-token-value"
      }
    }
  }
}

For stdio-based services:

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
    }
  }
}

Implementation:

  • IsInstalled(): check if claude binary is in PATH (use exec.LookPath).
  • Install(): read ~/.claude/settings.json → unmarshal to map[string]any → add entry under mcpServers → write back with json.MarshalIndent. Create the file/directory if it doesn't exist.
  • Uninstall(): same flow, delete the key from mcpServers.
  • List(): read mcpServers keys.

2.2 — Codex Target ✅

Create internal/target/codex.go.

Before implementing, research the current Codex CLI config file location and format. As of early 2025, Codex uses ~/.codex/config.json or similar. The MCP server config structure needs to be verified.

Action: search for the Codex CLI documentation or config format before implementing. The structure is likely similar to Claude Code but may differ in key names or nesting.

Implementation follows the same pattern as Claude Code.

2.3 — Future Targets (not in v0.1)

Each of these would be a new file in internal/target/:

  • geminicli.go — Gemini CLI
  • opencode.go — OpenCode

Research their config formats when adding support. Each should take roughly 50-100 lines of Go following the established pattern.


Phase 3: Credential Resolution ✅

3.1 — Credential Source Interface ✅

Create internal/credential/resolver.go.

type Source interface {
    Name() string
    Get(envName string) (string, bool)
    Store(envName string, value string) error // may return ErrNotSupported
}

type Resolver struct {
    sources []Source
}

// Resolve tries each source in order. Returns the value and which source it came from.
func (r *Resolver) Resolve(envName string) (value string, source string, found bool)

Resolution order:

  1. Environment variable (highest priority — user explicitly set it)
  2. File store (~/.config/mcp-wire/credentials)
  3. Not found → trigger interactive prompt

3.2 — Environment Source ✅

Create internal/credential/env.go.

Simple wrapper around os.Getenv. Store() returns ErrNotSupported.

3.3 — File Source ✅

Create internal/credential/file.go.

Reads/writes ~/.config/mcp-wire/credentials in a simple KEY=VALUE format (one per line). This file should be created with 0600 permissions.

Format:

SENTRY_AUTH_TOKEN=snt_abc123...
JIRA_API_TOKEN=jira_xyz789...

On Get(): read the file, parse lines, return matching value. On Store(): read the file, update or append the key, write back.

3.4 — Interactive Credential Flow ✅

This lives in the install command logic (not in the credential package). When a required env var is not found by the resolver:

  1. Print the env var name, description, and why it is needed.
  2. If setup_url is provided, print it and offer to open in browser (use open / xdg-open / start depending on OS).
  3. If setup_hint is provided, print it.
  4. Show progress when multiple credentials are required (example: 1/2, 2/2).
  5. Prompt the user to paste the value (mask input for security).
  6. Ask where to store: file store or skip (manage manually).
  7. If file store chosen, call source.Store().

Example output:

🔧 Configuring: Sentry error tracking MCP

  SENTRY_AUTH_TOKEN is required.
  → Create one here: https://sentry.io/settings/account/api/auth-tokens/
    Tip: Create a token with project:read and event:read scopes

  Open URL in browser? [Y/n]: y

  Paste your token: ****************************

  Save to mcp-wire credential store? [Y/n]: y
  ✓ Saved

  Installing to: Claude Code, Codex
  ✓ Claude Code — configured
  ✓ Codex — configured

Phase 4: CLI Commands ✅

Use github.com/spf13/cobra for command structure.

4A — Guided Interactive UX (high priority) ✅

4A.1 — Main interactive entry (mcp-wire) ✅

When the user runs mcp-wire with no subcommand, open an interactive menu:

  1. Install a service
  2. Uninstall a service
  3. Exit

4A.2 — Install wizard flow ✅

  1. Service selection
    • Show a searchable/filterable list (name + description).
    • Support quick search by substring.
  2. Target selection
    • Show detected installed targets first.
    • Allow multi-select.
  3. Credential step (for each required env var)
    • Show description.
    • Show setup URL and ask whether to open it.
    • Show setup hint/scopes.
    • Prompt with masked input.
    • Ask whether to save in credential store.
  4. Confirmation step
    • Show selected service, targets, and credential source summary.
  5. Apply + results
    • Print per-target success/failure.
    • Print equivalent explicit command.

4A.3 — Uninstall wizard flow ✅

  1. Service selection (searchable)
  2. Target selection (multi-select)
  3. Confirmation
  4. Optional credential cleanup prompt

4A.4 — Fallback behavior for explicit commands ✅

  • mcp-wire install with no service argument should enter the service picker.
  • mcp-wire uninstall with no service argument should enter the service picker.
  • Explicit args/flags continue to work unchanged.

4.1 — mcp-wire list services ✅ (removed)

Removed — the list services command was a low-value informational view. Service discovery now happens through the install wizard's service picker.

4.2 — mcp-wire list targets ✅ (removed)

Removed — the list targets command was a low-value informational view. Target selection now happens through the install/uninstall wizard.

4.3 — mcp-wire install <service>

Flags:

  • --target <slug> — install to a specific target only (can be repeated). Default: all installed targets.
  • --no-prompt — fail if credentials are not already resolved (for CI/scripting).

Flow:

  1. Load the service definition by name.
  2. If <service> is omitted, enter interactive service selection.
  3. For each required env var, run the credential resolver.
  4. If not found and --no-prompt is not set, run the interactive prompt (Phase 3.4).
  5. If not found and --no-prompt is set, exit with error.
  6. For each target (filtered by --target or all installed), call target.Install().
  7. Print results.

4.4 — mcp-wire uninstall <service>

Flags:

  • --target <slug> — same as install.

Flow:

  1. For each target, call target.Uninstall(serviceName).
  2. If <service> is omitted, enter interactive service selection.
  3. Optionally ask if the user wants to remove stored credentials for this service.

4.5 — mcp-wire status ✅ (removed)

Removed — the status command only showed curated YAML services, not all servers installed in a target, making it misleading.


Phase 5: Initial Service Definitions ✅

Create YAML files in the services/ directory for at least these services:

sentry.yaml

name: sentry
description: "Sentry error tracking MCP"
transport: sse
url: "https://mcp.sentry.dev/sse"
env:
  - name: SENTRY_AUTH_TOKEN
    description: "Sentry authentication token"
    required: true
    setup_url: "https://sentry.io/settings/account/api/auth-tokens/"
    setup_hint: "Create a token with project:read and event:read scopes"

jira.yaml

name: jira
description: "Jira project management MCP (Atlassian)"
transport: sse
url: "https://mcp.atlassian.com/v1/sse"
env:
  - name: JIRA_API_TOKEN
    description: "Atlassian API token"
    required: true
    setup_url: "https://id.atlassian.com/manage-profile/security/api-tokens"
    setup_hint: "Create an API token from your Atlassian account settings"

context7.yaml

name: context7
description: "Context7 documentation lookup MCP"
transport: sse
url: "https://mcp.context7.com/mcp"
env: []

Research needed for additional services

Before writing more service definitions, verify the current MCP configuration format for each service. The URLs and env var names above are based on known configurations as of early 2025 and should be verified against current documentation.


Phase 6: Build, Test, Release ✅

6.1 — Go Module Setup ✅

go mod init github.com/<your-username>/mcp-wire
go get github.com/spf13/cobra
go get gopkg.in/yaml.v3

No other external dependencies should be needed for v0.1.

6.2 — Testing Strategy ✅

  • Service loading: test YAML parsing with valid and invalid files, test validation logic, test precedence when multiple paths are provided.
  • Target implementations: test Install/Uninstall/List against temporary config files. Create a temp dir, write a sample config, run the operation, assert the result.
  • Credential resolver: test the chain order (env takes precedence over file), test the file store read/write.
  • Integration test: write a test that loads a service YAML, creates a temp config file, runs Install, reads back the config, and verifies the MCP entry is correct.
  • Guided interactive flow tests: test menu navigation, service search/filter behavior, target multi-select, and credential prompt flow using mocked input/output streams.
  • Sandboxed CLI integration tests: run end-to-end install/status/uninstall flows in temporary HOME/PATH to avoid touching user config.
  • Manual QA checklist: test on a machine with existing MCP configs and a clean machine; verify no unrelated config keys are removed.

6.3 — Build ✅

Standard Go cross-compilation:

# Local
go build -o mcp-wire .

# Cross-compile
GOOS=darwin GOARCH=arm64 go build -o mcp-wire-darwin-arm64 .
GOOS=linux GOARCH=amd64 go build -o mcp-wire-linux-amd64 .
GOOS=windows GOARCH=amd64 go build -o mcp-wire-windows-amd64.exe .

6.4 — Release ✅

Use GoReleaser or GitHub Actions for automated releases. Provide binaries for macOS (arm64 + amd64), Linux (amd64), and Windows (amd64). Consider a Homebrew tap for macOS users.


Implementation Order

Implement in this exact order to have something working as early as possible:

  1. Go module + main.go + Cobra skeleton — just mcp-wire --help works.
  2. Service struct + YAML loader + validation — can parse service files.
  3. list services command — first working command, proves the YAML loading. (later removed — low-value informational view)
  4. Target interface + Claude Code implementation — can read/write Claude Code config.
  5. list targets command — detects installed tools. (later removed — low-value informational view)
  6. Credential resolver (env + file sources) — can resolve and store tokens.
  7. install command with interactive prompt — the core feature, end-to-end flow.
  8. uninstall command — straightforward once install works.
  9. status command — reads from all targets, displays matrix. (later removed — only showed curated services, misleading)
  10. Codex target implementation — second target, validates the abstraction.
  11. Initial service YAML files — ship with 3-5 verified services.
  12. README, contributing guide — explain how to add services and targets.
  13. Guided main menumcp-wire with no args opens interactive navigation.
  14. Install wizard UX — searchable service picker, target selection, credential guidance.
  15. Uninstall wizard parity — same guided UX model as install.
  16. Interactive UX tests + sandbox integration tests — prevent regressions in guided flows.
  17. Equivalent-command summary in guided mode — print scriptable command at the end of each workflow.

Design Decisions and Constraints

Config file safety

When reading a target's config file, always use map[string]any (not a strict struct). This ensures that any keys the user added manually are preserved when mcp-wire writes back to the file. This is the single most important implementation detail — getting it wrong means the tool destroys user config.

Service YAML as the contribution surface

The primary way community members contribute is by adding YAML files to services/. This must require zero Go knowledge. The YAML schema should be well-documented in CONTRIBUTING.md with examples for both SSE and stdio transports.

Credential storage is opt-in

The user is always asked before storing credentials to the file store. The --no-prompt flag makes credentials required from environment or file store (no interactive prompting), which is useful for CI/automation.

No daemon, no server, no background process

mcp-wire is a pure CLI tool. It reads files, writes files, and exits. No running processes, no state between invocations beyond the credential file and the target config files.

Embedded vs external service definitions

For v0.1, service YAML files are shipped alongside the binary (in the repo). The tool looks for them relative to the binary location and also in ~/.config/mcp-wire/services/. This avoids any network dependency. A future version could fetch definitions from a remote registry.


Phase 7: Official MCP Registry Integration (feature-gated, incremental)

This phase adds optional integration with the Official MCP Registry (https://registry.modelcontextprotocol.io) without changing the current safe defaults.

7.0 — Product guardrails (lock these first)

  • Curated stays default: mcp-wire bundled/user YAML services remain the default discovery/install source.
  • Registry is opt-in: registry support is disabled by default behind a one-time local setting until the feature is stable.
  • No hidden references when disabled: if registry is disabled, do not show registry flags/options/help/menu entries anywhere.
  • Latest-only selection: when a user selects a registry MCP, resolve details from the latest version only.
  • Runtime checks are warnings by default: missing package managers should warn, not block installation.

7.1 — Feature gate and local settings ✅

Add mcp-wire local settings file (for example ~/.config/mcp-wire/config.json) with:

{
  "features": {
    "registry": false
  }
}

Suggested commands:

  • mcp-wire feature enable registry
  • mcp-wire feature disable registry
  • mcp-wire feature list (optional but useful)

Acceptance criteria:

  • Fresh install behavior is unchanged.
  • With registry=false, all commands and guided flows behave exactly as today.

7.2 — Official registry API client (read-only) ✅

Create internal/registry/ for typed API client logic.

Use these endpoints:

  • List latest servers: GET /v0.1/servers?version=latest&limit=...&cursor=...
  • Search latest servers: GET /v0.1/servers?version=latest&search=...
  • Get selected server details (latest only): GET /v0.1/servers/{serverName}/versions/latest

Important details:

  • URL-encode serverName path values (/ must become %2F).
  • Parse application/problem+json errors and return friendly messages.
  • Do not expose historical version selection in this phase.

7.3 — Caching and indexing for responsive UX ✅

Add a local cache/index (for example under ~/.cache/mcp-wire/) to avoid network-per-keystroke UI.

Requirements:

  • Cold sync with pagination from version=latest list endpoint.
  • Incremental sync using updated_since.
  • Local in-memory filtering/search for interactive pickers.
  • Graceful fallback to stale cache when network fails.
  • Background sync at CLI startup when registry is enabled, with non-blocking UX and visible sync status in registry flows.
  • Provide a cache reset command for local recovery (mcp-wire cache clear).

Acceptance criteria:

  • Guided and non-guided search feel immediate.
  • Registry API latency does not block every filter action.

7.4 — Unified catalog model with explicit source labels ✅

Introduce an internal catalog type that supports both curated and registry entries.

Each entry should carry source metadata:

  • source: curated (maintained/tested by mcp-wire)
  • source: registry (from Official MCP Registry, not vetted by mcp-wire)

Keep existing service.Service and YAML format intact for curated entries.

7.5 — Source selection in explicit CLI mode ✅

When registry is enabled, add source filtering:

  • --source curated|registry|all (default: curated)

Apply this to install flows that pick services interactively.

When registry is disabled:

  • Do not expose --source.
  • Do not mention registry in help/usage text.

Note: mcp-wire list services was later removed as a low-value informational view.

7.6 — Guided UI changes (only when enabled) ✅

Add a source step before service selection:

  1. Curated services (recommended)
  2. Registry services (community)
  3. Both

If user selects "Both", show one merged searchable list with clear markers:

  • * = curated by mcp-wire
  • unmarked (or explicit label) = registry

Always print the legend near the list when markers are shown.

7.7 — Trust/safety messaging for registry entries ✅

Before confirming installation of a registry entry, show a short summary:

  • Source (registry)
  • Install type (remote or package)
  • Transport (streamable-http, sse, stdio)
  • Required secrets/credentials
  • Repository URL (if available)

Interactive mode should require explicit confirmation for registry entries.

7.8 — Install strategy (split delivery)

7.8.1 — Remote-first support ✅

First support registry entries that map cleanly to remote installs:

  • remotes with streamable-http or sse
  • URL/header variable prompting and substitution

7.8.2 — Package support ✅

Then support package-backed installs:

  • npm, pypi, oci, nuget, mcpb
  • map registry metadata into target-compatible stdio/local config

For selected registry MCPs, always fetch/install using latest details only (versions/latest).

7.9 — Runtime/package-manager preflight (warning-only default)

For package-backed installs, preflight-check required runtime commands (for example npx, uvx, docker, bun, dnx, pipx, python3).

Behavior:

  • Default: warn and continue.
  • Optional strict mode (for CI): fail fast when runtime is missing (for example --strict-runtime-check).

7.10 — Uninstall parity for mixed sources

Ensure uninstall handles registry-installed services as first-class entries, not only curated YAML service names.

Requirements:

  • Uninstall should work by installed service key regardless of source.

Note: the status command was removed because it only showed curated YAML services, not all servers installed in a target.

7.11 — Incremental implementation slices

Suggested order for shipping gradually:

  1. ✅ Feature gate + settings file + no-registry-references enforcement.
  2. ✅ Registry read client + latest-only detail lookup + basic tests.
  3. ✅ Cache/index + local search.
  4. list services source filter (curated default). (list services later removed)
  5. ✅ Guided UI source step and merged list markers.
  6. ✅ Registry remote-install support.
  7. Runtime preflight warnings.
  8. ✅ Registry package-install support.
  9. Uninstall parity for mixed sources.
  10. Hardening, integration tests, docs.

Phase 8: TUI Redesign — Bubble Tea Full-Screen UI

Replace the survey/v2-based interactive UI with a full-screen Bubble Tea TUI featuring breadcrumb navigation, fixed-height content area, live-filtered search, multi-select checkboxes, and progress indicators. The plain-text fallback (guided.go) and explicit CLI mode (mcp-wire install sentry --target claude) remain unchanged.

Architecture

Wizard state machine with composable screens. A single root Bubble Tea model owns the layout shell (title bar, breadcrumb, status bar) and delegates 13-15 content lines to the active screen sub-model. Each screen implements a Screen interface. Navigation via message passing (NavigateMsg, BackMsg). A screen stack enables Esc-back.

New package structure:

internal/tui/
    app.go              -- Root model (state machine, layout shell)
    theme.go            -- Lip Gloss styles, color palette, layout constants
    screen.go           -- Screen interface, message types, ScreenID enum
    breadcrumb.go       -- Breadcrumb bar renderer
    statusbar.go        -- Bottom status bar renderer
    menu.go             -- Screen 1: Main Menu
    source.go           -- Screen 2: Source Selection
    service.go          -- Screen 3: Service Selection (with search)
    trust.go            -- Screen 4: Registry Trust Warning
    target.go           -- Screen 5: Target Multi-select
    scope.go            -- Screen 5b: Scope Selection
    review.go           -- Screen 6: Review
    apply.go            -- Screen 7: Apply (progress/result)
    credential.go       -- Credential prompting within TUI

Each file gets a corresponding _test.go.

Key types:

type Screen interface {
    Init() tea.Cmd
    Update(msg tea.Msg) (Screen, tea.Cmd)
    View() string
    StatusHints() []KeyHint
}

type WizardState struct {
    Action  string              // "install" or "uninstall"
    Source  string              // "curated", "registry", "all"
    Service service.Service
    Entry   *catalog.Entry
    Targets []target.Target
    Scope   target.ConfigScope
    Results []TargetResult
}

New dependencies:

github.com/charmbracelet/bubbletea  v1.x
github.com/charmbracelet/lipgloss   v1.x
github.com/charmbracelet/bubbles    v0.x

After completion, AlecAivazis/survey/v2 and its transitive deps are removed.

8.0 — Foundation (dependencies and layout shell) ✅

Add Bubble Tea, Lip Gloss, and Bubbles to go.mod. Create the package skeleton with the root model, theme, and a placeholder main-menu screen. Wire into root.go alongside the existing dispatch logic, guarded by a feature flag so the old survey path remains reachable during development.

Create:

  • internal/tui/app.go — Root WizardModel (tea.Model). Owns terminal dimensions, renders layout frame (title row, breadcrumb, content, status bar). Delegates to active Screen.
  • internal/tui/screen.goScreen interface, ScreenID enum, navigation message types.
  • internal/tui/theme.goTheme struct with Lip Gloss styles matching mockup palette (cyan active, green completed, dim future, yellow warning, red error). Layout constant: ContentHeight = 13.
  • internal/tui/breadcrumb.go — Renders from WizardState. Completed = green ✓ + value. Active = bold cyan. Future = dim.
  • internal/tui/statusbar.go — Renders []KeyHint on bottom row.
  • internal/tui/menu.go — Placeholder main menu.

Modify:

  • go.mod / go.sum — Add bubbletea, lipgloss, bubbles.
  • internal/cli/root.go — Add TUI branch in runGuidedMainMenu(), guarded by feature flag.

Acceptance: make test passes. bin/mcp-wire shows the Bubble Tea menu.

8.1 — Main Menu (Screen 1) ✅

Full menu with ↑↓ navigation, Enter select, q quit. Three items: Install service, Uninstall service, Exit.

Acceptance: Menu navigable. Exit quits cleanly.

8.2 — Source Selection (Screen 2) ✅

Conditional on registry feature flag. Three options with inline descriptions: Curated (recommended), Registry, Both.

Modify internal/tui/app.go to skip source screen when registry feature is off, defaulting to "curated".

Acceptance: With registry enabled → source screen shown. Without → skipped directly to service selection.

8.3 — Service Selection (Screen 3) — hardest screen ✅

Live-filtered search over curated and/or registry catalog.

  • Uses Bubbles textinput.Model for search bar.
  • allEntries, filtered, cursor, offset for scroll state.
  • Right-aligned count ("786 services" / "2 matches").
  • Two-line entries: name + description.
  • Scroll indicator ("▼ N more").
  • Init() loads catalog via existing loadCatalog() from catalog_helpers.go.
  • Registry sync: poll registrySyncStatusLine() via tea.Tick every 500ms (replaces the blocking waitForRegistrySyncInSurvey).

Acceptance: Search filters live. Scroll works. Enter selects.

8.4 — Registry Trust Warning (Screen 4) ✅

Shown after selecting a non-curated service. Displays service metadata (source, transport, URL, repo) and horizontal Yes/No choice.

  • On "Yes": fetch latest via refreshRegistryEntry(), convert via catalogEntryToService(), proceed to targets.
  • On "No": back to service list.

Polished view: ⚠ warning header, service name (cyan/bold) + description, remote URL in cyan, caution text, descriptive choice labels ("No, go back" / "Yes, proceed").

Acceptance: Registry service → trust shown. Curated service → skipped.

8.5 — Target Multi-select + Scope (Screens 5, 5b) ✅

Target screen:

  • [x]/[ ] checkboxes. Space toggles, a all installed, n none.
  • Not-installed targets dimmed and non-selectable.
  • Enter confirms (≥1 required).

Scope screen:

  • User/Project choice. Only shown if any selected target supports project scope.

Acceptance: Full flow from source → service → target → scope works.

8.6 — Review (Screen 6) ✅

Summary of all selections: Source, Service, Targets, Scope, Credentials mode. Shows equivalent CLI command (reuse buildEquivalentInstallCommand logic from guided.go). Horizontal Apply/Cancel choice.

Acceptance: Review shows correct summary. Apply proceeds to execution.

8.7 — Apply + Credentials (Screen 7) — second hardest ✅

Credential prompting in TUI:

  • CredentialScreen steps through unresolved EnvVar entries sequentially.
  • Uses textinput.Model with EchoMode = EchoNone for secrets.
  • Shows setup URL/hint before each prompt.
  • On completion, stores resolved values and proceeds to apply.

Apply screen:

  • Per-target status rows: pending, configuring, done, failed.
  • Runs each target.Install() via tea.Cmd (goroutine), receives progress messages.
  • Success: green display + equivalent command + "Install another / Back to menu / Exit".
  • Partial failure: warning + error details + "Retry failed / Back to menu / Exit".
  • OAuth: runs Authenticate() in a tea.Cmd, shows "authenticating..." status.

Modify internal/cli/install.go — extract installSingleTarget() from executeInstall() loop so both CLI and TUI paths can call it.

Acceptance: Full end-to-end install with credential prompting and per-target progress.

8.8 — Uninstall Flow ✅

Reuses most install screens. WizardState.Action = "uninstall" controls rendering and business logic differences in Review and Apply screens. Add post-uninstall credential removal prompt (mirrors maybeRemoveStoredCredentials from uninstall.go).

Acceptance: Full uninstall flow works end-to-end in the TUI.

8.8.1 — Mockup alignment pass ✅

Visual polish across all TUI screens to match design mockups: source screen question header and inline descriptions, service search prompt and scroll indicator formatting, scope heading with target names and footer, review em-dash labels with Apply-first choice order and inline command display, apply service-name headers with per-state icons (✓/△/✗) and retry choice on failure.

8.9 — Cleanup and Survey Removal

Remove survey dependency and transition code.

Delete:

  • internal/cli/survey_ui.go
  • internal/cli/survey_escape_input.go
  • internal/cli/survey_escape_input_unix.go
  • internal/cli/survey_escape_input_other.go
  • internal/cli/survey_escape_input_test.go

Modify:

  • internal/cli/root.go — Remove survey branch and feature-flag guard. TTY → TUI, non-TTY → plain text.
  • go.mod — Remove AlecAivazis/survey/v2 and transitive deps.

Acceptance: make test passes. survey gone from go.mod. TUI, plain-text, and CLI flag modes all work.

Risk Mitigation

Risk Mitigation
Credential prompting in Bubble Tea Use Bubbles textinput with EchoNone — hidden input native to the event loop. term.ReadPassword stays in install_credentials.go for plain-text fallback only.
Registry sync blocking Replace waitForRegistrySyncInSurvey() with tea.Tick polling of registrySyncStatusLine(). Sync already runs in a background goroutine.
OAuth subprocess Run Authenticate() inside a tea.Cmd (goroutine). Browser opens outside terminal control — no conflict.
Terminal resize Handle tea.WindowSizeMsg in root model. Degrade gracefully if height is too small.
Coexistence during development Feature flag toggles between survey and TUI. Default stays survey until step 8.9.

Future Roadmap (post v0.1)

These are features to consider after the core works. Not to be implemented now.

  1. Manifest file (mcp-wire.yaml) — declare desired services in a file, run mcp-wire apply to sync all targets at once. Enables version-controlling your MCP setup.
  2. Target auto-detection improvements — check config file existence in addition to binary presence, handle edge cases like Homebrew vs manual installs.
  3. OS keychain integration — use zalando/go-keyring to store credentials in macOS Keychain / Linux Secret Service / Windows Credential Manager instead of a plaintext file.
  4. mcp-wire credentials list — show stored credentials (masked).
  5. mcp-wire credentials rotate <service> — re-open setup URL, re-prompt, update all targets.
  6. Additional registry providers — support third-party/private registries that implement the MCP Registry API, with per-registry trust controls.
  7. Gemini CLI target — add when config format is stable and documented.
  8. Target capability parity — expand and test Claude/Codex/OpenCode parity for advanced MCP config shapes (for example richer header mapping, variable substitution, and transport-specific options).
  9. mcp-wire diff — show what's configured in targets vs what a manifest says, dry-run mode.
  10. Profiles — "work" vs "personal" service sets, each with their own credentials and target selection.
  11. Service health checkmcp-wire check <service> verifies the MCP endpoint responds.
  12. Shell completions — Cobra has built-in support for bash/zsh/fish completions.