mcps

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package mcps wires MCP servers declared in baifo.yaml to ADK toolsets.

In Phase 2.x the package supports the two built-in MCPs (filesystem and browse) in-process. HTTP and stdio transports are accepted by the validator but not yet wired up — calling Tools on one of those returns ErrTransportNotImplemented.

Index

Constants

View Source
const (
	TypeBuiltin = "builtin"
	TypeHTTP    = "http"
	TypeStdio   = "stdio"
)

MCP types accepted in baifo.yaml.

View Source
const (
	BuiltinFilesystem = "filesystem"
	BuiltinBrowse     = "browse"
)

Built-in slugs accepted when type == "builtin".

Variables

View Source
var ErrTransportNotImplemented = errors.New("MCP transport not implemented yet")

ErrTransportNotImplemented is returned by Tools when the MCP entry uses a transport (http, stdio) whose wiring is still pending.

View Source
var ErrUnknownMCP = errors.New("unknown MCP")

ErrUnknownMCP is returned when a caller references an MCP name that was not declared in mcps[].

View Source
var ErrUnsupportedBuiltin = errors.New("unsupported built-in MCP")

ErrUnsupportedBuiltin is returned when type=builtin but the `builtin` slug is not one of the supported ones.

View Source
var ErrUnsupportedType = errors.New("unsupported MCP type")

ErrUnsupportedType is returned when an entry uses a type that baifo does not know how to handle.

Functions

This section is empty.

Types

type AuthenticateOptions

type AuthenticateOptions struct {
	// Force forgets any cached token + DCR client registration
	// and runs the full interactive flow from scratch. Used by
	// the `/mcps auth NAME --force` CLI path when the user
	// suspects a stuck credential.
	Force bool
}

AuthenticateOptions tweaks how Authenticate behaves. Zero value is the default ("reuse cached token if possible, prompt only when needed"). The Force flag is the only knob we expose today; add more here when the need arises rather than growing the Authenticate signature.

type AuthenticateResult

type AuthenticateResult struct {
	MCPName    string
	Reused     bool // true when an existing cached token was still valid
	ServerHint string
}

AuthenticateResult is what Authenticate returns on a successful OAuth flow. Today it carries only the MCP name plus a flag telling the caller whether we actually opened a browser or reused the cached token; the TUI uses it to print a precise chat row.

type BrowseConfig

type BrowseConfig = browse.Config

BrowseConfig groups the options handed to the built-in browse MCP when the registry instantiates it. Baifo's main wiring fills these from baifo.yaml (download_dir, optional Tavily/Serper keys, ...).

type Builders

type Builders struct {
	Filesystem FilesystemConfig
	Browse     BrowseConfig
}

Builders bundles the construction-time options for every built-in MCP. Callers pass it once to NewRegistry; each builtin instance is lazily created on first Tools() call.

type DCRClientStore

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

DCRClientStore persists the client credentials that a Dynamic Client Registration (RFC 7591) handshake produced for each MCP.

func NewDCRClientStore

func NewDCRClientStore(db *storage.DB) *DCRClientStore

NewDCRClientStore returns a store backed by storage SQLite DB.

func (*DCRClientStore) Delete

func (s *DCRClientStore) Delete(mcpName string) error

Delete removes the persisted client for mcpName, if any.

func (*DCRClientStore) Load

func (s *DCRClientStore) Load(mcpName string) (*PersistedDCRClient, error)

Load returns the persisted client for mcpName, or nil if none exists.

func (*DCRClientStore) Save

func (s *DCRClientStore) Save(mcpName string, c *PersistedDCRClient) error

Save writes c under the given mcpName, replacing whatever was there before.

type FilesystemConfig

type FilesystemConfig = filesystem.Config

FilesystemConfig groups the options handed to the built-in filesystem MCP. Kept thin on purpose so future per-agent overrides (sandbox path, log sink, ...) can be added here without touching every caller.

type PersistedDCRClient

type PersistedDCRClient struct {
	Issuer                  string `json:"issuer,omitempty"`
	ClientID                string `json:"client_id"`
	ClientSecret            string `json:"client_secret,omitempty"`
	RegistrationAccessToken string `json:"registration_access_token,omitempty"`
	RegistrationClientURI   string `json:"registration_client_uri,omitempty"`
}

PersistedDCRClient is the on-disk shape of a registered client.

type Registry

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

Registry validates the MCP declarations from baifo.yaml and resolves them on demand into ADK toolsets.

func NewRegistry

func NewRegistry(entries []config.MCPEntry, b Builders) (*Registry, error)

NewRegistry parses entries from baifo.yaml, validates each one, and returns a Registry ready to be queried by name.

func (*Registry) Authenticate

func (r *Registry) Authenticate(ctx context.Context, name string, opts AuthenticateOptions) (*AuthenticateResult, error)

Authenticate kicks the OAuth dance for the named MCP. The behaviour depends on auth.kind and credentials configured:

  • client_credentials: the SDK handler picks up a 401 challenge, fetches a fresh token at the discovered token endpoint, and persists it. No user interaction needed.

  • authorization_code (DCR / CIMD / preregistered): the SDK handler's AuthorizationCodeFetcher is wired to open the user's browser and spin a local server to receive the callback. We first try to reuse a token persisted from a previous run; if that works the call returns immediately with Reused=true.

The "kick" is a real MCP Initialize round trip — we connect a fresh mcp.Client against the transport, which triggers the SDK's auth machinery on the first 401/403. Doing it through the real protocol (instead of a synthetic HTTP GET) means a well-behaved MCP that authenticates at the JSON-RPC layer (i.e. inside Initialize, not on every HTTP method) still reaches the OAuth flow.

Authenticate is single-shot: when it returns, either a token has been persisted or an error explains why not. Callers should treat it as blocking for the duration of the round trip (seconds in the service flow, up to ~minute in the interactive one).

func (*Registry) ClearAuth

func (r *Registry) ClearAuth(name string) error

ClearAuth drops every cached credential baifo holds for the named MCP — the access/refresh token in TokenStore AND the Dynamic Client Registration credentials in DCRClientStore. The next Authenticate (or first agent message that touches the MCP) will start from scratch: re-register a client if DCR is needed, open the browser for the user, mint a fresh token.

Used by `/mcps logout NAME` and the Settings "Forget credentials" action when an authorisation gets stuck (consent revoked upstream, AS rotated keys, refresh token poisoned).

Returns nil even when nothing was cached — the operation is idempotent on purpose; the user just wants "no creds left", not a report.

func (*Registry) IsExternal

func (r *Registry) IsExternal(name string) (bool, error)

IsExternal reports whether the named MCP uses an external transport (http or stdio). Used by the agent builder to decide between the Tools() and Toolset() paths.

func (*Registry) Names

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

Names returns every registered MCP name. Useful for the TUI listing.

func (*Registry) Spec

func (r *Registry) Spec(name string) (Spec, error)

Spec returns the declaration for a given name, or ErrUnknownMCP.

func (*Registry) TestConnection

func (r *Registry) TestConnection(ctx context.Context, name string) (*TestConnectionResult, error)

TestConnection opens a fresh MCP session against the named server and lists its tools as a smoke test. Returns a populated TestConnectionResult on success or a descriptive error on failure. Built-in MCPs short-circuit to "always healthy" because they live in-process — there's nothing remote to test.

We use a deliberate 15 second deadline so a hung MCP doesn't freeze the TUI; that's enough for one round trip through OAuth + protocol initialise + ListTools on any reasonable network.

func (*Registry) Tools

func (r *Registry) Tools(name string) ([]tool.Tool, error)

Tools resolves the MCP named name into an ADK tool list. Built-in MCPs are constructed lazily and cached on first call so multiple agents that share an MCP also share its in-memory state (undo, scratch, processes). HTTP and stdio transports return ErrTransportNotImplemented for now.

func (*Registry) Toolset

func (r *Registry) Toolset(name string) (tool.Toolset, error)

Toolset returns a lazily-connected ADK Toolset for the named MCP. Use this for HTTP and stdio transports; builtin MCPs go through Tools() instead because they are in-process and don't need the MCP wire protocol.

The toolset connects to the server lazily on the first ListTools call (driven by the agent runtime). That means boot stays fast even when an MCP server is slow or unreachable; the failure surfaces at the user's first turn instead, with a clear "MCP server X is unreachable" message coming back from the model loop.

We do NOT cache the toolset between calls. Each Toolset() returns a fresh instance so reload events (config edits, secret rotation) produce a brand-new client with the updated transport. The MCP client itself memoises its connection internally, so this is not a correctness issue, only a small allocation.

func (*Registry) WithDCRClientStore

func (r *Registry) WithDCRClientStore(c *DCRClientStore) *Registry

WithDCRClientStore attaches the SQLite-backed Dynamic Client Registration persistence so the registry can reuse client_id / client_secret across boots and avoid registering a fresh client with the authorisation server on every authenticate call. Optional; when nil, every interactive flow re-registers.

func (*Registry) WithSecrets

func (r *Registry) WithSecrets(s *secrets.Store) *Registry

WithSecrets attaches a secrets store to the registry so HTTP header values and stdio env entries can reference ${secret:NAME} placeholders. Idempotent; callers typically wire it once in App.New right after the secrets store opens.

func (*Registry) WithTokenStore

func (r *Registry) WithTokenStore(t *TokenStore) *Registry

WithTokenStore attaches the SQLite-backed OAuth token persistence so the registry can reuse tokens across process restarts and let the underlying handler refresh them transparently. Optional; when nil, OAuth flows still work but tokens are lost on every boot.

type Spec

type Spec struct {
	Name string
	Type string

	// Builtin slug; only meaningful when Type == TypeBuiltin.
	Builtin string

	// HTTP transport fields.
	Endpoint string
	Headers  map[string]string
	Insecure bool

	// Stdio transport fields.
	Command string
	Args    []string
	Env     map[string]string
	Workdir string

	// Auth carries the authentication descriptor for HTTP MCPs. The
	// zero value means no authentication; Headers are still applied on
	// top of whatever auth produces, so a fully-static "X-API-Key"
	// setup uses Auth{Kind: none} + a header.
	Auth config.MCPAuth

	// Options carries the output-cap and tuning settings for
	// type=builtin MCPs. Ignored for all other MCP types.
	Options config.MCPOptions
}

Spec is the in-memory description of one MCP declaration. It mirrors config.MCPEntry but lives in this package so callers do not couple themselves to the on-disk YAML shape.

type TestConnectionResult

type TestConnectionResult struct {
	MCPName       string
	ServerName    string
	ServerVersion string
	ToolCount     int
	Elapsed       time.Duration
}

TestConnectionResult is the outcome of a TestConnection call. Fields are populated best-effort: ServerName / ServerVersion may be empty if the server doesn't supply InitializeResult.ServerInfo (it's optional per the MCP spec), and ToolCount is 0 when the server returns no tools.

type TokenStore

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

TokenStore persists OAuth tokens per MCP across process restarts.

func NewTokenStore

func NewTokenStore(db *storage.DB) *TokenStore

NewTokenStore returns a store backed by storage SQLite DB.

func (*TokenStore) Delete

func (s *TokenStore) Delete(mcpName string) error

Delete removes the token for mcpName, if any.

func (*TokenStore) Load

func (s *TokenStore) Load(mcpName string) (*oauth2.Token, error)

Load returns the token previously persisted for mcpName, or nil if no token exists.

func (*TokenStore) Save

func (s *TokenStore) Save(mcpName string, tok *oauth2.Token) error

Save writes tok under the given mcpName, replacing whatever was there before.

Directories

Path Synopsis
builtin
browse
Package browse implements the in-process built-in browse MCP.
Package browse implements the in-process built-in browse MCP.
browse/web
Package web is the low-level HTTP primitive used by the built-in browse MCP.
Package web is the low-level HTTP primitive used by the built-in browse MCP.
filesystem
Package filesystem implements the in-process built-in filesystem MCP.
Package filesystem implements the in-process built-in filesystem MCP.

Jump to

Keyboard shortcuts

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