provisioner

package
v1.0.0-rc.3 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package provisioner detects installed LLM clients and manages their MCP gateway configuration, enabling zero-friction connection between gridctl and tools like Claude Desktop, Cursor, VS Code, and others.

Index

Constants

View Source
const (
	OpenCodeImportSelected   = "selected"
	OpenCodeImportMissing    = "missing"
	OpenCodeImportUnreadable = "unreadable"
	OpenCodeImportMalformed  = "malformed"
	OpenCodeImportEmpty      = "empty"
)

OpenCode import file statuses. They describe the selected file only. A failed read does not fall through to another file.

View Source
const OpenCodeImportPolicy = "" /* 252-byte string literal not displayed */

OpenCodeImportPolicy explains why import reads one file instead of OpenCode's merged effective configuration.

Variables

View Source
var (
	ErrAlreadyLinked  = errors.New("already linked with identical config")
	ErrConflict       = errors.New("existing entry has unexpected config")
	ErrNotLinked      = errors.New("no gridctl entry found")
	ErrClientNotFound = errors.New("client not detected on this system")
	ErrNpxNotFound    = errors.New("npx not found in PATH")
)

Sentinel errors for link operations.

View Source
var NpxAvailable = func() bool {
	_, err := exec.LookPath("npx")
	return err == nil
}

NpxAvailable checks if npx is available in PATH. Exported as a variable to allow test overrides.

Functions

func AppendClientParam

func AppendClientParam(rawURL, clientID string) string

AppendClientParam adds (or replaces) the `client` query parameter on a gateway URL so the gateway can resolve the connecting client's stable access identifier from the wire. Returns the URL unchanged when clientID is empty or the URL cannot be parsed.

func AppendQueryParams

func AppendQueryParams(rawURL string, params map[string]string) string

AppendQueryParams adds or replaces the given query parameters. Empty values are skipped. Returns the URL unchanged when it cannot be parsed or no parameter is set.

func CreateBackup

func CreateBackup(path string) (string, error)

CreateBackup exposes the timestamped-backup helper (".gridctl-backup-<ts>", pruned to the most recent three) for callers outside the package. The import command uses it on stack.yaml before appending, so the one backup convention covers both directions of config mutation.

func DryRunDiff

func DryRunDiff(configPath string, prov ClientProvisioner, opts LinkOptions) (before, after string, err error)

DryRunDiff returns the before/after config for a dry run. Supports both JSON and YAML formats based on the provisioner type.

func GatewayHTTPURL

func GatewayHTTPURL(port int) string

GatewayHTTPURL constructs the streamable HTTP gateway URL from a port.

func GatewayURL

func GatewayURL(port int) string

GatewayURL constructs the legacy SSE gateway URL from a port. New links never write this shape (legacy HTTP+SSE is deprecated upstream); it exists so existing /sse entries are still recognized on read.

func GroupGatewayHTTPURL

func GroupGatewayHTTPURL(port int, group string) string

GroupGatewayHTTPURL constructs a tool group's streamable HTTP gateway URL.

func GroupGatewayURL

func GroupGatewayURL(port int, group string) string

GroupGatewayURL constructs a tool group's legacy SSE gateway URL. Like GatewayURL, it is read-side only: new links write the HTTP shape.

func HasComments

func HasComments(configPath string) bool

HasComments checks if a config file contains comments that would be lost on write. The comment syntax depends on the file format: TOML and YAML use '#' line comments, while JSON/JSONC uses '//' and '/*'. Returns false if the file doesn't exist.

func LooksLikeLegacyLink(raw map[string]any, needsBridge bool) bool

LooksLikeLegacyLink reports whether an unrecorded entry is plausibly a gridctl link written before ownership recording existed. Per Article XVI this heuristic assists one-time migration only: it feeds the "likely a pre-lockfile link; adopt it" remediation hint and is never a steady-state ownership check. It recognizes every shape gridctl has written: localhost URLs under the known URL keys (flat or nested in a Continue-style transport object) and npx-launched mcp-remote bridges.

func PlannedEntry

func PlannedEntry(prov ClientProvisioner, opts LinkOptions) map[string]any

PlannedEntry returns the entry value prov's Link would write for opts, or nil when the provisioner cannot plan (every registered client can).

func PostLinkNotesFor

func PostLinkNotesFor(prov ClientProvisioner) []string

PostLinkNotesFor returns the provisioner's post-link notes, or nil when it declares none.

func ProjectWalk

func ProjectWalk(ctx context.Context, startDir string) ([]string, error)

ProjectWalk returns the directories project discovery visits, nearest first. The walk stops at the first git root, inclusive. When no directory in the chain is a git root, only startDir is returned. It does not read file contents and does not use the Gridctl home.

func TransportDescription

func TransportDescription(needsBridge bool) string

TransportDescription returns a human-readable transport description.

func TransportDescriptionFor

func TransportDescriptionFor(prov ClientProvisioner) string

TransportDescriptionFor returns a transport description for a specific provisioner. Every non-bridge client now links over streamable HTTP.

Types

type Antigravity

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

Antigravity provisions the Google Antigravity IDE MCP config. Transport: native streamable HTTP (no bridge needed). Antigravity borrows Windsurf's `serverUrl` field name but speaks Streamable HTTP, so the entry points at the gateway's /mcp endpoint rather than /sse.

func (*Antigravity) Detect

func (c *Antigravity) Detect() (string, bool)

Detect prefers the Antigravity 2.0 shared config path, falling back to the pre-2.0 IDE path so installs that predate the migration still link.

func (*Antigravity) IsLinked

func (p *Antigravity) IsLinked(configPath string, serverName string) (bool, error)
func (p *Antigravity) Link(configPath string, opts LinkOptions) error

func (*Antigravity) ListServers

func (p *Antigravity) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*Antigravity) Name

func (p *Antigravity) Name() string

func (*Antigravity) NeedsBridge

func (p *Antigravity) NeedsBridge() bool

func (*Antigravity) PlannedEntry

func (p *Antigravity) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*Antigravity) Slug

func (p *Antigravity) Slug() string
func (p *Antigravity) Unlink(configPath string, serverName string) error

type AnythingLLM

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

AnythingLLM provisions the AnythingLLM desktop MCP config. Transport: native streamable HTTP (no bridge needed); AnythingLLM's token is "streamable" ("streamable-http" is rejected, and omitting type means SSE). Config uses standard { "mcpServers": { "name": {...} } } structure.

func (*AnythingLLM) Detect

func (p *AnythingLLM) Detect() (string, bool)

func (*AnythingLLM) IsLinked

func (p *AnythingLLM) IsLinked(configPath string, serverName string) (bool, error)
func (p *AnythingLLM) Link(configPath string, opts LinkOptions) error

func (*AnythingLLM) ListServers

func (p *AnythingLLM) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*AnythingLLM) Name

func (p *AnythingLLM) Name() string

func (*AnythingLLM) NeedsBridge

func (p *AnythingLLM) NeedsBridge() bool

func (*AnythingLLM) PlannedEntry

func (p *AnythingLLM) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*AnythingLLM) Slug

func (p *AnythingLLM) Slug() string
func (p *AnythingLLM) Unlink(configPath string, serverName string) error

type ClaudeCode

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

ClaudeCode provisions the Claude Code CLI MCP config. Transport: native HTTP (no bridge needed).

func (*ClaudeCode) Detect

func (c *ClaudeCode) Detect() (string, bool)

Detect overrides mcpServersProvisioner.Detect() because the config file ~/.claude.json has ~ as its parent, which always exists. Instead, check for the ~/.claude/ directory as the installation indicator. When CLAUDE_CONFIG_DIR points at an existing config file, that file wins over the home-directory default, matching Claude Code's own resolution order.

func (*ClaudeCode) IsLinked

func (p *ClaudeCode) IsLinked(configPath string, serverName string) (bool, error)
func (p *ClaudeCode) Link(configPath string, opts LinkOptions) error

func (*ClaudeCode) ListServers

func (p *ClaudeCode) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*ClaudeCode) Name

func (p *ClaudeCode) Name() string

func (*ClaudeCode) NeedsBridge

func (p *ClaudeCode) NeedsBridge() bool

func (*ClaudeCode) PlannedEntry

func (p *ClaudeCode) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*ClaudeCode) Slug

func (p *ClaudeCode) Slug() string
func (p *ClaudeCode) Unlink(configPath string, serverName string) error

type ClaudeDesktop

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

ClaudeDesktop provisions the Claude Desktop MCP config. Transport: stdio only (requires mcp-remote bridge).

func (*ClaudeDesktop) Detect

func (p *ClaudeDesktop) Detect() (string, bool)

func (*ClaudeDesktop) IsLinked

func (p *ClaudeDesktop) IsLinked(configPath string, serverName string) (bool, error)
func (p *ClaudeDesktop) Link(configPath string, opts LinkOptions) error

func (*ClaudeDesktop) ListServers

func (p *ClaudeDesktop) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*ClaudeDesktop) Name

func (p *ClaudeDesktop) Name() string

func (*ClaudeDesktop) NeedsBridge

func (p *ClaudeDesktop) NeedsBridge() bool

func (*ClaudeDesktop) PlannedEntry

func (p *ClaudeDesktop) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*ClaudeDesktop) Slug

func (p *ClaudeDesktop) Slug() string
func (p *ClaudeDesktop) Unlink(configPath string, serverName string) error

type ClientInfo

type ClientInfo struct {
	Name       string
	Slug       string
	Detected   bool
	Linked     bool
	Transport  string
	ConfigPath string
	// Notes carries the provisioner's post-link guidance (PostLinkNoter);
	// empty for clients without client-specific caveats.
	Notes []string
}

ClientInfo holds detection and link status for one client provisioner.

type ClientProvisioner

type ClientProvisioner interface {
	// Name returns a human-readable client name (e.g., "Claude Desktop").
	Name() string

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

	// Detect checks if this client is installed on the current system.
	// Returns the config file path if found, empty string if not installed.
	Detect() (configPath string, found bool)

	// IsLinked checks if a gridctl entry already exists in the config.
	IsLinked(configPath string, serverName string) (bool, error)

	// Link injects or updates the gridctl entry in the client config.
	Link(configPath string, opts LinkOptions) error

	// Unlink removes the gridctl entry from the client config.
	Unlink(configPath string, serverName string) error

	// NeedsBridge returns true if this stdio-only client requires the
	// mcp-remote bridge to reach the gateway's HTTP endpoint.
	NeedsBridge() bool

	// ListServers returns every MCP server entry present in the client
	// config at configPath, gridctl-created or not. It is the read-only
	// inverse of Link: a missing or empty config yields no entries and no
	// error, while an unparseable one returns an error so callers can warn
	// and continue scanning other clients. The config file is never written.
	ListServers(configPath string) ([]ServerEntry, error)
}

ClientProvisioner handles config for a single LLM client.

type Cline

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

Cline provisions the Cline VS Code extension MCP config. Transport: stdio only (requires mcp-remote bridge). Adds Cline-specific fields: "disabled" and "alwaysAllow".

func (*Cline) Detect

func (p *Cline) Detect() (string, bool)

func (*Cline) IsLinked

func (p *Cline) IsLinked(configPath string, serverName string) (bool, error)
func (p *Cline) Link(configPath string, opts LinkOptions) error

func (*Cline) ListServers

func (p *Cline) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*Cline) Name

func (p *Cline) Name() string

func (*Cline) NeedsBridge

func (p *Cline) NeedsBridge() bool

func (*Cline) PlannedEntry

func (p *Cline) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*Cline) Slug

func (p *Cline) Slug() string
func (p *Cline) Unlink(configPath string, serverName string) error

type ContinueDev

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

func (*ContinueDev) Detect

func (c *ContinueDev) Detect() (string, bool)

func (*ContinueDev) IsLinked

func (c *ContinueDev) IsLinked(configPath string, serverName string) (bool, error)
func (c *ContinueDev) Link(configPath string, opts LinkOptions) error

func (*ContinueDev) ListServers

func (c *ContinueDev) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates Continue's experimental MCP ARRAY, the one registered client that stores a list of objects (keyed by a "name" field) rather than a map.

func (*ContinueDev) Name

func (c *ContinueDev) Name() string

func (*ContinueDev) NeedsBridge

func (c *ContinueDev) NeedsBridge() bool

func (*ContinueDev) PlannedEntry

func (c *ContinueDev) PlannedEntry(opts LinkOptions) map[string]any

func (*ContinueDev) Slug

func (c *ContinueDev) Slug() string
func (c *ContinueDev) Unlink(configPath string, serverName string) error

type Cursor

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

Cursor provisions the Cursor editor MCP config. Transport: native streamable HTTP (no bridge needed). Cursor's remote entries are url-only; the client infers the transport from the endpoint.

func (*Cursor) Detect

func (p *Cursor) Detect() (string, bool)

func (*Cursor) IsLinked

func (p *Cursor) IsLinked(configPath string, serverName string) (bool, error)
func (p *Cursor) Link(configPath string, opts LinkOptions) error

func (*Cursor) ListServers

func (p *Cursor) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*Cursor) Name

func (p *Cursor) Name() string

func (*Cursor) NeedsBridge

func (p *Cursor) NeedsBridge() bool

func (*Cursor) PlannedEntry

func (p *Cursor) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*Cursor) Slug

func (p *Cursor) Slug() string
func (p *Cursor) Unlink(configPath string, serverName string) error

type DetectedClient

type DetectedClient struct {
	Provisioner ClientProvisioner
	ConfigPath  string
}

DetectedClient pairs a provisioner with its found config path.

type EntryPlanner

type EntryPlanner interface {
	PlannedEntry(opts LinkOptions) map[string]any
}

EntryPlanner is implemented by provisioners that can report the entry value Link would write, without touching any file. The wiring ownership manager hashes the planned value to detect staleness and to adopt identical pre-existing entries silently.

type GeminiCLI

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

GeminiCLI provisions the Gemini CLI MCP config. Transport: native streamable HTTP (no bridge needed).

func (*GeminiCLI) Detect

func (p *GeminiCLI) Detect() (string, bool)

func (*GeminiCLI) IsLinked

func (p *GeminiCLI) IsLinked(configPath string, serverName string) (bool, error)
func (p *GeminiCLI) Link(configPath string, opts LinkOptions) error

func (*GeminiCLI) ListServers

func (p *GeminiCLI) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*GeminiCLI) Name

func (p *GeminiCLI) Name() string

func (*GeminiCLI) NeedsBridge

func (p *GeminiCLI) NeedsBridge() bool

func (*GeminiCLI) PlannedEntry

func (p *GeminiCLI) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*GeminiCLI) Slug

func (p *GeminiCLI) Slug() string
func (p *GeminiCLI) Unlink(configPath string, serverName string) error

type Goose

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

Goose provisions the Goose (Block) MCP config. Transport: native streamable HTTP (no bridge needed); Goose's extension type token is "streamable_http" (snake_case) and the URL field is "uri". Uses YAML format with "extensions" dictionary.

func (*Goose) Detect

func (g *Goose) Detect() (string, bool)

func (*Goose) IsLinked

func (g *Goose) IsLinked(configPath string, serverName string) (bool, error)
func (g *Goose) Link(configPath string, opts LinkOptions) error

func (*Goose) ListServers

func (g *Goose) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates entries under Goose's YAML "extensions" key. YAML deserialization can yield map[any]any values, so entries are normalized through toStringMap before being returned.

func (*Goose) Name

func (g *Goose) Name() string

func (*Goose) NeedsBridge

func (g *Goose) NeedsBridge() bool

func (*Goose) PlannedEntry

func (g *Goose) PlannedEntry(opts LinkOptions) map[string]any

func (*Goose) Slug

func (g *Goose) Slug() string
func (g *Goose) Unlink(configPath string, serverName string) error

type GrokBuild

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

GrokBuild provisions the Grok Build (xAI `grok` CLI) MCP config. Transport: native streamable HTTP (no bridge needed). Uses TOML format with an "mcp_servers" table keyed by server name.

func (*GrokBuild) Detect

func (g *GrokBuild) Detect() (string, bool)

func (*GrokBuild) IsLinked

func (g *GrokBuild) IsLinked(configPath string, serverName string) (bool, error)
func (g *GrokBuild) Link(configPath string, opts LinkOptions) error

func (*GrokBuild) ListServers

func (g *GrokBuild) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates entries under Grok Build's TOML "mcp_servers" table.

func (*GrokBuild) Name

func (g *GrokBuild) Name() string

func (*GrokBuild) NeedsBridge

func (g *GrokBuild) NeedsBridge() bool

func (*GrokBuild) PlannedEntry

func (g *GrokBuild) PlannedEntry(opts LinkOptions) map[string]any

func (*GrokBuild) Slug

func (g *GrokBuild) Slug() string
func (g *GrokBuild) Unlink(configPath string, serverName string) error

type ImportRead

type ImportRead = OpenCodeImportRead

ImportRead is the status vocabulary for one selected config file. The name is neutral; the fields are the OpenCode read result.

func ReadProjectSource

func ReadProjectSource(ctx context.Context, src ProjectSourceFile) (ImportRead, error)

ReadProjectSource reads exactly src.Path under src.ContainerKey. Statuses match ReadOpenCodeImport and carry no file contents. Cancellation is returned; a missing, unreadable, or malformed file is a status, not an error.

type LMStudio

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

LMStudio provisions the LM Studio desktop app MCP config (MCP host since 0.3.17). Transport: native streamable HTTP (no bridge needed). LM Studio follows Cursor's mcp.json notation and its remote entries are url-only; the in-app editor strips unknown keys such as "type", so none is written. Servers load lazily since 0.4.0, so a written entry connects on first use.

func (*LMStudio) Detect

func (p *LMStudio) Detect() (string, bool)

func (*LMStudio) IsLinked

func (p *LMStudio) IsLinked(configPath string, serverName string) (bool, error)
func (p *LMStudio) Link(configPath string, opts LinkOptions) error

func (*LMStudio) ListServers

func (p *LMStudio) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*LMStudio) Name

func (p *LMStudio) Name() string

func (*LMStudio) NeedsBridge

func (p *LMStudio) NeedsBridge() bool

func (*LMStudio) PlannedEntry

func (p *LMStudio) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*LMStudio) PostLinkNotes

func (c *LMStudio) PostLinkNotes() []string

PostLinkNotes returns the client-specific guidance shown after a successful link (CLI) and on the Connections detail pane.

func (*LMStudio) Slug

func (p *LMStudio) Slug() string
func (p *LMStudio) Unlink(configPath string, serverName string) error

type LinkOptions

type LinkOptions struct {
	GatewayURL string // e.g., "http://localhost:8180/mcp" (streamable HTTP)
	Port       int    // Gateway port for HTTP URL construction
	ServerName string // Key name in config (default: "gridctl")
	ClientID   string // Stable client identifier embedded as the `client` query param (empty = none)
	Group      string // Tool group whose endpoint to link (empty = the default full surface)
	Discovery  string // Per-link discovery override ("deferred" or "full"; empty = stack default)
	Force      bool   // Overwrite existing entry
	DryRun     bool   // Show what would change without modifying files
	// OwnershipResolved marks the caller (the pkg/wiring manager) as having
	// already decided ownership from the recorded lockfile state, so Link
	// writes without consulting the legacy shape heuristic or the
	// DeepEqual no-op check. Callers without recorded ownership leave it
	// false and keep the pre-lockfile behavior.
	OwnershipResolved bool
}

LinkOptions configures how a link is created.

type LinkResult

type LinkResult struct {
	Client     string // Human-readable client name
	ConfigPath string
	BackupPath string
	Action     string // "linked", "updated", "skipped", "already-linked"
	Transport  string // "native HTTP" or "mcp-remote bridge"
	Error      error
}

LinkResult describes what happened during a link operation.

type LocalScopeEntries

type LocalScopeEntries struct {
	Dir     string
	Entries []ServerEntry
	Status  string
	Detail  string
}

LocalScopeEntries is one Claude Code local-scope match, or a file-level status when the config could not be read. Dir is empty on a file-level status. A readable file with no matching project key yields no element.

func ClaudeCodeLocalScope

func ClaudeCodeLocalScope(ctx context.Context, configPath string, dirs []string) ([]LocalScopeEntries, error)

ClaudeCodeLocalScope reads configPath once and returns mcpServers entries under projects.<dir> for each directory in dirs that has that object. Path keys are compared after filepath.Clean, and case-insensitively on Windows. A missing, empty, malformed, or unreadable file yields one status element and no entries. A readable file whose projects key is absent, empty, or not an object yields no element, the same as a file with no matching directory. It does not abort the caller.

type OpenCode

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

OpenCode provisions the OpenCode AI coding assistant MCP config. Transport: native HTTP (no bridge needed). Uses "mcp" key with { "type": "remote", "url": "..." } entries.

func (*OpenCode) Detect

func (o *OpenCode) Detect() (string, bool)

func (*OpenCode) IsLinked

func (o *OpenCode) IsLinked(configPath string, serverName string) (bool, error)
func (o *OpenCode) Link(configPath string, opts LinkOptions) error

func (*OpenCode) ListServers

func (o *OpenCode) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates entries under OpenCode's "mcp" key.

func (*OpenCode) Name

func (o *OpenCode) Name() string

func (*OpenCode) NeedsBridge

func (o *OpenCode) NeedsBridge() bool

func (*OpenCode) PlannedEntry

func (o *OpenCode) PlannedEntry(opts LinkOptions) map[string]any

func (*OpenCode) Slug

func (o *OpenCode) Slug() string
func (o *OpenCode) Unlink(configPath string, serverName string) error

type OpenCodeImportRead

type OpenCodeImportRead struct {
	Status  string
	Detail  string
	Entries []ServerEntry
}

OpenCodeImportRead is the result of reading exactly one selected file.

func ReadOpenCodeImport

func ReadOpenCodeImport(ctx context.Context, path string) (OpenCodeImportRead, error)

ReadOpenCodeImport reads exactly path. It does not search for a sibling or a fallback file. Cancellation is checked before the read; the status is value-free and does not include file contents.

type OpenCodeImportSelection

type OpenCodeImportSelection struct {
	Path        string
	JSONPath    string
	JSONCPath   string
	Explicit    bool
	BothPresent bool
	JSONAbsent  bool
	Notes       []string
}

OpenCodeImportSelection is the single config file chosen for an OpenCode import. Detect's write target is a separate contract and is not changed here.

func DiscoverOpenCodeImport

func DiscoverOpenCodeImport(ctx context.Context, explicit string) (OpenCodeImportSelection, error)

DiscoverOpenCodeImport chooses the OpenCode file import will read. An empty explicit path uses the Gridctl home (GRIDCTL_HOME, otherwise the OS home): existing opencode.json, or opencode.jsonc only when that JSON file is absent. It does not scan the working directory, XDG_CONFIG_HOME, OPENCODE_CONFIG, OPENCODE_CONFIG_DIR, OPENCODE_CONFIG_CONTENT, or config.json. A non-empty explicit path is returned unchanged, with no sibling lookup.

type PostLinkNoter

type PostLinkNoter interface {
	PostLinkNotes() []string
}

PostLinkNoter is an optional interface for provisioners with client-specific guidance worth showing after a successful link. The CLI prints the notes below the link confirmation; the web UI surfaces them on the client's Connections detail pane.

type ProjectSource

type ProjectSource struct {
	RelPath      string
	ContainerKey string
	Clients      []string
	Scope        string
}

ProjectSource is one project-scoped MCP file gridctl knows how to read. RelPath is relative to a project directory. ContainerKey is the object that holds server entries. Clients are the provisioner slugs that read the file. Scope is the label attached to candidates from the file.

func ProjectSources

func ProjectSources() []ProjectSource

ProjectSources returns the project-scoped MCP files in scan order. opencode.jsonc precedes opencode.json so its entries win under first-occurrence keep. That order is a gridctl choice.

type ProjectSourceFile

type ProjectSourceFile struct {
	Dir          string
	Path         string
	Scope        string
	Clients      []string
	ContainerKey string
	Depth        int
}

ProjectSourceFile is one existing project file discovered by a walk. Dir is the project directory the relative path was resolved against. Depth 0 is the start directory. Path is the absolute file path.

func DiscoverProjectSources

func DiscoverProjectSources(ctx context.Context, startDir string, clients []string) ([]ProjectSourceFile, error)

DiscoverProjectSources stats project files from startDir up to the nearest git root, inclusive. An empty clients filter matches every table row. A non-empty filter keeps rows whose client set intersects it, and the returned Clients slice is that intersection in table order. Missing paths are omitted. Only regular files are returned. File contents are not read.

func DiscoverProjectSourcesIn

func DiscoverProjectSourcesIn(ctx context.Context, dirs []string, clients []string) ([]ProjectSourceFile, error)

DiscoverProjectSourcesIn stats project files in dirs. dirs is a nearest-first chain, typically from ProjectWalk. Depth is the index in dirs. It does not walk, does not read file contents, and does not stat paths outside dirs.

type Registry

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

Registry manages all known client provisioners.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a Registry with all known client provisioners.

func NewRegistryWith

func NewRegistryWith(clients ...ClientProvisioner) *Registry

NewRegistryWith creates a Registry with an explicit provisioner list. Production code uses NewRegistry; this exists so tests can drive registry consumers with fakes.

func (*Registry) AllClientInfo

func (r *Registry) AllClientInfo(serverName string) []ClientInfo

AllClientInfo returns detection and link status for every registered client.

func (*Registry) AllSlugs

func (r *Registry) AllSlugs() []string

AllSlugs returns the slugs of all registered clients.

func (*Registry) DetectAll

func (r *Registry) DetectAll() []DetectedClient

DetectAll returns all clients found on this system.

func (*Registry) FindBySlug

func (r *Registry) FindBySlug(slug string) (ClientProvisioner, bool)

FindBySlug returns the provisioner matching the given slug.

func (*Registry) IsAnyLinked

func (r *Registry) IsAnyLinked(serverName string) bool

IsAnyLinked checks if any known client has a gridctl entry.

type RooCode

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

RooCode provisions the Roo Code VS Code extension MCP config. Transport: native streamable HTTP (no bridge needed). Roo's schema requires an explicit "type" on url entries ("streamable-http" or legacy "sse"); the old "transportType" key is no longer in its schema. Adds Roo-specific fields: "disabled", "alwaysAllow".

func (*RooCode) Detect

func (p *RooCode) Detect() (string, bool)

func (*RooCode) IsLinked

func (p *RooCode) IsLinked(configPath string, serverName string) (bool, error)
func (p *RooCode) Link(configPath string, opts LinkOptions) error

func (*RooCode) ListServers

func (p *RooCode) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*RooCode) Name

func (p *RooCode) Name() string

func (*RooCode) NeedsBridge

func (p *RooCode) NeedsBridge() bool

func (*RooCode) PlannedEntry

func (p *RooCode) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*RooCode) Slug

func (p *RooCode) Slug() string
func (p *RooCode) Unlink(configPath string, serverName string) error

type ServerEntry

type ServerEntry struct {
	Name string
	Raw  map[string]any
}

ServerEntry is one raw MCP server definition found in a client config. Raw holds the entry object exactly as parsed; interpretation (command versus URL shapes, key spelling differences between clients) is the caller's concern.

type VSCode

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

VSCode provisions the VS Code / GitHub Copilot MCP config. Transport: native streamable HTTP (no bridge needed); "type": "http" is VS Code's streamable token, with client-side fallback to SSE. Uses "servers" key (not "mcpServers") and requires a "type" field.

func (*VSCode) Detect

func (v *VSCode) Detect() (string, bool)

func (*VSCode) IsLinked

func (v *VSCode) IsLinked(configPath string, serverName string) (bool, error)
func (v *VSCode) Link(configPath string, opts LinkOptions) error

func (*VSCode) ListServers

func (v *VSCode) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates entries under VS Code's "servers" key.

func (*VSCode) Name

func (v *VSCode) Name() string

func (*VSCode) NeedsBridge

func (v *VSCode) NeedsBridge() bool

func (*VSCode) PlannedEntry

func (v *VSCode) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implementations for the clients with bespoke Link logic.

func (*VSCode) Slug

func (v *VSCode) Slug() string
func (v *VSCode) Unlink(configPath string, serverName string) error

type Windsurf

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

Windsurf provisions the Windsurf (Codeium) editor MCP config. Transport: native streamable HTTP (no bridge needed). Entries are serverUrl-only; the client infers the transport from the endpoint.

func (*Windsurf) Detect

func (p *Windsurf) Detect() (string, bool)

func (*Windsurf) IsLinked

func (p *Windsurf) IsLinked(configPath string, serverName string) (bool, error)
func (p *Windsurf) Link(configPath string, opts LinkOptions) error

func (*Windsurf) ListServers

func (p *Windsurf) ListServers(configPath string) ([]ServerEntry, error)

ListServers implements the shared read-only enumeration for standard { "mcpServers": { name: {...} } } clients.

func (*Windsurf) Name

func (p *Windsurf) Name() string

func (*Windsurf) NeedsBridge

func (p *Windsurf) NeedsBridge() bool

func (*Windsurf) PlannedEntry

func (p *Windsurf) PlannedEntry(opts LinkOptions) map[string]any

PlannedEntry implements EntryPlanner for the shared mcpServers clients, including the per-client extra keys Link merges in.

func (*Windsurf) Slug

func (p *Windsurf) Slug() string
func (p *Windsurf) Unlink(configPath string, serverName string) error

type Zed

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

Zed provisions the Zed Editor MCP config. Transport: native streamable HTTP (no bridge needed). Zed's HTTP transport is streamable-only (no legacy SSE fallback), so the entry must point at /mcp. Uses "context_servers" key (not "mcpServers").

func (*Zed) Detect

func (z *Zed) Detect() (string, bool)

func (*Zed) IsLinked

func (z *Zed) IsLinked(configPath string, serverName string) (bool, error)
func (z *Zed) Link(configPath string, opts LinkOptions) error

func (*Zed) ListServers

func (z *Zed) ListServers(configPath string) ([]ServerEntry, error)

ListServers enumerates entries under Zed's "context_servers" key.

func (*Zed) Name

func (z *Zed) Name() string

func (*Zed) NeedsBridge

func (z *Zed) NeedsBridge() bool

func (*Zed) PlannedEntry

func (z *Zed) PlannedEntry(opts LinkOptions) map[string]any

func (*Zed) Slug

func (z *Zed) Slug() string
func (z *Zed) Unlink(configPath string, serverName string) error

Jump to

Keyboard shortcuts

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