mcp

package
v1.26.3 Latest Latest
Warning

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

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

Documentation

Overview

attach.go — the exported boot-time MCP server attachment helper (absorbs cmd/harbor's attachDevMCPServer from INCLUDING the config→ToolPolicy projection that the devstack mirror had silently dropped).

Attach wires ONE configured MCP server into a running stack: it projects the operator-facing policy YAML onto the driver's runtime ToolPolicy fields, spawns the transport, opens the MCP session, discovers tools, registers each ToolDescriptor on the tool catalog, surfaces the live Provider on the Registry (with its configured per-server policy + seeded discovery stats), and threads the Provider's Close into the caller's closer chain so stack teardown drains the subprocess. Fail-loud on every step: a misconfigured / unreachable MCP server must not boot silently (CLAUDE.md §13).

Package mcp is Harbor's Model Context Protocol (MCP) southbound driver. It implements `tools.ToolProvider` against a remote MCP server, exposing the server's tools / resources / prompts as Harbor `Tool` entries (RFC §6.4). Three wire transports are supported (stdio, SSE, streamable-HTTP) with auto-detect.

Concurrent reuse: a constructed *Provider is safe to share across N concurrent goroutines after Connect returns. All per-call state lives on the goroutine stack + the request `ctx`; descriptor fields are immutable after Discover.

Identity (RFC §4): the (tenant, user, session) triple is forwarded to the remote MCP server in the request's `_meta` map so trust signals flow across the seam.

Reliability shell: every Invoke runs inside `tools.RunWithPolicy` so timeout / retry / classifier behaviour is identical to the in-process driver.

Index

Constants

View Source
const EventTypeMCPAppAvailable events.EventType = "mcp.app_available"

EventTypeMCPAppAvailable is the canonical event the runtime emits when an invoked MCP tool declares an interactive MCP App via the `_meta.ui.resourceUri` slot on its tool DEFINITION (the spec-conformant placement in the `io.modelcontextprotocol/ui` dialect, captured at discovery). It is the discovery signal a Protocol client consumes off the event stream to mount the inline MCP App renderer in the chat surface for the run's turn — without it, a planner-initiated call to a tool carrying a `ui://` app reference reaches no surface and the renderer never activates. The payload is SafePayload by construction: it carries the server source id, the `ui://` resource URI, the display-mode hint (empty unless the server supplied one — the renderer defaults to inline), the default-deny raw-HTML trust posture, and the actor identity quadruple — no upstream MCP content bytes and no caller-controlled argument data.

View Source
const EventTypeMCPArtifactEgressed events.EventType = "mcp.artifact_egressed"

EventTypeMCPArtifactEgressed is the canonical event the runtime emits when it resolves an artifact reference and places the resolved BYTES into an outbound MCP tool call — egress substitution.

It records the FACT of the substitution and nothing else: the artifact id, the server, the tool, the parameter, the byte count and a `sha256:` digest. Never the bytes.

It exists because dispatch-local byte movement would otherwise be the one content-movement path in Harbor that leaves no trace. The nearest thing an operator can do without this feature is instruct a model to paste content into a tool argument, which is bounded by the LLM edge's leak check and leaves a trajectory trail; egress substitution is MiB-scale and never enters the model's context, so it would leave none. The record closes that gap, and it is emitted FAIL-CLOSED before the wire request: a substitution that could not be recorded does not happen.

The payload is SafePayload by construction — ids, names, a size and a digest identify WHICH bytes moved without carrying them.

View Source
const EventTypeMCPResourceOffloaded events.EventType = "mcp.resource_offloaded"

EventTypeMCPResourceOffloaded is the canonical event the runtime emits when an MCP resource read (an MCP App `ui://` document fetch, or an app-tool-call result) meets the heavy-content threshold and is routed through the ArtifactStore by reference instead of inlined. It is the loud bypass record the context-window safety net requires — heavy content is never inlined past the threshold and never silently truncated; the offload is observable. The payload is SafePayload by construction: it carries the artifact reference id, the resource URI or tool name, the byte size, and the actor identity quadruple — no upstream MCP content bytes.

View Source
const EventTypeMCPResourceUpdated events.EventType = "mcp.resource_updated"

EventTypeMCPResourceUpdated is the canonical event type emitted when the remote MCP server pushes a resource-update notification for a URI the driver previously subscribed to. The payload is SafePayload by construction — the URI is operator- trust-equivalent (it originates from an operator-configured MCP server) and the source ID is operator-supplied.

View Source
const ResourceMIMEType = "text/html;profile=mcp-app"

ResourceMIMEType is the single canonical media type an MCP App (`io.modelcontextprotocol/ui`) UI document carries. It mirrors the ext-apps SDK's `RESOURCE_MIME_TYPE` constant; the host advertises it in the `mimeTypes` UI capability so a conformant server gates its `ui://` tool registration on it.

Variables

View Source
var (
	// ErrInvalidConfig — operator-side misconfiguration: missing URL
	// for HTTP-flavoured transports, missing Command for stdio,
	// unknown transport mode, etc.
	ErrInvalidConfig = errors.New("mcp: invalid config")
	// ErrTransportFailed — every candidate transport failed at
	// Connect time. The wrapped causes preserve the per-transport
	// failure messages.
	ErrTransportFailed = errors.New("mcp: transport failed")
	// ErrNotConnected — Discover / Invoke / SubscribeResource called
	// before Connect (or after Close).
	ErrNotConnected = errors.New("mcp: provider not connected")
	// ErrMCPToolError — the server returned a CallToolResult with
	// IsError == true. The wrapped message carries the rendered
	// text body.
	ErrMCPToolError = errors.New("mcp: server returned tool error")
	// ErrSchemaInvalid — the server-advertised InputSchema failed to
	// compile; the descriptor is rejected at Discover time so the
	// catalog never holds a Tool whose Validate is broken.
	ErrSchemaInvalid = errors.New("mcp: invalid tool input schema")
	// ErrIdentityMissing — the per-invocation ctx had no identity
	// triple. AGENTS.md §6 rule 9: identity is mandatory; the MCP
	// driver fails closed rather than dispatching to a remote server
	// with an empty `_meta` block. Mirrors the HTTP and A2A drivers.
	ErrIdentityMissing = errors.New("mcp: identity missing from ctx")
	// ErrEmptyBearer — a bound OAuth provider returned a nil error AND
	// an empty access token. Defence-in-depth on the fail-closed
	// surface: the call aborts rather than proceeding unauthenticated
	// on a connection the operator declared per-identity-authorized.
	ErrEmptyBearer = errors.New("mcp: oauth provider returned an empty bearer token")
	// ErrMetaPathCollision — two declared `_meta` paths on one connection
	// overlap: they are equal, or one is a prefix of the other, so writing
	// both would make the same node a scalar leaf and an intermediate map at
	// once. The declared set is every `meta_annotations` key (a dotted key is
	// a PATH) plus the credential-injection `meta_key`.
	//
	// Every validation door refuses a colliding declaration, so this is the
	// last-resort merge-time defence — reachable for a connection whose
	// revision was persisted before the path rules shipped, since nothing
	// rejected a colliding pair then. It fails the call rather than picking a
	// silent (and, under Go's randomised map iteration, non-deterministic)
	// winner. Callers compare with errors.Is.
	ErrMetaPathCollision = errors.New("mcp: declared _meta paths collide")
	// ErrAmbiguousOAuthBinding — a per-tool `oauth_provider` binding key
	// addresses more than ONE MCP surface on this server at once (e.g. both a
	// tool AND a prompt of the same name, or a resource whose URI equals a tool
	// name). The per-tool binding map is a single per-entry namespace keyed by
	// MCP-side name; when a key would bind more than one surface the operator's
	// intent is ambiguous, so the binding is rejected LOUD at discovery rather
	// than silently resolved by an undocumented precedence.
	ErrAmbiguousOAuthBinding = errors.New("mcp: ambiguous oauth_provider binding (key addresses more than one MCP surface)")
)

Sentinel errors. Callers compare with errors.Is.

View Source
var (
	// ErrServerNotFound — the named server is not registered.
	ErrServerNotFound = fmt.Errorf("mcp: server not found")
	// ErrRegistryIdentityMissing — the read ctx had no identity triple.
	// Identity is mandatory (AGENTS.md §6 rule 9); the read fails closed.
	ErrRegistryIdentityMissing = fmt.Errorf("mcp: identity missing from ctx")
	// ErrAmbiguousServerID — the server id being registered would make the
	// `<sourceID>_<tool>` catalog key space ambiguous against an
	// already-registered id. See CheckServerIDUnambiguous for why that
	// matters and what the rule is.
	ErrAmbiguousServerID = fmt.Errorf("mcp: ambiguous server id (separator collision with a registered server)")
)

Sentinel errors. Callers compare with errors.Is.

View Source
var ErrArtifactEgressNotEligible = errors.New("mcp: invalid artifact egress declaration")

ErrArtifactEgressNotEligible — a connection carries an artifact-parameter mapping without the operator's byte-eligibility declaration, or carries either on a transport that cannot deliver them. Callers compare with errors.Is.

View Source
var ErrArtifactEgressSchema = errors.New("mcp: artifact_params mapping does not match the server's discovered inputSchema")

ErrArtifactEgressSchema — an artifact-parameter mapping does not match the server's OWN discovered inputSchema: it names a tool the server does not declare, a parameter that tool does not declare, or a parameter the server declares as a non-string type.

It fails the ATTACH rather than the first call, so a server that changes its schema out from under a validated mapping is caught at the next attach loudly instead of at the next call silently. Callers compare with errors.Is.

View Source
var ErrArtifactEgressUnrecorded = errors.New("mcp: artifact egress substitution could not be recorded; the call is refused rather than moving bytes untraceably")

ErrArtifactEgressUnrecorded — a substitution could not be recorded, so it did not happen. Returned when no bus is wired, when the call context carries no identity, or when the publish itself failed.

Callers compare with errors.Is. It is a REFUSAL rather than a degraded path on purpose: the substitution record is the compensating control that makes byte-eligibility acceptable, and a byte movement that outlives its own record is exactly what the control exists to prevent.

View Source
var ErrConnectionNameOwnerConflict = errors.New("mcp: connection name already registered to a different owner")

ErrConnectionNameOwnerConflict — a same-name attach collided with a live registration owned by a DIFFERENT (tenant, agent). The idempotent same-name replace is scoped to the caller's OWN registration, so a cross-owner collision is rejected loud rather than tearing down another owner's live tools and transport. Callers compare with errors.Is.

View Source
var ErrOAuthBinding = errors.New("mcp: invalid oauth_provider binding")

ErrOAuthBinding — a connection's `oauth_provider` binding is invalid: it names an unregistered provider, sits on a stdio transport, or conflicts with a static `Authorization` header. Callers compare with errors.Is.

View Source
var ErrPreparationAuthRequired = errors.New("mcp: preparation requires authorization")

ErrPreparationAuthRequired marks a private MCP prepare that observed a structured HTTP authentication challenge before discovery could complete.

View Source
var ErrRedirectToUnlistedHost = errors.New("mcp: redirect target host is not in the bound provider's allowed_downstream_hosts")

ErrRedirectToUnlistedHost is the typed sentinel the MCP bearer client refuses a redirect with when the redirect target host is not in the bound provider's downstream-sink allow-list. Callers compare with errors.Is.

Functions

func Attach added in v1.3.0

func Attach(ctx context.Context, ms config.MCPServerConfig, deps AttachDeps) error

Attach preserves the boot-time one-shot API by preparing and immediately activating. Runtime control-plane callers use Prepare directly so durable desired state can be written between those stages.

func IsUIResourceURI added in v1.4.0

func IsUIResourceURI(uri string) bool

IsUIResourceURI reports whether uri carries the reserved `ui://` scheme — the distinct recognition the MCP Apps extension requires so an ordinary file:// / https:// resource is never mistaken for an app.

func IsValidTransportMode

func IsValidTransportMode(s string) bool

IsValidTransportMode is the exported helper used by `internal/config` to validate the raw string from YAML.

func ProjectToolPolicies added in v1.3.0

func ProjectToolPolicies(ms config.MCPServerConfig) (tools.ToolPolicy, map[string]tools.ToolPolicy, error)

ProjectToolPolicies converts an MCPServerConfig's operator-facing policy YAML into the driver's runtime ToolPolicy fields: the per-server default and the per-tool override map (keyed by the MCP server-side tool name). The config package owns the single config→policy translation seam (config.ToolPolicyConfig.ToToolPolicy); this helper performs only the trivial primitive→tools.ToolPolicy copy. It lives next to the driver (— promoted from cmd/harbor, where it was stranded because internal/config cannot import internal/tools). Any projection error (e.g. an unknown retry_on class) is returned so the boot path fails loud (CLAUDE.md §5).

A nil ms.Policy yields a zero-valued default policy, so the driver applies tools.DefaultPolicy() per-field at dispatch — preserving the no-policy behaviour exactly.

func ToolCallID added in v1.4.1

func ToolCallID(runID, serverID, tool string, args json.RawMessage) string

ToolCallID mints the stable, collision-free identifier for one tool invocation that declared an app. It is a deterministic content hash of the run / server / tool / args — NOT a counter or any mutable Provider field (the Provider is a compiled artifact, immutable after construction; per-call identity rides ctx, CLAUDE.md §5). Two invocations with distinct (run, server, tool, args) tuples never collide; the same tuple within a run is idempotent by construction (it re-derives the same id and overwrites the same captured slot).

Types

type AppAvailablePayload added in v1.4.0

type AppAvailablePayload struct {
	events.SafeSealed
	// Identity scopes the discovery to the (tenant, user, session) triple
	// the tool ran under; its RunID is the turn-correlation key.
	Identity identity.Quadruple
	// ServerID is the MCP server (source id) hosting the app — the value a
	// client passes to mcp.servers.read_resource to fetch the document.
	ServerID tools.ToolSourceID
	// ToolCallID is the stable per-invocation id (a content hash, not a
	// counter) the client passes to mcp.apps.tool_context to fetch the tool
	// context — the input + lowered result — that produced this app. Safe
	// by construction: an opaque hash, never caller content. EMPTY when no
	// context was captured for the invocation (no capturer wired, or the
	// capture failed): a non-empty id promises a fetchable record, so a
	// client may treat a miss as an expired one and say so.
	ToolCallID string
	// ToolName is the server-side tool name that declared the app — display
	// metadata only. Safe by construction: a tool name, never content.
	ToolName string
	// ResourceURI is the `ui://`-scheme URI of the app's UI document.
	ResourceURI string
	// DisplayMode is the display-mode hint (one of inline / fullscreen /
	// pip), or empty when the server stated none. The tool-definition
	// binding carries no mode in the canonical dialect, so this is empty on
	// the golden path and the renderer defaults to inline; a server MAY
	// supply a per-result hint that wins over the binding.
	DisplayMode string
	// RawHTMLTrusted is the raw-HTML trust posture carried on the
	// discovery. The driver emits the default-deny posture; a client
	// reconciles the full per-server trust via mcp.servers.get.
	RawHTMLTrusted bool
	// OccurredAt is the wall-clock instant the app was discovered.
	OccurredAt time.Time
}

AppAvailablePayload is the typed payload for EventTypeMCPAppAvailable. SafePayload: no caller-controlled MCP content survives on the payload — only the server source id, the `ui://` resource URI, the per-result display-mode hint, the default-deny raw-HTML trust posture, and the actor identity quadruple (its RunID correlates the discovery to the turn that produced it).

type AppRef added in v1.4.0

type AppRef struct {
	// ResourceURI is the `ui://`-scheme URI of the app's UI document.
	// The host fetches the document via a resource read scoped to the
	// request identity triple. Its canonical source is the tool
	// definition's `_meta.ui.resourceUri`.
	ResourceURI string
	// PreferredDisplayMode is an optional display-mode hint (one of
	// inline / fullscreen / pip), or empty when none was stated. The tool
	// definition's `_meta.ui` carries no display mode in the canonical
	// dialect, so this is empty on the golden path; a server MAY supply a
	// per-result hint on the CallToolResult `_meta.ui` slot, which the
	// host merges over the binding. When empty, the renderer defaults to
	// inline. It is a hint only; the host reconciles it against the
	// server's negotiated capability set.
	PreferredDisplayMode string
	// ToolCallID is the stable, per-invocation identifier minted at the
	// tool-call site (a content hash of the run / server / tool / args).
	// It correlates a discovered app to the captured tool context — the
	// input + lowered result that produced it — so a Protocol client can
	// fetch that context via mcp.apps.tool_context. It is NOT parsed from
	// the server's `_meta` (a result never carries it).
	//
	// A non-empty value is a PROMISE that a context record was persisted:
	// the invocation path stamps it only after the capture succeeded, so it
	// stays empty when no capturer is wired or the capture failed. A reader
	// may therefore treat a fetch miss as "the record is gone" rather than
	// "it may never have existed".
	ToolCallID string
}

AppRef is the host-side reference to an MCP App — the interactive HTML UI an MCP tool declares via the official `io.modelcontextprotocol/ui` (ext-apps) extension. The canonical dialect binds the UI resource to the tool DEFINITION: the `ui://` resource URI rides the tool's `_meta.ui.resourceUri` slot, captured at discovery. It is recognised distinctly from ordinary content: ONLY a `ui://`-scheme URI is treated as an app. An ordinary `file://` / `https://` resource reference is never promoted to an AppRef.

Concurrent reuse: AppRef is a value type with no mutable state after construction.

type ArtifactEgressedPayload added in v1.24.0

type ArtifactEgressedPayload struct {
	events.SafeSealed
	// Identity scopes the substitution to the (tenant, user, session)
	// triple the call ran under; its RunID correlates it to the turn.
	// The reachable artifact set was this triple's own and nothing wider.
	Identity identity.Quadruple
	// ServerID is the MCP server the bytes were sent to — the RECIPIENT,
	// which is what byte-eligibility governs and what this record makes
	// auditable.
	ServerID tools.ToolSourceID
	// ToolName is the server-side tool name that received the
	// substitution.
	ToolName string
	// Records is one entry per substituted parameter: the artifact id,
	// the parameter name, the byte count, and the `sha256:` digest that
	// says WHICH bytes moved without carrying them.
	Records []artifactegress.Record
	// OccurredAt is the wall-clock instant the substitution was made —
	// necessarily BEFORE the wire request, because the record is emitted
	// fail-closed ahead of it.
	OccurredAt time.Time
}

ArtifactEgressedPayload is the typed payload for EventTypeMCPArtifactEgressed. SafePayload: no artifact content survives on the payload — only the actor identity quadruple, the server source id, the tool name, and one content-free record per substituted parameter.

type AttachDeps added in v1.3.0

type AttachDeps struct {
	// Catalog receives one ToolDescriptor per discovered tool.
	Catalog tools.ToolCatalog
	// Registry receives the live Provider registration so observability
	// surfaces (the Console MCP Connections page) can project it.
	Registry *Registry
	// Bus carries the driver's mcp.* events. Mandatory — the driver's
	// own constructor validates it (mcp.resource_updated emission).
	Bus events.EventBus
	// Logger receives the per-server attachment Info line. Optional.
	Logger *slog.Logger
	// DefaultIdentity is the FALLBACK identity stamped on server-pushed
	// events that arrive without an inflight call (transport-side
	// notifications — Item 1). Per-call subscriptions
	// stamp the inflight caller's ctx-resident identity via the
	// driver's pushIdentity helper; this default only covers
	// transport-level events.
	DefaultIdentity identity.Identity
	// Closers is the caller's ordered closer chain. Attach appends the
	// Provider's Close immediately after a successful Connect so a
	// later Discover/Register failure still drains the live subprocess.
	Closers *[]func(context.Context) error
	// HostDisplayModes lists the MCP App (`io.modelcontextprotocol/ui`)
	// display modes the host can render. Projected onto the Provider's
	// Config.HostDisplayModes so the provider advertises the UI extension
	// during the initialize handshake. The boot loader sources this once
	// from the deployment-level `tools.mcp_app_host.display_modes` config
	// (defaulting to inline); empty leaves the SDK's default advertisement
	// untouched. This is the programmatic seam an embedder sets without YAML.
	HostDisplayModes []string
	// ToolContext is the optional MCP Apps tool-context capturer. When set,
	// the Provider persists the input + lowered result behind a declared
	// `ui://` app so the host can deliver it to the rendered app. A nil
	// capturer leaves tool-context delivery unwired (the host read returns
	// not-found). Optional.
	ToolContext ToolContextCapturer
	// Owner is the (tenant, agent) reconcile-view tag stamped on the
	// registry entry for a RUNTIME-ADDED connection. The boot loader leaves it
	// zero (boot-declared servers are untagged and never reconciled); the
	// runtime-add attach path sets a non-zero owner so the run-start reconcile
	// view scopes to it. It is a reconcile-view filter, never a dispatch or
	// isolation key.
	Owner auth.Owner
	// DescriptorFingerprint is the canonical digest of the NON-SECRET
	// runtime-added descriptor. It is retained on the live registration so
	// run-start reconciliation can distinguish an exact no-op from a same-name
	// descriptor replacement. Boot-declared attachments leave it empty.
	DescriptorFingerprint string
	// OAuthProviders is the declared OAuth-provider registry (keyed by the
	// non-secret provider NAME) Attach resolves a connection's
	// `oauth_provider` binding against. Populated by the runtime assembler
	// from its constructed provider map (and the devstack twin). A binding
	// naming a provider absent from this map fails the attach loud, listing
	// the registered names (§4.4 factory-error convention). Nil / empty is
	// valid when no connection binds a provider. The driver depends ONLY on
	// the `auth.OAuthProvider` interface — no concrete driver import (§13).
	OAuthProviders map[string]auth.OAuthProvider
	// OAuthProviderSet is the runtime provider SET a RUNTIME-ADDED connection's
	// `oauth_provider` binding resolves against, so a Protocol-installed
	// (owner-tagged) provider is bindable in addition to the boot map. When set
	// it TAKES PRECEDENCE over OAuthProviders (the set is seeded from the same
	// boot map at assembly, so boot providers stay resolvable). Optional — nil
	// leaves resolution on the OAuthProviders map (the boot catalog path). The
	// driver depends only on the narrow resolver interface (bare-name Get +
	// Names for the fail-loud message) — no concrete import.
	OAuthProviderSet OAuthProviderResolver
	// OAuthProviderOverride is a privately prepared provider used for this
	// attachment's named binding before it is published to the shared set.
	OAuthProviderOverride auth.OAuthProvider
	// OwnOAuthProvider transfers teardown ownership of the override to the MCP
	// provider. General provider-set bindings leave this false.
	OwnOAuthProvider bool
	// ToolAllowlist/ToolDenylist project a signed restrictive policy onto the
	// discovered tool descriptors before catalog publication.
	ToolAllowlist []string
	ToolDenylist  []string
	// ArtifactEgressMaxBytes bounds ONE substituted artifact value on one
	// outbound call for connections this attach wires. Sourced by the boot
	// loader (and the runtime attacher) from the deployment-level
	// `tools.mcp_artifact_egress_max_bytes`, which carries a documented
	// default. Zero resolves to config.DefaultMCPArtifactEgressMaxBytes so
	// an embedder that does not set it still gets a real ceiling rather
	// than an unbounded one. Optional.
	ArtifactEgressMaxBytes int
}

AttachDeps bundles the collaborators Attach wires the server into. Catalog, Registry, Closers, and Bus are mandatory — a nil Bus fails loud at mcp.New (Config.validate rejects it; the driver publishes mcp.resource_updated). Only Logger is optional (a nil Logger silences the attachment log line — test stacks omit it).

type AudioRef

type AudioRef struct {
	Data     []byte
	MIMEType string
}

AudioRef is the lowered form of an MCP AudioContent.

type AuthChallenge added in v1.13.0

type AuthChallenge struct {
	// Scheme is the challenge auth scheme (e.g. "Bearer").
	Scheme string
	// ResourceMetadataURL is the RFC 9728 `resource_metadata` pointer, when
	// the challenge carried one.
	ResourceMetadataURL string
	// Realm is the optional `realm` challenge parameter.
	Realm string
	// Error is the optional RFC 6750 §3.1 `error` challenge parameter (e.g.
	// "insufficient_scope", "invalid_token"). Empty when the challenge
	// carried none.
	Error string
	// Scope is the optional RFC 6750 §3.1 `scope` challenge parameter — the
	// space-delimited scopes the downstream requires. Empty when absent.
	Scope string
	// Raw is the verbatim header value (provenance / debugging).
	Raw string
	// CapturedAt is the wall-clock instant the challenge was observed.
	CapturedAt time.Time
}

AuthChallenge is a parsed `WWW-Authenticate` Bearer challenge captured off a `401` response from an MCP HTTP server. It is inert data recorded on the connection's registry state.

type CapturedToolContext added in v1.4.1

type CapturedToolContext struct {
	// ServerID is the MCP server (source id) the tool belongs to. Paired
	// with ToolCallID it forms the lookup key the host reads back through.
	ServerID tools.ToolSourceID
	// ToolCallID is the stable, collision-free per-invocation id minted by
	// ToolCallID (the content hash). It is the same value carried on the
	// discovery event and projected onto the app reference.
	ToolCallID string
	// Tool is the server-side tool name (display metadata only — the
	// lookup keys on ServerID + ToolCallID, never the tool name).
	Tool string
	// Input is the raw JSON argument object the tool was invoked with.
	Input json.RawMessage
	// Result is the JSON-encoded lowered tool result.
	Result json.RawMessage
	// IsError reports whether the tool returned a server-side error result.
	IsError bool
}

CapturedToolContext is the input a ToolContextCapturer persists when an invoked MCP tool declared an interactive app. It is the "Data Delivery" half of the MCP Apps lifecycle: the input arguments and the lowered result that produced the app, keyed by the stable ToolCallID so a Protocol client (the rendered app) can fetch them later. The capture rides the SAME ctx the tool call ran under, so the persisted record is scoped to the caller's identity triple.

type Config

type Config struct {
	// Name is the unique source ID prefix. Empty rejects with
	// ErrInvalidConfig.
	Name string
	// TransportMode selects the wire transport. Empty defaults to
	// TransportAuto.
	TransportMode MCPTransportMode
	// URL is the endpoint for SSE / streamable-HTTP transports.
	// Required for those modes.
	URL string
	// Command is the argv-form subprocess command for the stdio
	// transport. [0] is the binary; [1:] are args. Required for
	// stdio. NEVER shell-form — the driver enforces this in
	// transport_stdio.go.
	Command []string
	// Headers are operator-supplied HTTP headers added to every
	// SSE / streamable-HTTP request (auth tokens, custom auth).
	// "URL connections require explicit headers for auth (no
	// implicit env passthrough)" — a settled security rule.
	Headers map[string]string
	// KeepAlive is the ping interval for the MCP session; zero
	// disables. The SDK's KeepAlive runs the underlying ping/pong.
	KeepAlive time.Duration
	// Logger is the per-provider slog logger. nil → a discard
	// logger; runtime never panics on absent Logger.
	Logger *slog.Logger
	// Bus is the event bus used to publish `mcp.resource_updated`
	// notifications. Required.
	Bus events.EventBus
	// ToolContext captures the tool context (input + lowered result) behind
	// a declared MCP App so the host can deliver it to the rendered app
	// (the MCP Apps "Data Delivery" lifecycle). Optional — a nil capturer
	// means tool-context delivery is not wired (the host's tool-context
	// read then returns not-found). When set, the driver calls it from the
	// tool-invocation path whenever a result declares a `ui://` app; a
	// Capture error is logged loudly but never fails the tool call.
	ToolContext ToolContextCapturer
	// DefaultPolicy is the ToolPolicy applied to descriptors built
	// from this provider. Zero-valued → tools.DefaultPolicy().
	DefaultPolicy tools.ToolPolicy
	// ToolPolicies are per-tool ToolPolicy overrides keyed by the
	// MCP server-side tool name (NOT the `<source>_<tool>` Harbor
	// name). When a discovered tool's name is present here, its
	// descriptor uses the override instead of DefaultPolicy; a tool
	// absent from the map falls back to DefaultPolicy.
	// Per-tool overrides apply to TOOLS only — MCP resources and
	// prompts always run under DefaultPolicy (the per-server default).
	//
	// Concurrent reuse: the map is read-only after New — it is
	// never mutated per-run. buildToolDescriptor only reads it, and
	// the resolved ToolPolicy is copied by value into each descriptor
	// at Discover time, so concurrent invocations of different tools
	// never share or race this map.
	ToolPolicies map[string]tools.ToolPolicy
	// DefaultIdentity is the fallback identity stamped on
	// transport-side events (notifications that arrive without an
	// inflight call). Required so the bus's ValidateEvent does not
	// reject the event when the SDK-supplied ctx carries no triple.
	//
	// the role narrows. For events the
	// SDK delivers WITH a populated ctx (per-call notifications
	// originating from an inflight tool / resource subscription),
	// the driver prefers `identity.From(ctx)` over this cached
	// default — `pushIdentity(ctx, cfg)` is the single helper that
	// implements the preference. The DefaultIdentity remains the
	// fallback for genuine transport-level events (a server-pushed
	// `notifications/resources/updated` arriving outside any
	// inflight call) where the ctx has no triple to read.
	DefaultIdentity identity.Identity

	// HostDisplayModes lists the MCP App (`io.modelcontextprotocol/ui`)
	// display modes this host can render. When non-empty, the provider
	// advertises the UI extension during the MCP initialize handshake with
	// these modes (filtered against the closed valid-mode set), so a server
	// can tailor the app references it returns to what the host actually
	// renders. Empty leaves the SDK's default capability advertisement
	// untouched (no UI extension) — the behaviour for an embedder that does
	// not opt in. The boot loader sources this from the deployment-level
	// `tools.mcp_app_host.display_modes` config (defaulting to inline), but
	// a programmatic embedder may set it directly. Read once at construction;
	// immutable thereafter.
	HostDisplayModes []string

	// OAuthProvider, when non-nil, binds this connection to a per-identity
	// OAuth credential source: every identity-stamped per-call RPC resolves a
	// fresh bearer via Token(ctx, source) and injects Authorization on THAT
	// request only. Nil leaves the connection on its static Headers (the
	// unbound default). Resolved once at construction from the operator's
	// non-secret provider NAME (config `oauth_provider`) against the declared
	// provider registry — the name selects an acquisition strategy; the
	// secret stays on the provider. Immutable after construction: no per-call
	// transport state mutates (the token rides the call's ctx). A pair-private
	// owned binding also authenticates preparation: each initialize attempt and
	// each discovery RPC resolves a bearer from that exact ctx before any wire
	// request, failing closed on an error or empty token. Shared boot bindings
	// retain credential-neutral connect/discovery for run-start reattachment.
	OAuthProvider auth.OAuthProvider
	// OwnOAuthProvider transfers teardown ownership of OAuthProvider to this
	// connection. It is reserved for a privately prepared signed capability;
	// ordinary boot/shared providers remain owned by their provider set.
	OwnOAuthProvider bool

	// MetaAnnotations is a static, non-secret set of operator-declared
	// key/values merged into the `_meta` map on every identity-stamped
	// per-call RPC, so a deployment can carry its own attribution vocabulary
	// to a shared server.
	//
	// Each KEY is a `_meta` PATH: a key with no `.` sets a top-level key, a
	// DOTTED key NESTS — the same interpretation, through the same helper
	// (injectMeta), that the credential-injection MetaKey has always had.
	// Reserved keys (the triple keys, `agent_id`, `traceparent`, `tracestate`,
	// and any `io.modelcontextprotocol/`-prefixed key) are rejected at config
	// / wire / attach validation — at the whole key AND at any dot-segment —
	// and can never shadow the triple or agent provenance (those are stamped
	// last). Colliding declared paths are rejected at validation and fail the
	// call with ErrMetaPathCollision if a legacy pair reaches the merge. Read
	// once at construction; immutable thereafter.
	MetaAnnotations map[string]string

	// OnAuthChallenge, when non-nil, is invoked whenever an MCP HTTP call to
	// this server answers `401` with a `WWW-Authenticate` Bearer challenge —
	// the MCP authorization spec's OAuth step-up. The callback records the
	// advertised OAuth requirement on the connection's registry state so an
	// operator can inspect it. Capture is pure observation: it never
	// retries, never attaches credentials, and never alters the call's error
	// semantics. Optional; nil disables challenge capture (stdio connections
	// never set it — the challenge is an HTTP-auth construct). Read once at
	// construction; immutable thereafter.
	OnAuthChallenge func(AuthChallenge)

	// OnScopeShortfall, when non-nil, is invoked whenever an MCP HTTP call
	// answers `403` with a `WWW-Authenticate` marking
	// `error="insufficient_scope"` (RFC 6750 §3.1) — a downstream step-up
	// scope shortfall. The callback records the parsed shortfall on the
	// connection's registry state so an operator can inspect the last
	// observed required-vs-granted gap. Best-effort observability: it never
	// retries, never widens a binding, and never alters the call's error
	// semantics (the per-call error enrichment rides a request-scoped ctx
	// slot instead). Optional; nil disables the registry-side record (stdio
	// connections never set it). Read once at construction; immutable
	// thereafter.
	OnScopeShortfall func(ScopeShortfall)

	// ToolOAuthProviders are per-entry OAuth-provider overrides keyed by the
	// MCP-side name (mirroring ToolPolicies' shape). An entry named here binds
	// THAT provider for every identity-stamped RPC that addresses by the entry's
	// key — a CallTool by tool name, a ReadResource / SubscribeResource by
	// resource URI, and a GetPrompt by prompt name; an unlisted key falls back
	// to OAuthProvider (the connection-level binding). Each entry was resolved +
	// validated against the same binding rules as OAuthProvider at attach time
	// (unknown name / stdio transport / static-Authorization conflict /
	// downstream-host allow-list). The map is a single per-entry namespace; a
	// key that would address more than one surface at once (a tool AND a prompt
	// of the same name) is rejected loud at discovery (ErrAmbiguousOAuthBinding),
	// never silently resolved by precedence. Read-only after New; the resolved
	// provider is read per-call from the call's own addressing key, so concurrent
	// invocations of different tools / resources / prompts never share or race
	// this map (the concurrent-reuse contract).
	ToolOAuthProviders map[string]auth.OAuthProvider

	// Injection, when non-nil, binds this connection to per-user credential
	// INJECTION for a receiver-style MCP server: on each identity-stamped
	// outbound tool call the driver SOURCES the acting principal's credential
	// from Injection.Provider (the same per-user broker-pull as OAuthProvider —
	// per-user via the ctx identity, fetched-not-held) and INJECTS it in the
	// declared form (a request header, an `Authorization: Basic` value, or a
	// `_meta` key). Mutually exclusive with OAuthProvider / ToolOAuthProviders /
	// a static `Authorization` header (one auth mode per connection). Resolved
	// once at construction; immutable thereafter — the per-user value is pulled
	// per-call from the ctx and never held here (the concurrent-reuse contract).
	Injection *CredentialInjection

	// ArtifactEgress, when non-empty, declares which parameters on which
	// of this server's tools carry artifact BYTES. When a mapped
	// parameter arrives carrying an artifact id, the driver resolves it
	// through the run-scoped resolver seated on the dispatch ctx and
	// writes the resolved bytes into the outbound tool-call body as
	// standard base64 — the model authored an id and never sees content.
	//
	// Set only when the operator declared the connection byte-eligible;
	// the boot validator, both control-plane persistence doors and
	// [Attach] each refuse a mapping without that declaration, so the
	// driver never has to re-derive the eligibility rule. Empty leaves
	// every outbound call byte-identical to a build without the feature.
	//
	// Read once at construction and captured BY VALUE into each tool's
	// invocation closure at Discover, so a live mapping change takes
	// effect at the next attach / reconcile and never mid-flight (the
	// concurrent-reuse contract).
	ArtifactEgress artifactegress.Mapping

	// ArtifactEgressMaxBytes bounds ONE substituted artifact value on one
	// outbound call. Resolved from the operator's
	// `tools.mcp_artifact_egress_max_bytes` (which carries a documented
	// default) before construction. A value above it is REFUSED loud,
	// never truncated. Ignored when ArtifactEgress is empty; when it is
	// not, a non-positive value fails the call rather than being read as
	// "unbounded".
	ArtifactEgressMaxBytes int
}

Config is the operator-supplied configuration for one MCP attachment. Operator-facing fields map 1:1 to the `config.MCPServerConfig` yaml shape; the runtime entry point (cmd/harbor wiring, future phase) is responsible for the projection.

type ContentKind

type ContentKind string

ContentKind discriminates a ContentPart.

const (
	ContentKindImage    ContentKind = "image"
	ContentKindAudio    ContentKind = "audio"
	ContentKindLink     ContentKind = "link"
	ContentKindEmbedded ContentKind = "embedded"
)

The ContentKind values, one per MCP content-part shape.

type ContentPart

type ContentPart struct {
	Kind     ContentKind
	Image    *ImageRef
	Audio    *AudioRef
	Link     *LinkRef
	Embedded *EmbeddedRef
}

ContentPart is the discriminated union of non-text content shapes. Exactly one of Image / Audio / Link / Embedded is set; Kind names which.

type CredentialInjection added in v1.18.0

type CredentialInjection struct {
	// Provider is the resolved broker the per-user credential is pulled from.
	Provider auth.OAuthProvider
	// Form selects the injection form.
	Form InjectionForm
	// Header is the target request header name for InjectionFormHeader.
	Header string
	// BasicUsername is the (non-secret, optional) username half for
	// InjectionFormBasic; the pulled credential is the password half.
	BasicUsername string
	// MetaKey is the split target `_meta` key path for InjectionFormMeta.
	MetaKey []string
}

CredentialInjection is the resolved, NON-SECRET per-connection binding that sources the acting principal's credential from Provider on each identity-stamped outbound tool call and injects it in Form. Immutable after construction: the per-user value is pulled per-call from the ctx identity (Provider.Token, reading identity.From(ctx)) and never held on this struct, so one shared Provider serves N concurrent identities with no value bleed (the concurrent-reuse contract). The credential still originates from the broker (fetched-not-held, memory-only TTL); injection changes only the LAST hop — the runtime delivers the value because the receiver server cannot pull it.

type Cursor

type Cursor struct {
	// NextPageToken is the cursor for the next page, or empty when the
	// page is the last.
	NextPageToken string
}

Cursor is the opaque pagination cursor a paged read returns.

type DiscoveryResult

type DiscoveryResult struct {
	DiscoveryID   string
	ToolCount     int
	ResourceCount int
	PromptCount   int
}

DiscoveryResult is the outcome of a RefreshDiscovery call.

type EmbeddedRef

type EmbeddedRef struct {
	URI      string
	MIMEType string
	Text     string
	Blob     []byte
}

EmbeddedRef is the lowered form of an MCP EmbeddedResource (a resource embedded directly in the tool call result).

type ExactRemovalFence added in v1.26.0

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

ExactRemovalFence is a process-local admission receipt shared by signed publication and exact teardown. Seal records that desired pair absence has committed; Cancel is valid only while that durable transition is unproven.

func (*ExactRemovalFence) Cancel added in v1.26.0

func (f *ExactRemovalFence) Cancel(ctx context.Context) error

Cancel releases an unsealed admission after the desired-state CAS is proven not to have committed. If admission invalidated a private stage, Cancel closes that exact never-dispatchable provider before releasing the name.

func (*ExactRemovalFence) Seal added in v1.26.0

func (f *ExactRemovalFence) Seal()

Seal keeps the admission fence installed after desired pair absence commits. Exact teardown removes it only after the exact transport has closed.

type HealthBucket

type HealthBucket struct {
	StartMs   int64
	LatencyMs int64
}

HealthBucket is one handshake-latency sparkline bucket.

type HealthSnapshot

type HealthSnapshot struct {
	HandshakeLatencyBuckets []HealthBucket
	ReconnectHistory        []ReconnectEntry
	TransportErrorRate      float64
}

HealthSnapshot is the Health read result.

type ImageRef

type ImageRef struct {
	Data     []byte
	MIMEType string
}

ImageRef is the lowered form of an MCP ImageContent.

type InjectionForm added in v1.18.0

type InjectionForm string

InjectionForm selects how a receiver-style MCP server's per-user credential is delivered on each outbound tool call.

const (
	// InjectionFormHeader sets the pulled credential as the value of a declared
	// request header.
	InjectionFormHeader InjectionForm = "header"
	// InjectionFormBasic sets the pulled credential as the password half of an
	// `Authorization: Basic base64(username ":" credential)` header.
	InjectionFormBasic InjectionForm = "basic"
	// InjectionFormMeta sets the pulled credential as the leaf value of a
	// declared `_meta` key path.
	InjectionFormMeta InjectionForm = "meta"
)

type LinkRef

type LinkRef struct {
	URI         string
	Name        string
	Title       string
	Description string
	MIMEType    string
}

LinkRef is the lowered form of an MCP ResourceLink (a pointer to a resource the server hosts; the client may follow it with ReadResource if desired).

type ListFilter

type ListFilter struct {
	// State filters to servers in any of the given states. Empty = all.
	State []ServerState
	// Transport filters to servers on any of the given transports.
	Transport []string
	// HasOAuth, when set, filters on OAuth-binding presence.
	HasOAuth *bool
	// HasRecentError, when set, filters on recent-error presence.
	HasRecentError *bool
	// NamePrefix filters to servers whose name has the prefix.
	NamePrefix string
	// PageToken is the opaque cursor from a prior page.
	PageToken string
	// PageSize is the requested max row count (clamped by the Registry).
	PageSize int
}

ListFilter is the filter shape ListServers applies.

type MCPToolValue

type MCPToolValue struct {
	// Text concatenates every TextContent block in encounter order.
	Text string
	// Parts is the ordered, typed slice of every non-text content
	// block. Empty when the response is pure text.
	Parts []ContentPart
	// StructuredContent is the MCP `structuredContent` field on
	// servers that support typed JSON output (mcpsdk.ToolHandlerFor).
	// nil when absent.
	StructuredContent any
	// AppRef is the MCP Apps reference for an invoked tool that declared
	// an interactive UI. The canonical `io.modelcontextprotocol/ui`
	// (ext-apps) dialect binds the `ui://` UI resource to the tool
	// DEFINITION (`_meta.ui.resourceUri` on the tool, captured at
	// discovery); a server MAY additionally place a per-result hint on the
	// CallToolResult `_meta.ui` slot. The value here is the reconciled
	// reference — the tool-definition binding, merged with any per-result
	// hint — set on the result by `Provider.callTool`. `lowerCallToolResult`
	// alone populates it only from the per-result `_meta`; the binding is
	// merged in by `callTool`, which holds the discovery-time binding. It is
	// nil for ordinary (non-app) tools. AppRef is excluded from the JSON
	// wire form (`json:"-"`) so it never reaches the LLM-facing observation —
	// it is a host-side projection consumed by the app-tool-call proxy and
	// the discovery event, not planner context.
	AppRef *AppRef `json:"-"`
	// ArtifactEgress records the artifact values the runtime resolved
	// into THIS call's outbound arguments — one content-free entry per
	// substituted parameter (artifact id, parameter name, byte count,
	// `sha256:` digest). Nil for a tool that maps no artifact parameters.
	//
	// Unlike AppRef above, it is deliberately INCLUDED in the JSON wire
	// form and therefore reaches the observation and the trajectory. The
	// contrast is the design: AppRef is a host-side projection the model
	// has no business reading, whereas the model AUTHORED the artifact
	// id, and telling it "the id you named was delivered, N bytes" is
	// honest, content-free and replayable. Without it a model could not
	// distinguish a delivered document from an ignored parameter.
	//
	// It carries no bytes, so it is safe everywhere the observation goes.
	ArtifactEgress []artifactegress.Record `json:"artifact_egress,omitempty"`
}

MCPToolValue is the typed shape returned from `Invoke` when the remote MCP server returns a CallToolResult. Heterogeneous parts preserve the wire ordering so downstream consumers (LLM context assembly, audit) can reconstruct the server's response.

func (MCPToolValue) MarshalJSON added in v1.2.0

func (v MCPToolValue) MarshalJSON() ([]byte, error)

MarshalJSON renders the value LLM-edge-friendly. The text-only degenerate case (Parts + StructuredContent both empty) emits just the raw Text — most MCP tools return their result as a TextContent block carrying JSON-as-string, and the default struct marshal produces a `{"Text": "<escaped JSON>"}` wrapper that doubles the encoding. When Text is itself well-formed JSON, MarshalJSON emits the JSON value directly so the LLM reads a clean structure; otherwise the text rides as a JSON string. Audit / observability consumers that need the typed shape can re-derive it from the underlying CallToolResult on the bus.

When StructuredContent is set, it wins (it's the MCP-server-typed projection). When Parts are non-empty, the wrapper carries the non-text shape verbatim — there is no clean unwrap for mixed- content responses, so the default struct render applies. When this call carried an egress substitution, the collapsed body is WRAPPED alongside the content-free substitution record, so the model that authored the artifact id is told the id was delivered and how many bytes it was. Without the wrapper the collapse below would drop the record for the commonest (text-only) result shape, and the model could not distinguish a delivered document from an ignored parameter. A call with NO substitution is unaffected — its rendering is byte-identical to what it was before egress existed.

type MCPTransportMode

type MCPTransportMode string

MCPTransportMode selects the wire transport for one MCP attachment. Mirrors the settled transport design ("`MCPTransportMode = Auto | SSE | StreamableHTTP`"). Stdio is the implicit fourth mode: selected when `Auto` sees a `Command` but no `URL`.

const (
	// TransportAuto inspects Config and picks: streamable-HTTP first
	// if URL is set; on connect failure fall back to SSE; if Command
	// is set with no URL, stdio.
	TransportAuto MCPTransportMode = "auto"
	// TransportSSE selects the SDK's SSEClientTransport. URL must
	// be set.
	TransportSSE MCPTransportMode = "sse"
	// TransportStreamableHTTP selects the SDK's
	// StreamableClientTransport. URL must be set.
	TransportStreamableHTTP MCPTransportMode = "streamable_http"
	// TransportStdio selects the SDK's CommandTransport. Command
	// (argv form) must be set.
	TransportStdio MCPTransportMode = "stdio"
)

type OAuthProviderResolver added in v1.14.0

type OAuthProviderResolver interface {
	// Get resolves a provider by bare name; the bool reports presence.
	Get(name string) (auth.OAuthProvider, bool)
	// Names returns every resolvable provider name, sorted, for a fail-loud
	// error message.
	Names() []string
}

OAuthProviderResolver is the narrow bare-name resolution seam Attach uses to resolve a connection's `oauth_provider` binding — satisfied by `auth.ProviderSet` (the runtime provider set) and by the boot map adapter. Bare-name resolution across every session; Names feeds the fail-loud "registered: …" message.

type PreparationAuthRequiredError added in v1.25.0

type PreparationAuthRequiredError struct{ Challenge AuthChallenge }

PreparationAuthRequiredError carries the parsed, defensive challenge without exposing it through Error text or relying on transport error strings.

func (*PreparationAuthRequiredError) Error added in v1.25.0

func (*PreparationAuthRequiredError) Is added in v1.25.0

func (e *PreparationAuthRequiredError) Is(target error) bool

type PreparedAttachment added in v1.25.0

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

PreparedAttachment is a connected and discovered MCP provider that has not yet been published to the tool catalog or live registry. Activate publishes it once; Close drains it on refusal or shutdown. Its state is internally synchronized so a cancellation/cleanup race cannot publish after close.

func Prepare added in v1.25.0

Prepare validates, connects, and discovers one MCP server without changing the shared catalog or registry. The returned attachment owns the connected provider until Activate or Close.

func (*PreparedAttachment) Activate added in v1.25.0

func (p *PreparedAttachment) Activate(ctx context.Context) error

Activate privately reserves the reversible registry replacement first, then swaps the catalog source as the dispatch linearization point. The old same-owner provider remains callable through both the old catalog descriptors and direct registry reads until that point and is closed only after both shared structures publish successfully.

func (*PreparedAttachment) ActivateIf added in v1.26.0

func (p *PreparedAttachment) ActivateIf(ctx context.Context, prove func(context.Context) error) error

ActivateIf reserves the exact provider handle in the non-dispatchable MCP registry, then runs prove immediately before the catalog publication. Exact teardown can address and close that staged handle while prove performs durable reads. Publication commits through the same reservation, so either teardown invalidates it first or every later teardown sees the live handle; there is no catalog-only generation between those outcomes.

func (*PreparedAttachment) ActivateUnder added in v1.26.0

func (p *PreparedAttachment) ActivateUnder(ctx context.Context, admit func(context.Context, func() error) error) error

ActivateUnder stages the exact private registry handle, then delegates the final local publication callback to admit. Signed capability callers use an exact durable operation-slot fence in admit, so removal CAS and local catalog visibility have one cross-runtime ordering point.

func (*PreparedAttachment) Close added in v1.25.0

func (p *PreparedAttachment) Close(ctx context.Context) error

Close drains the prepared provider. It is idempotent.

type ProbeResult

type ProbeResult struct {
	OK        bool
	LatencyMs int64
	Error     string
}

ProbeResult is the outcome of a Probe call.

type PromptArgView

type PromptArgView struct {
	Name        string
	Description string
	Required    bool
}

PromptArgView is one declared prompt argument.

type PromptView

type PromptView struct {
	Name        string
	Description string
	Arguments   []PromptArgView
}

PromptView is one advertised prompt.

type Provider

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

Provider implements tools.ToolProvider against a remote MCP server. Safe for N concurrent goroutines after Connect; per-call state lives on the call's ctx, never on the Provider.

Concurrent reuse contract:

  • `session` is set once by Connect under mu; subsequent reads are guarded by mu.RLock so Invoke / Discover / Close races are safe.
  • `closed` flips to true on Close; subsequent Invoke / Discover return ErrNotConnected.
  • The resource-update goroutine reads `session` once at Connect time and exits when the session closes.

func New

func New(cfg Config) (*Provider, error)

New constructs a Provider. The Provider is NOT connected; the caller MUST call Connect before Discover / Invoke / SubscribeResource.

func (*Provider) Close

func (p *Provider) Close(ctx context.Context) error

Close shuts the session and pair-owned OAuth provider down idempotently and joins any in-flight SDK goroutines. A failure is retryable: successful legs retain their receipt while failed legs keep their exact handle for the next call. Safe to call multiple times.

func (*Provider) Connect

func (p *Provider) Connect(ctx context.Context) error

Connect establishes the MCP session. Calling Connect twice without an interleaving Close returns the existing session (Connect is idempotent on the second call only when the first succeeded).

Auto-mode fallback: when TransportMode is TransportAuto and the URL is set, the Provider tries streamable-HTTP first. On a non-cancellation failure of `client.Connect` (which covers both transport-Connect and the MCP initialize handshake), it retries with SSE.

func (*Provider) Discover

func (p *Provider) Discover(ctx context.Context) ([]tools.ToolDescriptor, error)

Discover returns one ToolDescriptor per remote tool, plus one per resource (rendered as a `__resource.<uri>` tool) and one per prompt (`__prompt.<name>`). All descriptors carry Transport = TransportMCP and Source = p.source.

func (*Provider) DisplayModes added in v1.4.0

func (p *Provider) DisplayModes() []string

DisplayModes returns the MCP App display modes THIS HOST can render — the deployment's configured host modes (`Config.HostDisplayModes`, sourced from `tools.mcp_app_host.display_modes`), filtered against the closed valid-mode set with duplicates removed and order preserved. It is NOT a server read: display modes are not a spec capability field — they ride the `ui/initialize` host-context `availableDisplayModes` the host dictates — so the Registry / Console column reports what the host renders, not a value scraped off the server's capabilities.

Concurrent reuse: the configured modes are immutable after construction; DisplayModes reads no per-call or session state.

func (*Provider) ReadResource added in v1.4.0

func (p *Provider) ReadResource(ctx context.Context, uri string) (content []byte, mimeType string, err error)

ReadResource fetches a single resource's content from the remote MCP server under the request identity triple. It is the runtime-side leg of the `mcp.servers.read_resource` Protocol method: the Console reads an MCP App's `ui://` UI document through it (the URI is validated to the `ui://` scheme by the caller; ReadResource itself is scheme- agnostic so the same path serves any resource read).

The returned bytes are the first resource-content item's payload — Text rendered as UTF-8 bytes, or the raw Blob when the server sent a binary resource. The MIME type is the content item's declared type. Identity is mandatory: a ctx without a full (tenant, user, session) triple fails closed with ErrIdentityMissing (the `_meta` builder rejects it) — never a read with an empty `_meta` block.

Concurrent reuse: ReadResource holds no per-call state on the Provider; identity + the URI ride the call.

func (*Provider) SelectedMode

func (p *Provider) SelectedMode() MCPTransportMode

SelectedMode reports the transport mode that succeeded at Connect time. Empty before Connect.

func (*Provider) SourceID

func (p *Provider) SourceID() tools.ToolSourceID

SourceID returns the source ID under which this provider's descriptors are stamped. Implements tools.ToolProvider.

func (*Provider) SubscribeResource

func (p *Provider) SubscribeResource(ctx context.Context, uri string) error

SubscribeResource registers a server-side resource subscription. Updates received via the SDK's ResourceUpdatedHandler are published as `mcp.resource_updated` on the configured event bus (see Provider.onResourceUpdated).

type ReconnectEntry

type ReconnectEntry struct {
	OccurredAt time.Time
	Reason     string
}

ReconnectEntry is one reconnect-history entry.

type RegistrationSwap added in v1.25.0

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

RegistrationSwap is a reversible live-registry publication. Commit makes the staged entry final and drains the exact displaced provider. Rollback restores the exact prior entry only while this staged entry is still current.

func (*RegistrationSwap) Commit added in v1.25.0

func (s *RegistrationSwap) Commit(ctx context.Context) error

Commit finalizes a staged registration without an external publication callback. Prepared MCP attachments use RegistrationSwap.Publish so the catalog dispatch swap and live-registry publication share one exact reservation linearization.

func (*RegistrationSwap) Publish added in v1.26.0

func (s *RegistrationSwap) Publish(ctx context.Context, publish func() error) (bool, error)

Publish atomically validates this exact private reservation, runs publish while the registry write lock excludes exact teardown, and installs the staged handle in the live registry. A teardown that wins before this method invalidates and closes the staged handle; Publish then fails with published=false. A teardown that starts after publish necessarily observes the live exact handle. publish must make the external dispatch state visible only after all durable authority checks have completed.

The returned boolean distinguishes a cleanup error after irreversible publication from a pre-publication refusal. Callers log the former and must close/rollback the latter.

func (*RegistrationSwap) RecordAuthChallenge added in v1.25.0

func (s *RegistrationSwap) RecordAuthChallenge(ch AuthChallenge)

RecordAuthChallenge records on this receipt's exact staged entry, never on a same-name healthy prior registration while preparation is unpublished.

func (*RegistrationSwap) RecordScopeShortfall added in v1.25.0

func (s *RegistrationSwap) RecordScopeShortfall(sf ScopeShortfall)

RecordScopeShortfall records a defensive copy on the exact staged entry.

func (*RegistrationSwap) Rollback added in v1.25.0

func (s *RegistrationSwap) Rollback() error

Rollback drops the private reservation iff it is still current. Ordinary reads never stopped seeing the prior entry, and the staged provider is closed later through PreparedAttachment.Close.

type Registry

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

Registry is the process-local MCP-server read API. It is a compiled artifact — built once at construction; the provider set is write-once after Register; per-server stats are guarded by mu.

func NewRegistry

func NewRegistry(opts ...RegistryOption) *Registry

NewRegistry builds an empty Registry. Servers are added via Register.

func (*Registry) BeginExactPublisherRemoval added in v1.26.0

func (r *Registry) BeginExactPublisherRemoval(name string, owner auth.Owner, descriptorFingerprint string) (*ExactRemovalFence, error)

BeginExactPublisherRemoval is the durable-publisher variant used only after the shared operation record has entered removal_admitted. A runtime may hold an older same-owner publisher epoch while another runtime owns the durable current epoch. That stale handle is already bearer-inert, so it is not a local mismatch that may block durable teardown; a foreign owner still fails closed. A matching local generation retains the exact close behavior of [BeginExactRemoval].

func (*Registry) BeginExactRemoval added in v1.26.0

func (r *Registry) BeginExactRemoval(name string, owner auth.Owner, descriptorFingerprint string) (*ExactRemovalFence, error)

BeginExactRemoval prevents the exact generation from becoming newly dispatchable until its durable removal either fails definitively or exact teardown completes. A publication already holding the registry lock wins first; otherwise a matching private stage is invalidated before this method returns. An absent generation is still fenced so a stale preparation cannot publish between admission and the desired-state CAS.

func (*Registry) CheckServerIDUnambiguous added in v1.22.0

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

CheckServerIDUnambiguous reports whether registering `name` would leave the `<sourceID>_<tool>` catalog key space ambiguous against an already-registered MCP server id, returning a wrapped ErrAmbiguousServerID when it would.

Why the key space has to be unambiguous

A tool discovered from server S is registered in the catalog as `S_<toolName>` — a single underscore join in which NEITHER side is charset-constrained: a server id may contain underscores, and server-side tool names routinely do. The join is therefore not injective across arbitrary id pairs. When two ids are separator-ambiguous, a single catalog key can be parsed as belonging to either of them, and a consumer that BUILDS a key by prefixing a server id cannot know which server it just addressed.

That matters because a key prefix is used as a confinement boundary: the Console's MCP-Apps host scopes a sandboxed App to its own server by qualifying every app-supplied tool name with the App's host-derived server id. The qualification is unconditional and the App cannot choose the id, so the boundary holds exactly as well as the key space is unambiguous — and no better. Downstream gates evaluate the posture of whichever server the key resolved to, so an ambiguous resolution is not visible to them either.

Refusing an ambiguous pairing at registration is what makes the key space unambiguous AMONG MCP SERVER IDS, which is the precondition that boundary depends on.

Scope of the guarantee — MCP ids only

This registry sees MCP servers. The tool catalog is SHARED: in-proc and HTTP tools register operator-chosen names into the same namespace with no source prefix at all, and this check never sees them. A bare tool name that happens to look like `<mcpServerID>_<something>` therefore reintroduces the same ambiguity through a door this guard does not cover.

So the honest statement is: `<sourceID>_<tool>` identifies exactly one server AMONG MCP SERVER IDS. Closing the non-MCP door needs the resolved descriptor's `Source` compared exactly at dispatch — a runtime-side check, not a naming rule — which is recorded as a follow-up rather than bolted on here.

The rule

Registering `N` is refused when a registered id `E` (E != N) satisfies either direction: `N` is under `E`'s namespace (`N == E + "_" + …`), or `E` is under `N`'s (`E == N + "_" + …`). Both directions are checked so ORDER does not matter — whichever of an ambiguous pair lands second is refused, and the runtime never ends up in a state the boot order alone decided.

Re-registering the SAME id is always allowed: it is the hot-reload and runtime re-attach path, and replacing an entry cannot create an ambiguity that the id did not already have.

func (*Registry) Deregister added in v1.11.0

func (r *Registry) Deregister(ctx context.Context, name string, owner auth.Owner) error

func (*Registry) DeregisterExact added in v1.26.0

func (r *Registry) DeregisterExact(ctx context.Context, name string, owner auth.Owner, descriptorFingerprint string, withdrawCatalog func() int) (int, error)

DeregisterExact withdraws one live registration from dispatch only after atomically proving its owner and complete descriptor fingerprint. The exact provider handle then remains in a private retryable closing state until Close returns success. withdrawCatalog runs while the registry write lock holds the name against replacement, so catalog withdrawal can never race a same-name registration from another owner. A retry addresses the same closing generation and never converts an absent-after-error observation into a teardown receipt.

func (*Registry) DeregisterExactPublisher added in v1.26.0

func (r *Registry) DeregisterExactPublisher(ctx context.Context, name string, owner auth.Owner, descriptorFingerprint string, withdrawCatalog func() int) (int, error)

DeregisterExactPublisher closes a matching current publisher generation. If this process has only an older same-owner epoch, it leaves that already-inert handle untouched and returns success: the durable removal phase, not local absence, is the security revocation receipt. Foreign-owner state still fails closed and a matching generation retains retryable exact-close semantics.

func (*Registry) GetServer

func (r *Registry) GetServer(ctx context.Context, name string) (*ServerView, error)

GetServer returns the per-server detail view. Identity is mandatory.

func (*Registry) Health

func (r *Registry) Health(ctx context.Context, name string, window time.Duration) (*HealthSnapshot, error)

Health returns the per-server handshake-latency sparkline + reconnect history + transport-error rate. The window argument bounds the reconnect-history slice. Identity is mandatory.

func (*Registry) ListPrompts

func (r *Registry) ListPrompts(ctx context.Context, name string) ([]PromptView, error)

ListPrompts returns the advertised prompts for a server. Identity is mandatory.

func (*Registry) ListResources

func (r *Registry) ListResources(ctx context.Context, name string) ([]ResourceView, error)

ListResources returns the advertised resources for a server. It runs a Discover and projects the synthetic resource descriptors. Identity is mandatory.

func (*Registry) ListServers

func (r *Registry) ListServers(ctx context.Context, f ListFilter) ([]ServerView, *Cursor, error)

ListServers returns the filtered, paginated server list. The view shapes are projection-only; no per-call state lives on the Registry. Identity is mandatory.

func (*Registry) OAuthDiscoveryTarget added in v1.13.0

func (r *Registry) OAuthDiscoveryTarget(name string) (challenge *AuthChallenge, serverURL string, allowedOrigins []string, err error)

OAuthDiscoveryTarget returns the inputs the on-demand discovery walker needs for a server: the captured challenge (nil when none seen), the server URL, and the per-connection cross-origin allowance list. An unknown name returns ErrServerNotFound. The returned slices/pointers are copies — the caller may read them without holding the registry lock.

func (*Registry) OwnerOf added in v1.18.0

func (r *Registry) OwnerOf(name string) (auth.Owner, bool)

OwnerOf returns the (tenant, agent) owner tag of the named registration and whether a registration by that name currently exists. It is the read the same-name replace consults to keep an atomic upsert scoped to the caller's OWN registration: a re-attach that supersedes a still-live connection is the operator replacing their own, so tearing the old one down first is intended; a same-name attach by a DIFFERENT owner must never tear down another owner's live tools/transport. A boot-declared server carries the zero owner. The returned owner is a value copy, safe to read without the registry lock.

func (*Registry) Probe

func (r *Registry) Probe(ctx context.Context, name string) (*ProbeResult, error)

Probe runs a transport round-trip (a Discover acting as a tools/list ping) and returns the latency. Identity is mandatory.

It is a READ, and its bare-name resolution is deliberate

Like Registry.RefreshDiscovery, Probe records only what the round-trip it just performed OBSERVED — the measured latency, and a reachable/unreachable state transition. A failed probe's recordError bump is likewise a truthful observation: it fires only when the server genuinely failed to answer, and ordinary dispatch traffic writes the same fields. No caller-chosen value is persisted and nothing recorded here is consulted as policy later, so resolution stays bare-name and process-global alongside the other read projections. See Registry.SetRawHTMLTrust for the contrasting WRITE shape.

func (*Registry) ReadResource added in v1.4.0

func (r *Registry) ReadResource(ctx context.Context, name, uri string) (content []byte, mimeType string, err error)

ReadResource fetches a single resource's content from the named MCP server under the request identity triple — the runtime-side leg of the `mcp.servers.read_resource` Protocol method. Identity is mandatory: a ctx without a full triple fails closed with ErrRegistryIdentityMissing. An unknown server name returns ErrServerNotFound.

func (*Registry) RecordAuthChallenge added in v1.13.0

func (r *Registry) RecordAuthChallenge(name string, ch AuthChallenge)

RecordAuthChallenge records a captured `WWW-Authenticate` Bearer challenge on a server's state. It is invoked from the HTTP transport's challenge-capture callback whenever an MCP call answers `401`. Pure observation: it records inert, server-supplied data and never alters transport state or call semantics. An unknown name is a no-op (the connection may have been deregistered mid-flight) — recording a challenge is best-effort observability, never a hard failure on the call path.

func (*Registry) RecordDiscovery

func (r *Registry) RecordDiscovery(name string, descs []tools.ToolDescriptor) error

RecordDiscovery seeds the per-server stats from an already-fetched descriptor slice without re-calling provider.Discover. The boot-time dev attach path uses this so the Console MCP-page wire surface (`mcp.servers.list`) reports the actual tool count + a real `last_discovery_at`, not zero values.

Pre-RecordDiscovery the boot-time path called Register() with initial-zero stats; the only API that updated stats was RefreshDiscovery, which re-runs the network call. Operators saw `tool_count: 0` and `last_discovery_at: 0001-01-01T00:00:00Z` on every just-booted Runtime because the boot-time discovery never reached the registry's stats — its result went straight to the tool catalog. a walkthrough fix.

RecordDiscovery is a no-network counterpart to RefreshDiscovery: caller already has the descriptors (from a previous provider.Discover at boot), so the method just classifies them + writes the stats. State is set to Online (the descriptors arrived successfully) and recentLatencyMs is set to 0 (the boot-time latency is not threaded through; a follow-up RefreshDiscovery from the Console will populate it).

Identity is NOT required — this is a server-side seeding gesture from the boot path, not a Protocol-edge read.

func (*Registry) RecordOAuthRequirement added in v1.13.0

func (r *Registry) RecordOAuthRequirement(name string, req *auth.OAuthRequirement) error

RecordOAuthRequirement records the discovered OAuth requirement chain on a server's state. Invoked by the on-demand discovery orchestrator after a probe walks the chain. An unknown name returns ErrServerNotFound.

func (*Registry) RecordReconnect

func (r *Registry) RecordReconnect(name, reason string)

RecordReconnect appends a reconnect-history entry. The runtime wires this to the transport-reconnect path; tests call it directly.

func (*Registry) RecordScopeShortfall added in v1.16.0

func (r *Registry) RecordScopeShortfall(name string, sf ScopeShortfall)

RecordScopeShortfall records a captured downstream insufficient-scope step-up on a server's state (mirrors RecordAuthChallenge for the 403 path). It is invoked from the HTTP transport's shortfall-capture callback whenever an MCP call answers `403` + `error="insufficient_scope"`. Pure observation: it records inert, server-supplied data and never alters transport state or call semantics. An unknown name is a no-op (the connection may have been deregistered mid-flight) — recording is best-effort observability, never a hard failure on the call path.

func (*Registry) RefreshDiscovery

func (r *Registry) RefreshDiscovery(ctx context.Context, name string) (*DiscoveryResult, error)

RefreshDiscovery re-runs the named server's discovery and updates the per-server counts + state. Identity is mandatory.

It is a READ, and its bare-name resolution is deliberate

RefreshDiscovery writes registry state, but everything it writes is an OBSERVATION derived from the round-trip it just performed — tool / resource / prompt counts, the discovery timestamp, the measured latency, the reachable state. Nothing it writes is chosen by the caller, and nothing it writes is consulted as policy on any later authorization or rendering decision. The same fields are written unsolicited by the transport's own callbacks (Registry.RecordDiscovery, Registry.RecordReconnect, recordError) from any session's ordinary traffic, so owner- or tenant-scoping this call would not change who can affect the state — it would only make a boot-declared server's refresh unreachable.

Resolution therefore stays bare-name and process-global, like every other read projection (ListServers, GetServer, ListResources, ListPrompts, Health, OAuthDiscoveryTarget). The connection WRITES — the ones that persist caller-chosen policy or remove the registration itself — are the scoped ones (see Registry.SetRawHTMLTrust, Registry.SetOAuthDiscoveryOrigins, Registry.Deregister).

func (*Registry) Register

func (r *Registry) Register(ctx context.Context, reg ServerRegistration) error

Register adds a server to the Registry. Re-registering the same name replaces the prior entry (the dev hot-reload path and the runtime re-attach path both re-register). A same-name replacement CLOSES the prior provider's transport so the replaced session drains instead of leaking: the entry is swapped under the write lock, then the displaced provider's Close runs OUTSIDE the lock (a transport close can block on session teardown and must not stall concurrent reads — mirroring Deregister). When the replacement re-registers the very same provider instance (an idempotent re-register of the live one), the close is skipped so the just-registered transport is not torn down.

func (*Registry) RegistrationIdentity added in v1.25.0

func (r *Registry) RegistrationIdentity(name string) (auth.Owner, string, bool)

RegistrationIdentity atomically returns the reconcile owner and canonical descriptor fingerprint of one live registration. The pair must be read under one lock: separate owner/fingerprint reads could compare fields from two same-name replacements and incorrectly classify a stale registration as current.

func (*Registry) RuntimeAddedSources added in v1.14.0

func (r *Registry) RuntimeAddedSources(owner auth.Owner) []string

RuntimeAddedSources returns the source ids of the runtime-added servers whose owner tag equals owner — the OWNER-SCOPED reconcile VIEW. Boot-declared (zero-owner) servers and every OTHER owner's runtime-adds are excluded, so a run-start reconcile for one owner enumerates only its own runtime-added set and can never detach a boot server or another owner's connection. A zero owner returns nil (a reconcile with no owner has nothing of its own to reconcile — it never falls back to the whole registry). The result is a fresh sorted slice, safe to retain.

This is the ONLY owner-aware read on the Registry: the bare-name read / dispatch paths (ListServers, GetServer, OAuthDiscoveryTarget, ReadResource) stay process-global and untouched, so boot servers remain visible to every session regardless of the reconciling owner.

func (*Registry) SetOAuthDiscoveryOrigins added in v1.14.0

func (r *Registry) SetOAuthDiscoveryOrigins(ctx context.Context, name string, owner auth.Owner, origins []string) (prev []string, err error)

SetOAuthDiscoveryOrigins FULL-REPLACES a server's OAuth-discovery cross-origin allowance list on the live registry and returns the prior set so the caller can compute the granted / revoked delta. It is the live half of the `agent_config.set_mcp_discovery_origins` write: the very next discovery walk reads the new set via OAuthDiscoveryTarget, so a grant lets a previously refused authorization-server hop through and a revoke refuses it.

Revoke is symmetric: dropping an origin also PRUNES the recorded OAuth requirement's authorization-server entries whose provenance origin (SourceURL) is no longer allowed — by building a FRESH requirement and swapping the stored pointer under the lock. The registry hands the requirement out BY POINTER (GetServer returns it directly), so mutating the pointee in place would be a data race against a concurrent reader; the swap leaves any reader holding the prior (immutable) pointer with a consistent value. Origin matching reuses the discovery walker's exported origin normaliser (auth.OriginOf), so a port-differing origin never spuriously matches.

The registry stays PROCESS-GLOBAL bare-name — identity is mandatory for AUTHORIZATION (a caller with no identity triple is refused), NOT for keying. The WRITE, however, is OWNER-SCOPED: owner is the caller's (tenant, agent) tag and the allow-list is replaced only on the registration carrying that same tag, so an allowance write lands on the caller's OWN connection. A name that is unregistered, boot-declared (zero owner), or registered to a different owner all return ErrServerNotFound — resolution and dispatch are unaffected and stay bare-name (see [Registry.ownedEntry]). This mirrors the owner comparison the same-name attach replace already performs via Registry.OwnerOf.

func (*Registry) SetRawHTMLTrust

func (r *Registry) SetRawHTMLTrust(ctx context.Context, name string, trusted bool) (prev bool, err error)

SetRawHTMLTrust persists the per-server raw-HTML trust flag in the runtime-side mirror (the legitimate carve-out for a preference with audit consequences). It returns the prior value so a caller can detect a no-op toggle. Identity is mandatory.

The write is TENANT-SCOPED. The flag governs the sandbox posture a rendered MCP App is given, so it is caller-chosen policy consulted on a later render — a connection WRITE, not an observation. It therefore resolves through [Registry.tenantEntry] rather than the bare-name [Registry.entry]: it lands on a registration the caller's own tenant owns, or on a boot-declared (deployment-global) one, and answers ErrServerNotFound for a registration another tenant owns — indistinguishably from a name nobody registered.

The scoping tenant is read from ctx, NOT taken as a parameter. ctx already carries the verified triple this method requires, and it is the identity the Protocol edge reconciled against the request body before dispatching; taking it as an argument would add a seam a caller could populate with a tenant it does not hold. Deriving it here also makes the write and any COMPENSATING REVERT of that write resolve identically, since both run on the same ctx — an admin write whose audit emit fails must be revertible, and a revert that could fail to resolve where the apply succeeded would leave the toggle observably applied but unrecorded.

Registry READS stay bare-name and process-global — boot servers and runtime-added servers alike remain visible to every session, and resolution and dispatch are untouched.

func (*Registry) SourceIDs added in v1.11.0

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

SourceIDs returns the source ids of every currently-registered server — boot-declared AND runtime-added, across every owner. It is the PROCESS-GLOBAL enumeration (the deployment-shared attached set), NOT the owner-scoped reconcile view: the run-start reconcile uses Registry.RuntimeAddedSources instead so one owner's reconcile never sees (and never detaches) a boot server or another owner's runtime-add. Identity-free (a process-local read of the attached set, not an identity-scoped projection like ListServers); the result is a fresh sorted slice, safe to retain.

func (*Registry) StageRegistration added in v1.25.0

func (r *Registry) StageRegistration(reg ServerRegistration, descs []tools.ToolDescriptor) (*RegistrationSwap, error)

StageRegistration privately reserves one registry entry and returns an exact publication/rollback receipt. The staged provider is deliberately not inserted into servers: direct registry reads must keep reaching the exact prior provider until the catalog's dispatch publication has succeeded.

type RegistryOption

type RegistryOption func(*Registry)

RegistryOption configures a Registry at construction.

func WithRegistryClock

func WithRegistryClock(now func() time.Time) RegistryOption

WithRegistryClock overrides the Registry's wall clock — tests inject a deterministic clock so latency / timestamps are stable.

type ResourceOffloadedPayload added in v1.4.0

type ResourceOffloadedPayload struct {
	events.SafeSealed
	// Identity scopes the offload to the (tenant, user, session) triple
	// the read ran under.
	Identity identity.Quadruple
	// ArtifactID is the content-addressed reference the heavy content
	// was stored under.
	ArtifactID string
	// Source identifies what was offloaded: the resource URI for a
	// `read_resource`, or the tool name for an app-tool-call result.
	Source string
	// SizeBytes is the length of the offloaded content.
	SizeBytes int64
	// OccurredAt is the wall-clock instant the offload happened.
	OccurredAt time.Time
}

ResourceOffloadedPayload is the typed payload for EventTypeMCPResourceOffloaded. SafePayload: no caller-controlled MCP content survives on the payload — only the reference id, the source identifier (resource URI or tool name), the byte size, and the actor identity quadruple.

type ResourceUpdatedPayload

type ResourceUpdatedPayload struct {
	events.SafeSealed
	Identity   identity.Quadruple
	Source     tools.ToolSourceID
	URI        string
	OccurredAt time.Time
}

ResourceUpdatedPayload is the typed payload for EventTypeMCPResourceUpdated. SafePayload: no caller-controlled bytes survive on the payload.

  • Identity scopes the event to the (tenant, user, session) triple under which the resource subscription was registered.
  • Source is the originating MCP attachment's source ID, so subscribers can route by provider.
  • URI is the resource URI the server reported as updated; this may be a sub-resource of the URI the client actually subscribed to.
  • OccurredAt is the wall-clock time the driver received the notification.

type ResourceView

type ResourceView struct {
	URI       string
	MimeType  string
	SizeBytes int64
	Name      string
	Title     string
}

ResourceView is one advertised resource.

type ScopeShortfall added in v1.16.0

type ScopeShortfall struct {
	// RequiredScopes is the parsed `scope` challenge parameter.
	RequiredScopes []string
	// GrantedScopes is the binding's most-recently-granted scope set
	// (populated by the reader that resolved the token, not the transport).
	GrantedScopes []string
	// DownstreamResource is the host the challenge came from.
	DownstreamResource string
	// Origin is the scheme://host[:port] the challenge was observed on.
	Origin string
	// WWWAuthenticate is the verbatim challenge header value.
	WWWAuthenticate string
	// ToolName is the server-side tool name the call targeted (populated by
	// the reader that owns the tool name, not the transport).
	ToolName string
	// CapturedAt is the wall-clock instant the shortfall was observed.
	CapturedAt time.Time
}

ScopeShortfall is a parsed downstream insufficient-scope step-up captured off a `403` whose `WWW-Authenticate` carried `error="insufficient_scope"` (RFC 6750 §3.1). It is inert, server-supplied data — the connection view's LastScopeShortfall record and the per-call error enrichment both read it.

type ServerRegistration

type ServerRegistration struct {
	// Provider is the live MCP provider. Required.
	Provider serverProvider
	// Transport is the wire transport string ("stdio" / "http+sse" /
	// "streamable-http" / "websocket"). Required.
	Transport string
	// URLOrCommand is the transport-prefixed endpoint or argv command.
	URLOrCommand string
	// Policy is the server's ToolPolicy. Zero-valued → DefaultPolicy.
	Policy tools.ToolPolicy
	// ContentShapes lists the canonical content shapes the tools return.
	ContentShapes []string
	// OAuthBindingCount is the configured OAuth binding count.
	OAuthBindingCount int
	// InitialState is the server's starting state. Zero-valued →
	// ServerStateOffline.
	InitialState ServerState
	// OAuthDiscoveryAllowedOrigins is the explicit per-connection cross-origin
	// allowance list for OAuth-requirement discovery fetches. Empty
	// leaves the authorization-server hop needs-allowance (partial discovery).
	OAuthDiscoveryAllowedOrigins []string
	// Owner is the (tenant, agent) reconcile-view tag for a RUNTIME-ADDED
	// server. Boot-declared servers leave it zero (untagged) — the
	// owner-scoped reconcile view never enumerates a zero-owner entry. The
	// runtime-add attach path stamps a non-zero owner (fail-closed there when
	// either component is missing); nothing about resolution or dispatch reads
	// it (those stay bare-name and process-global).
	Owner auth.Owner
	// DescriptorFingerprint is the canonical digest of the complete
	// NON-SECRET runtime-added descriptor. Empty for boot registrations.
	DescriptorFingerprint string
}

ServerRegistration is the operator-supplied static descriptor for one MCP server attachment the Registry tracks.

type ServerState

type ServerState string

ServerState mirrors the canonical state chip the Console renders. The V1 set is closed.

const (
	// ServerStateOnline — transport connected, last discovery / probe
	// succeeded.
	ServerStateOnline ServerState = "online"
	// ServerStateReconnecting — transport dropped, re-establishing.
	ServerStateReconnecting ServerState = "reconnecting"
	// ServerStateOffline — transport down (never connected / closed).
	ServerStateOffline ServerState = "offline"
	// ServerStateAuthPending — server needs an incomplete OAuth binding.
	ServerStateAuthPending ServerState = "auth_pending"
	// ServerStateError — last discovery / probe failed.
	ServerStateError ServerState = "error"
)

The canonical MCP server states.

type ServerView

type ServerView struct {
	// Name is the unique server / source id.
	Name string
	// Transport is the wire transport string.
	Transport string
	// URLOrCommand is the transport-prefixed endpoint or argv command.
	URLOrCommand string
	// State is the canonical state chip.
	State ServerState
	// LastDiscoveryAt is the last successful discovery instant (zero
	// when discovery has never run).
	LastDiscoveryAt time.Time
	// ToolCount / ResourceCount / PromptCount are the advertised counts.
	ToolCount     int
	ResourceCount int
	PromptCount   int
	// RecentLatencyMs is the most recent observed handshake / probe
	// latency.
	RecentLatencyMs int64
	// ErrorRatePerMin is the transport-error rate over the window.
	ErrorRatePerMin float64
	// OAuthBindingCount is the number of OAuth bindings configured.
	OAuthBindingCount int
	// RawHTMLTrusted reports the per-server raw-HTML trust flag.
	RawHTMLTrusted bool
	// DisplayModes lists the advertised MCP-Apps DisplayMode values.
	DisplayModes []string
	// ContentShapes lists the canonical content shapes the server's
	// tools return.
	ContentShapes []string
	// Policy is the read-only ToolPolicy projection.
	Policy tools.ToolPolicy
	// OAuthRequirement is the OAuth requirement advertised by the server and
	// discovered on demand — the verbatim RFC 9728RFC 8414 chain
	// plus provenance. Nil when no discovery has run. Populated only on the
	// DETAIL read (GetServer); the list projection leaves it nil so the hot
	// list row stays compact (§4.3-recorded — the requirement rides get/probe,
	// not list). It is inert, server-supplied, UNVERIFIED data.
	OAuthRequirement *auth.OAuthRequirement
	// LastScopeShortfall is the most recent downstream insufficient-scope
	// step-up (a `403` + `WWW-Authenticate` marking
	// `error="insufficient_scope"`) observed on this connection. Nil when
	// none seen. Populated only on the DETAIL read (GetServer), mirroring how
	// OAuthRequirement rides get — the list row stays compact. Inert,
	// server-supplied data; the operator acts on it, the runtime never does.
	LastScopeShortfall *ScopeShortfall
}

ServerView is the per-server projection the Registry returns. It is a flat shape — no MCP-SDK type crosses the package boundary.

type ToolContextCapturer added in v1.4.1

type ToolContextCapturer interface {
	Capture(ctx context.Context, in CapturedToolContext) error
}

ToolContextCapturer persists the tool context behind a declared MCP App so the host can deliver it to the rendered app. It is an optional seam on the MCP Config: when set, the driver calls Capture from the tool-invocation path whenever a result declares a `ui://` app. The implementation lives in the runtime (over the StateStore + ArtifactStore — heavy-aware), wired at boot; the driver holds only this narrow interface so it never imports the runtime concretes.

Capture is best-effort relative to the tool call: a Capture error is surfaced to the caller (the invocation path logs it loudly and never silently swallows it, CLAUDE.md §13), but it does not fail the tool call itself — the planner still receives the tool result.

Jump to

Keyboard shortcuts

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