protocol

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: 2 Imported by: 0

Documentation

Overview

Package protocol layers MCP admission on top of the version-agnostic jsonrpc decoder: supported-version negotiation and adapters (MCP-PROTO-010/011), peer-role- and direction-aware method admission against the Culvert-reviewed registry (MCP-PROTO-002/016), session lifecycle validation (MCP-PROTO-012), and the listener-independent terminal-status transport primitive (MCP-PROTO-017).

Nothing here binds a socket, speaks HTTP, or holds a stream. The transport primitive is a pure decision function a future PR-5 listener will consult; PR-1 ships the decision, not the listener.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AdmittedMethods

func AdmittedMethods() []string

AdmittedMethods returns the admitted method names (registry order). Fresh copy.

func IsExplicitlyRejected

func IsExplicitlyRejected(v Version) bool

IsExplicitlyRejected reports whether v is a known, named-excluded revision (as opposed to an unknown future/garbage string). Both are denied; this only aids diagnostics.

func IsSupported

func IsSupported(v Version) bool

IsSupported reports whether v is in the reviewed allowlist.

func LifecycleAdmits

func LifecycleAdmits(s State, method string) bool

LifecycleAdmits reports whether method is legal in state s, treating the handshake methods as one-time and gating everything else behind establishment. It is a pure function; the session package holds the state under its own lock.

  • StateNew admits ONLY initialize (pre-negotiation bootstrap).
  • StateInitializing admits notifications/initialized (to complete the handshake) and ping.
  • StateInitialized admits every method EXCEPT the one-time handshake pair, so a duplicate initialize or a second notifications/initialized is an invalid-lifecycle error, not a silent no-op.
  • StateClosed admits nothing.

func ValidateRegistry

func ValidateRegistry() error

ValidateRegistry asserts the forward/reverse parity invariants of the admitted registry at runtime: every admitted method resolves to exactly one of {kernel-terminal, a per-capability decision point} — never both, never neither — and no method name is duplicated. It returns an error describing the first violation, or nil. A test calls this so a future registry edit that breaks parity fails the build.

Types

type Adapter

type Adapter interface {
	Version() Version
	Normalize(msg jsonrpc.Message) (jsonrpc.Message, error)
}

Adapter normalizes a decoded message from a specific supported version into the kernel's single internal, version-agnostic representation, so no downstream stage ever branches on protocol version (MCP-PROTO-011). For the V1 admitted six methods the two supported revisions share an identical envelope shape, so normalization is the identity — but the per-version adapter boundary is real and proven equivalent by tests, keeping the door open for a future revision with wire differences without leaking version into downstream code.

func AdapterFor

func AdapterFor(v Version) (Adapter, bool)

AdapterFor returns the adapter for a supported version. An unsupported version has no adapter (default deny at negotiation, MCP-PROTO-010 / §8): the caller must never reach here with an unsupported version.

type Admission

type Admission struct {
	Handling Handling
	// DecisionPoint is the named downstream owner for HandlingDecisionPoint.
	DecisionPoint string
	// Reason is set for HandlingRejected (always ReasonUnsupportedMethod).
	Reason mcperr.Reason
	// Detail is a fixed, non-hostile description for a rejection.
	Detail string
}

Admission is the result of admitting a method on a leg/direction/capability.

func Admit

func Admit(capability Capability, dir Direction, class jsonrpc.Class, method string) Admission

Admit resolves how a method is handled for the given capability, requestor direction and wire class. It is the sole admission gate:

  • a method absent from the registry is rejected (MCP-PROTO-016);
  • a message whose wire class disagrees with the method's role is rejected — a notification-only method carrying an id, or a request method sent without one (the "notifications with ids" / "requests without ids" guard);
  • a non-bidirectional method originated by the server (reverse channel) is rejected, so server-originated sampling/elicitation/roots and any other reverse request never dispatch (MCP-PROTO-015 / §5).

Admission is intentionally version-agnostic: the registry is Culvert's reviewed set, NOT "whatever the negotiated version contains". A method valid in the negotiated spec version but absent here is rejected all the same.

type Capability

type Capability int

Capability is one of the two MCP surfaces. They share the strict parser but carry independent admission decision points, limits and namespaces (ADR-0024 §D-13): a method admitted on both surfaces still resolves to a different downstream owner per surface.

const (
	// Gateway is the business MCP Security Gateway surface (Capability B).
	Gateway Capability = iota
	// Management is the read-only + draft/validate/simulate surface (Capability A).
	Management
)

func (Capability) String

func (c Capability) String() string

String returns the capability label.

type Direction

type Direction int

Direction is the requestor direction of a message — who originated the request. It is a first-class dimension of correlation state (session, direction, id): the same JSON-RPC id may be outstanding in BOTH directions at once, and one direction must never touch the other's state (MCP-PROTO-015). It is also an admission input: reverse-channel (server-originated) requests are not proxied in V1.

const (
	// ClientOriginated — the natural client→server request flow (the client, or
	// Culvert acting as a client toward an upstream server, is the requestor).
	ClientOriginated Direction = iota
	// ServerOriginated — the reverse channel (the server is the requestor). In V1
	// server-originated requests are rejected at admission.
	ServerOriginated
)

func (Direction) String

func (d Direction) String() string

String returns the direction label.

type Handling

type Handling int

Handling is how the kernel treats an admitted method.

const (
	// HandlingRejected — the method is not admitted; there is no dispatch path.
	HandlingRejected Handling = iota
	// HandlingKernelTerminal — the kernel handles and answers the method itself and
	// never dispatches it downstream.
	HandlingKernelTerminal
	// HandlingDecisionPoint — the method is admitted and dispatched to exactly one
	// named downstream decision point (implemented in a later slice, not PR-1).
	HandlingDecisionPoint
)

func (Handling) String

func (h Handling) String() string

String returns the handling label.

type Negotiation

type Negotiation struct {
	Requested Version
	// Selected is always a supported version.
	Selected Version
	// Accepted is true when Requested was directly supported (Selected == Requested).
	Accepted bool
	// CounterOffered is true when Requested was unsupported and Culvert is
	// counter-offering Selected (the client then accepts or terminates). This is
	// the 200-InitializeResult path, preferred over a 4xx that would recruit a
	// legacy probe (MCP-PROTO-017).
	CounterOffered bool
}

Negotiation is the outcome of an initialize-body version negotiation.

func Negotiate

func Negotiate(requested Version) Negotiation

Negotiate performs the initialize-body version decision. A supported requested version is accepted as-is; any other requested version yields a counter-offer of the primary supported version. Culvert never adopts an unsupported version and never silently downgrades to a legacy adapter (MCP-PROTO-010).

type PeerRole

type PeerRole int

PeerRole identifies which untrusted leg a message arrived on. The SAME strict parser serves both (MCP-PROTO-015); only admission may differ.

const (
	// ClientFacing — the agent → Culvert leg (TB-1 / TB-7).
	ClientFacing PeerRole = iota
	// UpstreamFacing — the Culvert ↔ upstream-MCP-server leg (TB-2).
	UpstreamFacing
)

func (PeerRole) String

func (r PeerRole) String() string

String returns the peer-role label.

type State

type State int

State is a session's protocol lifecycle state. Lifecycle is validated so a session cannot skip establishment (MCP-PROTO-012): before negotiation the only admissible method is initialize (MCP-PROTO-002); the handshake methods are one-time.

const (
	// StateNew — created, not yet initialized. Only initialize is admissible.
	StateNew State = iota
	// StateInitializing — initialize accepted, awaiting notifications/initialized.
	StateInitializing
	// StateInitialized — steady state; all admitted non-handshake methods allowed.
	StateInitialized
	// StateClosed — terminal; nothing is admissible.
	StateClosed
)

func LifecycleNext

func LifecycleNext(s State, method string) State

LifecycleNext returns the state after a method that LifecycleAdmits accepted. Only the two handshake methods advance the state; everything else leaves it unchanged.

func (State) String

func (s State) String() string

String returns the lifecycle-state label.

type TransportCondition

type TransportCondition int

TransportCondition is a transport-layer situation whose terminal HTTP status the kernel decides. These are the security-motivated rejection cases from the D-1 baseline / MCP-PROTO-017. This is a PURE DECISION FUNCTION: it maps a condition to a terminal status and asserts the no-stream invariant. It binds no socket, opens no SSE stream, and knows nothing about HTTP transport beyond the status code — a future PR-5 listener consults it.

const (
	// CondSessionlessMissingVersion — a sessionless / first request with no
	// MCP-Protocol-Version header. D-1 CLOSED: reject with 400 (never silently
	// assume 2025-03-26).
	CondSessionlessMissingVersion TransportCondition = iota
	// CondInvalidVersionHeader — an invalid or unsupported MCP-Protocol-Version
	// header on a subsequent request → 400.
	CondInvalidVersionHeader
	// CondMissingSessionID — a required session identifier is missing → 400.
	CondMissingSessionID
	// CondUnknownOrTerminatedSession — an unknown or terminated session → 404.
	CondUnknownOrTerminatedSession
	// CondDeleteUnsupported — an unsupported DELETE → 405.
	CondDeleteUnsupported
	// CondGetWithoutNegotiatedContext — a GET without a valid negotiated context →
	// terminal 405, and NO text/event-stream is opened (no legacy SSE, no
	// pre-negotiation held stream).
	CondGetWithoutNegotiatedContext
	// CondInitializeVersionUnsupported — an initialize whose requested version is
	// unsupported. Preferred handling is a 200 InitializeResult counter-offer of a
	// supported version — NOT a 4xx — so a spec-conformant or catch-any SDK client
	// is never recruited into the legacy 2024-11-05 probe.
	CondInitializeVersionUnsupported
)

type TransportDecision

type TransportDecision struct {
	// Status is the HTTP status the listener must return.
	Status int
	// RetainStream is ALWAYS false: no condition here ever allocates or holds a
	// stream. It is a field (not an implicit) so the invariant is visible and
	// testable — "N rejected clients ⇒ zero retained streams" begins here.
	RetainStream bool
	// CounterOffer is true only for the initialize counter-offer (a 200 carrying a
	// supported-version InitializeResult), false for every terminal 4xx.
	CounterOffer bool
	// Reason is a stable machine reason string.
	Reason string
}

TransportDecision is the terminal outcome for a transport condition.

func DecideTransport

func DecideTransport(cond TransportCondition) TransportDecision

DecideTransport maps a transport condition to its terminal decision. Every decision retains zero streams; only the initialize-unsupported case is a non-4xx (a 200 counter-offer). There is deliberately no case that returns a legacy SSE stream, an endpoint event, or an automatic fallback.

type Version

type Version string

Version is an MCP protocol revision string.

const (
	// VersionPrimary is the primary supported revision.
	VersionPrimary Version = "2025-11-25"
	// VersionFloor is the compatibility floor.
	VersionFloor Version = "2025-06-18"
)

The frozen V1 baseline (D-1 CLOSED). The supported set is a finite reviewed allowlist; every other revision — including the explicitly-rejected ones below — is denied at negotiation with no best-effort interpretation and no silent downgrade (MCP-PROTO-010).

func SupportedVersions

func SupportedVersions() []Version

SupportedVersions returns the allowlist (primary first) for callers that need to advertise or test it. The returned slice is a fresh copy.

Jump to

Keyboard shortcuts

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