connect

package
v0.48.0-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package connect provides functionality to register MCPProxy as an MCP server in various client configuration files (Claude Code, Cursor, VS Code, Windsurf, Codex, Gemini).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfigPath

func ConfigPath(clientID, homeDir string) string

ConfigPath returns the expected configuration file path for the given client on the current operating system. homeDir overrides os.UserHomeDir when non-empty (useful for testing).

Types

type AccessError added in v0.41.0

type AccessError struct {
	Client      string        // client id/name
	Path        string        // config path attempted
	Outcome     AccessOutcome // accessDenied (could also wrap malformed)
	Remediation string        // actionable fix text
	Err         error         // underlying OS cause
}

AccessError is returned by connect/disconnect (and surfaced by GetStatus via the Remediation field) when a client-config access is permission-denied. It is errors.As-discoverable so the REST layer can map it to the right field, and unwraps to the underlying OS error so errors.Is(err, fs.ErrPermission) still holds (data-model.md AccessError).

func (*AccessError) Error added in v0.41.0

func (e *AccessError) Error() string

func (*AccessError) Unwrap added in v0.41.0

func (e *AccessError) Unwrap() error

Unwrap exposes the underlying OS error for errors.Is/errors.As.

type AccessOutcome added in v0.41.0

type AccessOutcome = string

AccessOutcome classifies an attempt to read or write a client config file (Spec 075 FR-003). It is an alias of string so it interoperates with the existing untyped access* constants and the ClientStatus.AccessState wire field, while giving classifier signatures a documented, enum-like type.

Valid values are the access* constants in connect.go: accessAccessible, accessAbsent, accessDenied, accessMalformed. (accessUnknown is the overall, not-content-checked status default, not a classification outcome.)

type ClientDef

type ClientDef struct {
	ID        string // Unique identifier, e.g. "claude-code"
	Name      string // Human-readable name, e.g. "Claude Code"
	Format    string // File format: "json" or "toml"
	ServerKey string // Top-level key for server entries: "mcpServers" or "servers"
	Supported bool   // Whether this client can be connected (directly or via a bridge)
	Reason    string // Explanation when Supported is false
	Note      string // Optional caveat shown for supported clients (e.g. bridge requirement)
	Bridge    bool   // Connects via a stdio bridge; Connect can create the config when absent
	Icon      string // Icon identifier for frontend use
}

ClientDef describes a known MCP client and its configuration file format.

func FindClient

func FindClient(clientID string) *ClientDef

FindClient looks up a client definition by ID. Returns nil if not found.

func GetAllClients

func GetAllClients() []ClientDef

GetAllClients returns the definitions of all known clients.

type ClientStatus

type ClientStatus struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	ConfigPath string `json:"config_path"`
	Exists     bool   `json:"exists"`           // config file exists on disk
	Connected  bool   `json:"connected"`        // mcpproxy entry present in config
	Supported  bool   `json:"supported"`        // client can be connected (directly or via a bridge)
	Reason     string `json:"reason,omitempty"` // why not supported
	Note       string `json:"note,omitempty"`   // caveat for supported clients (e.g. bridge requirement)
	Bridge     bool   `json:"bridge,omitempty"` // connects via a stdio bridge; connectable even without an existing config
	Icon       string `json:"icon"`
	ServerName string `json:"server_name,omitempty"` // name under which mcpproxy is registered

	// AccessState classifies the per-client content access (Spec 075, additive).
	// Empty/"unknown" in the content-read-free overall status; resolved to
	// "accessible"/"absent"/"malformed" (and "denied" in US2) by on-demand reads.
	AccessState string `json:"access_state"`
	// Remediation carries actionable fix text, populated only when access is denied.
	Remediation string `json:"remediation,omitempty"`
}

ClientStatus describes the current state of a client's configuration with respect to an MCPProxy entry.

type ConnectPreview added in v0.47.0

type ConnectPreview struct {
	Client         string                 `json:"client"`
	ConfigPath     string                 `json:"config_path"`
	Format         string                 `json:"format"`           // "json" | "toml"
	ServerKey      string                 `json:"server_key"`       // mcpServers / servers / mcp_servers / mcp
	ServerName     string                 `json:"server_name"`      // key written into the config ("mcpproxy")
	Entry          map[string]interface{} `json:"entry"`            // exact entry (masked) that will be written
	EntryText      string                 `json:"entry_text"`       // entry rendered in the client's format (masked)
	EntryExists    bool                   `json:"entry_exists"`     // an entry with this name already exists (overwrite/force case)
	ContainsAPIKey bool                   `json:"contains_api_key"` // the written URL embeds an apikey credential
	Bridge         bool                   `json:"bridge,omitempty"` // connects via a stdio bridge (config created if absent)
	// AccessState classifies the on-demand config read used to determine
	// EntryExists (Spec 075): accessible|absent|malformed. A denied read never
	// reaches here — it is returned as a typed *AccessError (403 + remediation).
	AccessState string `json:"access_state"`
}

ConnectPreview describes the exact change a subsequent Connect would make to a client config, WITHOUT modifying the file or creating a backup (Spec 078 US1). The entry is derived from the same buildServerEntry used by the real write, so what is previewed equals what is written for the same client and configuration (FR-002); the embedded API key is masked for display (FR-004).

type ConnectResult

type ConnectResult struct {
	Success    bool   `json:"success"`
	Client     string `json:"client"`
	ConfigPath string `json:"config_path"`
	BackupPath string `json:"backup_path,omitempty"`
	ServerName string `json:"server_name"`
	Action     string `json:"action"` // "created", "updated", "already_exists", "removed", "not_found"
	Message    string `json:"message"`
}

ConnectResult describes the outcome of a connect or disconnect operation.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service provides connect/disconnect operations for MCP client configurations.

func NewService

func NewService(listenAddr, apiKey string) *Service

NewService creates a Service that will inject the given listen address and optional API key into client configurations.

func NewServiceWithHome

func NewServiceWithHome(listenAddr, apiKey, homeDir string) *Service

NewServiceWithHome creates a Service with a custom home directory (for testing).

func NewServiceWithReader added in v0.41.0

func NewServiceWithReader(listenAddr, apiKey, homeDir string, readFile func(string) ([]byte, error)) *Service

NewServiceWithReader creates a Service with a custom content reader (for testing the access-classification seam without a real OS denial).

func (*Service) Connect

func (s *Service) Connect(clientID, serverName string, force bool) (*ConnectResult, error)

Connect registers MCPProxy in the specified client's configuration file. serverName defaults to "mcpproxy" if empty. If force is false and an entry already exists, an error is returned.

func (*Service) DetectAppDataDenial added in v0.43.0

func (s *Service) DetectAppDataDenial() (denied bool, remediation string)

DetectAppDataDenial probes installed client configs for a persisted macOS App-Data (TCC) permission denial, for the doctor diagnostic (Spec 075 US3, FR-007/008). It walks the supported clients and, for the first whose config file exists (os.Stat metadata only), performs a single content read through the seam; if that read classifies as accessDenied it reports the denial with the canonical, one-command remediation. It returns (false, "") when no installed client config is permission-denied — including when none are installed — so the check never raises a false positive on a machine that simply has no clients or has granted access (FR-008, T022).

Unlike GetAllStatus this DOES read content: the doctor command is an explicit user action, the one place a macOS App-Data prompt may legitimately appear.

func (*Service) Disconnect

func (s *Service) Disconnect(clientID, serverName string) (*ConnectResult, error)

Disconnect removes the MCPProxy entry from the specified client's configuration.

func (*Service) GetAllStatus

func (s *Service) GetAllStatus() []ClientStatus

GetAllStatus returns the connection status for every known client.

It determines "installed" via os.Stat metadata only and performs ZERO config content reads (Spec 075 FR-001): no client config file is opened, so simply viewing status raises no macOS App-Data privacy prompt. AccessState is left as "unknown" and Connected stays false for installed clients until an explicit per-client read via GetStatus.

func (*Service) GetConnectedCount added in v0.29.0

func (s *Service) GetConnectedCount() int

GetConnectedCount returns the number of supported clients in which mcpproxy is currently registered. Used as the "has any client connected?" wizard predicate (Spec 046).

func (*Service) GetConnectedIDs added in v0.29.0

func (s *Service) GetConnectedIDs() []string

GetConnectedIDs returns the identifiers of supported clients in which mcpproxy is currently registered. Identifiers come from the fixed per-client adapter table; user-entered values never appear here.

func (*Service) GetStatus added in v0.41.0

func (s *Service) GetStatus(clientID string) (ClientStatus, error)

GetStatus returns the status for a single client, reading its config contents on demand (Spec 075 FR-002). This is the scoped, explicit-action path where a macOS App-Data prompt may legitimately appear. It resolves Connected and AccessState (accessible/absent/malformed; "denied" is added in US2).

func (*Service) Preview added in v0.47.0

func (s *Service) Preview(clientID, serverName string) (*ConnectPreview, error)

Preview computes the exact entry a Connect would write for the given client, without modifying the config or creating a backup (Spec 078 FR-001). It reads the config on demand only to classify create-vs-overwrite (FR-003) and to resolve the Spec 075 access state; a permission denial surfaces as the same typed *AccessError that connect/disconnect return (FR-012).

func (*Service) Undo added in v0.47.0

func (s *Service) Undo(clientID, serverName, backupName string) (*ConnectResult, error)

Undo reverts the connect that produced the named backup, restoring the client config to its exact pre-connect state (Spec 078 US3 / FR-008). backupName is the bare filename (filepath.Base) of the backup the connect returned, NOT a path — undo resolves the full path itself against the client's own config directory (see the resolution/validation below):

  • backupName != "": the config is restored byte-for-byte from that backup — this is the only revert that can bring back a pre-existing same-named entry a force-connect overwrote (surgical disconnect cannot).
  • backupName == "": the preceding connect created the file (no prior file existed, ConnectResult.backup_path was empty); undo deletes the file so the pre-connect "no file" state is restored.

Safety first: Undo refuses (Action "conflict") unless the CURRENT file is byte-identical to what that connect produced — i.e. the backup content with exactly the mcpproxy entry applied, reconstructed via the same buildServerEntry/marshal path the write used. Any other content means the user (or another tool) changed the file since the connect, and a restore would clobber those edits. Callers should fall back to Disconnect (surgical entry removal) in that case. A missing backup refuses with Action "not_found". Every mutation takes its own safety backup before touching the file, and a permission denial anywhere surfaces as the same typed *AccessError as connect/disconnect (403 + remediation at the REST boundary).

Note the drift check also intentionally refuses when the effective listen address / API key / require_mcp_auth changed since the connect: the entry the service would write today no longer matches the one on disk, so mcpproxy can no longer prove the file is untouched.

func (*Service) WithConfigProvider added in v0.47.0

func (s *Service) WithConfigProvider(fn func() (listenAddr, apiKey string, requireMCPAuth bool)) *Service

WithConfigProvider installs a live-config accessor so the service reflects runtime changes to listen/api_key/require_mcp_auth (hot-reloaded via the file watcher or the wizard's require_mcp_auth toggle) rather than a startup snapshot. Wired only for the long-lived HTTP server; CLI one-shots leave it nil. Returns the receiver for chaining.

func (*Service) WithRequireMCPAuth added in v0.47.0

func (s *Service) WithRequireMCPAuth(v bool) *Service

WithRequireMCPAuth sets whether the /mcp endpoint requires authentication, which decides whether connect embeds a credential in client configs at all. Threaded from config.RequireMCPAuth at the wiring sites, alongside listenAddr and apiKey. Returns the receiver for chaining.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL