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 ¶
- Variables
- type AppsAccessor
- func (a *AppsAccessor) CallTool(ctx context.Context, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
- func (a *AppsAccessor) ReadResource(ctx context.Context, serverID, resourceURI string) (protocol.MCPResourceContent, error)
- func (a *AppsAccessor) ToolContext(ctx context.Context, serverID, toolCallID string) (protocol.AppToolContextRow, error)
- type AppsDeps
- type NoOAuthAccessor
- type OAuthAccessor
- func (a *OAuthAccessor) InitiateBinding(ctx context.Context, server, _ string) (string, string, error)
- func (a *OAuthAccessor) ListBindings(ctx context.Context, server string) ([]protocol.MCPBindingRow, error)
- func (a *OAuthAccessor) RevokeBinding(ctx context.Context, server, _ string) (bool, error)
- type RegistryAccessor
- func (a *RegistryAccessor) GetServer(ctx context.Context, name string) (protocol.MCPServerRow, error)
- func (a *RegistryAccessor) Health(ctx context.Context, name string) (protocol.MCPHealthRow, error)
- func (a *RegistryAccessor) ListPrompts(ctx context.Context, name string) ([]protocol.MCPPromptRow, error)
- func (a *RegistryAccessor) ListResources(ctx context.Context, name string) ([]protocol.MCPResourceRow, error)
- func (a *RegistryAccessor) ListServers(ctx context.Context, f protocol.MCPListFilter) ([]protocol.MCPServerRow, string, error)
- func (a *RegistryAccessor) Probe(ctx context.Context, name string) (protocol.MCPProbeRow, error)
- func (a *RegistryAccessor) RefreshDiscovery(ctx context.Context, name string) (protocol.MCPDiscoveryRow, error)
- func (a *RegistryAccessor) SetRawHTMLTrust(ctx context.Context, name string, trusted bool) (bool, error)
- type ToolContextDeps
- type ToolContextStore
Constants ¶
This section is empty.
Variables ¶
var ErrAppsMisconfigured = errors.New("mcpconsole: AppsAccessor missing a mandatory dependency")
ErrAppsMisconfigured — NewAppsAccessor was called with a missing mandatory dependency. Fails closed.
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.
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
func (a *AppsAccessor) CallTool(ctx context.Context, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
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
// 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 ¶
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 ¶
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) (*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 ¶
func (a *RegistryAccessor) Health(ctx context.Context, name string) (protocol.MCPHealthRow, error)
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 ¶
func (a *RegistryAccessor) ListServers(ctx context.Context, f protocol.MCPListFilter) ([]protocol.MCPServerRow, string, error)
ListServers implements protocol.MCPAccessor.
func (*RegistryAccessor) Probe ¶
func (a *RegistryAccessor) Probe(ctx context.Context, name string) (protocol.MCPProbeRow, error)
Probe implements protocol.MCPAccessor.
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 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
func (s *ToolContextStore) Capture(ctx context.Context, in mcp.CapturedToolContext) error
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.