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, serverID, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
- func (a *AppsAccessor) CallToolAdmitted(ctx context.Context, serverID, resourceURI, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
- func (a *AppsAccessor) CallToolWithBinding(ctx context.Context, serverID, binding, resourceURI, tool string, ...) (protocol.MCPAppToolResultRow, error)
- func (a *AppsAccessor) CurrentGeneration(serverID string) (string, bool)
- 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 RegistryAccessorOption
- type ToolContextDeps
- type ToolContextStore
Constants ¶
This section is empty.
Variables ¶
var ErrAppToolExposureDenied = fmt.Errorf( "%w: mcpconsole: tool unavailable — paused or disabled by agent configuration", protocol.ErrAccessorScopeDenied)
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.
It wraps protocol.ErrAccessorScopeDenied so the wire edge classifies it by errors.Is. The edge previously matched this message's TEXT, which a southbound server's error could contribute to; see the Protocol sentinel's godoc for why a refusal must not be mintable from foreign wording.
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 ordinary inline-payload bound — 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, serverID, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
CallTool implements protocol.AppToolInvoker. It resolves the named tool and invokes the SAME wrapped descriptor the planner resolves — 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.
Two deliberately different resolution views ¶
The tool is resolved against the ordinary planner/model catalog FIRST (the pre-existing path — ordinary tools, including ones that declare a `ui://` app for a rendered App to re-invoke). When the name does NOT resolve there, the only remaining authority is the serverID-named server's App dispatch catalog — the app-only callbacks the provider declared with `_meta.ui.visibility: ["app"]` and the attach path kept OUT of the ordinary catalog. That catalog is keyed by the host-derived server identity, not by any string prefix or remembered global name:
- an app-only callback is NOT resolvable without its own serverID (missing server identity → typed not-found before invocation);
- a serverID that does not hold the name answers not-found, so one server's rendered App can never invoke another server's callback;
- an ordinary tool called with a serverID that disagrees with its actual Source is refused as a scope mismatch BEFORE invocation (the App claims an identity it does not hold). An ordinary tool called with NO serverID keeps the pre-catalog behavior (legacy clients).
Whatever view the tool resolved through, the invocation path is the SAME: the identity / agent-reach / current-state exposure gate below and the wrapped descriptor's approval / OAuth / identity path.
func (*AppsAccessor) CallToolAdmitted ¶ added in v1.28.0
func (a *AppsAccessor) CallToolAdmitted(ctx context.Context, serverID, resourceURI, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
CallToolAdmitted implements protocol.AppToolAdmissionInvoker — the distinct, narrow admission-verified invocation seam (HA-56). The AppsSurface calls it ONLY after verifying a fresh render admission for the exact (identity, agent, server, resource) tuple and minting the unforgeable call-local proof.
The call-local proof is the authority — never method selection ¶
This exported method must not reach ResolveAppTool merely because an internal caller invoked it with a fully verified identity / effective-agent context. The accessor therefore verifies the call-local proof (protocol.CheckRenderAdmissionProof) against the EXACT tuple this call names — identity from ctx, the effective agent, serverID, the resourceURI, AND the exact CURRENT provider/catalog generation read from the registry — BEFORE any resolution, before the paused/disabled exposure gate, and before any invocation. A direct call with no proof, or a proof that does not bind the exact tuple, is refused here with zero callbacks. The proof can only be minted by the Protocol surface after it opened the sealed admission AND re-verified the current render tuple, so a call that did not ride the surface's verified path can never resolve an app-only callback.
The generation closes the TOCTOU window ¶
The proof binds the generation the surface verified. The accessor re-reads the CURRENT generation before resolving, so a refresh/replacement that landed after the surface's verification changes the generation and the proof no longer matches — the call is refused with zero callbacks. Even the window between this generation read and the descriptor lookup is closed: resolution goes through the registry's atomic ResolveAppToolAtGeneration, which compares the exact generation and resolves the app-only descriptor under ONE read lock, so a race that changes the generation mid-call fails typed and never returns (never executes) a newer-generation row.
Unlike CallTool / CallToolWithBinding, this path resolves the named tool EXCLUSIVELY through its own server's App dispatch catalog — never the ordinary planner/model catalog, never a provider-local ValidateAppBinding. The sealed admission token is never handed to a provider-local validator. The current paused/disabled exposure gate re-runs, and the SAME wrapped descriptor the ordinary path invokes fires, so approval / OAuth / policy / redaction / retry / audit still work exactly as they do on a planner call.
A host-derived server identity is MANDATORY: an empty server never falls through to ordinary/global resolution.
func (*AppsAccessor) CallToolWithBinding ¶ added in v1.27.0
func (a *AppsAccessor) CallToolWithBinding(ctx context.Context, serverID, binding, resourceURI, tool string, args json.RawMessage) (protocol.MCPAppToolResultRow, error)
CallToolWithBinding dispatches an app callback using its opaque render capability while preserving the ordinary invoker compatibility seam. It is the LEGACY live-binding path (HA-56): the binding is passed to the provider-local ValidateAppBinding for app-only resolution. A render-admission-backed call NEVER rides here — it rides the distinct admission-aware seam (CallToolAdmitted).
func (*AppsAccessor) CurrentGeneration ¶ added in v1.28.0
func (a *AppsAccessor) CurrentGeneration(serverID string) (string, bool)
CurrentGeneration returns the deterministic current provider/catalog generation fingerprint for the named server, delegating to the MCP registry. The value is content-derived from the canonical CURRENT descriptor set (resources + app-only callbacks + ordinary catalog), so it changes on detach, replacement, and every successful discovery change, and is stable across replicas with the same canonical current descriptor set. Unknown/empty fails closed (false) — a render admission never binds an empty generation.
This is the "current provider/catalog generation" a fresh render-admission gate binds into the sealed tuple. It is NOT itself a proof of erasure / session+agent exposure / paused+disabled state — the gate composes those checks (G4).
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 Console inline-payload bound. 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 legacy/default slot the gate reads only when a direct
// embedder did not seat EffectiveAgentConfig on ctx. Production Protocol
// dispatch always seats its reach-admitted effective selection first.
// 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 inline-payload bound in bytes. ≤ 0 falls back to
// the Console inline-payload bound. The wiring passes that pinned
// bound rather than the operator's LLM-context heavy-output
// threshold: these payloads are browser-rendered, never prompt bytes.
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 Console inline-payload bound; 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, 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 ¶
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. 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 9728 → RFC 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 inline-payload bound in bytes. ≤ 0 falls back to
// the Console inline-payload bound. The wiring passes that pinned
// bound rather than the operator's LLM-context heavy-output
// threshold: a captured tool context is read back by the Console,
// never replayed into a prompt.
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 Console inline-payload bound; 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 inline-payload bound 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.