Documentation
¶
Overview ¶
Package mcp is the shared MCP (Model Context Protocol) client layer: it connects to a configured mcp_servers: entry (config.MCPServer) over either Streamable HTTP (endpoint:) or a local subprocess speaking newline- delimited JSON on stdin/stdout (command:, see stdio.go), lists its tools, and calls one. internal/agent (a tool source for agent steps) and internal/resource (a check/in/out backend) both build on this package rather than importing the vendored SDK directly, so both share one transport/auth implementation.
Bearer and oauth auth are both supported (see config.MCPServerAuth); oauth token persistence and the interactive `steps mcp login` bootstrap live in oauth.go/login.go respectively.
Index ¶
- Variables
- func ForgetLogins(pipeline string) error
- func ListServerTools(ctx context.Context, pipeline string, srv config.MCPServer) ([]*sdkmcp.Tool, error)
- func Login(ctx context.Context, pipeline string, srv config.MCPServer, ...) error
- func LoginHosted(ctx context.Context, pipeline string, srv config.MCPServer, ...) error
- func LoginsExist(pipeline string) (string, bool)
- func MoveLogins(from, to string) error
- func PrintAndOpen(w io.Writer, open func(string) error, authURL string)
- func TokenPath(pipeline, server string) (string, error)
- type Client
- type HostedCallback
- type MemoryVault
- func (m *MemoryVault) Get(pipeline, server string) *TokenFile
- func (m *MemoryVault) Load(_ context.Context, pipeline, server string) ([]byte, string, bool, error)
- func (m *MemoryVault) Put(pipeline, server string, tf *TokenFile) error
- func (m *MemoryVault) Save(_ context.Context, pipeline, server, _ string, data []byte) error
- type TokenFile
- type TokenState
- type Vault
Constants ¶
This section is empty.
Variables ¶
var ErrLoginOrphaned = errors.New("the pipeline this login was read under is gone")
ErrLoginOrphaned is a write-back for a pipeline that is gone: destroyed, or its name taken by another since the login was read.
var ErrLoginsExist = errors.New("mcp logins already exist")
ErrLoginsExist is MoveLogins refusing a destination that already holds logins: merging would hand one pipeline another's accounts unnoticed, and overwriting would destroy them.
var ErrNeedsLogin = errors.New("run `steps mcp login`")
ErrNeedsLogin marks an oauth failure that only a human re-running `steps mcp login` can clear: no token on disk, one authorized for a different endpoint, an expired token with nothing to refresh it with, or a refresh the authorization server actively rejected.
It exists to split one question preflight cannot answer from the error text alone: would WAITING fix this? Every other way a server fails to answer — DNS, a VPN not up yet, a 502 — is a fact about right now, and a `steps web` that quits over one is a watcher found dead on Monday for a blip that healed in a minute. These four are facts about the credential, and no interval grows a refresh token. See config.Problem.Transient, which is what both preflight callers set from this.
Functions ¶
func ForgetLogins ¶
ForgetLogins removes every login pipeline has, for a destroy, so a later pipeline given the same name does not silently inherit them.
func ListServerTools ¶
func ListServerTools(ctx context.Context, pipeline string, srv config.MCPServer) ([]*sdkmcp.Tool, error)
ListServerTools connects to srv, lists its tools, and closes the connection — the discovery path `steps mcp tools <pipeline> <server>` calls, and a smoke test of connectivity/auth for any auth type.
func Login ¶
func Login(ctx context.Context, pipeline string, srv config.MCPServer, open func(url string) error) error
Login runs the interactive OAuth authorization-code + PKCE flow for an auth: {type: oauth} server and persists the resulting token to its per-pipeline token path (see TokenPath) — the implementation behind `steps mcp login <server>`. open is the browser-launch function (injected so this is testable without a real browser and so this package stays free of an os/exec dependency — see main.go's openBrowser for the production implementation), falling back to printing the URL to stdout whenever it returns an error.
Login does its own protected-resource/authorization-server metadata discovery and dynamic client registration up front (rather than letting auth.AuthorizationCodeHandler do it internally, as it would for an ordinary request-triggered flow) specifically so it can capture the resulting client_id/client_secret/token_endpoint for persistence — the handler never exposes what it discovered/registered internally, and a headless run/watch process needs those fields to silently refresh later without repeating discovery or registering a new client every time (see oauthTokenSource in oauth.go, which is what run/watch actually use — this function is never called from there, only from the CLI's login command).
func LoginHosted ¶
func LoginHosted(ctx context.Context, pipeline string, srv config.MCPServer, hosted *HostedCallback) error
LoginHosted is Login for a redirect that arrives through hosted, blocking until it does, ctx ends, or the flow fails. The token lands where Login puts it: on THIS machine, which is the one that will spend it.
func LoginsExist ¶
LoginsExist reports whether pipeline already has a login directory, and where, so a rename can refuse before anything has moved. A directory that cannot be looked at counts as existing: the rename it guards would otherwise go ahead over whatever is there.
func MoveLogins ¶
MoveLogins carries from's logins to to, for a pipeline rename, which keeps history and so keeps its logins. A pipeline with no logins is a no-op; a destination that exists, even empty, is ErrLoginsExist, checked here because os.Rename silently replaces an empty directory on Linux.
func PrintAndOpen ¶
PrintAndOpen announces an authorization URL the way `steps mcp login` always has.
Types ¶
type Client ¶
type Client interface {
ListTools(ctx context.Context) ([]*sdkmcp.Tool, error)
CallTool(ctx context.Context, name string, args map[string]any) (*sdkmcp.CallToolResult, error)
Close() error
}
Client is a live connection to one MCP server: list its tools, call one, and close when done. It wraps a *sdkmcp.ClientSession so callers never import the vendored SDK directly.
func Connect ¶
Connect opens a session to srv, over whichever transport it's configured for (see config.MCPServer.IsStdio/newTransport). For an HTTP server, per its configured auth (see config.MCPServerAuth): "bearer" reads a static token from the named env var (mirroring internal/agent's lookupAPIKey exactly); "oauth" loads a persisted, self-refreshing token from the per-pipeline token store (see oauth.go) — never an interactive flow here, that's `steps mcp login` (login.go) alone. "" / "none" connects unauthenticated. A stdio server is always unauthenticated by construction (config.validateMCPServerTransport rejects any other auth.type at load time), since there is no HTTP request to attach a token to.
type HostedCallback ¶
type HostedCallback struct {
// contains filtered or unexported fields
}
HostedCallback is one pending login's redirect target, mounted by whoever owns the HTTP server.
func NewHostedCallback ¶
func NewHostedCallback(redirectURL, returnURL string, announce func(authURL string)) *HostedCallback
NewHostedCallback builds the redirect target for one login. redirectURL is where the provider sends the browser, which the caller must route to ServeHTTP; returnURL is where the browser is sent afterwards, empty when a terminal rather than a page is waiting; announce is told the authorization URL, once per attempt, so it can travel to whoever has the browser.
func (*HostedCallback) Matches ¶
func (h *HostedCallback) Matches(state string) bool
Matches reports whether state belongs to this login's outstanding request. It is the whole of the route's authentication: a provider's redirect is a bare browser navigation carrying no credential of the daemon's, and the state is a nonce this process minted that only the party holding the authorization URL has seen.
func (*HostedCallback) ReturnsTo ¶
func (h *HostedCallback) ReturnsTo() string
ReturnsTo is where this login sends the browser when it is done, empty for one a terminal started. It is what tells a caller holding a replaced login whether there is a page to send its abandoned browser back to.
func (*HostedCallback) ServeHTTP ¶
func (h *HostedCallback) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP receives the redirect, under the rules loopback.go's handle already keeps: an unrecognized state is refused without consuming the flow, and a second delivery is a conflict rather than an answer.
type MemoryVault ¶ added in v0.2.0
type MemoryVault struct {
// contains filtered or unexported fields
}
MemoryVault keeps logins in memory, for tests that are not about where they land.
func (*MemoryVault) Get ¶ added in v0.2.0
func (m *MemoryVault) Get(pipeline, server string) *TokenFile
Get is the login saved for pipeline's server, or nil.
func (*MemoryVault) Load ¶ added in v0.2.0
func (m *MemoryVault) Load(_ context.Context, pipeline, server string) ([]byte, string, bool, error)
Load is the login saved for pipeline's server; a MemoryVault has one scope.
type TokenFile ¶
type TokenFile struct {
Endpoint string `json:"endpoint"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
TokenURL string `json:"token_url"`
Scopes []string `json:"scopes,omitempty"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
// contains filtered or unexported fields
}
TokenFile is one pipeline's login for one oauth-configured server, as handed to the Vault. It holds everything a later process needs to silently refresh an access token without repeating discovery or dynamic client registration: the resolved client credentials and token endpoint (captured once, during `steps mcp login` — see login.go) alongside the token itself. Endpoint guards against a stale login authorizing the wrong server if a name is ever reused for a different endpoint.
Never merkle-hashed, and never reaches the state database unsealed: the vault internal/cli installs encrypts it first. See docs/mcp.md's "Trust boundary" section.
func LoadTokenFile ¶
LoadTokenFile reads and parses the token file at path.
type TokenState ¶
type TokenState struct {
// Connected is a token that can be used right now, whether or not it can renew itself.
Connected bool
// Renews is a refresh token in hand, which is what makes a credential survive unattended.
Renews bool
// Detail is the sentence a person acts on: what is wrong, or that nothing is.
Detail string
}
TokenState is where one oauth server's saved credential stands. It carries no part of the credential itself — a page renders this, and the token is the one thing that must never reach one.
func InspectToken ¶
InspectToken reports the state of srv's saved credential from the saved login alone: no request, no refresh, nothing spent. The checks and their order are checkCredential's, which is what a run goes through, so the page and the run cannot disagree — but the WORDING is not, because a run's error names the login command to go and type and a page has a button for that.
type Vault ¶ added in v0.2.0
type Vault interface {
// Load is the login saved for server in pipeline, and scope names the pipeline it was read under: stable across a rename, different for a pipeline later set under a destroyed one's name. found is false when there is none.
Load(ctx context.Context, pipeline, server string) (data []byte, scope string, found bool, err error)
// Save replaces it. A non-empty scope is a write-back, refused with ErrLoginOrphaned unless the pipeline is still the one the login was read under; empty is a fresh login for whichever pipeline holds the name. A vault that cannot keep it safe refuses rather than keep it in the clear.
Save(ctx context.Context, pipeline, server, scope string, data []byte) error
}
Vault is where a pipeline's oauth logins are kept. internal/cli installs the one that seals them into the state database; this package hands it plaintext and never decides where it lands.