Documentation
¶
Overview ¶
Package mcp persists and serves the user's configured MCP (Model Context Protocol) servers.
Storage model ¶
A single file at <configDir>/mcp.json, mode 0600, written atomically (temp + rename) via fileutil.SaveBytes. The file holds an ordered array of Server records. Order is the display order; no separate index.
Scope ¶
One scope only: user-global. Vibekit runs one container per user; per- chat or per-workspace MCP sets would add schema churn with no clear benefit. This matches how kiro-cli's mcpServers parameter is scoped to a session (not a chat), and we intentionally use the same set for every bridge spawned within the same container.
Secrets ¶
Env values and header values often contain API keys. Store returns them masked ("***") from public reads; Update preserves the stored value when the client sends "***" so the UI can round-trip without re-submitting secrets. On disk the file is plaintext with 0600 perms — the threat model is the same as the chat files that already live in the same directory (user's own container).
Index ¶
- Constants
- Variables
- func NewPrewarmRunner(ctx context.Context, store *Store) *prewarm.Runner
- func Validate(s *Server) error
- type KeyPair
- type RegistryEntry
- type RegistryEnvVar
- type RegistryHeader
- type RegistryPackage
- type RegistryProxy
- type RegistryRemote
- type Server
- type ServerID
- type ServerOption
- type Store
- func (s *Store) ACPServers(ctx context.Context) []map[string]any
- func (s *Store) Create(ctx context.Context, in *Server) (*Server, error)
- func (s *Store) Delete(ctx context.Context, id ServerID) error
- func (s *Store) EnabledNames(_ context.Context) map[string]struct{}
- func (s *Store) EnabledRaw(ctx context.Context) []*Server
- func (s *Store) EnabledServers(ctx context.Context) []prewarm.ServerInfo
- func (s *Store) Get(_ context.Context, id ServerID) *Server
- func (s *Store) List(ctx context.Context) []*Server
- func (s *Store) RegisterRoutes(mux *http.ServeMux)
- func (s *Store) SetEnabled(ctx context.Context, id ServerID, enabled bool) (*Server, error)
- func (s *Store) SetKnownTools(ctx context.Context, name string, tools []string)
- func (s *Store) SetOnChange(fn func(context.Context))
- func (s *Store) Update(ctx context.Context, id ServerID, in *Server) (*Server, error)
- type Transport
Constants ¶
const SecretMask = api.SecretMask
SecretMask references the shared api.SecretMask constant.
Variables ¶
var ( ErrNotFound = errors.New("server not found") ErrNameConflict = errors.New("server name already exists") ErrPersist = errors.New("persist failed") // ErrPersistMarshal and ErrPersistWrite are typed sub-sentinels for // ErrPersist. Callers can still match the parent via // errors.Is(err, ErrPersist); the sub-sentinels let the HTTP layer // log at different levels (marshal = programmer bug, write = transient infra). ErrPersistMarshal = fmt.Errorf("%w: marshal", ErrPersist) ErrPersistWrite = fmt.Errorf("%w: write", ErrPersist) )
Package-level errors. All HTTP handlers map these to 4xx responses; anything else is a 500. ErrPersist wraps the underlying filesystem error from SaveJSON's temp+rename so writeErr can route mutator failures to 500 with a generic body (no filesystem path leaked to the browser) while the full error still shows up in slog.
Functions ¶
func NewPrewarmRunner ¶
NewPrewarmRunner creates a prewarm runner using the store directly as the server lister (Store satisfies prewarm.ServerLister via its EnabledServers method).
Types ¶
type KeyPair ¶
KeyPair is an ordered env-var or header entry. Ordered (vs map) so the UI can edit entries without dropping duplicates; the on-wire ACP format is a JSON object so we flatten on export.
type RegistryEntry ¶
type RegistryEntry struct {
Name string `json:"name"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Version string `json:"version,omitempty"`
Repository string `json:"repository,omitempty"`
Packages []RegistryPackage `json:"packages,omitempty"`
Remotes []RegistryRemote `json:"remotes,omitempty"`
}
RegistryEntry is the browser-facing shape of one search result.
type RegistryEnvVar ¶
type RegistryEnvVar struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Format string `json:"format,omitempty"`
Required bool `json:"required,omitempty"`
Secret bool `json:"secret,omitempty"`
}
RegistryEnvVar / RegistryHeader describe a configurable field the user must fill in before the server will run.
type RegistryHeader ¶
type RegistryHeader struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Value string `json:"value,omitempty"`
Required bool `json:"required,omitempty"`
Secret bool `json:"secret,omitempty"`
}
RegistryHeader describes an HTTP header the user must configure before the remote server will run.
type RegistryPackage ¶
type RegistryPackage struct {
RegistryType string `json:"registry_type"`
Identifier string `json:"identifier"`
Version string `json:"version,omitempty"`
EnvVars []RegistryEnvVar `json:"env_vars,omitempty"`
}
RegistryPackage is one install option from a stdio-speaking server. Only npm and oci are surfaced; everything else is hidden so the UI doesn't offer install paths we can't fulfil on the container.
type RegistryProxy ¶
type RegistryProxy struct {
// contains filtered or unexported fields
}
RegistryProxy fetches + caches + shapes upstream registry responses.
func NewRegistryProxy ¶
func NewRegistryProxy() *RegistryProxy
NewRegistryProxy returns a ready-to-use proxy with sensible timeouts and a redirect allowlist. The default http.Client follows up to 10 redirects, which would let a compromised or moved upstream bounce the proxy to 169.254.169.254, 127.0.0.1, or a LAN service. We restrict redirects to the registry host itself and cap at 3 hops.
func (*RegistryProxy) RegisterRoutes ¶
func (p *RegistryProxy) RegisterRoutes(mux *http.ServeMux)
RegisterRoutes wires /api/mcp/registry/search.
GET /api/mcp/registry/search?q=<query>&limit=<n>
type RegistryRemote ¶
type RegistryRemote struct {
Type string `json:"type"`
URL string `json:"url"`
Headers []RegistryHeader `json:"headers,omitempty"`
}
RegistryRemote is one remote transport (http/sse) option.
type Server ¶
type Server struct {
URL string `json:"url,omitempty"`
Name string `json:"name"`
Command string `json:"command,omitempty"`
OAuthClientID string `json:"oauth_client_id,omitempty"`
OAuthClientSecret string `json:"oauth_client_secret,omitempty"`
ID ServerID `json:"id"`
Transport Transport `json:"transport"`
Args []string `json:"args,omitempty"`
Env []KeyPair `json:"env,omitempty"`
Headers []KeyPair `json:"headers,omitempty"`
DisabledTools []string `json:"disabled_tools,omitempty"`
AutoApprove []string `json:"auto_approve,omitempty"`
KnownTools []string `json:"known_tools,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
Prewarm bool `json:"prewarm,omitempty"`
Enabled bool `json:"enabled"`
}
Server is one user-configured MCP server. ID is a short stable identifier used in URLs and events (generated at create time); Name is the user-visible label that also becomes the kiro-cli mcpServer name (must be unique across the configured set).
func NewServer ¶
func NewServer(transport Transport, name string, opts ...ServerOption) (*Server, error)
NewServer constructs a Server with validated transport-specific fields. Fields inappropriate for the given transport are rejected at creation time rather than deferred to Validate(). Returns an error if the transport is unknown or if transport-incompatible fields are populated.
func (*Server) UnmarshalJSON ¶
UnmarshalJSON validates the transport field at the JSON parse boundary, rejecting unknown transport values early rather than letting them flow through to runtime dispatch. All three transports (stdio, http, sse) are accepted as first-class values and preserved verbatim — "sse" is no longer folded into "http", since KAS accepts a distinct SSE entry over the v3 wire (see the Transport doc in store.go).
type ServerID ¶
type ServerID string
ServerID is a validated identifier for an MCP server record. Values are generated at Create time via newID() and are base32-encoded random values. The type prevents accidental Name-as-ID confusion at compile time, mirroring api.ChatID and api.SessionID.
func ParseServerID ¶
ParseServerID validates a raw string as a server ID. Returns an error if the value is empty or exceeds 32 characters.
type ServerOption ¶
type ServerOption func(*Server)
ServerOption configures a Server during construction via NewServer.
func WithCommand ¶
func WithCommand(cmd string, args ...string) ServerOption
WithCommand sets the command for stdio transport servers.
func WithEnv ¶
func WithEnv(env []KeyPair) ServerOption
WithEnv sets environment variables for stdio transport servers.
func WithHeaders ¶
func WithHeaders(headers []KeyPair) ServerOption
WithHeaders sets HTTP headers for HTTP transport servers.
func WithOAuthClientID ¶
func WithOAuthClientID(id string) ServerOption
WithOAuthClientID sets the OAuth client ID for HTTP transport servers.
func WithURL ¶
func WithURL(url string) ServerOption
WithURL sets the URL for HTTP transport servers.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store holds the persisted list in memory plus the coordination needed to serialise writes and notify watchers on changes.
func New ¶
New loads the file (or initialises empty) and returns a ready store. onChange is invoked on a fresh goroutine (without the store mutex held) whenever the persisted set is mutated; nil is valid if no one cares. The callback is free to call back into the store — it won't deadlock on the caller's write lock. The ctx is stored for use in fire-and-forget persist paths (e.g. SetKnownTools) so writes are cancellable on shutdown.
func (*Store) ACPServers ¶
ACPServers satisfies api.MCPConfig by returning every enabled server shaped for kiro-cli's session/new / session/load mcpServers parameter. Secrets are included; the slice must not be logged.
func (*Store) Create ¶
Create inserts a new server, assigning a fresh ID and timestamps. Validates shape before persisting. Returns a masked copy of the stored record.
func (*Store) EnabledNames ¶
EnabledNames satisfies api.MCPConfig, returning the set of enabled server names for the hub's defensive filtering of init notifications.
func (*Store) EnabledRaw ¶
EnabledRaw returns a deep copy of every enabled server with secrets intact. Used when constructing the mcpServers parameter for kiro-cli. Not exposed over HTTP.
func (*Store) EnabledServers ¶
func (s *Store) EnabledServers(ctx context.Context) []prewarm.ServerInfo
EnabledServers returns the prewarm-relevant view of enabled servers. Satisfies prewarm.ServerLister so the Store can be passed directly to prewarm.NewRunner without an adapter.
func (*Store) List ¶
List returns a deep copy of every server with secrets masked. Safe to serve directly over the wire.
func (*Store) RegisterRoutes ¶
RegisterRoutes wires the MCP config endpoints.
GET /api/mcp → list (secrets masked)
POST /api/mcp → create
GET /api/mcp/{id} → one (secrets masked)
PUT /api/mcp/{id} → replace (preserves "***" values)
PATCH /api/mcp/{id} → toggle enabled: body {"enabled": bool}
DELETE /api/mcp/{id} → remove
func (*Store) SetEnabled ¶
SetEnabled flips the enabled flag for one server. Returns the updated masked copy.
func (*Store) SetKnownTools ¶
SetKnownTools updates the cached tool list for the named server. Called when kiro-cli reports commands/available with per-server tool names. Persists to mcp.json so the UI can show suggestions even when the server is disconnected. No-op if the server name isn't found, or if the incoming set is identical to what's already stored.
func (*Store) SetOnChange ¶
SetOnChange replaces the change callback.
type Transport ¶
type Transport string
Transport names the MCP transports vibekit accepts in mcp.json. "stdio" is universal; "http" is the Streamable HTTP transport (2025-03-26 MCP spec); "sse" is the legacy HTTP+SSE remote transport.
SSE is a first-class, stored transport (not normalized to "http"): kiro-cli v3 (KAS) re-advertises mcpCapabilities.sse:true and accepts a distinct {type:"sse", url, headers} entry on session/new (verified against the KAS 2.12 acp-server bundle + a live session/new probe; a bogus transport is rejected, so "sse" is genuinely accepted, not merely tolerated). "sse" and "http" share the same remote wire shape (url + headers) and differ only in the ACP `type` discriminator, so both are validated as remote transports and both round-trip through the store verbatim.
const ( TransportStdio Transport = "stdio" TransportHTTP Transport = "http" TransportSSE Transport = "sse" )
TransportStdio, TransportHTTP, and TransportSSE define the valid Transport values for MCP server connections.
func ParseTransport ¶
ParseTransport validates a raw string as a known transport. All three transports (stdio, http, sse) are first-class: "sse" is preserved as TransportSSE, not folded into "http" — KAS accepts a distinct SSE mcpServers entry over the v3 wire (see the Transport doc).