Documentation
¶
Overview ¶
Package mcp implements evva's Model Context Protocol client. It loads MCP server configurations from settings.json, connects to each configured server via the official modelcontextprotocol/go-sdk, and surfaces discovered tools and resources so the agent can register them dynamically into the deferred-tool channel.
The package is Experimental — public types may change in a minor version (see docs/sdk-stability.md). Stabilization candidate for v1.7 or later once downstream consumers have exercised the surface.
Architectural seam:
- The host calls mcp.Load(workdir, evvaHome) once at boot to read the mcpServers block, then mcp.Open(ctx, cfg, opts) to build a *Manager. The Manager opens connections concurrently and is safe to call before any agent exists.
- The host passes the *Manager into agent.New via WithMcpManager; internal/agent installs it on the per-agent ToolState and registers a dynamic factory per discovered tool on pubtoolset.DefaultRegistry.
- When the model invokes mcp__server__tool, agent.ResolveTool builds the tool through the dynamic factory, which captures the Manager's session for that server.
Subagents inherit the parent's *Manager — no re-connection, no session duplication. The Manager is the single source of truth for every MCP interaction in the agent tree.
Transports supported: stdio (subprocess) and Streamable HTTP (2025-03-26 spec). SSE-only, WebSocket, SDK, claudeai-proxy, SSE-IDE, and WS-IDE transports are deliberately out of scope — see docs/roadmap/v1/v1-3-mcp.md §6.
Index ¶
- func BuildToolName(server, tool string) string
- func ConvertResult(r *mcpsdk.CallToolResult, server, tool, evvaHome string) (tools.Result, error)
- func ExpandEnv(s string) (expanded string, missing []string)
- func Load(workdir, evvaHome string) (*Config, []Warning)
- func NewListResourcesTool(m *Manager) tools.Tool
- func NewReadResourceTool(m *Manager) tools.Tool
- func NormalizeName(name string) string
- func Open(ctx context.Context, cfg *Config, opts OpenOptions) (*Manager, []Warning)
- func ToolNamePrefix(server string) string
- type Client
- type Config
- type ConfigScope
- type Manager
- type OAuthHandler
- type OAuthPrompt
- type OAuthPromptFn
- type OAuthPromptResult
- type OpenOptions
- type ServerConfig
- type ServerState
- type ServerStatus
- type ToolNameInfo
- type TransportType
- type Warning
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildToolName ¶
BuildToolName returns "mcp__<server>__<tool>" with both names normalized. Inverse of ParseToolName.
func ConvertResult ¶
ConvertResult turns an SDK CallToolResult into the agent's tools.Result. Text blocks concatenate into Content; image blocks become ContentBlocks; embedded binary resource blobs are persisted to disk under <evvaHome>/mcp-blobs/<random> and replaced with a "[binary saved at <path>]" line.
evvaHome is the resolved cfg.AppHome — passed in so this stays a pure conversion helper (no env reads, no global state). When evvaHome is empty, blob persistence is disabled and the conversion emits a "[binary received but no AppHome configured]" note instead. That is the only path that touches the filesystem.
func ExpandEnv ¶
ExpandEnv expands ${VAR} and ${VAR:-default} references in s using the process environment. Returns the expanded string and a slice of any referenced variables that were unset and had no default. Direct port of ref/src/services/mcp/envExpansion.ts:expandEnvVarsInString.
func Load ¶
Load reads .evva/settings.json (project) and <evvaHome>/settings.json (user), merges the mcpServers blocks (project wins on name collision), expands env vars, and returns the normalized config + non-fatal warnings. Missing files are not errors. Malformed entries become Warnings; the rest of the file still loads.
func NewListResourcesTool ¶
NewListResourcesTool builds the deferred list_mcp_resources tool against the supplied manager. A nil manager yields a tool that reports "no MCP manager configured" — safe for hosts with no MCP servers.
func NewReadResourceTool ¶
NewReadResourceTool builds the deferred read_mcp_resource tool against the supplied manager.
func NormalizeName ¶
NormalizeName maps an arbitrary server or tool name into the API-safe pattern ^[a-zA-Z0-9_-]{1,64}$. Replaces every invalid character with an underscore. Direct port of ref/src/services/mcp/normalization.ts: normalizeNameForMCP, minus the claude.ai-specific underscore-collapse branch (we don't ship claude.ai-proxy servers).
func Open ¶
Open is the one-call constructor: build a Manager, connect every non-disabled server in parallel, fetch each connected server's tool catalog, and return the result. Connection failures do not abort — they surface as Warnings and a per-server failed/needs-auth status.
func ToolNamePrefix ¶
ToolNamePrefix returns "mcp__<server>__" for the normalized server.
Types ¶
type Client ¶
type Client struct {
Name string
Config ServerConfig
// contains filtered or unexported fields
}
Client wraps one SDK ClientSession with the lifecycle policy this phase needs: lazy re-connect on session-expired, lock-protected swap of the underlying session, and a small set of convenience methods the dynamic tool factories call.
func (*Client) CallTool ¶
func (c *Client) CallTool(ctx context.Context, name string, args map[string]any) (*mcpsdk.CallToolResult, error)
CallTool invokes a tool, retrying once on session-expired.
func (*Client) ListTools ¶
ListTools fetches the server's tool catalog. Returns nil for disconnected / failed / auth-needed states (no error — these are expected states the manager already tracks).
func (*Client) Status ¶
func (c *Client) Status() ServerStatus
Status returns the live connection status under the read lock.
type Config ¶
type Config struct {
Servers []ServerConfig
}
Config is the merged + normalized server list ready for Open.
type ConfigScope ¶
type ConfigScope string
ConfigScope identifies where a server config was loaded from. Mirrors hooks/skills sourcing — workdir overrides user.
const ( ScopeUser ConfigScope = "user" // <APP_HOME>/settings.json ScopeProject ConfigScope = "project" // <workdir>/.evva/settings.json )
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager holds every Client and is the seam internal/agent threads through ToolState. Safe for concurrent use.
func NewManager ¶
func NewManager(opts OpenOptions) *Manager
NewManager returns an empty Manager configured from opts.
func (*Manager) DiscoveredToolNames ¶
DiscoveredToolNames returns every mcp__<server>__<tool> name across all connected servers, plus one mcp__<server>__authenticate name per needs-auth server, sorted. Called by internal/agent at profile-build time to extend the deferred allowlist.
func (*Manager) RegisterFactories ¶
func (m *Manager) RegisterFactories(reg *pubtoolset.Registry)
RegisterFactories registers a pubtoolset.ToolFactory for every tool discovered across every connected server, keyed by the qualified mcp__<server>__<tool> name, plus one authenticate factory per needs-auth server. Idempotent: duplicate registrations are absorbed (same instance, same factory).
func (*Manager) SetOnToolsChanged ¶
func (m *Manager) SetOnToolsChanged(fn func())
SetOnToolsChanged installs a callback the Manager fires after the authenticate flow discovers a server's real tools, so the host can refresh its deferred allowlist. nil-safe.
func (*Manager) Shutdown ¶
func (m *Manager) Shutdown()
Shutdown closes every active session. Idempotent; bound to the agent's RootContext cancel.
func (*Manager) Status ¶
func (m *Manager) Status() []ServerState
Status returns a snapshot of every server's runtime state, sorted by name for stable output.
type OAuthHandler ¶
type OAuthHandler struct {
// contains filtered or unexported fields
}
OAuthHandler wraps the SDK's auth.AuthorizationCodeHandler with one piece of evva-specific glue: the URL the SDK derives from the server's OAuth metadata is routed through promptFn so a human (or any other prompt sink the host chose) can confirm completion. A local HTTP listener captures the authorization-code redirect out of band, as the SDK's AuthorizationCodeFetcher contract requires.
func NewOAuthHandler ¶
func NewOAuthHandler(server string, logger *slog.Logger, promptFn OAuthPromptFn) *OAuthHandler
NewOAuthHandler builds a handler for one server. promptFn may be nil — fetchCode then returns a clear "no prompt callback installed" error.
func (*OAuthHandler) SDKHandler ¶
func (h *OAuthHandler) SDKHandler() (auth.OAuthHandler, error)
SDKHandler returns the SDK-shaped handler set on StreamableClientTransport.OAuthHandler. It stands up a local callback listener on 127.0.0.1:<random-port> so the authorization-code redirect can be captured; the redirect URI is registered via Dynamic Client Registration. The listener is closed when fetchCode returns (or after a backstop timeout if the flow never reaches the fetcher).
type OAuthPrompt ¶
type OAuthPrompt struct {
Server string // MCP server name from settings.json
AuthURL string // URL the user must open in their browser
}
OAuthPrompt is the data an MCP-driven OAuth flow needs to surface to the end user. The host receives this struct via OAuthPromptFn, shows it to the user however it sees fit (TUI dialog, ask_user_question, custom UI, headless allow-list), and returns whether the user completed the in-browser flow or cancelled.
pkg/mcp deliberately does NOT import internal/question. Callers adapt the OAuthPromptFn signature into whatever question/broker shape their host uses; the bundled cmd/evva does this in internal/agent/mcp_wiring.go.
type OAuthPromptFn ¶
type OAuthPromptFn func(ctx context.Context, prompt OAuthPrompt) (OAuthPromptResult, error)
OAuthPromptFn is the seam the host installs to surface auth URLs to the user. Returning OAuthCancelled aborts the auth. Returning a non-nil error is treated as a transport failure — the connect fails and the server's status moves to failed/needs-auth.
type OAuthPromptResult ¶
type OAuthPromptResult int
OAuthPromptResult is the user's decision after seeing an OAuthPrompt.
const ( // OAuthCompleted means the user reports the browser flow finished; the // local callback listener has captured the code+state. OAuthCompleted OAuthPromptResult = iota // OAuthCancelled aborts the connect — the server stays needs-auth until // the user retries via the per-server authenticate tool. OAuthCancelled )
type OpenOptions ¶
type OpenOptions struct {
// Logger receives mcp.* slog entries. nil yields a discard logger.
Logger *slog.Logger
// EvvaHome is the resolved per-user home dir used for binary-blob
// persistence under <EvvaHome>/mcp-blobs. Empty disables blob writes.
EvvaHome string
// OAuthPrompt is called when an HTTP MCP server requires OAuth. nil
// disables the interactive flow — needs-auth servers surface a clear
// error if their authenticate tool is invoked.
OAuthPrompt OAuthPromptFn
}
OpenOptions carries the host-supplied dependencies the Manager needs at construction time. Every field is optional with a defined nil/zero behavior so Open's signature stays stable as the option set grows.
type ServerConfig ¶
type ServerConfig struct {
Name string // map key from settings.json
Type TransportType
Disabled bool // "disabled": true skips connect
// Stdio fields
Command string // required when Type == TransportStdio
Args []string
Env map[string]string // ${VAR} / ${VAR:-default} expansion happens at Load
// HTTP fields
URL string // required when Type == TransportStreamableHTTP
Headers map[string]string
// Common
Timeout time.Duration // connect timeout; default 30s; max 600s
Scope ConfigScope // Project | User — for telemetry/logging only
}
ServerConfig is the parsed shape of one entry under mcpServers. Mirrors ref/src/services/mcp/types.ts McpStdioServerConfigSchema + McpHTTPServerConfigSchema, simplified to the two transports v1.6 ships.
type ServerState ¶
type ServerState struct {
Name string
Config ServerConfig
Status ServerStatus
Error string // populated for StatusFailed / StatusNeedsAuth
ToolCount int // number of tools discovered (0 unless Connected)
ResourceCount int // 0 unless server advertises resources/list capability
ConnectedAt time.Time // zero unless Connected
}
ServerState is what Manager.Status() returns per server.
type ServerStatus ¶
type ServerStatus string
ServerStatus is the live runtime state of one server's connection.
const ( StatusConnected ServerStatus = "connected" StatusPending ServerStatus = "pending" // Connect in flight StatusFailed ServerStatus = "failed" // Connect returned err; tools=0 StatusNeedsAuth ServerStatus = "needs-auth" // HTTP 401; auth tool offered StatusDisabled ServerStatus = "disabled" )
type ToolNameInfo ¶
ToolNameInfo is the parsed shape: server + tool.
func ParseToolName ¶
func ParseToolName(name string) *ToolNameInfo
ParseToolName extracts server + tool from a mcp__<server>__<tool> string. Returns nil if name lacks the prefix or has no tool segment. Known limitation: if a server name contains "__", parsing reports the first segment as the server. Server names with double-underscore are rare in practice (ref has the same limitation).
type TransportType ¶
type TransportType string
TransportType is the wire-level transport kind.
const ( TransportStdio TransportType = "stdio" TransportStreamableHTTP TransportType = "http" )