mcp

package
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 34 Imported by: 0

Documentation

Overview

Package mcp wires user-configured Model Context Protocol servers into the agent loop.

At startup the host calls Build, which reads .agents/mcp.json, spawns each declared server (stdio child or Streamable HTTP client), wraps the resulting MCP toolsets via ADK's google.golang.org/adk/tool/mcptoolset, and returns:

  • the toolsets, so they can be passed to agent.New(WithToolsets…)
  • per-server records the host can render (e.g. a /mcp slash command).

Failures are non-fatal: a server whose process won't start surfaces in the per-server record with its error; the agent continues with whichever servers did connect.

Index

Constants

View Source
const (
	StatusOK    = "ok"
	StatusError = "error"
)

Status values surfaced via the per-server records.

View Source
const DefaultAgenticWrapThreshold = 8000

DefaultAgenticWrapThreshold is the default byte threshold below which MCP responses bypass the digest wrapper. 8000 bytes ≈ 2000 tokens — the wrap/router overhead exceeds the bloat cost below this.

View Source
const MCPFileName = "mcp.json"

MCPFileName is the project-local MCP config file inside .agents/.

Variables

This section is empty.

Functions

func DeclineHandler

func DeclineHandler(serverName string, send func(string)) func(context.Context, *mcp.ElicitRequest) (*mcp.ElicitResult, error)

DeclineHandler returns an MCP elicitation handler that declines every request and emits a one-line notice through send. Used as the fallback when no interactive elicitor is wired.

func InterpolateEnv

func InterpolateEnv(s string) string

InterpolateEnv is retained as a delegating alias for pkg/agentenv. The regex + resolver logic migrated to pkg/agentenv when the wider ${env:VAR} substitution mechanism landed (#322); this file keeps the symbol so lifecycle.go call sites don't need touching. New callers should use agentenv.NewResolver to get manifest-aware interpolation (fail-loud required checks, sensitive-value tracking, drift diagnostics) rather than this bare-os.Getenv path.

func InterpolateMap

func InterpolateMap(m map[string]string) map[string]string

InterpolateMap runs each value through InterpolateEnv. Used for ServerSpec.Env and ServerSpec.Headers. Same delegation note as InterpolateEnv applies.

func SetImplementationName

func SetImplementationName(name string)

SetImplementationName overrides the name reported during the MCP client handshake. Useful for hosts that want to identify themselves to the server. Call before Build.

Types

type AuthSpec

type AuthSpec struct {
	GoogleOAuth *GoogleOAuthAuth `json:"google_oauth,omitempty"`
}

AuthSpec selects an authentication strategy for an HTTP MCP server. Exactly one inner field may be set. Future strategies (audience- scoped ID tokens for Cloud Run / IAP, mTLS, etc.) slot in as sibling pointers.

func (*AuthSpec) Validate

func (a *AuthSpec) Validate(name string) error

Validate reports whether the AuthSpec is internally consistent and at least one strategy is set.

type DigestOptions

type DigestOptions struct {
	// Store is the CCR backing for retrieve_raw. When nil, digest
	// still runs but retrieve_raw returns "no raw payload" — matches
	// digest-design.md OQ1 default when --session-db is off.
	Store digest.Store

	// Threshold is the byte size below which responses bypass the
	// router entirely. Zero → DefaultAgenticWrapThreshold.
	Threshold int

	// NeverServers names MCP servers (by mcp.json key) that opt out
	// of digesting. Operator escape hatch for debug-sensitive or
	// known-tiny servers.
	NeverServers map[string]bool

	// LLMFallback, when non-nil, invokes a small-tier subagent to
	// digest MCP responses the structural JSON pruner can't reduce
	// (prose, malformed JSON, or JSON that's structurally minimal
	// and mostly-values). Callers pass a closure that owns a
	// reference to an Agent + a resolved small-model; the wrapper
	// invokes it with the raw payload and gets back a compressed
	// digest plus subagent usage numbers.
	//
	// Returned SubagentModel + token counts populate
	// digest.Savings.Subagent* on the resulting Result so /stats,
	// per-tool footer, and OTel span attributes have real cost
	// figures for the agentic path.
	//
	// A nil LLMFallback preserves the #128-shipped structural-only
	// behavior (bounded passthrough for anything structural can't
	// reduce). No opt-in from operators required to keep that.
	LLMFallback func(ctx context.Context, raw []byte) (LLMFallbackResult, error)

	// OnResult, when non-nil, fires after every successful Process
	// call with the (fully-decorated) Result. Callers use this to
	// aggregate per-call Savings into session-level counters — the
	// usage.Tracker sink in cmd/core-agent wires this to a cumulative
	// digest-savings block rendered by /context.
	//
	// Firing is best-effort: skipped on Process errors / marshal
	// failures where Result is undefined. Runs synchronously on the
	// wrapper's Run goroutine, so callers should keep the callback
	// fast (increment counters, don't do I/O).
	OnResult func(*digest.Result)
}

DigestOptions configures how Build wraps MCP tool responses through pkg/digest. A nil *DigestOptions passed to Build disables wrapping entirely (existing behavior). A non-nil options struct wraps every tool from every server that isn't in NeverServers.

LLMFallback opt-in (#223): a non-nil LLMFallback enables the LLM subagent digester for prose-shaped MCP responses that the structural pruner can't reduce below Threshold. Left nil, those responses take the bounded passthrough branch (#128's shipped default).

type ElicitorFn

type ElicitorFn func(ctx context.Context, serverName string, req *mcp.ElicitRequest) (*mcp.ElicitResult, error)

ElicitorFn is the host-supplied bridge that turns a server's elicitation request into a user response. Interactive hosts plug in a function that opens a prompt and blocks; the headless path leaves it nil and falls back to DeclineHandler.

The implementation must respect ctx — if it returns ctx.Err the SDK translates that into a protocol-level cancel.

type GoogleOAuthAuth

type GoogleOAuthAuth struct {
	// Scopes is the OAuth 2.0 scopes requested on the access token.
	// No default — each server documents its own scope requirements,
	// and an implicit broad default (e.g. cloud-platform) would grant
	// more privilege than necessary. Explicit is safer.
	//
	// For the GKE MCP server, typical values:
	//   https://www.googleapis.com/auth/container.read-only
	//   https://www.googleapis.com/auth/container
	Scopes []string `json:"scopes"`
}

GoogleOAuthAuth authenticates outbound MCP requests with a Google OAuth 2.0 access token sourced from Application Default Credentials. Suitable for Google-hosted API endpoints that accept scoped access tokens (e.g. the GKE MCP server at container.googleapis.com/mcp).

For audience-scoped ID-token auth (Cloud Run / IAP / custom OIDC), add a sibling GoogleIDToken field when a consumer needs it.

func (*GoogleOAuthAuth) Validate

func (g *GoogleOAuthAuth) Validate(name string) error

Validate reports whether the GoogleOAuthAuth is usable.

type LLMFallbackResult

type LLMFallbackResult struct {
	Text                 string
	SubagentModel        string
	SubagentInputTokens  int
	SubagentOutputTokens int
}

LLMFallbackResult is what an operator-supplied LLMFallback returns after running the raw MCP payload through a small-tier subagent. Text is the digest the model sees; SubagentModel + token counts feed digest.Savings.Subagent* for /stats + OTel display without pkg/mcp needing to import pkg/agent (which would create an import cycle down the line — pkg/agent lives above pkg/mcp in the layer hierarchy).

The caller (cmd/core-agent, or any host code) constructs the closure supplying LLMFallback and captures the *Agent needed to invoke RunSubtask in that closure's environment.

type Server

type Server struct {
	Name      string
	Status    string
	Tools     []string   // tool names exposed; populated lazily by Toolset
	ToolInfos []ToolInfo // name + description pairs, parallel to Tools
	Err       error      // non-nil when Status == StatusError
	// contains filtered or unexported fields
}

Server is one configured MCP server's runtime state.

func Build

func Build(ctx context.Context, agentsDir string, send func(string), gate *permissions.Gate, elicitor ElicitorFn, digestOpts *DigestOptions) ([]*Server, []tool.Toolset, error)

Build reads .agents/mcp.json and starts every declared server in parallel. The send callback is plumbed into each server's elicitation handler (when no interactive elicitor is provided) so the host can surface elicitation requests in the right place.

gate (optional) gates each MCP tool call through the permission system so MCP tools are subject to the same ask/allow/yolo rules as built-in tools. Pass nil to skip gating.

elicitor (optional) is the interactive bridge for elicitation requests. Headless callers leave it nil and fall back to the decline-with-notice stub.

Servers that fail to start come back with Status==StatusError so they're visible without breaking the rest of the agent.

func (*Server) Close

func (s *Server) Close()

Close terminates any child process this server owns. For HTTP transports there's no process to kill — Close is a no-op.

Termination strategy: SIGTERM, give the process up to 3 seconds to exit gracefully, then SIGKILL.

func (*Server) Toolset

func (s *Server) Toolset() tool.Toolset

Toolset returns the MCP toolset, or nil for failed servers.

type ServerSpec

type ServerSpec struct {
	Transport    string            `json:"transport"`               // "stdio" | "http"
	Command      string            `json:"command,omitempty"`       // stdio
	Args         []string          `json:"args,omitempty"`          // stdio
	Env          map[string]string `json:"env,omitempty"`           // stdio
	URL          string            `json:"url,omitempty"`           // http
	Headers      map[string]string `json:"headers,omitempty"`       // http
	Auth         *AuthSpec         `json:"auth,omitempty"`          // http
	AgenticNever bool              `json:"agentic_never,omitempty"` // skip digest wrap for this server
}

ServerSpec describes one MCP server. Either Command (stdio) or URL (Streamable HTTP) must be set; we intentionally don't support both.

AgenticNever opts this specific server out of the digest wrap layer — the operator escape hatch for debug-sensitive or known-tiny servers where wrapping would hurt more than it helps.

func (ServerSpec) Validate

func (s ServerSpec) Validate(name string) error

Validate checks that the spec describes a single, complete transport.

type Servers

type Servers struct {
	Version              int                   `json:"version"`
	Servers              map[string]ServerSpec `json:"servers"`
	AgenticWrap          *bool                 `json:"agentic_wrap,omitempty"`           // default true; ptr for explicit off
	AgenticWrapThreshold int                   `json:"agentic_wrap_threshold,omitempty"` // default 8000
	AgenticWrapLLM       *bool                 `json:"agentic_wrap_llm,omitempty"`       // default false; ptr for explicit on
	AgenticWrapModel     string                `json:"agentic_wrap_model,omitempty"`     // MCP-specific small-model override; empty falls through to --agentic-small-model resolution
}

Servers is the on-disk schema for .agents/mcp.json.

AgenticWrap + AgenticWrapThreshold are the operator knobs for the structural-digester wrap layer (#130). CLI --no-mcp-digest kills the whole surface regardless; per-server AgenticNever opts one server out without touching the global flag.

AgenticWrapLLM + AgenticWrapModel gate the #223 LLM subagent second-chance path — a small-tier subagent that digests responses the structural pruner can't reduce under threshold. Default off: the mechanism is safe but the cost profile depends on the operator's MCP surface, so opt-in until dogfooded.

func Load

func Load(agentsDir string) (Servers, error)

Load reads <agentsDir>/mcp.json. A missing file is treated as "no servers configured" — not an error, since most projects never declare MCP servers.

func (*Servers) AgenticWrapEnabled

func (s *Servers) AgenticWrapEnabled() bool

AgenticWrapEnabled reports whether the operator has opted the wrap layer in. Default true; the flag ships as a kill switch (matches #217 OTel posture — no --enable-* flags), so absence == on.

func (*Servers) AgenticWrapLLMEnabled

func (s *Servers) AgenticWrapLLMEnabled() bool

AgenticWrapLLMEnabled reports whether the operator has opted the LLM subagent second-chance path in via mcp.json. Absence == off (opposite of AgenticWrap): the structural pruner is a safe default but the LLM fallback trades wall-clock + subagent cost for its compression win, so opt-in until an operator confirms the trade-off works for their MCP surface.

func (*Servers) AgenticWrapThresholdBytes

func (s *Servers) AgenticWrapThresholdBytes() int

AgenticWrapThresholdBytes returns the operator-configured threshold or the built-in default when unset / zero.

type ToolInfo

type ToolInfo struct {
	Name        string
	Description string
}

ToolInfo is a name + description pair for one exposed MCP tool. Surfaced so the TUI's /mcp command can render the same rich "name + description" format /tools uses, without each consumer re-enumerating the toolset (which requires constructing a stub ReadonlyContext). Sorted by Name to match Tools' ordering.

Jump to

Keyboard shortcuts

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