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 ¶
- func NewBoundedSink(size int) *boundedChanSink
- type ClientCertMode
- type Config
- type Deps
- type Disposition
- type EventProvider
- type ExecInput
- type ExecOutput
- type ExecutionProvider
- type HealthSnapshot
- type InspectionProvider
- type LimitConfig
- type Limits
- func (l Limits) AdmissionBudget() int
- func (l Limits) AuthConcurrency() int
- func (l Limits) CleanupPerOp() int
- func (l Limits) DPoPConcurrency() int
- func (l Limits) HandshakeTimeout() time.Duration
- func (l Limits) IdleTimeout() time.Duration
- func (l Limits) MaxBodyBytes() int
- func (l Limits) MaxConcurrent() int
- func (l Limits) MaxConns() int
- func (l Limits) MaxHeaderBytes() int
- func (l Limits) MaxObservations() int
- func (l Limits) MaxOutstanding() int
- func (l Limits) MaxResponseBytes() int
- func (l Limits) MaxSessions() int
- func (l Limits) QueueDepth() int
- func (l Limits) ReadHeaderTimeout() time.Duration
- func (l Limits) ReadTimeout() time.Duration
- func (l Limits) RequestDeadline() time.Duration
- func (l Limits) SessionTTL() time.Duration
- func (l Limits) ShutdownTimeout() time.Duration
- func (l Limits) WriteTimeout() time.Duration
- type Listener
- type ListenerConfig
- type MessageClass
- type ObserveRecord
- type Outcome
- type Phase
- type PolicyProvider
- type ProtectedResourceMetadata
- type Request
- type Runtime
- type Sink
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.
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 ¶
AdmissionBudget returns the per-source admission token-bucket size.
func (Limits) AuthConcurrency ¶
AuthConcurrency returns the concurrent-authentication cap.
func (Limits) CleanupPerOp ¶
CleanupPerOp returns the bounded per-operation cleanup-scan size.
func (Limits) DPoPConcurrency ¶
DPoPConcurrency returns the concurrent DPoP-verification cap.
func (Limits) HandshakeTimeout ¶
HandshakeTimeout returns the TLS-handshake deadline.
func (Limits) IdleTimeout ¶
IdleTimeout returns the idle keep-alive deadline.
func (Limits) MaxBodyBytes ¶
MaxBodyBytes returns the request-body byte cap.
func (Limits) MaxConcurrent ¶
MaxConcurrent returns the concurrent in-flight request cap (worker-pool size).
func (Limits) MaxHeaderBytes ¶
MaxHeaderBytes returns the request-header byte cap.
func (Limits) MaxObservations ¶
MaxObservations returns the in-flight observe-record cap.
func (Limits) MaxOutstanding ¶
MaxOutstanding returns the outstanding-request cap across sessions.
func (Limits) MaxResponseBytes ¶
MaxResponseBytes returns the response byte cap.
func (Limits) MaxSessions ¶
MaxSessions returns the live-session cap.
func (Limits) QueueDepth ¶
QueueDepth returns the admission-queue depth beyond the workers.
func (Limits) ReadHeaderTimeout ¶
ReadHeaderTimeout returns the header-read deadline (slowloris defense).
func (Limits) ReadTimeout ¶
ReadTimeout returns the full-request read deadline.
func (Limits) RequestDeadline ¶
RequestDeadline returns the absolute per-request deadline.
func (Limits) SessionTTL ¶
SessionTTL returns the idle-session expiry window.
func (Limits) ShutdownTimeout ¶
ShutdownTimeout returns the graceful-shutdown budget.
func (Limits) WriteTimeout ¶
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 )
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 )
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 ¶
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 ¶
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) 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 ¶
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.
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.