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.
mcp-wire/
├── cmd/
│ └── root.go # Cobra root command
│ └── install.go # install command
│ └── uninstall.go # uninstall command
│ └── list.go # list command (services, targets)
│ └── status.go # status 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)
│ │ ├── claudecode.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
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: []Create internal/service/registry.go.
This module discovers and loads all .yaml files from the services/ directory. It should:
- Accept a base path (default:
services/relative to the binary, but also support~/.config/mcp-wire/services/for user-added definitions). - Read each
.yamlfile and unmarshal into aServicestruct. - Validate required fields (name, transport, and either url or command depending on transport).
- Return a map of
name → Service. - 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) errorCreate 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., "claudecode")
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)
}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)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.
Create internal/target/claudecode.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 ifclaudebinary is in PATH (useexec.LookPath).Install(): read~/.claude/settings.json→ unmarshal tomap[string]any→ add entry undermcpServers→ write back withjson.MarshalIndent. Create the file/directory if it doesn't exist.Uninstall(): same flow, delete the key frommcpServers.List(): readmcpServerskeys.
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.
Each of these would be a new file in internal/target/:
geminicli.go— Gemini CLIopencode.go— OpenCode
Research their config formats when adding support. Each should take roughly 50-100 lines of Go following the established pattern.
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:
- Environment variable (highest priority — user explicitly set it)
- File store (
~/.config/mcp-wire/credentials) - Not found → trigger interactive prompt
Create internal/credential/env.go.
Simple wrapper around os.Getenv. Store() returns ErrNotSupported.
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.
This lives in the install command logic (not in the credential package). When a required env var is not found by the resolver:
- Print the env var name and description.
- If
setup_urlis provided, print it and offer to open in browser (useopen/xdg-open/startdepending on OS). - If
setup_hintis provided, print it. - Prompt the user to paste the value (mask input for security).
- Ask where to store: file store or skip (manage manually).
- 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
Use github.com/spf13/cobra for command structure.
List all available service definitions found in the services directory.
Output:
Available services:
sentry Sentry error tracking MCP
jira Jira project management MCP (Atlassian)
context7 Context7 documentation lookup MCP
filesystem Local filesystem access MCP
List all known targets and whether they are detected on this system.
Output:
Targets:
claudecode Claude Code ✓ installed
codex Codex CLI ✓ installed
geminicli Gemini CLI ✗ not found
opencode OpenCode ✗ not found
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:
- Load the service definition by name.
- For each required env var, run the credential resolver.
- If not found and
--no-promptis not set, run the interactive prompt (Phase 3.4). - If not found and
--no-promptis set, exit with error. - For each target (filtered by
--targetor all installed), calltarget.Install(). - Print results.
Flags:
--target <slug>— same as install.
Flow:
- For each target, call
target.Uninstall(serviceName). - Optionally ask if the user wants to remove stored credentials for this service.
Show a matrix of services × targets.
Output:
Claude Code Codex
sentry ✓ ✓
jira ✓ ✗
context7 ✗ ✗
Implementation: for each installed target, call target.List() and cross-reference with known services.
Create YAML files in the services/ directory for at least these services:
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: 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"name: context7
description: "Context7 documentation lookup MCP"
transport: sse
url: "https://mcp.context7.com/mcp"
env: []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.
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.
- 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.
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 .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.
Implement in this exact order to have something working as early as possible:
- Go module + main.go + Cobra skeleton — just
mcp-wire --helpworks. - Service struct + YAML loader + validation — can parse service files.
list servicescommand — first working command, proves the YAML loading.- Target interface + Claude Code implementation — can read/write Claude Code config.
list targetscommand — detects installed tools.- Credential resolver (env + file sources) — can resolve and store tokens.
installcommand with interactive prompt — the core feature, end-to-end flow.uninstallcommand — straightforward once install works.statuscommand — reads from all targets, displays matrix.- Codex target implementation — second target, validates the abstraction.
- Initial service YAML files — ship with 3-5 verified services.
- README, contributing guide — explain how to add services and targets.
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.
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.
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.
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.
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.
These are features to consider after the core works. Not to be implemented now.
- Manifest file (
mcp-wire.yaml) — declare desired services in a file, runmcp-wire applyto sync all targets at once. Enables version-controlling your MCP setup. - Target auto-detection improvements — check config file existence in addition to binary presence, handle edge cases like Homebrew vs manual installs.
- OS keychain integration — use
zalando/go-keyringto store credentials in macOS Keychain / Linux Secret Service / Windows Credential Manager instead of a plaintext file. mcp-wire credentials list— show stored credentials (masked).mcp-wire credentials rotate <service>— re-open setup URL, re-prompt, update all targets.- Remote service registry —
mcp-wire updatefetches latest service definitions from the GitHub repo without requiring a binary update. - Gemini CLI target — add when config format is stable and documented.
- OpenCode target — add when config format is stable and documented.
mcp-wire diff— show what's configured in targets vs what a manifest says, dry-run mode.- Profiles — "work" vs "personal" service sets, each with their own credentials and target selection.
- Service health check —
mcp-wire check <service>verifies the MCP endpoint responds. - Shell completions — Cobra has built-in support for bash/zsh/fish completions.