client

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package client provides the MCP client surface for the looprig/mcp module.

This file defines the typed error taxonomy every package in the module classifies failures with. All message text carried by Error is normalized and bounded at construction, and wrapped/server-derived text is rendered — bounded and normalized — only when no explicit message was provided; an explicit Msg fully suppresses it. Callers should supply an explicit Msg for auth-adjacent failures rather than relying on redaction, since bounded wrapped-error text is otherwise rendered verbatim.

Index

Constants

View Source
const (
	// ClientName is the MCP client name this module reports.
	ClientName = "looprig-mcp"
	// ClientVersion is the module version reported alongside ClientName.
	ClientVersion = "0.1.0"
	// ClientTitle is the human-readable client name.
	ClientTitle = "looprig MCP client"
)

The identity this module presents to servers. It is cosmetic — a server learns who is calling, not what the caller may do.

View Source
const (
	KindText         = "text"
	KindImage        = "image"
	KindAudio        = "audio"
	KindResource     = "resource"
	KindResourceLink = "resource_link"
	KindToolUse      = "tool_use"
	KindToolResult   = "tool_result"
	KindUnknown      = "unknown"
)

Values for Unsupported.Kind. They mirror the MCP wire "type" discriminator, with KindUnknown for a content type this module does not model.

View Source
const (
	// DefaultStartupTimeout bounds transport connect plus MCP initialize.
	DefaultStartupTimeout = 30 * time.Second
	// DefaultRequestTimeout bounds a single request/response exchange.
	DefaultRequestTimeout = 60 * time.Second
	// DefaultElicitationTimeout bounds how long a server-initiated
	// elicitation may wait for a human answer; generous because a person,
	// not a machine, is on the other end.
	DefaultElicitationTimeout = 5 * time.Minute
)

Default timeouts applied when the corresponding Timeouts field is zero.

View Source
const (
	// DefaultRetryAttempts is how many times an operation is tried in total
	// (the first try plus its retries).
	DefaultRetryAttempts = 5
	// DefaultRetryBaseDelay is the delay before the second attempt; each
	// subsequent delay doubles it.
	DefaultRetryBaseDelay = 200 * time.Millisecond
	// DefaultRetryMaxDelay caps one delay however far the backoff has doubled.
	DefaultRetryMaxDelay = 30 * time.Second
	// DefaultRetryMaxTotal caps the wall-clock time one retry loop may span,
	// including the attempts themselves.
	DefaultRetryMaxTotal = 2 * time.Minute
)

Default retry bounds, applied when the corresponding RetryPolicy field is zero.

View Source
const (
	// StateConfigured is a binding that has been validated but not started.
	StateConfigured = State(lifecycle.StateConfigured)
	// StateStarting is a binding whose transport is being established.
	StateStarting = State(lifecycle.StateStarting)
	// StateAuthenticating is a binding performing authentication.
	StateAuthenticating = State(lifecycle.StateAuthenticating)
	// StateDiscovering is a binding fetching its catalog from the server.
	StateDiscovering = State(lifecycle.StateDiscovering)
	// StateReady is a binding serving calls normally.
	StateReady = State(lifecycle.StateReady)
	// StateDegraded is a binding still serving calls but with reduced
	// capability or a known fault.
	StateDegraded = State(lifecycle.StateDegraded)
	// StateReconnecting is a binding re-establishing its transport.
	StateReconnecting = State(lifecycle.StateReconnecting)
	// StateFailed is a binding that is not serving calls.
	StateFailed = State(lifecycle.StateFailed)
	// StateClosing is a binding shutting down.
	StateClosing = State(lifecycle.StateClosing)
	// StateClosed is the terminal state.
	StateClosed = State(lifecycle.StateClosed)
)

The lifecycle states. See internal/lifecycle for the transition rules.

View Source
const DefaultLogLevel = LogInfo

DefaultLogLevel is the level requested when Definition.LogLevel is empty.

Info rather than Debug: a binding that installed a log handler wants to know what the server is doing, not to receive its trace output — and the debug stream of a chatty server is a volume problem that arrives on the connection's notification goroutine. A host that wants more asks for it.

View Source
const MaxMessageBytes = 1024

MaxMessageBytes bounds every message rendered by Error, both the explicit message passed to NewError and any wrapped-error text substituted for it. Longer text is truncated at a rune boundary and suffixed with a marker.

View Source
const MaxNameBytes = 64

MaxNameBytes is the maximum length of a Name in bytes.

View Source
const MaxProfileNameBytes = 64

MaxProfileNameBytes bounds a profile name. A profile name is an identifier that reaches status, diagnostics and configuration identity; it is not a description.

View Source
const MaxRetryAttempts = 64

MaxRetryAttempts caps RetryPolicy.Attempts. The bound exists so that a configured policy cannot express an effectively unbounded loop by way of a very large count; MaxTotal would still stop it, but a policy whose attempts are absurd is a configuration error worth reporting at Validate rather than silently ignoring at runtime.

Variables

View Source
var (
	// ProfileStrict tolerates nothing: every deviation from the specification
	// makes the offending catalog invalid.
	//
	// It is the right choice against a server you control, where a defect is a
	// bug worth failing over rather than a fact of life worth working around.
	ProfileStrict = Profile{Name: "strict", Version: 1}

	// ProfileDefault tolerates the deviations that are common in servers in the
	// wild and safe to absorb: a defective optional output schema, and a raw
	// name no inference provider would accept.
	//
	// It is what a Definition gets when it names no profile. Legacy SSE is not
	// in it: a transport is a deliberate choice, and a binding should not
	// acquire an older wire protocol by default.
	ProfileDefault = Profile{
		Name:    "default",
		Version: 1,
		Tolerances: []Tolerance{
			TolerateInvalidOutputSchema,
			TolerateDisplayNameNormalization,
		},
	}

	// ProfileLegacy is ProfileDefault plus the legacy SSE transport, for
	// interoperating with servers that predate Streamable HTTP.
	ProfileLegacy = Profile{
		Name:    "legacy",
		Version: 1,
		Tolerances: []Tolerance{
			TolerateInvalidOutputSchema,
			TolerateDisplayNameNormalization,
			TolerateLegacySSE,
		},
	}
)

The profiles this module ships. An application may also declare its own.

Functions

This section is empty.

Types

type Audio

type Audio struct {
	Data     []byte
	MIMEType string
}

Audio is an audio payload within Limits.MaxBinaryItemBytes.

type CallOpts

type CallOpts struct {
	// Progress, when non-nil, receives the call's progress notifications.
	//
	// Installing it is what asks the server for them: a server may only send
	// progress for a request that carries a progress token, and a token is only
	// attached when this is set. So a nil Progress does not merely discard the
	// notifications — it stops them being generated.
	//
	// It is invoked on the connection's notification goroutine and blocks it,
	// so it must not do work. Hand off anything expensive.
	//
	// Progress NEVER extends the call's deadline. See Deadline.
	Progress func(Progress)

	// Deadline bounds this call. Zero means now plus the binding's
	// Timeouts.Request.
	//
	// It is an absolute instant, fixed before the request is sent, and nothing
	// the server does afterwards moves it. In particular progress notifications
	// do not: a deadline that reset on activity would be a deadline the server
	// controls, and any server — hostile or merely stuck in a retry loop —
	// could hold a call, a goroutine and a permission open indefinitely just by
	// remaining chatty. A long-running tool needs a long deadline, which the
	// caller sets here, deliberately and in advance.
	//
	// The caller's ctx applies as well; whichever expires first wins.
	Deadline time.Time
}

CallOpts are the per-call options for CallTool.

type Catalog

type Catalog struct {
	// Binding names the server binding.
	Binding Name
	// Generation is the generation's ordinal within the binding. It is 0 in the
	// zero Catalog and 1 or more in any real one.
	Generation uint64
	// Digest is the hex canonical catalog digest. Two catalogs with the same
	// digest describe the same server offering; it does not depend on this
	// host's filter or policy.
	Digest string

	// ProtocolVersion is the version negotiated at initialize.
	ProtocolVersion string
	// Server is what the server claims to be. Cosmetic: it names a peer, it
	// never authorizes one.
	Server ServerIdentity
	// Capabilities is what the server advertised.
	Capabilities ServerCapabilities
	// Instructions is the server's bounded usage hint.
	//
	// It is reported, never applied. Concatenating it into a host's system
	// instructions would let any connected server acquire instruction authority
	// merely by being connected; an application that wants it must install it
	// deliberately.
	Instructions string

	// Tools are the tools this binding's ToolFilter permits, in a stable order.
	Tools []ToolSpec
	// Prompts are the prompts the server advertises.
	Prompts []PromptSpec
	// Resources are the concrete resources the server advertises.
	Resources []ResourceSpec
	// ResourceTemplates are the resource templates the server advertises.
	ResourceTemplates []ResourceTemplateSpec

	// Warnings records defects tolerated during discovery, such as a dropped
	// tool. It is bounded.
	Warnings []string
	// AppliedTolerances are the compatibility tolerances this generation
	// actually needed, in a deterministic order. It is empty for a server that
	// implements the specification faithfully — it reports what was bent, not
	// what the binding's profile would have allowed.
	AppliedTolerances []Tolerance
}

Catalog is a snapshot of the generation a binding has adopted.

The zero Catalog is what a binding with no adopted generation reports; Valid distinguishes it from a real, if empty, catalog.

func (Catalog) ToolByModelName

func (c Catalog) ToolByModelName(modelName string) (ToolSpec, bool)

ToolByModelName returns the tool a model knows as modelName. It is the reverse mapping a caller must use instead of parsing a model name.

func (Catalog) ToolByRawName

func (c Catalog) ToolByRawName(rawName string) (ToolSpec, bool)

ToolByRawName returns the tool the server calls rawName.

func (Catalog) Valid

func (c Catalog) Valid() bool

Valid reports whether c describes an adopted generation. It is false for the zero Catalog — a binding that has not discovered yet, or never will.

It is not the same as "has tools": a server may legitimately offer none.

type CatalogAdopted

type CatalogAdopted struct {
	// Binding names the binding.
	Binding Name
	// Generation is the newly adopted ordinal.
	Generation uint64
	// Digest is its hex catalog digest.
	Digest string
	// Previous is the ordinal it replaced, or 0 if there was none.
	Previous uint64
	// At is when adoption happened.
	At time.Time
}

CatalogAdopted reports that a candidate became the binding's adopted generation, at the caller's request. It is the completion of the sequence CatalogStale -> CatalogCandidate -> (caller's safe boundary) -> Adopt.

type CatalogCandidate

type CatalogCandidate struct {
	// Binding names the binding.
	Binding Name
	// Generation is the candidate's ordinal, and the value Adopt takes.
	Generation uint64
	// Digest is the candidate's hex catalog digest.
	Digest string
	// Adopted is the ordinal of the generation still in force.
	Adopted uint64
	// At is when the candidate was published.
	At time.Time
}

CatalogCandidate reports that a complete, validated generation was fetched and differs from the adopted one. It is the event a caller waits for before choosing a safe boundary at which to Adopt.

The candidate is not adopted by this event. Nothing a Loop can see has changed yet — that is the point of the candidate/adopted split.

type CatalogRefreshed

type CatalogRefreshed struct {
	// Binding names the binding.
	Binding Name
	// Generation is the ordinal of the generation still in force.
	Generation uint64
	// Digest is its hex catalog digest.
	Digest string
	// At is when the refresh completed.
	At time.Time
}

CatalogRefreshed reports that a refresh completed and produced a catalog identical to the adopted one — the server announced a change that, once refetched and digested, turned out not to change what this binding sees.

It is a distinct event from CatalogCandidate rather than a candidate nobody need adopt, because the two ask different things of a caller: a candidate needs a safe boundary and an Adopt, and this needs nothing at all. Publishing a no-op candidate would make every caller re-derive that distinction, and some would get it wrong by adopting — churning a toolset generation for a catalog that did not change.

type CatalogRejected

type CatalogRejected struct {
	// Binding names the binding.
	Binding Name
	// Class classifies why the refresh failed.
	Class FailureClass
	// Message is the failure's bounded, normalized text.
	Message string
	// Adopted is the ordinal of the generation still in force.
	Adopted uint64
	// Retrying reports whether the binding will try again under its policy.
	Retrying bool
	// At is when the refresh failed.
	At time.Time
}

CatalogRejected reports that a refresh failed to produce a usable candidate: the server was unreachable, its catalog was defective, or it exceeded a bound.

The prior adopted generation remains in force. A rejection never blanks a catalog that was valid — a server that starts answering badly makes its *changes* unavailable, not its existing tools.

type CatalogStale

type CatalogStale struct {
	// Binding names the binding whose catalog went stale.
	Binding Name
	// Family is the catalog family the server says changed: "tools",
	// "prompts", or "resources".
	Family string
	// At is when the notification was observed.
	At time.Time
}

CatalogStale reports that a server announced a change to one of its catalog families. It means a refresh has been scheduled, not that anything has been fetched, validated, or adopted.

It carries no delta: MCP's list-change notifications say only that a list changed, and a server's own account of how it changed would be untrusted input. What changed is knowable only from the candidate that follows.

type Client

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

Client is one connection to one MCP server. It is safe for concurrent use. A Client is always either usable or closed: Connect never returns one that failed to start.

func Connect

func Connect(ctx context.Context, def Definition, h Handlers) (*Client, error)

Connect establishes a binding: it validates def, opens a transport, performs the MCP handshake, and returns a ready Client. The whole sequence is bounded by def.Timeouts.Startup (or the caller's ctx, whichever fires first).

It fails closed at every step. On any error it returns a nil Client and an *Error bound to def.Name — the transport, if one was opened, is always closed before returning, so a failed Connect leaks nothing.

ctx bounds startup only; it does not govern the returned Client's lifetime. Cancelling it after Connect returns has no effect — use Close.

func (*Client) Adopt

func (c *Client) Adopt(generation uint64) error

Adopt makes the candidate generation the binding's adopted catalog.

The caller decides when. That is the design's rule and it is not a detail: adoption changes what a model sees, so it belongs at a boundary where nothing is mid-turn — knowledge this client does not have and must not guess at. What the client enforces is the other half: that adoption targets a candidate it validated, and the one the caller actually looked at.

generation must be the ordinal from Candidate. Passing any other value — a generation already adopted, one superseded by a later refresh, or one that was withdrawn when the server reverted its change — is refused with FailureCatalogStale rather than resolved to "whatever is current now". A caller that read a candidate, decided it was safe, and raced a refresh must re-read and re-decide; the client cannot make that decision for it, because the safety it established was about the catalog it saw.

Adoption is atomic and cannot half-apply: the generation is immutable and already validated, so this is a pointer swap. On success the candidate is consumed — a generation is adopted once.

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, rawName string, args json.RawMessage, opts CallOpts) (ToolResult, error)

CallTool invokes a tool by its raw server name — the name in ToolSpec.RawName, not the model-facing one.

args is the tool's arguments as raw JSON. They are sent verbatim; validating them against the tool's InputSchema is the caller's job, because the caller is the layer that knows which generation's schema it validated against.

A tool that reports failure comes back as a ToolResult with IsError set and a nil error. An error return means the call did not produce a result: the binding is not serving, the tool is not available, the deadline expired, the caller cancelled, or the connection failed.

Cancelling ctx cancels the call at the protocol level — the server is told to stop, not merely abandoned.

func (*Client) Candidate

func (c *Client) Candidate() (Catalog, bool)

Candidate returns the binding's outstanding candidate generation: a complete, validated catalog that differs from the adopted one and is waiting to be adopted.

It reports false when there is none, which is the normal state of a binding whose server has not changed. A candidate is not a promise that adopting it will succeed: a later refresh may supersede or withdraw it (see Adopt).

Like Catalog, it is the model-facing projection — the binding's ToolFilter is applied on the way out.

func (*Client) Catalog

func (c *Client) Catalog() Catalog

Catalog returns the binding's adopted catalog.

It is the model-facing projection: the tools are those the Definition's ToolFilter permits. See this file's header for why the filter lives here and not in the generation.

Before a binding is ready it returns the zero Catalog, whose Valid reports false. Connect never returns a Client that has not adopted a generation, so a caller holding one from Connect always gets a real catalog.

After Close it keeps returning the last adopted catalog rather than going blank. A catalog is an immutable snapshot of what a server offered, and that remains true after the connection is gone; a caller that wants to know whether the binding is usable asks Status, and every call path refuses a closed binding on its own. Blanking it would only destroy information a post-mortem wants.

func (*Client) Close

func (c *Client) Close(ctx context.Context) error

Close shuts the binding down and releases its transport. It is idempotent and safe to call concurrently: the first call performs the shutdown and reports any error closing the transport; later calls do nothing and return nil.

Closing a binding that already failed is not an error — shutdown is reachable from every non-terminal state, and a caller must never have to know which state it is in to release the resources.

func (*Client) GetPrompt

func (c *Client) GetPrompt(ctx context.Context, name string, args map[string]string) (Prompt, error)

GetPrompt fetches a prompt's messages, substituting args.

func (*Client) ReadResource

func (c *Client) ReadResource(ctx context.Context, uri string) (Resource, error)

ReadResource reads a resource by URI.

The URI is an opaque protocol identifier the server issued — not a host path. Nothing here resolves it against a filesystem.

func (*Client) Status

func (c *Client) Status() Status

Status returns a snapshot of the binding's observable state. It carries safe metadata only — no credentials, no payloads — and is a value, so the caller may keep or mutate it freely.

func (*Client) Subscribe

func (c *Client) Subscribe(ctx context.Context, uri string) error

Subscribe asks the server to report changes to a resource.

It is refused unless the server advertised resources *and* subscription: subscribing is a separate capability from reading, and a server that only advertised resources has not promised resources/subscribe exists.

func (*Client) Unsubscribe

func (c *Client) Unsubscribe(ctx context.Context, uri string) error

Unsubscribe asks the server to stop reporting changes to a resource.

It is the counterpart to Subscribe and is gated the same way: a server that never advertised resource subscription cannot have a subscription to end, so the call is refused rather than sent. After it returns, no further ResourceUpdated events arrive for uri.

Unsubscribing a resource that was never subscribed is the server's to judge, not this client's: MCP has no local record of what is subscribed, so the request is forwarded and whatever the server answers is returned.

type ClientCapabilities

type ClientCapabilities struct {
	// Elicitation lets the server ask the human for input mid-operation. The
	// server-initiated form of this feature is deprecated in spec revision
	// 2026-07-28 (SEP-2577) and served via multi-round-trip requests there; it
	// remains fully functional against peers negotiating ≤2025-11-25.
	Elicitation bool
	// Sampling lets the server request LLM completions from the client. The
	// server-initiated form of this feature is deprecated in spec revision
	// 2026-07-28 (SEP-2577) and served via multi-round-trip requests there; it
	// remains fully functional against peers negotiating ≤2025-11-25.
	Sampling bool
	// Roots exposes filesystem roots to the server. The server-initiated form
	// of this feature is deprecated in spec revision 2026-07-28 (SEP-2577)
	// and served via multi-round-trip requests there; it remains fully
	// functional against peers negotiating ≤2025-11-25.
	Roots bool
}

ClientCapabilities declares which optional client-side MCP capabilities the caller is prepared to serve for this connection. Everything defaults to off: a capability the host cannot actually honor must not be advertised.

type ConnectionLost

type ConnectionLost struct {
	// Binding names the binding.
	Binding Name
	// Class classifies the failure that ended the connection.
	Class FailureClass
	// Message is the failure's bounded, normalized text.
	Message string
	// Adopted is the ordinal of the generation still in force. It survives a
	// lost connection.
	Adopted uint64
	// Retrying reports whether the binding will try to reconnect. It is false
	// when policy forbids it, and false on the last report when the retry
	// budget is spent.
	Retrying bool
	// At is when the loss was observed.
	At time.Time
}

ConnectionLost reports that a binding's connection failed for a reason that means it is gone: a transport that closed, a stream that desynchronized.

It does not mean the binding is unusable. What it means is that every request in flight when it happened has failed — tool calls indeterminately, since the connection took the evidence with it — and that the binding is degraded until it reconnects.

type ConnectionRestored

type ConnectionRestored struct {
	// Binding names the binding.
	Binding Name
	// Server is what the reconnected server claims to be. Cosmetic: it names a
	// peer, it never authorizes one.
	Server ServerIdentity
	// Drift is bounded text describing how the server's identity differs from
	// the one this binding first connected to, or empty when it is the same
	// server. It is reported, never enforced — a restarted server with a new
	// version is the ordinary reason to be reconnecting.
	Drift string
	// Adopted is the ordinal of the generation still in force, which the
	// reconnect did not touch.
	Adopted uint64
	// Generation is the ordinal of the generation discovered over the new
	// connection. It is a candidate unless it was identical to the adopted one,
	// in which case there is nothing to adopt.
	Generation uint64
	// At is when the connection was restored.
	At time.Time
}

ConnectionRestored reports that a binding has a new logical connection, with its own handshake and its own freshly discovered catalog.

It is not an adoption. The catalog that came with the new connection is a candidate like any other: the caller adopts it at a safe boundary, because a socket reconnecting is not a reason to change what a model was told mid-turn.

type Content

type Content interface {
	// contains filtered or unexported methods
}

Content is a sealed union of the content kinds this client produces. Only this package can add a member, so a caller may exhaust it with a type switch — but it must still handle Unsupported, and should keep a default case: Unsupported is what a kind this module does not model becomes, and it exists precisely so that a new one is a case to handle rather than a panic.

type Definition

type Definition struct {
	// Name is the binding this server is mounted under.
	Name Name
	// Transport produces connections. Required.
	Transport TransportFactory
	// Timeouts holds per-connection deadlines; zero fields select defaults.
	Timeouts Timeouts
	// Limits bounds resource consumption; zero fields select defaults.
	Limits Limits
	// Capabilities declares the optional client capabilities to advertise.
	Capabilities ClientCapabilities
	// ToolFilter restricts which tools are visible and callable.
	ToolFilter ToolFilter
	// AllowParallelCalls opts in to bounded parallel tool calls.
	AllowParallelCalls bool
	// Compat is the named, versioned compatibility profile: how far this
	// binding will bend for a server that does not implement the specification
	// perfectly. The zero value selects ProfileDefault.
	//
	// It is part of the binding's secret-free configuration identity; see
	// Profile.Digest.
	Compat Profile
	// Reconnect governs rebuilding a connection that failed transiently. The
	// zero value reconnects under the default bounds.
	Reconnect ReconnectPolicy
	// Refresh bounds the retries of a catalog refresh that failed. Zero fields
	// select their defaults.
	//
	// A refresh is triggered by the server announcing a change to one of its
	// lists (see Client.Candidate). Failing one costs the binding its view of
	// that change, never its adopted catalog, so the policy here decides how
	// hard a binding tries to catch up — not whether it stays usable.
	Refresh RetryPolicy
	// LogLevel is the minimum severity of server log message to request. Zero
	// means DefaultLogLevel.
	//
	// It only takes effect when Handlers.Log is installed and the server
	// advertises logging: a server sends nothing until a level is set, so this
	// is what turns its logs on, and there is no point turning them on with
	// nowhere to deliver them.
	LogLevel LogLevel
}

Definition is the immutable, secret-free configuration for one MCP server connection. Zero Timeouts/Limits fields mean "use the default" and are filled in by normalized() at connect time; Validate never mutates. Treat a Definition as immutable once Validate has returned nil.

func (Definition) Validate

func (d Definition) Validate() error

Validate checks the whole definition and fails closed: the first violation is returned as a *Error with class FailureInvalidConfig, binding d.Name, and op "validate", naming the offending field. It never mutates d.

type ElicitAction

type ElicitAction uint8

ElicitAction is how a human answered an elicitation. The zero value is not a valid action: a handler must say what happened.

const (
	// ElicitAccept means the human supplied the requested content.
	ElicitAccept ElicitAction = iota + 1
	// ElicitDecline means the human refused to answer.
	ElicitDecline
	// ElicitCancel means the human dismissed the request without deciding.
	ElicitCancel
)

The elicitation outcomes, per the MCP elicitation result actions.

func (ElicitAction) String

func (a ElicitAction) String() string

String returns a stable lowercase identifier, or "unknown".

type ElicitMode

type ElicitMode uint8

ElicitMode is which kind of elicitation a server sent. The zero value is not a mode: a request the client cannot classify never reaches a handler, so a handler that switches on this may treat an unknown value as unreachable — but must still fail closed if it ever sees one.

const (
	// ElicitModeForm is a bounded schema of typed fields to fill in. Schema is
	// set (or nil for a bare confirmation); URL is empty.
	ElicitModeForm ElicitMode = iota + 1
	// ElicitModeURL is an out-of-band action at a URL. URL is set; Schema is
	// nil.
	ElicitModeURL
)

The elicitation modes MCP defines. They ask a person for different things, so a handler must branch: a form is answered, a URL is *visited*.

func (ElicitMode) String

func (m ElicitMode) String() string

String returns a stable lowercase identifier, or "unknown".

type ElicitRequest

type ElicitRequest struct {
	// Binding names the server that asked.
	Binding Name
	// Mode is the kind of elicitation. It is always a declared mode.
	Mode ElicitMode
	// Message is the bounded prompt to show the human.
	Message string
	// Schema is the JSON Schema the answer must satisfy, in form mode only; nil
	// otherwise. It is raw JSON because a schema is a serialization-boundary
	// document, not domain data.
	Schema json.RawMessage
	// URL is the action's URL, in url mode only; empty otherwise. It is bounded
	// but not otherwise vetted: whether it is safe to show, or to open, is the
	// host's decision and nothing the server says can make it.
	URL string
	// ElicitationID is the server's correlation id, in url mode only. It may be
	// empty even there: MCP does not require one.
	ElicitationID string
}

ElicitRequest is a server's request for human input. Every field but Binding is server-supplied and untrusted: the message is bounded text to show a person, the schema constrains the answer, and the URL is somewhere a server would like a human to go — none of them may be treated as an instruction to the host.

Which fields are meaningful is Mode's to say. The client enforces that: exactly the fields of the mode that arrived are populated, and the others are zero, so a handler cannot be led to act on a URL that came with a form.

type ElicitResult

type ElicitResult struct {
	Action  ElicitAction
	Content json.RawMessage
}

ElicitResult is the human's answer. Content is meaningful only when Action is ElicitAccept; it must satisfy the request's Schema, which the client re-checks before it reaches the server.

type ElicitationHandler

type ElicitationHandler interface {
	Elicit(ctx context.Context, req ElicitRequest) (ElicitResult, error)
}

ElicitationHandler serves server-initiated requests for human input. It is called on the connection's goroutine and must respect ctx, which carries the elicitation timeout.

Installing one is what allows the elicitation capability to be advertised.

The server-initiated form of this feature is deprecated in spec revision 2026-07-28 (SEP-2577) and served via multi-round-trip requests there; it remains fully functional against peers negotiating ≤2025-11-25.

type ElicitationRequested

type ElicitationRequested struct {
	// Binding names the server that asked.
	Binding Name
	// Mode is the kind of elicitation.
	Mode ElicitMode
	// At is when the request reached the handler.
	At time.Time
}

ElicitationRequested reports that a server asked for human input, and that the request passed the boundary's checks and reached the host's handler.

It deliberately carries none of what was asked. The message, the schema and — above all — the URL stay out of the event stream: design §Elicitation is explicit that the full action URL and its query parameters are not written to journals or ordinary events, and a URL's query string is where a server would put a token. Mode is what an observer needs and all it gets: enough to know a person is being asked something and what kind of thing it is.

It is not a promise that a human saw anything. What happened next is the handler's business, and ElicitationResolved is where it is reported.

type ElicitationResolved

type ElicitationResolved struct {
	// Binding names the server that asked.
	Binding Name
	// Mode is the kind of elicitation, repeated from the request so an observer
	// need not correlate to know what was answered.
	Mode ElicitMode
	// Action is what the server was told, or the zero action when the
	// elicitation failed instead of being answered.
	Action ElicitAction
	// Duration is how long the handler took: the time a person was being waited
	// on, which is the figure an operator wants and the only one this event can
	// honestly report.
	Duration time.Duration
	// At is when the outcome was observed.
	At time.Time
}

ElicitationResolved reports how an elicitation ended. Exactly one follows every ElicitationRequested.

Action is the answer that went to the server, and it is the zero ElicitAction ("unknown") when none did: a handler that failed, timed out, or answered with something the client refused to put on the wire. That is a real outcome and not a gap — the server was told the request failed — so it is reported rather than silently dropped, and it is distinguishable from a decline, which is a person's answer rather than a host's failure.

type EmbeddedResource

type EmbeddedResource struct {
	// URI is the resource's opaque protocol identifier. It is not a host path
	// and must never be resolved as one.
	URI       string
	MIMEType  string
	Text      string
	Data      []byte
	Truncated bool
}

EmbeddedResource is resource content inlined in a result. Exactly one of Text or Data is meaningful, per what the server sent.

type Error

type Error struct {
	// Class states what kind of failure occurred.
	Class FailureClass
	// Binding names the server binding the failure belongs to, if any.
	Binding Name
	// Op names the operation that failed (e.g. "initialize", "call_tool").
	Op string
	// Msg is a bounded, normalized human-readable description.
	Msg string
	// Err is the wrapped cause, if any.
	Err error
}

Error is the module's operational error. Msg is already normalized and bounded (NewError enforces this); construct values with NewError rather than a composite literal so the bound holds.

func NewError

func NewError(class FailureClass, binding Name, op string, msg string, wrapped error) *Error

NewError builds an *Error with msg normalized (control characters replaced by spaces, invalid UTF-8 repaired) and bounded to MaxMessageBytes.

func (*Error) Error

func (e *Error) Error() string

Error renders "mcp: <binding>: <op>: <class>: <msg>", omitting empty segments. When Msg is empty and a wrapped error is present, its text is substituted — bounded to MaxMessageBytes — so output length stays bounded regardless of what a server or transport produced.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the wrapped cause for errors.Is / errors.As traversal.

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is a client-emitted notification about a binding. It is a sealed union: only this package can add a member, so callers may exhaust it with a type switch — but must still tolerate an unknown member, since a later task adds events and a default case is what keeps that from breaking them.

type EventHandler

type EventHandler func(Event)

EventHandler receives binding events. It is invoked synchronously on the goroutine that caused the event and blocks that goroutine — a handler that needs to do work must hand it off. A nil EventHandler drops every event.

type Failure

type Failure struct {
	// Class states what kind of failure occurred.
	Class FailureClass
	// Message is bounded, normalized human-readable text.
	Message string
}

Failure is a classified, bounded summary of what went wrong on a binding. Its Message comes from an Error, so it is already normalized and capped at MaxMessageBytes.

type FailureClass

type FailureClass uint8

FailureClass classifies a failure so callers can branch on what went wrong without parsing error text. The zero value is not a valid class.

const (
	FailureInvalidConfig FailureClass = iota + 1
	FailureUnsupportedProtocol
	FailureStartupTimeout
	FailureAuthRequired
	FailureAuthDenied
	FailureAuthExpired
	FailureAuthFailed
	FailureTransportClosed
	FailureFraming
	FailureRemoteHTTP
	FailureServerProtocol
	FailureDeadline
	FailureCancelled
	FailureCatalogInvalid
	FailureCatalogStale
	FailureCatalogOverLimit
	FailureNotFound
	FailureToolUnavailable
	FailureToolSchemaChanged
	FailureRemoteToolError
	FailureLimitExceeded
	FailureElicitationDeclined
	FailureElicitationCancelled
	FailureElicitationInvalid
	FailureElicitationTimeout
	FailureSamplingDenied
	FailureSamplingOverBudget
	FailureIndeterminate
	FailureShutdown
)

Failure classes. Values are contiguous starting at 1; the zero value is reserved as "no class".

func ClassOf

func ClassOf(err error) (FailureClass, bool)

ClassOf walks err's chain and reports the class of the outermost *Error. It returns false when the chain contains no *Error.

func (FailureClass) String

func (c FailureClass) String() string

String returns a stable lowercase snake_case identifier for the class. Undeclared values return "unknown".

type Handlers

type Handlers struct {
	// Elicitation serves human-input requests. Nil means the elicitation
	// capability is not advertised.
	Elicitation ElicitationHandler
	// Sampling serves LLM completion requests. Nil means the sampling
	// capability is not advertised.
	Sampling SamplingHandler
	// Roots supplies filesystem roots. Nil means the roots capability is not
	// advertised.
	Roots RootsProvider
	// Log receives server log messages. Nil drops them.
	Log LogHandler
	// Event receives binding events. Nil drops them.
	Event EventHandler
}

Handlers are the application callbacks for one connection. It is legal to install a handler for a capability the Definition does not request — the handler is simply never called — but requesting a capability without its handler is a configuration error that Connect rejects, rather than a silent downgrade of what the application asked for.

type Image

type Image struct {
	Data     []byte
	MIMEType string
}

Image is an image payload within Limits.MaxBinaryItemBytes.

type Limits

type Limits struct {
	// MaxConcurrentRequests caps in-flight requests on one connection.
	MaxConcurrentRequests int
	// MaxCatalogPages caps list-pagination round trips per catalog fetch.
	MaxCatalogPages int
	// MaxCatalogItems caps total tools accepted from one server.
	MaxCatalogItems int

	// MaxFrameBytes caps a single wire frame (one JSON-RPC message).
	MaxFrameBytes int
	// MaxBodyBytes caps a whole HTTP response body.
	MaxBodyBytes int
	// MaxSchemaBytes caps one tool's input/output schema document.
	MaxSchemaBytes int
	// MaxSchemaDepth caps schema nesting to bound recursive traversal.
	MaxSchemaDepth int

	// MaxTextResultBytes caps the text content of one tool result.
	MaxTextResultBytes int
	// MaxStructuredBytes caps the structured content of one tool result.
	MaxStructuredBytes int
	// MaxBinaryItemBytes caps one binary (image/audio/blob) result item.
	MaxBinaryItemBytes int
	// MaxBinaryItems caps how many binary items one result may carry.
	MaxBinaryItems int

	// MaxLogMessageBytes caps one server log notification's payload.
	MaxLogMessageBytes int
	// MaxElicitMessageBytes caps one elicitation prompt: its message, and in
	// url mode the URL and correlation id shown with it. A server that exceeds
	// it has its request refused, not trimmed — see internal/protocol's
	// FromSDKElicitParams for why a prompt is the one server text this module
	// will not truncate.
	MaxElicitMessageBytes int
	// MaxElicitSchemaBytes caps the schema an elicitation may request. It is
	// separate from MaxSchemaBytes because a form a person fills in is not a
	// tool's interface, and raising one must not raise the other.
	MaxElicitSchemaBytes int
	// MaxPromptCount caps prompts accepted from one server.
	MaxPromptCount int
	// MaxResourceCount caps resources accepted from one server.
	MaxResourceCount int

	// MaxSamplingDepth caps nested sampling (sampling issued while serving
	// a sampling request).
	MaxSamplingDepth int
	// MaxSamplingConcurrency caps concurrent sampling requests.
	MaxSamplingConcurrency int
	// MaxSamplingTokens caps tokens per sampling completion.
	MaxSamplingTokens int
}

Limits bounds every resource an MCP server (or a runaway client caller) can consume on one connection. The zero value of any field selects the corresponding DefaultLimits value; negative values fail validation. There is deliberately no "unlimited" setting: a bound can be raised, never removed.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns the module defaults. Every field is non-zero (a test enforces this by reflection): defaults exist so that forgetting to configure a limit can never mean "unbounded".

type LogHandler

type LogHandler func(LogMessage)

LogHandler receives server log messages. A nil LogHandler drops them.

type LogLevel

type LogLevel string

LogLevel is the severity of a server log message, per the MCP logging levels (which follow syslog).

const (
	LogDebug     LogLevel = "debug"
	LogInfo      LogLevel = "info"
	LogNotice    LogLevel = "notice"
	LogWarning   LogLevel = "warning"
	LogError     LogLevel = "error"
	LogCritical  LogLevel = "critical"
	LogAlert     LogLevel = "alert"
	LogEmergency LogLevel = "emergency"
)

The MCP log levels, ordered least to most severe.

type LogMessage

type LogMessage struct {
	// Binding names the server that logged.
	Binding Name
	// Level is the severity the server claimed.
	Level LogLevel
	// Logger is the server-side logger name, if it sent one.
	Logger string
	// Text is the bounded message.
	Text string
}

LogMessage is one log record a server sent. Every field is server-supplied and bounded before it reaches a handler: Text is truncated to Limits.MaxLogMessageBytes. Treat it as diagnostics from an untrusted peer — never as a fact about the host.

type Name

type Name string

Name identifies a server binding (the configured name a server is mounted under). A valid Name is 1..MaxNameBytes bytes of [a-z0-9_-] and starts with a lowercase letter or digit (never '-' or '_').

func (Name) Validate

func (n Name) Validate() error

Validate reports whether n is a well-formed binding name. Violations are returned as *Error with class FailureInvalidConfig.

type Profile

type Profile struct {
	// Name identifies the profile. It is a stable identifier, not prose.
	Name string
	// Version distinguishes revisions of a profile under one name. Bump it
	// whenever the tolerances change, so that a record of "default v1" keeps
	// meaning what it meant.
	Version int
	// Tolerances are the deviations this profile permits. Order and duplicates
	// are not significant; Digest canonicalizes both.
	Tolerances []Tolerance
}

Profile is a named, versioned compatibility policy: the deviations a binding is willing to tolerate in a server.

It is secret-free by construction — a name, a version, and a set of enum values — and is part of the binding's configuration identity (see Digest). The zero Profile means "unset" and Definition.normalized replaces it with ProfileDefault; a Profile with a name and no tolerances is a real, strict policy and is left alone.

func (Profile) Digest

func (p Profile) Digest() string

Digest returns the profile's hex identity digest: the value a configuration manifest records so that "this session ran under this compatibility policy" is checkable rather than a claim.

Nothing in this module consumes it yet. It is the compatibility half of the design's manifest identity — which lists "compatibility policy digests" separately from the "adopted catalog digest" — and the manifest work is a later stage, so this is the seam waiting for it rather than something already wired. What a binding reports about its profile today is Status.CompatProfile ("name/vN"), which is a label for a human; this is the checkable form. See catalog.Generation.AppliedTolerances for why the two identities are separate and why neither belongs inside the other.

It is canonical. The tolerances are sorted and deduplicated first, so two profiles that permit the same things digest the same however they were written; and every value is length-delimited, so no two different profiles can encode to the same bytes. The framing is internal/canonical's, shared with the catalog and binding-identity digests: one encoder, so the three cannot drift apart into disagreeing about what a canonical encoding is.

It covers the whole profile, name and version included. Two profiles that permit the same deviations under different names are different policies: the name is what an operator reasons about and what a later revision is compared against, so a digest that ignored it would report no drift when "strict" was quietly swapped for "default" with the same contents.

func (Profile) Permits

func (p Profile) Permits(t Tolerance) bool

Permits reports whether the profile allows tolerance t.

func (Profile) String

func (p Profile) String() string

String renders the profile's identity as "name/version", which is what Status and diagnostics carry.

type Progress

type Progress struct {
	// Binding names the server reporting progress.
	Binding Name
	// Progress is how far the server claims to have got.
	Progress float64
	// Total is the server's claimed total, or 0 when it did not say.
	Total float64
	// Message is the server's bounded description of what it is doing.
	Message string
}

Progress is one progress report from an in-flight call. Every field is server-supplied: it is a hint to render, never a fact to act on — a server may claim any progress it likes, including going backwards or never finishing.

type Prompt

type Prompt struct {
	Description string
	Messages    []PromptMessage
}

Prompt is the outcome of GetPrompt.

Its messages are external content. They are not promoted into a host's instructions by being fetched: an application that wants a prompt to carry instruction authority must decide that itself.

type PromptArg

type PromptArg struct {
	Name        string
	Title       string
	Description string
	Required    bool
}

PromptArg is one argument a prompt accepts.

type PromptMessage

type PromptMessage struct {
	Role    string
	Content Content
}

PromptMessage is one message of a prompt. Role is the server's string verbatim ("user" or "assistant" in practice).

type PromptSpec

type PromptSpec struct {
	Name        string
	Title       string
	Description string
	Arguments   []PromptArg
}

PromptSpec is one prompt a server advertises.

type ReconnectPolicy

type ReconnectPolicy struct {
	// Disabled refuses reconnection entirely. A binding whose connection is
	// lost then stays degraded until its owner closes it.
	//
	// It is a negative flag because the zero value must mean "the default", as
	// it does everywhere else in a Definition, and the default is to reconnect.
	// An application disables this when re-establishing the connection costs
	// something it would rather decide about itself — a subprocess with side
	// effects at startup, a metered endpoint, an auth flow that prompts a human.
	Disabled bool

	// RetryPolicy bounds the attempts. Zero fields select their defaults.
	//
	// It is embedded rather than named because a reconnect policy *is* a retry
	// policy plus the decision to retry at all; there is no second dimension for
	// a field name to distinguish.
	RetryPolicy
}

ReconnectPolicy governs whether and how hard a binding tries to rebuild a connection that failed transiently. The zero value reconnects under the default bounds, which is the useful default: a binding that gave up on the first dropped socket would make every server restart an operator's problem.

type RequestProgress

type RequestProgress struct {
	// Binding names the server reporting progress.
	Binding Name
	// Progress is how far the server claims to have got.
	Progress float64
	// Total is the server's claimed total, or 0 when it did not say.
	Total float64
	// Message is the server's bounded description of what it is doing.
	Message string
	// At is when the report was observed.
	At time.Time
}

RequestProgress reports one progress notification from an in-flight call.

It arrives only for calls that asked for progress (CallOpts.Progress): a server may only send progress for a request carrying a progress token, and a token is only attached when the caller installed a callback. So this event mirrors that stream for observers, it does not create one.

Progress is a server's claim about itself. It may go backwards, stall, or never finish, and it never extends a call's deadline (see CallOpts.Deadline).

type Resource

type Resource struct {
	Contents []ResourceContent
}

Resource is the outcome of ReadResource.

type ResourceContent

type ResourceContent struct {
	// URI is the item's opaque protocol identifier.
	URI       string
	MIMEType  string
	Text      string
	Data      []byte
	Truncated bool
}

ResourceContent is one item of a resource's contents. Exactly one of Text or Data is meaningful. Truncated reports that the payload was cut, or that a binary one was refused on a bound and only its metadata survives.

type ResourceSpec

type ResourceSpec struct {
	// URI is the resource's opaque protocol identifier — not a host path.
	URI         string
	Name        string
	Title       string
	Description string
	MIMEType    string
}

ResourceSpec is one concrete resource a server advertises.

type ResourceTemplateSpec

type ResourceTemplateSpec struct {
	URITemplate string
	Name        string
	Title       string
	Description string
	MIMEType    string
}

ResourceTemplateSpec is an RFC 6570 template for a family of resources.

type ResourceUpdated

type ResourceUpdated struct {
	// Binding names the binding whose subscribed resource changed.
	Binding Name
	// URI names the resource the server says changed. It may be a sub-resource
	// of the one that was subscribed to.
	URI string
	// At is when the notification was observed.
	At time.Time
}

ResourceUpdated reports that a server announced a change to a resource this binding subscribed to (see Client.Subscribe). It arrives only for a subscribed resource, and only until Client.Unsubscribe.

Like CatalogStale it carries no delta: MCP's resource-update notification says only that a resource changed, never how, and a server's account of its own change would be untrusted input. A caller that wants the new value re-reads the resource with ReadResource. The URI is server-supplied and bounded before it gets here — it names what changed, it is not an instruction.

type RetryPolicy

type RetryPolicy struct {
	// Attempts is the total number of tries, including the first. 1 means try
	// once and do not retry. Zero means DefaultRetryAttempts.
	Attempts int
	// BaseDelay is the delay before the second attempt. Zero means
	// DefaultRetryBaseDelay.
	BaseDelay time.Duration
	// MaxDelay caps one delay. Zero means DefaultRetryMaxDelay.
	MaxDelay time.Duration
	// MaxTotal caps the loop's total wall-clock span. Zero means
	// DefaultRetryMaxTotal.
	MaxTotal time.Duration
}

RetryPolicy bounds a background retry loop. The zero value of any field selects its default; negative values fail validation.

type Root

type Root struct {
	// URI is the root's file:// URI.
	URI string
	// Name is a display name for the root.
	Name string
}

Root is a filesystem root exposed to a server. Exposing one grants a server knowledge of a path, never access to it: the host still mediates every read.

type RootsProvider

type RootsProvider interface {
	Roots(ctx context.Context) ([]Root, error)
}

RootsProvider supplies the filesystem roots visible to a server. It is called whenever the server asks, so a host may narrow the set at any time.

Installing one is what allows the roots capability to be advertised.

The server-initiated form of this feature is deprecated in spec revision 2026-07-28 (SEP-2577) and served via multi-round-trip requests there; it remains fully functional against peers negotiating ≤2025-11-25.

type SampleMessage

type SampleMessage struct {
	Role SampleRole
	Text string
}

SampleMessage is one turn of a sampling conversation.

type SampleOutcome

type SampleOutcome uint8

SampleOutcome is how a sampling request ended.

const (
	// SampleCompleted means the host produced a completion and the server got
	// it.
	SampleCompleted SampleOutcome = iota + 1
	// SampleDenied means the host refused: the binding's depth or concurrency
	// cap was reached, or the handler declined. It is an ordinary outcome — a
	// host is always entitled to decline to spend.
	SampleDenied
	// SampleFailed means the host tried and could not: the handler errored, ran
	// out of time, or returned something that could not go on the wire.
	SampleFailed
)

The sampling outcomes.

func (SampleOutcome) String

func (o SampleOutcome) String() string

String returns a stable lowercase identifier, or "unknown".

type SampleRequest

type SampleRequest struct {
	// Binding names the server that asked.
	Binding Name
	// SystemPrompt is the server's requested system prompt, bounded.
	SystemPrompt string
	// Messages is the bounded conversation to complete.
	Messages []SampleMessage
	// MaxTokens is the server's requested completion budget. The host caps it
	// against Limits.MaxSamplingTokens; a server never raises the ceiling.
	MaxTokens int
}

SampleRequest is a server's request for an LLM completion. Every field is server-supplied: the host decides which model runs it, and whether it runs at all.

type SampleResult

type SampleResult struct {
	// Model names the model the host actually used, which need not be one the
	// server asked for.
	Model string
	// Text is the completion.
	Text string
	// StopReason is why generation stopped, verbatim from the host.
	StopReason string
}

SampleResult is the completion the host produced.

type SampleRole

type SampleRole uint8

SampleRole is who authored a sampling message.

const (
	SampleRoleUser SampleRole = iota + 1
	SampleRoleAssistant
)

The sampling message roles.

func (SampleRole) String

func (r SampleRole) String() string

String returns a stable lowercase identifier, or "unknown".

type SamplingHandler

type SamplingHandler interface {
	Sample(ctx context.Context, req SampleRequest) (SampleResult, error)
}

SamplingHandler serves server-initiated LLM completion requests. Returning an error of class FailureSamplingDenied is how a host refuses one.

Installing one is what allows the sampling capability to be advertised.

The server-initiated form of this feature is deprecated in spec revision 2026-07-28 (SEP-2577) and served via multi-round-trip requests there; it remains fully functional against peers negotiating ≤2025-11-25.

type SamplingRequested

type SamplingRequested struct {
	// Binding names the server that asked.
	Binding Name
	// Messages is how many messages the conversation carried.
	Messages int
	// MaxTokens is the budget the host granted, after capping the server's
	// request against Limits.MaxSamplingTokens. It is what the handler was told
	// it may spend, not what the server asked for.
	MaxTokens int
	// Depth is this request's position in a sampling chain: 1 for a request the
	// binding was not already doing sampling work for, and n+1 for one that
	// arrived while an outbound request issued from a depth-n sampling handler
	// was in flight. See sampleGate for what this can and cannot see.
	Depth int
	// At is when the request arrived.
	At time.Time
}

SamplingRequested reports that a server asked for a completion, emitted when the request arrives rather than when it is admitted — a request the gate refuses is exactly the one an operator wants to see.

It is an audit record, so it carries the *shape* of the request and none of its content: no system prompt, no messages, no completion. A server's prompt may contain anything at all, including whatever it has been told by a tool result, and an event stream is not the place to find out. Everything here is a count, a cap, or a name this module chose.

type SamplingResolved

type SamplingResolved struct {
	// Binding names the server that asked.
	Binding Name
	// Outcome is how it ended.
	Outcome SampleOutcome
	// Model is the model the host used, or empty when none ran.
	Model string
	// Duration is how long the host was working: the figure an operator wants
	// when the question is what a server is costing them.
	Duration time.Duration
	// At is when the outcome was observed.
	At time.Time
}

SamplingResolved reports how a sampling request ended. Exactly one follows every SamplingRequested, including the ones the gate refused before any model ran.

Like SamplingRequested it carries no content: the completion is not here, and Model is safe to report because it is the host's own name for the host's own model — this module chose it, a server did not supply it.

type ServerCapabilities

type ServerCapabilities struct {
	// Tools reports whether the server exposes tools.
	Tools bool
	// Prompts reports whether the server exposes prompts.
	Prompts bool
	// Resources reports whether the server exposes resources.
	Resources bool
	// ResourcesSubscribe reports whether the server supports subscribing to
	// resource updates. Only meaningful when Resources is set.
	ResourcesSubscribe bool
	// Logging reports whether the server sends log messages.
	Logging bool
	// Completions reports whether the server supports argument autocompletion.
	Completions bool
}

ServerCapabilities is what a server advertised at initialize, reduced to the presence flags this client acts on. It reports what the server said it can do; it grants nothing.

type ServerIdentity

type ServerIdentity struct {
	Name    string
	Version string
	Title   string
}

ServerIdentity is what a server claims to be. Every field is server-supplied and cosmetic: it names a peer, it never authorizes one. Do not use it to make a trust decision.

type ServerLog

type ServerLog struct {
	// Binding names the server that logged.
	Binding Name
	// Level is the severity the server claimed.
	Level LogLevel
	// Logger is the server-side logger name, if it sent one.
	Logger string
	// Text is the bounded message.
	Text string
	// At is when the record was observed.
	At time.Time
}

ServerLog reports one log record a server sent. It is the Event-stream mirror of LogMessage, for an application that observes a binding through events alone rather than installing a separate log handler.

Every field is server-supplied and bounded (Text to Limits.MaxLogMessageBytes) before it gets here. It is diagnostics from an untrusted peer: never a fact about the host, and never an instruction.

type State

type State uint8

State is a binding's lifecycle state. It mirrors internal/lifecycle's State exactly — the constants below are derived from it, so the two can never drift numerically — and exists so that consumers depend on this package's contract rather than on an internal one. The zero value is not a valid state.

func (State) String

func (s State) String() string

String returns the state's stable lowercase identifier, or "unknown" for any value outside the declared range. The identifiers match internal/lifecycle's, which a test enforces.

type StateChanged

type StateChanged struct {
	// Binding names the binding that moved.
	Binding Name
	// From and To are the transition.
	From, To State
	// At is when the transition was observed.
	At time.Time
}

StateChanged reports a lifecycle transition. It carries safe metadata only.

type Status

type Status struct {
	// Binding names the server binding.
	Binding Name
	// State is the lifecycle state at the moment of the call.
	State State
	// ProtocolVersion is the version negotiated at initialize. Empty before
	// the handshake completes.
	ProtocolVersion string
	// Server is what the server said it is. Zero before the handshake.
	Server ServerIdentity
	// TransportKind names the transport, e.g. "stdio".
	TransportKind string
	// RedactedOrigin is a display origin that never contains credentials.
	RedactedOrigin string
	// Failure summarizes the last failure, or nil when there is none.
	Failure *Failure
	// LastChange is when the state last changed.
	LastChange time.Time

	// CatalogGeneration is the ordinal of the adopted catalog, or 0 before one
	// is adopted.
	CatalogGeneration uint64
	// CatalogDigest is the hex digest of the adopted catalog, empty before one
	// is adopted. It identifies the server's offering independently of this
	// host's policy, so two bindings reporting the same digest are looking at
	// the same catalog.
	CatalogDigest string

	// CandidateGeneration is the ordinal of the validated candidate awaiting
	// adoption, or 0 when there is none. See Client.Candidate.
	CandidateGeneration uint64
	// CandidateDigest is the candidate's hex catalog digest, empty when there
	// is no candidate.
	CandidateDigest string
	// StaleFamilies names the catalog families a server has announced a change
	// to and which have not been refetched since, as stable identifiers
	// ("tools", "prompts", "resources") in a deterministic order.
	//
	// It is normally empty even on a binding whose server changes constantly: a
	// family is stale only between the notification and the refetch that
	// answers it. A family that stays here is a binding whose refreshes are
	// failing — which State (degraded) and Failure describe.
	StaleFamilies []string

	// CompatProfile is the binding's compatibility profile as "name/vN". It is
	// part of the binding's configuration identity; Profile.Digest is the
	// checkable form.
	CompatProfile string

	// ReconnectAttempt is the reconnect attempt currently in flight, counting
	// from 1, or 0 when the binding is not reconnecting. It is the retry state
	// an operator reads to tell "trying" from "stuck": a binding that stays on
	// attempt 1 is dialing a server that never answers, and one whose attempts
	// climb is being refused repeatedly.
	ReconnectAttempt int
}

Status is a snapshot of a binding's observable state. It is a value: callers may hold, copy and mutate it freely without affecting the client.

type Text

type Text struct {
	Text      string
	Truncated bool
}

Text is a text payload. Truncated reports whether it was cut at Limits.MaxTextResultBytes; truncation is normal, not an error.

type Timeouts

type Timeouts struct {
	// Startup bounds connect plus initialize. Zero means
	// DefaultStartupTimeout.
	Startup time.Duration
	// Request bounds one request/response exchange. Zero means
	// DefaultRequestTimeout.
	Request time.Duration
	// Elicitation bounds a server-initiated elicitation round trip. Zero
	// means DefaultElicitationTimeout.
	Elicitation time.Duration
}

Timeouts holds the per-connection deadlines. The zero value of any field selects the corresponding default; negative values fail validation.

type Tolerance

type Tolerance uint8

Tolerance is one safe deviation a compatibility profile may permit. The zero value is not a valid tolerance.

Permitting a tolerance does not apply it: a server that implements the specification faithfully needs none of these, and a binding reports only what it actually had to bend (see Catalog.AppliedTolerances).

const (
	// TolerateInvalidOutputSchema drops a defective *optional* output schema and
	// keeps its tool, with a warning.
	//
	// It is safe because an output schema only describes what comes back: no
	// authority rests on it, and dropping it is what the bound existed for. The
	// input schema is never treated this way — a tool whose input schema is
	// missing or malformed is rejected, because keeping it would mean letting a
	// model send unconstrained arguments, which is the design's first named
	// unsafe tolerance.
	TolerateInvalidOutputSchema Tolerance = iota + 1

	// TolerateLegacySSE accepts the legacy SSE transport.
	//
	// It is safe because it is opt-in and narrow: it admits an older wire
	// protocol for interoperability, and weakens no validation, auth, limit or
	// cancellation rule. Without it a binding configured with an SSE transport
	// is refused at Validate — the design's "accepting a legacy SSE transport
	// only when explicitly configured", enforced rather than documented.
	TolerateLegacySSE

	// TolerateDisplayNameNormalization rewrites a raw name that inference
	// providers will not accept into one they will.
	//
	// It is safe because it is display-only and lossless where it counts: the
	// raw name is preserved and remains the only thing that ever goes on the
	// wire, and the mapping back is a lookup table rather than a reparse. A
	// binding without it refuses a tool it cannot show a model under its own
	// name, rather than showing it under a name that might mean something else.
	TolerateDisplayNameNormalization
)

The tolerances a profile may permit. This list is exhaustive and every member is safe by construction — see this file's header.

func (Tolerance) String

func (t Tolerance) String() string

String returns the tolerance's stable identifier, or "unknown" for any value outside the declared range.

type ToolAnnotations

type ToolAnnotations struct {
	Title           string
	ReadOnlyHint    bool
	IdempotentHint  bool
	DestructiveHint *bool
	OpenWorldHint   *bool
}

ToolAnnotations are a server's behavioural hints about a tool.

They are untrusted policy *input* and never authority: a server claiming ReadOnlyHint does not make a tool read-only, and a host must not skip a permission check because a tool said it was safe. The tri-state hints are pointers because "unspecified" differs from "false" — absent means the server said nothing, and the caller applies its own default.

type ToolFilter

type ToolFilter struct {
	// Allow, when non-empty, is the complete set of permitted raw names.
	Allow []string
	// Deny lists raw names that are always rejected.
	Deny []string
}

ToolFilter restricts which server tools are visible and callable. Entries are exact raw tool names (no globs, case-sensitive). An empty Allow set allows every tool; Deny always wins over Allow.

func (ToolFilter) Permits

func (f ToolFilter) Permits(rawName string) bool

Permits reports whether rawName passes the filter: denied names never pass; otherwise an empty Allow set passes everything, and a non-empty Allow set passes only its members.

type ToolResult

type ToolResult struct {
	// IsError reports that the tool itself failed.
	IsError bool
	// Content is the unstructured result, already bounded and converted.
	Content []Content
	// Structured is the tool's structured result, within
	// Limits.MaxStructuredBytes. Nil when the server sent none or an
	// over-bound one was dropped; see Warnings.
	Structured json.RawMessage
	// Warnings records defects tolerated while converting the result.
	Warnings []string
}

ToolResult is the outcome of a tool call.

A tool that fails is not an error here: IsError reports a protocol-level tool error — the call reached the server, ran, and the tool says it did not work — and Content carries its explanation. That is the design's rule, and it matters because the two failures need opposite handling: a tool error is information for the model to react to, while a transport error is a fault the host must handle. Collapsing them would either hide a broken connection inside a plausible-looking result, or turn a tool saying "no such file" into a binding failure.

type ToolSpec

type ToolSpec struct {
	// RawName is the server's own name for the tool. It is what goes on the
	// wire, and what CallTool takes.
	RawName string
	// ModelName is the sanitized, binding-qualified identity to show a model.
	// It is deterministic, unique within the catalog, and bounded to fit
	// inference-provider limits.
	//
	// It must never be parsed to recover RawName: sanitization is lossy, so the
	// mapping only holds in this direction. Resolve it with
	// Catalog.ToolByModelName.
	ModelName string

	Title       string
	Description string

	// InputSchema is the JSON Schema a model's arguments must satisfy. It is
	// raw JSON because a schema is a serialization-boundary document, not
	// domain data. It is always present.
	InputSchema json.RawMessage
	// OutputSchema describes the tool's result, when the server supplied a
	// usable one. Nil otherwise; see Warnings.
	OutputSchema json.RawMessage

	// InputSchemaDigest is the hex digest of InputSchema, for detecting a
	// schema change across generations without comparing documents.
	InputSchemaDigest string
	// OutputSchemaDigest is the hex digest of OutputSchema, empty when there is
	// no output schema.
	OutputSchemaDigest string

	// Annotations are the server's hints, or nil.
	Annotations *ToolAnnotations
	// Warnings records defects tolerated for this tool, such as a dropped
	// output schema.
	Warnings []string
}

ToolSpec is one tool as this binding sees it.

type TransportFactory

type TransportFactory interface {
	// Kind names the transport, e.g. "stdio", "streamablehttp", "sse".
	Kind() string
	// RedactedOrigin returns a safe display origin. It must never contain
	// credentials.
	RedactedOrigin() string
	// Connect establishes one connection using cfg.
	Connect(ctx context.Context, cfg protocol.ConnectConfig) (protocol.Conn, error)
}

TransportFactory produces connections for one configured transport (stdio, streamable HTTP, ...). It is exported but sealed by construction: Connect's signature uses internal/protocol types, so only packages inside this module can implement it.

type Unsupported

type Unsupported struct {
	// Kind is the content kind that was refused, one of the Kind* constants.
	Kind string
	// Bytes is the size of the refused payload. It is exact for an item dropped
	// on a size bound and a diagnostic lower bound for a kind dropped as
	// unsupported. Treat it as a diagnostic, never as an accounting figure.
	Bytes int
}

Unsupported stands in for content this client will not retain: a kind it does not model, or a payload over a bound.

It carries only bounded metadata, which is the point: the design requires that unknown or oversized content becomes bounded opaque metadata rather than being injected or silently disappearing. An item a caller cannot use is always still visible as an item.

Jump to

Keyboard shortcuts

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