mcpconsole

package
v1.19.0 Latest Latest
Warning

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

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

Documentation

Overview

Package mcpconsole wires the MCP-Connections Protocol surface to its runtime-side dependencies — the MCP driver registry and the tool-side OAuth provider.

Why a separate package

The `internal/protocol` package owns the MCPSurface dispatcher and the MCPAccessor / MCPOAuthAccessor interfaces, but it MUST NOT import the `mcp` driver or the `tools/auth` package (CLAUDE.md §13 — the Protocol package stays driver-free; a Protocol type that re-exported a driver type would be the reject-on-sight smell). The adapters that bridge the two live here, in a wiring package both `cmd/harbor` and the integration test import. The MCPSurface depends ONLY on the interfaces; this package is the single concrete that satisfies them.

Concurrent reuse

RegistryAccessor and OAuthAccessor are thin, immutable adapters — the wrapped Registry / Provider are themselves safe for concurrent reuse compiled artifacts, and the adapters add no mutable state.

Index

Constants

This section is empty.

Variables

View Source
var ErrAppToolExposureDenied = errors.New("mcpconsole: tool unavailable — paused or disabled by agent configuration")

ErrAppToolExposureDenied — an App app→host tool call targeted a tool whose MCP server is currently paused, or a tool that is currently disabled, in the agent's active config. It is an authorization rejection (mapped to CodeScopeMismatch at the wire edge), the functional basis for the operator-legible "paused by a system administrator" overlay. Its message carries the stable "paused or disabled by agent configuration" marker the Protocol edge classifies on (the `protocol` package does not import this package, mirroring the existing not-found / identity-missing markers).

View Source
var ErrAppsMisconfigured = errors.New("mcpconsole: AppsAccessor missing a mandatory dependency")

ErrAppsMisconfigured — NewAppsAccessor was called with a missing mandatory dependency. Fails closed.

View Source
var ErrNoOAuthConfigured = errors.New("mcpconsole: no OAuth provider configured (set tools.oauth_providers in your config to enable OAuth binding flows)")

ErrNoOAuthConfigured — the Runtime has no OAuth providers configured, so OAuth flow methods cannot be served. Surfaces as a Protocol CodeRuntimeError with an actionable message naming the missing config key.

View Source
var ErrToolContextMisconfigured = errors.New("mcpconsole: ToolContextStore missing a mandatory dependency")

ErrToolContextMisconfigured — NewToolContextStore was called with a missing mandatory dependency. Fails closed.

Functions

This section is empty.

Types

type AppsAccessor added in v1.4.0

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

AppsAccessor is the runtime-side adapter that satisfies BOTH the protocol.MCPResourceReader and protocol.AppToolInvoker seams the AppsSurface reads through — the MCP Apps host's two methods.

It bridges the driver-free `protocol` package to the runtime concretes it must not import directly (CLAUDE.md §13): the MCP driver registry (for `ui://` resource reads) and the tool catalog (for the app-tool- call proxy's re-entry into the approval / OAuth / identity invocation path). The heavy-content safety net (RFC §6.5) lives here too: content at or above the configured threshold is routed through the ArtifactStore by reference, with a loud `mcp.resource_offloaded` bypass event — never inlined past the threshold, never silently truncated.

A `ui://` MCP App document is the one carve-out: it is a Console-render payload, not LLM context, so it rides inline up to the dedicated, much larger App-document cap instead of the LLM-context heavy threshold — see appDocumentInlineCap. Above that cap it still offloads (the same loud bypass), so a pathologically large App is never inlined unbounded.

Concurrent reuse: AppsAccessor is an immutable adapter — the wrapped registry / catalog / store / bus are themselves concurrent-safe compiled artifacts, and the adapter adds no mutable state. Per-call identity rides ctx.

func NewAppsAccessor added in v1.4.0

func NewAppsAccessor(deps AppsDeps) (*AppsAccessor, error)

NewAppsAccessor builds the MCP Apps host adapter. Registry, Catalog, Store, and Bus are mandatory; a nil fails loud.

func (*AppsAccessor) CallTool added in v1.4.0

CallTool implements protocol.AppToolInvoker. It resolves the named tool from the SAME catalog a planner resolves against and invokes the SAME wrapped descriptor — so the approval gate (the unified pause primitive) + tool-side OAuth fire exactly as on a planner call. The result is heavy-content-disciplined (the context-window safety net); a `ui://` MCP App the invoked tool declared (on its tool-definition `_meta.ui`, reconciled by the driver onto the result's app reference) is projected onto the App field.

func (*AppsAccessor) ReadResource added in v1.4.0

func (a *AppsAccessor) ReadResource(ctx context.Context, serverID, resourceURI string) (protocol.MCPResourceContent, error)

ReadResource implements protocol.MCPResourceReader. It fetches the resource bytes from the MCP registry under the ctx identity triple, then applies the inline-or-offload decision against an effective cap: a `ui://` MCP App document — a Console-render payload that never enters the LLM context — rides inline up to the dedicated App-document cap (appDocumentInlineCap); any other resource keeps the LLM-context heavy threshold. Content at or above the effective cap is offloaded to the ArtifactStore by reference (with a loud `mcp.resource_offloaded` event); below it, the content rides inline.

func (*AppsAccessor) ToolContext added in v1.4.1

func (a *AppsAccessor) ToolContext(ctx context.Context, serverID, toolCallID string) (protocol.AppToolContextRow, error)

ToolContext implements protocol.AppToolContextReader. It resolves the tool context (input + lowered result) a captured MCP App call produced, scoped to the ctx identity triple, projecting each half inline or by reference exactly as ReadResource does. An unknown or cross-identity (serverID, toolCallID) is not found. It delegates to the same ToolContextStore the MCP providers capture through.

type AppsDeps added in v1.4.0

type AppsDeps struct {
	// Registry resolves `ui://` resource reads against the live MCP
	// providers. Mandatory.
	Registry *mcp.Registry
	// Catalog resolves the app-tool-call proxy target — the SAME catalog
	// a planner resolves against, so the approval gate + tool-side OAuth
	// fire on the proxy path. Mandatory.
	Catalog tools.ToolCatalog
	// Store is the ArtifactStore the heavy-content branch offloads to.
	// Mandatory.
	Store artifacts.ArtifactStore
	// Bus carries the `mcp.resource_offloaded` loud bypass event.
	// Mandatory.
	Bus events.EventBus
	// ToolContext is the tool-context store backing `mcp.apps.tool_context`
	// — the same store the MCP providers capture through, so a discovered
	// app's context is readable here. Mandatory.
	ToolContext *ToolContextStore
	// AgentConfig is the agent-config desired-state registry the app→host
	// call gate reads CURRENT exposure from (the planner-snapshot /
	// app-call-current asymmetry): an App callback to a tool whose server is
	// currently paused — or whose tool is currently disabled — is rejected,
	// while an in-flight planner snapshot is undisturbed. Optional: a nil
	// registry (or empty AgentID) leaves the gate inert (backward
	// compatible). The transport is NEVER consulted — pause is desired-state,
	// not transport state; the live transport stays warm.
	AgentConfig agentcfg.Registry
	// AgentID is the agent slot the gate reads desired-state from. Required
	// when AgentConfig is set; ignored when it is nil.
	AgentID string
	// SessionOverlay is the session-safe narrow-only disable set the app→host
	// gate UNIONS into the admin exposure, so a tool a SESSION user disabled
	// (via agent_config.session.set_source_disables) is also rejected from an
	// App callback — parity with the run-start planner-view projection, which
	// unions the same overlay. Optional: nil leaves only the admin exposure
	// gating. Keyed by the ctx (tenant, user, session) triple.
	SessionOverlay sessionoverlay.Store
	// Threshold is the heavy-content threshold in bytes. ≤ 0 falls back
	// to artifacts' shared default.
	Threshold int
	// Clock returns the current wall-clock time. Optional — defaults to
	// time.Now.
	Clock func() time.Time
}

AppsDeps bundles the runtime collaborators the AppsAccessor bridges. Registry, Catalog, Store, and Bus are mandatory — a nil fails closed at construction (CLAUDE.md §5). Threshold ≤ 0 falls back to the shared heavy-output default; Clock defaults to time.Now.

type NoOAuthAccessor

type NoOAuthAccessor struct{}

NoOAuthAccessor is the protocol.MCPOAuthAccessor implementation for Runtimes that have an MCP registry but NO operator-configured OAuth providers — the V1 `harbor dev` default when an operator attaches an MCP server without OAuth (the common case today).

The accessor projects the canonical "no binding configured" shape: ListBindings returns an empty slice for every server (no bindings to project), and InitiateBinding / RevokeBinding fail loudly with ErrNoOAuthConfigured so the Console renders an actionable message instead of a silent no-op.

This keeps the F6 contract clean: a Runtime can serve the read-only `mcp.servers.list` / `.get` / `.resources` / `.prompts` / `.health` methods (which the Console MCP Connections page leans on) without needing OAuth wiring, while the OAuth-specific verbs fail loudly per CLAUDE.md §13 (no silent degradation).

func NewNoOAuthAccessor

func NewNoOAuthAccessor() *NoOAuthAccessor

NewNoOAuthAccessor returns a NoOAuthAccessor.

func (*NoOAuthAccessor) InitiateBinding

func (a *NoOAuthAccessor) InitiateBinding(_ context.Context, server, _ string) (string, string, error)

InitiateBinding fails loud — without an OAuth provider there is no flow to initiate. Operators see the message in the Console toast.

func (*NoOAuthAccessor) ListBindings

func (a *NoOAuthAccessor) ListBindings(_ context.Context, _ string) ([]protocol.MCPBindingRow, error)

ListBindings reports zero bindings for every server. The Console MCP Connections page renders this as "OAuth not connected" rather than as an error — the empty list is a first-class state.

func (*NoOAuthAccessor) RevokeBinding

func (a *NoOAuthAccessor) RevokeBinding(_ context.Context, server, _ string) (bool, error)

RevokeBinding fails loud — without an OAuth provider there is no binding to revoke.

type OAuthAccessor

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

OAuthAccessor adapts a *auth.Provider to the protocol.MCPOAuthAccessor interface — the runtime-side seam the MCPSurface calls for the OAuth binding methods (`bindings.list` read + the `refresh_binding` / `revoke_binding` admin verbs).

V1 binding-enumeration scope

The `auth.Provider` keys tokens by `(BindingScope, subject, source)` and exposes no fleet-wide binding enumeration API. At V1 the adapter therefore projects the caller-visible binding state: it reports the configured binding scope for the source and the caller's own token freshness. A fleet-wide per-server binding catalog is a post-V1 `auth.Provider` extension (page-mcp-connections.md §8 — non-admin operators see only their own ScopeUser binding regardless).

func NewOAuthAccessor

func NewOAuthAccessor(provider *auth.Provider) (*OAuthAccessor, error)

NewOAuthAccessor wraps a *auth.Provider as a protocol.MCPOAuthAccessor. A nil provider is rejected — fail closed.

func (*OAuthAccessor) InitiateBinding

func (a *OAuthAccessor) InitiateBinding(ctx context.Context, server, _ string) (string, string, error)

InitiateBinding implements protocol.MCPOAuthAccessor. It invokes auth.Provider.InitiateFlow and returns the AuthorizeURL + flow State the Console opens in a popup. The principalID argument is reserved for a post-V1 delegated-flow API; V1 drives the flow for the caller's own identity (the auth.Provider reads the subject from ctx).

func (*OAuthAccessor) ListBindings

func (a *OAuthAccessor) ListBindings(ctx context.Context, server string) ([]protocol.MCPBindingRow, error)

ListBindings implements protocol.MCPOAuthAccessor. It projects the configured binding for the server (the OAuthConfig's BindingScope + requested scopes) and the caller's own token freshness. NEVER returns token plaintext (invariant).

func (*OAuthAccessor) RevokeBinding

func (a *OAuthAccessor) RevokeBinding(ctx context.Context, server, _ string) (bool, error)

RevokeBinding implements protocol.MCPOAuthAccessor. It invokes auth.Provider.Revoke for the server's binding.

type RegistryAccessor

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

RegistryAccessor adapts a *mcp.Registry to the protocol.MCPAccessor interface. It is the runtime-side read/control seam the MCPSurface calls for the nine `mcp.servers.*` read methods plus the raw-HTML trust toggle.

func NewRegistryAccessor

func NewRegistryAccessor(reg *mcp.Registry, opts ...RegistryAccessorOption) (*RegistryAccessor, error)

NewRegistryAccessor wraps a *mcp.Registry as a protocol.MCPAccessor. A nil registry is rejected — fail closed (CLAUDE.md §5).

func (*RegistryAccessor) GetServer

func (a *RegistryAccessor) GetServer(ctx context.Context, name string) (protocol.MCPServerRow, error)

GetServer implements protocol.MCPAccessor.

func (*RegistryAccessor) Health

Health implements protocol.MCPAccessor.

func (*RegistryAccessor) ListPrompts

func (a *RegistryAccessor) ListPrompts(ctx context.Context, name string) ([]protocol.MCPPromptRow, error)

ListPrompts implements protocol.MCPAccessor.

func (*RegistryAccessor) ListResources

func (a *RegistryAccessor) ListResources(ctx context.Context, name string) ([]protocol.MCPResourceRow, error)

ListResources implements protocol.MCPAccessor.

func (*RegistryAccessor) ListServers

ListServers implements protocol.MCPAccessor.

func (*RegistryAccessor) Probe

Probe implements protocol.MCPAccessor. Beyond the transport round-trip, a probe TRIGGERS OAuth-requirement discovery when the connection has captured a `WWW-Authenticate` challenge and a discoverer is wired: it walks the RFC 9728RFC 8414 chain and records the verbatim requirement on the registry state, which the operator then reads via mcp.servers.get. The probe's own return shape is UNCHANGED — discovery rides the view, never the probe row. The probe error (e.g. a 401) is preserved: a server that requires auth still reports the failure, and discovery still runs off the captured challenge so the requirement is inspectable.

func (*RegistryAccessor) RefreshDiscovery

func (a *RegistryAccessor) RefreshDiscovery(ctx context.Context, name string) (protocol.MCPDiscoveryRow, error)

RefreshDiscovery implements protocol.MCPAccessor.

func (*RegistryAccessor) SetRawHTMLTrust

func (a *RegistryAccessor) SetRawHTMLTrust(ctx context.Context, name string, trusted bool) (bool, error)

SetRawHTMLTrust implements protocol.MCPAccessor.

type RegistryAccessorOption added in v1.13.0

type RegistryAccessorOption func(*RegistryAccessor)

RegistryAccessorOption configures a RegistryAccessor.

func WithOAuthDiscoverer added in v1.13.0

func WithOAuthDiscoverer(d *auth.Discoverer) RegistryAccessorOption

WithOAuthDiscoverer wires the on-demand OAuth-requirement discovery walker into the probe path: a probe that captures a `WWW-Authenticate` challenge triggers the chain walk and records the discovered requirement on the connection's registry state (read back via mcp.servers.get). A nil discoverer is a no-op — the probe path stays discovery-free.

type ToolContextDeps added in v1.4.1

type ToolContextDeps struct {
	// State is the StateStore the captured context persists through.
	// Mandatory.
	State state.StateStore
	// Store is the ArtifactStore a heavy (input or result) payload offloads
	// to by reference. Mandatory.
	Store artifacts.ArtifactStore
	// Bus carries the `mcp.resource_offloaded` loud bypass event on a heavy
	// offload. Mandatory.
	Bus events.EventBus
	// Threshold is the heavy-content threshold in bytes. ≤ 0 falls back to
	// the shared default.
	Threshold int
	// Clock returns the current wall-clock time. Optional — defaults to
	// time.Now.
	Clock func() time.Time
}

ToolContextDeps bundles the collaborators the ToolContextStore rides on. State, Store, and Bus are mandatory — a nil fails closed at construction. Threshold ≤ 0 falls back to the shared heavy-output default; Clock defaults to time.Now.

type ToolContextStore added in v1.4.1

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

ToolContextStore is the runtime-side store behind the MCP Apps "Data Delivery" lifecycle: the WRITE half (Capture) persists the input + lowered result behind a declared `ui://` app at the tool-invocation site, and the READ half (Load) projects it back for `mcp.apps.tool_context`. It rides the EXISTING StateStore — all three drivers (in-mem / SQLite / Postgres) and identity isolation come free — keyed by the caller's identity triple + a per-call Kind; the heavy content (a large result) offloads to the ArtifactStore by reference through the shared loud-bypass path, so a bulky context never bloats a StateRecord and never reaches the LLM.

The record is scoped to the (tenant, user, session) triple with an EMPTY RunID: the captured context is session-scoped (a rendered app reads it within the session that produced it), so the read — which knows the triple but not necessarily the producing run — resolves it. The per-invocation tool-call id disambiguates calls within the session via the Kind. A cross-identity Load returns not-found by construction (StateStore.Load filters by the triple).

Concurrent reuse: ToolContextStore is an immutable adapter — the wrapped StateStore / ArtifactStore / Bus are concurrent-safe compiled artifacts and the adapter adds no mutable state. Per-call identity rides ctx (CLAUDE.md §5).

func NewToolContextStore added in v1.4.1

func NewToolContextStore(deps ToolContextDeps) (*ToolContextStore, error)

NewToolContextStore builds the MCP Apps tool-context store. State, Store, and Bus are mandatory; a nil fails loud.

func (*ToolContextStore) Capture added in v1.4.1

Capture implements mcp.ToolContextCapturer. It persists the tool context (input + lowered result) keyed by the ctx identity triple + the per-call Kind. Each payload below the heavy threshold rides inline in the envelope; at or above it, the payload offloads to the ArtifactStore by reference (the shared loud-bypass path) and the envelope carries the reference. A missing identity fails closed (CLAUDE.md §6).

func (*ToolContextStore) Load added in v1.4.1

func (s *ToolContextStore) Load(ctx context.Context, serverID, toolCallID string) (protocol.AppToolContextRow, error)

Load resolves a captured tool context for `mcp.apps.tool_context`, scoped to the ctx identity triple. An unknown or cross-identity (serverID, toolCallID) returns a wrapped not-found error whose marker the Protocol surface maps to CodeNotFound — existence is never revealed across identities. A missing identity fails closed.

Jump to

Keyboard shortcuts

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