runtime

package
v1.0.217 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Overview

Package runtime is the PR-5 live MCP listener boundary. It binds dedicated, physically and logically isolated Management and Gateway HTTP listeners that run the merged PR-1 protocol kernel, PR-3 authentication + immutable session-identity binding, and PR-2 registry/catalog checks, enforce Host/Origin on every request and HTTP/2 stream, and produce sanitized observe records. It is OBSERVE-ONLY: no policy engine (PR-6), credential materialization (PR-4 is not invoked here), upstream call, inspection, or durable event spool exists, so decision-point methods (tools/list, tools/call) end in a deterministic observe-only rejection.

The runtime is DISABLED BY DEFAULT: when off it binds no socket, starts no goroutine/timer, allocates nothing on the SWG request path, and startup succeeds without MCP certificates, registry or auth configuration.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewBoundedSink

func NewBoundedSink(size int) *boundedChanSink

NewBoundedSink returns a bounded in-memory sink holding up to size records. It is non-blocking: at capacity it drops (and counts) rather than blocking the caller.

Types

type ClientCertMode

type ClientCertMode uint8

ClientCertMode is how a listener treats client certificates.

const (
	// ClientCertNone — no client certificate is requested.
	ClientCertNone ClientCertMode = iota
	// ClientCertRequest — request but do not require a client certificate.
	ClientCertRequest
	// ClientCertRequire — require and verify a client certificate (mTLS); the
	// canonical SHA-256 thumbprint is derived and passed to PR-3 as observed
	// binding metadata.
	ClientCertRequire
)

type Config

type Config struct {
	Gateway    ListenerConfig
	Management ListenerConfig
	Deps       Deps
}

Config is the whole MCP runtime configuration: two independent listeners plus the shared IMMUTABLE libraries (registry/catalog/auth-deps) they read. The listeners never share mutable state.

func (Config) Enabled

func (c Config) Enabled() bool

Enabled reports whether ANY MCP listener is enabled. When false the runtime binds nothing and starts no goroutine (disabled-by-default).

func (Config) Validate

func (c Config) Validate() error

Validate checks both listener configurations transactionally BEFORE anything binds: unsafe/zero/negative/wildcard/conflicting configurations fail here. A disabled listener is not validated (it binds nothing).

type Deps

type Deps struct {
	Registry     *registry.Registry
	Catalog      *catalog.Catalog
	Keys         authn.KeyResolver
	Introspector authn.Introspector
	Replay       *senderconstraint.ReplayCache
	// Sink receives sanitized observe records. A nil sink drops records (still
	// bounded). Sink failure NEVER permits a denied request or a decision-point
	// operation, and must not block shutdown.
	Sink Sink
	// Policy is the OPTIONAL capability-local policy provider (PR-6). When nil, the
	// listener keeps the PR-5 observe-only disposition for decision-point methods.
	// When set, decision-point methods are evaluated against the capability-local
	// policy snapshot (decision-only — never an upstream/credential/broker call); a
	// missing snapshot fails closed with MCP.POLICY.SNAPSHOT_UNAVAILABLE, never a
	// permissive fall-back.
	Policy PolicyProvider
	// Inspection is the OPTIONAL capability-local inspection provider (PR-7). When
	// nil, decision-point methods keep the pre-inspection path (byte-identical). When
	// set, a Gateway tools/call is semantically inspected (schema/DLP/destination)
	// BEFORE policy evaluation; a hard security failure blocks regardless of the
	// policy action, and an ALLOW_WITH_REDACTION obligation is satisfied by a
	// re-validated transform — still decision-only (no upstream/credential/broker
	// call, execution_state stays not_implemented).
	Inspection InspectionProvider
	// Events is the OPTIONAL capability-scoped PR-8 durable decision-event provider.
	// When nil, the pipeline keeps the PR-7 decision-only path byte-identically (no
	// event committed, no denial routed). When set, an ALLOW-class decision-point
	// outcome durably commits a sanitized decision event before the (still
	// not-implemented) response — a critical operation whose event cannot commit
	// fails closed — and auth/authorization denials are routed into the isolated
	// denial lane. It never causes an upstream/credential/broker call.
	Events EventProvider
	// Executor is the OPTIONAL capability-local guarded-execution provider (PR-11).
	// When nil, the pipeline keeps the PR-8 decision-only path byte-identically
	// (execution_state stays not_implemented). When set — only for the Gateway
	// capability, and only after rollout distribution arms it — a decision-point
	// outcome is handed to the rollout-mode executor AFTER inspection + policy have
	// run, which resolves the effective mode disposition (record-only / execute /
	// block) and, for an in-scope executing mode, performs the real guarded upstream
	// tools/call (credential broker + PR-8 commit-before-materialization + upstream
	// client + response DLP). A nil executor is the disabled-by-default posture.
	Executor ExecutionProvider
	// Clock is injected for deterministic tests; nil ⇒ time.Now.
	Clock func() time.Time
}

Deps are the shared IMMUTABLE libraries the listeners read. They are read-only from the listeners' perspective (snapshots / pure validators); the listeners never mutate them and never share mutable per-capability state through them. The replay cache is per-capability partitioned internally, so one instance is safe for both.

type Disposition

type Disposition uint8

Disposition is the terminal disposition of an observed request.

const (
	// DispRejected — the request was rejected before or at admission.
	DispRejected Disposition = iota
	// DispKernelTerminal — a protocol-correct kernel-terminal method completed
	// (initialize, notifications/initialized, ping, notifications/cancelled).
	DispKernelTerminal
	// DispObserveOnly — a decision-point method (tools/list, tools/call) reached the
	// observe boundary and was deterministically rejected (no policy/credential/
	// upstream). PR-5 default when no policy provider is wired.
	DispObserveOnly
	// DispPolicyAllowed — a decision-point method received an ALLOW-class policy
	// decision (PR-6). The policy result is recorded truthfully, but execution is NOT
	// implemented in this slice: no upstream call, no credential, no tool result.
	DispPolicyAllowed
)

func (Disposition) String

func (d Disposition) String() string

String returns the disposition label.

type EventProvider added in v1.0.180

type EventProvider interface {
	CommitDecision(f events.DecisionFacts) (spool.CommitReceipt, error)
	ObserveDenial(in events.DenialInput)
	WriteAllowedCritical(cap evmodel.Capability) bool
}

EventProvider is the OPTIONAL PR-8 durable decision-event dependency (Deps.Events). When nil, the pipeline keeps the PR-7 decision-only path BYTE-IDENTICALLY: no event is constructed or committed and no denial is routed. When set, an ALLOW-class decision-point outcome DURABLY COMMITS a sanitized decision event BEFORE the (still not-implemented) execution response — a critical operation whose event cannot commit fails closed — and authentication/authorization denials are routed into the isolated denial lane. It NEVER causes an upstream/credential/broker call; execution_state stays "not_implemented".

type ExecInput added in v1.0.185

type ExecInput struct {
	Capability   protocol.Capability
	Method       string
	MessageID    jsonrpc.ID
	RawParams    []byte
	Decision     policy.Decision
	Input        policy.DecisionInput
	Inspection   *inspection.Result // nil when inspection did not run
	Identity     *identity.ResolvedContext
	Server       *registry.ServerRecord // resolved server record (nil for tools/list on management)
	SnapshotHash string
	Now          time.Time

	// ToolStillCurrent re-resolves the decision's tool against the LIVE catalog and
	// reports whether it still carries the fingerprint the decision was computed
	// against. It is called by the executor at the LAST moment before the
	// irreversible upstream call.
	//
	// The check at the top of dispatchExecute NARROWS the decision/execution TOCTOU
	// window (OVN-09); it does not close it. After that check the executor still
	// commits durable evidence, plans credentials and fetches provider material —
	// all of which can block — while a concurrent execution.Discovery -> catalog
	// Ingest publishes a new snapshot. Only a re-check adjacent to the side effect
	// makes "the tool the decision was about" and "the tool being called" the same
	// tool.
	//
	// nil ⇒ no re-check (a caller with no catalog seam); the entry check still
	// applies. It returns a bool rather than an error so the executor maps it to
	// exactly one reason and cannot mistake a drift refusal for a transport fault.
	ToolStillCurrent func() bool
}

ExecInput carries the already-resolved request facts the executor needs. It contains no raw token; RawArgs is the exact (already-inspected) tools/call params for the upstream leg.

type ExecOutput added in v1.0.185

type ExecOutput struct {
	Status          int
	Disposition     Disposition
	Reason          mcperr.Reason
	ResponseBody    []byte
	ExecutionState  string // "executed" | "not_implemented" | "blocked" | "shadow_recorded"
	EvaluatedAction string
	EffectiveAction string
	ShadowOverride  bool
	HardFailure     bool
	Executed        bool
}

ExecOutput is the executor's truthful result. The runtime maps it into the terminal Outcome and records the observation fields.

type ExecutionProvider added in v1.0.185

type ExecutionProvider interface {
	// Execute runs the guarded rollout-mode path and returns the terminal result +
	// the observation fields to record. It performs its OWN durable
	// commit-before-side-effect; the runtime does not pre-commit for this path.
	Execute(ctx context.Context, in ExecInput) ExecOutput
}

ExecutionProvider is the PR-11 guarded-execution seam. It is consulted AFTER a decision-point request has passed inspection + the PR-6 policy evaluation, and it owns the rollout-mode resolution + (for an in-scope executing mode) the real guarded upstream execution. A nil provider ⇒ the runtime stays decision-only.

The provider MUST NOT be consulted for Management (which never executes an upstream tools/call); the runtime only wires it for the Gateway capability.

type HealthSnapshot

type HealthSnapshot struct {
	Capability         string
	ListenerID         string
	Phase              Phase
	AcceptedConns      int64
	RejectedConns      int64
	RequestsTotal      int64
	RequestsRejected   int64
	KernelTerminal     int64
	ObserveOnly        int64
	RequestsExecuted   int64
	ActiveSessions     int64
	Queued             int64
	InFlight           int64
	Timeouts           int64
	AuthFailures       int64
	AmbiguousHeaders   int64
	HostOriginFailures int64
	AdmissionRejected  int64
	ShutdownCancels    int64
	ObserveDrops       int64
}

HealthSnapshot is an immutable, listener-independent typed health/metrics view.

type InspectionProvider added in v1.0.179

type InspectionProvider interface {
	InspectionProfile(capNS protocol.Capability) (inspection.Profile, bool)
}

InspectionProvider supplies the capability-local, immutable inspection profile to a listener. It is READ-ONLY from the listener's perspective; the listener never mutates it. A false ok means no inspection is configured for that capability — the runtime then keeps the pre-inspection decision path.

type LimitConfig

type LimitConfig struct {
	MaxConns      int // accepted connections
	MaxConcurrent int // concurrent in-flight requests (worker pool size)
	QueueDepth    int // admission queue depth beyond the workers
	// MaxSessions mirrors the kernel bound for the listener-facing config; the cap
	// is ENFORCED by internal/mcp/session.Manager via ListenerConfig.SessionLimits.
	MaxSessions int
	// MaxOutstanding mirrors the kernel bound; outstanding-request accounting is
	// ENFORCED per (session, direction) in internal/mcp/session/ops.go.
	MaxOutstanding int
	MaxHeaderBytes int // request header bytes
	MaxBodyBytes   int // request body bytes
	// MaxResponseBytes: the observe runtime generates every response itself from
	// bounded internal values, so the attacker-sized case is the UPSTREAM leg,
	// where internal/mcp/upstreamclient enforces its own bound.
	MaxResponseBytes int
	AuthConcurrency  int // concurrent authentications
	DPoPConcurrency  int // concurrent DPoP verifications
	// MaxObservations is RESERVED: observe records are emitted synchronously on the
	// request goroutine, so in-flight records cannot exceed MaxConcurrent. It
	// becomes meaningful only if the sink becomes asynchronous.
	MaxObservations int
	// AdmissionBudget is RESERVED AND UNENFORCED (RISK-026). It is documented as a
	// per-source budget, but admission has no source identity and runs before
	// authentication, so nothing consumes it. Wiring it requires a
	// deployment-topology decision — see
	// docs/design/mcp/ADR-PROPOSAL-mcp-admission-fairness.md. The Limits ownership
	// wall (limits_ownership_test.go) fails the build if this is silently read.
	AdmissionBudget int
	// CleanupPerOp is RESERVED: the sweeper walks sessions the manager already caps
	// at MaxSessions, so the scan is bounded without it. (The credential broker's
	// MaxCleanupPerOp is a DIFFERENT, enforced bound.)
	CleanupPerOp      int
	ReadHeaderTimeout time.Duration // slowloris: header read deadline
	ReadTimeout       time.Duration // full request read deadline
	WriteTimeout      time.Duration // response write deadline
	IdleTimeout       time.Duration // idle keep-alive deadline
	// HandshakeTimeout: net/http bounds the TLS handshake itself from
	// max(ReadHeaderTimeout, ReadTimeout), which are set from this same set.
	HandshakeTimeout time.Duration
	RequestDeadline  time.Duration // absolute per-request deadline
	SessionTTL       time.Duration // idle session expiry
	ShutdownTimeout  time.Duration // graceful-shutdown budget
}

LimitConfig is the mutable input to NewLimits.

func (LimitConfig) Validate

func (c LimitConfig) Validate() error

Validate enforces positivity, hard-cap ceilings, and consistency.

type Limits

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

Limits is the immutable, validated per-capability runtime bound set. Every dimension an attacker (or overload) can drive is finite and validated. Management and Gateway each hold their OWN Limits — a shared mutable limit object is forbidden (a saturation in one capability must never exhaust the other). A zero, negative, or over-ceiling value fails construction (fail closed).

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns a conservative, valid runtime bound set for tests and the dormant default wiring.

func NewLimits

func NewLimits(c LimitConfig) (Limits, error)

NewLimits validates c into an immutable Limits.

func (Limits) AdmissionBudget

func (l Limits) AdmissionBudget() int

AdmissionBudget returns the per-source admission token-bucket size.

func (Limits) AuthConcurrency

func (l Limits) AuthConcurrency() int

AuthConcurrency returns the concurrent-authentication cap.

func (Limits) CleanupPerOp

func (l Limits) CleanupPerOp() int

CleanupPerOp returns the bounded per-operation cleanup-scan size.

func (Limits) DPoPConcurrency

func (l Limits) DPoPConcurrency() int

DPoPConcurrency returns the concurrent DPoP-verification cap.

func (Limits) HandshakeTimeout

func (l Limits) HandshakeTimeout() time.Duration

HandshakeTimeout returns the TLS-handshake deadline.

func (Limits) IdleTimeout

func (l Limits) IdleTimeout() time.Duration

IdleTimeout returns the idle keep-alive deadline.

func (Limits) MaxBodyBytes

func (l Limits) MaxBodyBytes() int

MaxBodyBytes returns the request-body byte cap.

func (Limits) MaxConcurrent

func (l Limits) MaxConcurrent() int

MaxConcurrent returns the concurrent in-flight request cap (worker-pool size).

func (Limits) MaxConns

func (l Limits) MaxConns() int

MaxConns returns the accepted-connection cap.

func (Limits) MaxHeaderBytes

func (l Limits) MaxHeaderBytes() int

MaxHeaderBytes returns the request-header byte cap.

func (Limits) MaxObservations

func (l Limits) MaxObservations() int

MaxObservations returns the in-flight observe-record cap.

func (Limits) MaxOutstanding

func (l Limits) MaxOutstanding() int

MaxOutstanding returns the outstanding-request cap across sessions.

func (Limits) MaxResponseBytes

func (l Limits) MaxResponseBytes() int

MaxResponseBytes returns the response byte cap.

func (Limits) MaxSessions

func (l Limits) MaxSessions() int

MaxSessions returns the live-session cap.

func (Limits) QueueDepth

func (l Limits) QueueDepth() int

QueueDepth returns the admission-queue depth beyond the workers.

func (Limits) ReadHeaderTimeout

func (l Limits) ReadHeaderTimeout() time.Duration

ReadHeaderTimeout returns the header-read deadline (slowloris defense).

func (Limits) ReadTimeout

func (l Limits) ReadTimeout() time.Duration

ReadTimeout returns the full-request read deadline.

func (Limits) RequestDeadline

func (l Limits) RequestDeadline() time.Duration

RequestDeadline returns the absolute per-request deadline.

func (Limits) SessionTTL

func (l Limits) SessionTTL() time.Duration

SessionTTL returns the idle-session expiry window.

func (Limits) ShutdownTimeout

func (l Limits) ShutdownTimeout() time.Duration

ShutdownTimeout returns the graceful-shutdown budget.

func (Limits) WriteTimeout

func (l Limits) WriteTimeout() time.Duration

WriteTimeout returns the response-write deadline.

type Listener

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

Listener is one capability's dedicated, bounded HTTP listener. It owns its own socket, TLS config, worker pool + admission queue, pipeline (session manager + binding store + counters) and shutdown state. Nothing mutable is shared with the other capability's listener, so saturation or failure in one can never exhaust or degrade the other.

func (*Listener) ServeHTTP

func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP is the listener's HTTP entrypoint (steps 1–3 + transport extraction, then the pipeline for steps 4–15). Every request/HTTP2 stream flows through here, so Host/Origin is re-checked per request/stream inside the pipeline.

type ListenerConfig

type ListenerConfig struct {
	Enabled        bool
	Capability     protocol.Capability
	BindAddress    string // interface/IP to bind (e.g. "127.0.0.1"); empty ⇒ invalid unless AllowWildcard
	Port           int
	TLS            *tls.Config // caller-supplied server TLS config (certs already loaded); never mutated
	ClientCertMode ClientCertMode
	AllowedHosts   []string // Host/:authority allowlist (mandatory, non-empty when enabled)
	AllowedOrigins []string
	RequireOrigin  bool
	AuthConfig     authn.CapabilityAuthConfig // this capability's immutable PR-3 config
	Limits         Limits                     // this capability's immutable runtime bounds (listener/HTTP bounds)
	// SessionLimits are the PR-1 kernel session/wire bounds for this capability's
	// dedicated session.Manager (distinct from the listener HTTP bounds above). A
	// zero value (MaxSessions()==0) is resolved to the capability default at
	// construction so an operator need not restate the kernel bounds.
	SessionLimits limits.Limits
	// AllowWildcard permits a 0.0.0.0/:: bind. Off by default — a wildcard bind is
	// rejected unless explicitly allowed by the accepted config.
	AllowWildcard bool
	// AllowInsecure permits a non-TLS listener. It is a TEST/loopback seam only
	// (httptest supplies its own TLS); a non-test deployment requires TLS. It never
	// weakens an enabled TLS config and is documented as the ownership boundary for
	// the (later-slice) production TLS wiring.
	AllowInsecure bool
	// Metadata is the OPTIONAL published OAuth 2.0 Protected Resource Metadata (RFC
	// 9728) for this capability. When set, the listener serves the bounded PUBLIC
	// document at its well-known path and advertises it in the WWW-Authenticate
	// challenge on a 401. Nil ⇒ no metadata document and no challenge header (the
	// pre-QUAL-1 behavior, byte-identical). It never carries a secret — only the
	// public resource identifier and authorization-server issuer URLs.
	Metadata *ProtectedResourceMetadata
}

ListenerConfig is one capability's dedicated listener configuration. Management and Gateway each have their OWN ListenerConfig — nothing (socket, port, pool, session manager, auth config, resource, limits, queue, counters, observe partition) is shared between them.

func (ListenerConfig) Addr

func (c ListenerConfig) Addr() string

Addr returns the host:port bind address.

type MessageClass

type MessageClass uint8

MessageClass is the coarse JSON-RPC class of an observed message (safe metadata).

const (
	// ClassUnknown — not yet classified / not a decodable message.
	ClassUnknown MessageClass = iota
	// ClassRequest — a JSON-RPC request.
	ClassRequest
	// ClassNotification — a JSON-RPC notification (no response).
	ClassNotification
	// ClassResponse — a JSON-RPC response.
	ClassResponse
)

func (MessageClass) String

func (c MessageClass) String() string

String returns the class label.

type ObserveRecord

type ObserveRecord struct {
	ObservationID string              // listener-generated, monotonic
	Capability    protocol.Capability // Gateway / Management
	ListenerID    string              // stable listener identity
	ProtocolVer   string              // negotiated protocol version, if any
	Class         MessageClass        // request / notification / response
	Method        string              // admitted method name (from the closed allow-list only)
	PrincipalHash string              // one-way digest of the resolved principal, if authenticated
	ClientID      string              // OAuth client id (safe), if present
	AgentID       string              // agent id (safe), if present
	ServerID      string              // Gateway server id from the route, if present
	ToolRefHash   string              // one-way hash of the tool ref (never the raw name)
	CatalogState  string              // catalog eligibility label (carried, never promoted)
	AuthResult    string              // "ok" / stable failure reason code
	Disposition   Disposition         // terminal disposition
	Reason        mcperr.Reason       // stable rejection/observe reason (ReasonNone on success)
	HostReason    string              // hostcheck reason (stable string), if host/origin failed
	SessionDigest string              // digest of the session id (never raw attacker input)
	Start         time.Time           // request start
	DurationMS    int64               // bounded duration in ms
	RequestBytes  int                 // bounded body-byte count
	RuntimeRev    uint64              // runtime configuration revision

	// PR-6 policy-decision fields (set only for a decision-point method evaluated
	// against a policy snapshot). They carry safe metadata only — the policy action,
	// stable reason code, matched rule id, policy revision and the execution state.
	PolicyAction   string // policy action (e.g. "ALLOW", "DENY", "QUARANTINE"), if evaluated
	PolicyReason   string // stable policy reason code (e.g. "MCP.POLICY.NO_MATCH_DEFAULT_DENY")
	MatchedRule    string // matched rule id, if any
	PolicyRevision uint64 // policy snapshot revision that produced the decision
	ExecutionState string // "not_implemented" for an ALLOW-class decision in PR-6

	// PR-7 inspection fields (set only for an inspected Gateway tools/call). Safe
	// metadata only — never arguments, output, secrets, URLs or credentials.
	InspectionRevision  uint64 // inspection profile revision
	InspectionSchema    string // schema status label ("valid"/"invalid"/"unsupported"/…)
	InspectionDestClass string // destination class label ("public"/"private"/"metadata"/…)
	InspectionDisp      string // worst inspection disposition ("pass"/"label"/"redact"/"block")
	SecretFound         bool   // a secret classification was found
	PIIFound            bool   // a PII/financial classification was found
	InjectionSuspected  bool   // injection labeling flagged content
	RedactionApplied    bool   // an ALLOW_WITH_REDACTION transform was produced
	RedactionProfile    string // opaque redaction-profile ref, if applied
	TransformedHash     string // transformed canonical hash, if redaction applied
}

ObserveRecord is an IMMUTABLE, sanitized observation. It contains ONLY safe metadata — never a bearer token, DPoP proof, credential material, provider secret path, raw request body, full tool arguments, private certificate material, or unbounded user-controlled text. All potentially attacker-controlled or sensitive values are opaque IDs or one-way digests.

type Outcome

type Outcome struct {
	Status       int
	Disposition  Disposition
	Reason       mcperr.Reason
	HostReason   string
	RetainStream bool // always false
	ResponseBody []byte
	SessionID    string // set on a successful initialize (MCP-Session-Id to return)
	NewSession   bool
	Record       ObserveRecord
}

Outcome is the pipeline's terminal decision for one request. Status is the HTTP status the listener writes; ResponseBody is the (optional) response bytes; RetainStream is ALWAYS false. The Record is the sanitized observation already emitted to the sink (returned too for tests/health).

type Phase

type Phase uint32

Phase is a listener's lifecycle phase (typed, low-cardinality).

const (
	// PhaseDisabled — the listener is off (no socket, no goroutine).
	PhaseDisabled Phase = iota
	// PhaseStarting — validating/binding.
	PhaseStarting
	// PhaseReady — accepting requests.
	PhaseReady
	// PhaseDegraded — running but shedding load (saturation).
	PhaseDegraded
	// PhaseDraining — graceful shutdown in progress.
	PhaseDraining
	// PhaseStopped — fully stopped.
	PhaseStopped
)

func (Phase) String

func (p Phase) String() string

String returns the phase label.

type PolicyProvider added in v1.0.177

type PolicyProvider interface {
	PolicySnapshot(capNS protocol.Capability) *policy.Snapshot
}

PolicyProvider supplies the current capability-local policy snapshot to a listener. It is READ-ONLY from the listener's perspective (an atomic snapshot load); the listener never mutates it. A nil return means no snapshot is published for that capability — the runtime then fails closed (never permissive).

type ProtectedResourceMetadata added in v1.0.192

type ProtectedResourceMetadata struct {
	// Resource is the exact canonical resource identifier a client must request a
	// token audience for (identical to the capability's CapabilityAuthConfig
	// CanonicalResource — the audience validator enforces the same string).
	Resource string
	// AuthorizationServers are the issuer identifiers (URLs) whose tokens this
	// resource accepts.
	AuthorizationServers []string
	// WellKnownPath is the exact request path this listener serves the document at
	// (e.g. "/.well-known/oauth-protected-resource/mcp/gateway").
	WellKnownPath string
	// MetadataURL is the absolute URL of WellKnownPath, advertised verbatim in the
	// WWW-Authenticate challenge's resource_metadata parameter.
	MetadataURL string
	// ResourceName is an optional human-readable label (safe, non-secret).
	ResourceName string
}

ProtectedResourceMetadata is the bounded, PUBLIC OAuth 2.0 Protected Resource Metadata (RFC 9728) a capability listener publishes so a Model-A client can discover the authorization server(s) and the exact canonical resource it must request a token audience for. It carries ONLY public, non-secret configuration — never a tenant id, a token, a key, a certificate, or any credential material.

Every value is precomputed by the composition root (package main) from the authoritative startup config: the runtime package parses no URL and trusts no request Host header when building the document or the challenge, so a host-header-confusion attempt can never influence the advertised resource or metadata URL.

func NewProtectedResourceMetadata added in v1.0.192

func NewProtectedResourceMetadata(canonicalResource string, authServers []string, resourceName string) (*ProtectedResourceMetadata, error)

NewProtectedResourceMetadata builds the published metadata from a canonical resource identifier (which MUST be an absolute https URL — the exact token audience) and the authorization-server issuer URLs. It derives the RFC 9728 well-known request path and absolute metadata URL from the resource: for a resource "https://host/mcp/gateway" the document is served at "https://host/.well-known/oauth-protected-resource/mcp/gateway". It parses the operator-supplied resource once at construction (never a request Host header), and returns an error for a non-absolute or non-https resource so a misconfigured audience fails activation closed rather than publishing a confusing document.

type Request

type Request struct {
	HTTPMethod       string              // GET / POST / DELETE / ...
	Capability       protocol.Capability // the capability the route resolved to
	Host             string              // Host or HTTP/2 :authority (re-extracted per request/stream)
	OriginPresent    bool
	Origin           string
	Path             string // request path (capability/server routing)
	ServerID         string // Gateway: opaque server id extracted from the route
	SessionID        string // MCP-Session-Id (may be empty)
	HasSession       bool
	ProtocolVersion  string // MCP-Protocol-Version header value (may be empty)
	HasVersionHeader bool

	// Credential material — headers ONLY.
	AuthorizationHeaders []string // every Authorization header value (duplicates rejected)
	BearerInQuery        bool     // a bearer credential was seen in the query string (forbidden)
	DPoPProof            string
	HasDPoP              bool

	// mTLS — the thumbprint is derived by the listener from the VERIFIED peer
	// certificate; a client-supplied thumbprint header is never trusted.
	PeerCertThumbprint string
	CanonicalURI       string // absolute request URI for the DPoP htu binding

	// Body carries a pre-read request body (unit-test path). The live listener leaves
	// Body nil and supplies BodyReader instead, so the pipeline reads the body LAZILY
	// — only after Host/Origin (per request/stream), method dispatch, path/capability
	// and registry resolution have passed. A cross-origin/foreign-route/unsupported-
	// method request is therefore rejected WITHOUT ever buffering its body.
	Body       []byte
	BodyReader io.Reader // bounded reader (live path); read at step 9 only for an admitted POST route
}

Request is the transport-extracted, pipeline-ready view of ONE inbound MCP HTTP request (or HTTP/2 stream). The listener extracts these fields PER request/stream — Host/:authority and Origin are re-read every time so a reused H1.1 or H2 connection can never smuggle a second request past the first request's Host/Origin check. It carries no raw token beyond the Authorization header value the PR-3 validator consumes, and never a client-supplied certificate thumbprint.

type Runtime

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

Runtime owns the two dedicated MCP listeners (Gateway and Management) and their transactional lifecycle. It is DISABLED BY DEFAULT: when neither listener is enabled, Start binds no socket, starts no goroutine/timer, and the SWG request path is completely untouched.

func NewRuntime

func NewRuntime(cfg Config) (*Runtime, error)

NewRuntime validates the whole configuration (both listeners + their isolation) and returns a Runtime. An unsafe/zero/negative/wildcard/conflicting configuration fails here, before anything binds.

func (*Runtime) Addr

func (rt *Runtime) Addr(management bool) string

Addr returns the bound address of a capability's listener (after Start), or "" if that listener is not enabled/bound. Primarily for tests that bind port 0.

func (*Runtime) Enabled

func (rt *Runtime) Enabled() bool

Enabled reports whether any MCP listener is enabled.

func (*Runtime) Health

func (rt *Runtime) Health() []HealthSnapshot

Health returns the per-listener typed health snapshots for the ENABLED listeners (Gateway first when both are enabled). A disabled runtime binds no listener, so it returns an empty slice.

func (*Runtime) Shutdown

func (rt *Runtime) Shutdown(ctx context.Context) error

Shutdown stops accepting new requests, drains in-flight requests bounded by ctx, force-closes anything still open at the deadline, closes the sockets and stops the sweepers — then leaves no goroutine, timer or socket behind. It is idempotent.

func (*Runtime) Start

func (rt *Runtime) Start() error

Start binds and serves every enabled listener TRANSACTIONALLY: both sockets are bound before either serves, so an address/port conflict (or any bind error) rolls back cleanly with nothing left serving. A disabled runtime returns nil immediately having bound nothing.

type Sink

type Sink interface {
	Observe(rec ObserveRecord) error
}

Sink receives sanitized observe records. Implementations MUST be bounded and MUST NOT block indefinitely; PR-5 does not implement the durable PR-8 spool. A sink error is advisory only — it never turns a rejection into a success, never permits a decision-point operation, and never blocks shutdown.

Jump to

Keyboard shortcuts

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