Documentation
¶
Overview ¶
Package sessions defines the core session abstraction shared by MCP transports and server capability code. A session represents the negotiated protocol version, authenticated principal, and optional capability surface for a connected client. Transports create and persist session metadata via a SessionHost implementation and attach capability handles returned from higher-level server code (mcpservice).
Layers & Roles
Transport -> orchestrates initialize handshake, manages lifetime SessionHost -> durability & coordination (ordered client stream + internal events + metadata + KV) Session object -> per-session view exposed to capability code
Host Interface ¶
SessionHost abstracts persistence and ordered fan-out semantics required by streaming transports:
- PublishSession / SubscribeSession : ordered client-visible message log (at-least-once)
- PublishEvent / SubscribeEvents : server-internal coordination topics
- Metadata CRUD + sliding TTL : lifecycle & revocation
- Bounded per-session KV : small auxiliary state
Implementations
memoryhost : in-memory reference used for tests / single-process examples redishost : Redis Streams backed implementation for horizontal scale and durability
Capabilities ¶
A Session may expose optional capability interfaces (sampling, roots, elicitation). Transport / engine layers interrogate these when handling incoming JSON-RPC requests. Absence simply means the server did not elect to provide that surface for the session.
Elicitation Helpers ¶
WithStrictKeys and WithRawCapture configure per-call decoding semantics for ElicitationCapability.Elicit. They modify ElicitConfig through functional options; additional options can be added without breaking callers.
Example (strict elicitation, shorthand pointer form):
var input struct { Name string `json:"name" jsonschema:"minLength=1"` }
action, err := session.GetElicitationCapability().Elicit(ctx, "Who are you?", &input, sessions.WithStrictKeys())
if err != nil { return err }
if action != sessions.ElicitActionAccept { return }
// use input.Name
Advanced: You can still pre-build a decoder:
dec, _ := elicitation.BindStruct(&input) action, err := session.GetElicitationCapability().Elicit(ctx, "Who are you?", dec)
Index ¶
- Variables
- type CapabilitySet
- type ClientInfo
- type ElicitAction
- type ElicitConfig
- type ElicitOption
- type ElicitationCapability
- type EventHandlerFunction
- type MessageHandlerFunction
- type MetadataClientInfo
- type RootsCapability
- type RootsListChangedListener
- type SampleResult
- type SamplingCapability
- type Session
- type SessionData
- type SessionHost
- type SessionMetadata
- type SessionState
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidElicitSubject = errors.New("sessions: invalid elicitation subject; must be SchemaDecoder or *struct")
ErrInvalidElicitSubject is returned by ElicitationCapability.Elicit when the provided subject value is neither a SchemaDecoder nor a non-nil pointer to a struct. Callers can test errors.Is(err, ErrInvalidElicitSubject) to branch.
var (
ErrSessionNotFound = errors.New("session not found")
)
Functions ¶
This section is empty.
Types ¶
type CapabilitySet ¶ added in v0.3.0
type CapabilitySet struct {
Roots bool `json:"roots,omitempty"`
RootsListChanged bool `json:"roots_list_changed,omitempty"`
Sampling bool `json:"sampling,omitempty"`
Elicitation bool `json:"elicitation,omitempty"`
}
CapabilitySet captures the immutable capability surface negotiated at session creation. Booleans keep it cheap to serialize, compare, and extend.
type ClientInfo ¶
ClientInfo identifies the client connecting to the server.
type ElicitAction ¶ added in v0.3.0
type ElicitAction string
ElicitAction indicates the client's chosen action for an elicitation. Exactly one action is returned. Server logic should usually proceed only when Action == ElicitActionAccept and treat Decline / Cancel as a signal to abort or provide alternative behavior.
const ( ElicitActionAccept ElicitAction = "accept" ElicitActionDecline ElicitAction = "decline" ElicitActionCancel ElicitAction = "cancel" )
type ElicitConfig ¶ added in v0.3.0
ElicitConfig accumulates option settings for an elicitation invocation. Fields are exported only so option helpers in other packages (if any) can apply them; prefer the provided With* helpers for forward compatibility.
type ElicitOption ¶ added in v0.3.0
type ElicitOption func(*ElicitConfig)
ElicitOption configures an elicitation invocation (functional options pattern).
func WithRawCapture ¶ added in v0.3.0
func WithRawCapture(dst *map[string]any) ElicitOption
WithRawCapture copies the raw returned content map into dst (if non-nil). The map is shallow-copied so callers can safely mutate it.
func WithStrictKeys ¶ added in v0.3.0
func WithStrictKeys() ElicitOption
WithStrictKeys enforces that the client returns no properties beyond those defined in the derived schema. Without this option, unknown keys are ignored to allow clients to evolve UI metadata without breaking servers.
type ElicitationCapability ¶
type ElicitationCapability interface {
// Elicit sends an elicitation request with the provided user-facing message text.
// subject may be either:
// 1. an elicitation.SchemaDecoder (advanced / pre-built form), OR
// 2. a non-nil pointer to a struct (shorthand). In this case the implementation
// will internally call elicitation.BindStruct(subject) to derive a schema
// and bind the decoder to that pointer.
//
// Most callers should simply pass &MyStruct{} (or a pointer they keep around)
// directly:
//
// type Input struct { Name string `json:"name" jsonschema:"minLength=1"` }
// var in Input
// action, err := elicCap.Elicit(ctx, "Who are you?", &in)
//
// Backwards-compatible usage with an explicit SchemaDecoder still works:
//
// dec, _ := elicitation.BindStruct(&in)
// action, err := elicCap.Elicit(ctx, "Who are you?", dec)
//
// If binding / schema construction fails (e.g. subject not a *struct), the
// returned action will be ElicitActionCancel along with a descriptive error.
Elicit(ctx context.Context, text string, subject any, opts ...ElicitOption) (ElicitAction, error)
}
ElicitationCapability exposes a reflective elicitation API.
Call pattern:
type Input struct { Name string `json:"name" jsonschema:"minLength=1,description=Your name"` }
var in Input
action, err := elicCap.Elicit(ctx, "Who are you?", &in)
if err != nil { /* transport / validation error */ }
if action != sessions.ElicitActionAccept { /* user declined/cancelled */ }
// use in.Name
Behavior:
- target MUST be a non-nil pointer to a struct; exported fields become schema properties.
- JSON + `jsonschema` struct tags (via invopop/jsonschema) are reflected into a flat object schema. Nested objects, arrays, refs and composition keywords are currently rejected to keep client implementation cost low.
- Pointer fields are treated as optional. Non-pointer fields present in the schema's required set are enforced.
- On success the target struct is populated in-place and the user action is returned.
- If decoding fails (type mismatch, enum violation, missing required), an error is returned.
Concurrency: Implementations MUST be safe for concurrent use. Each call derives its schema from type metadata and does not mutate the target until after a successful round-trip.
type EventHandlerFunction ¶
EventHandlerFunction handles server-internal events for a specific topic. Returning an error will terminate the subscription with that error.
type MessageHandlerFunction ¶
MessageHandlerFunction handles ordered messages for a session stream. If the handler returns an error, the subscription will terminate with that error.
type MetadataClientInfo ¶ added in v0.3.0
type MetadataClientInfo struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
InstanceID string `json:"instance_id,omitempty"`
}
ClientInfo records optional client identity details supplied at initialization for observability / logging. All fields are optional. MetadataClientInfo is an expanded form of client identity separate from the lightweight ClientInfo used at initialization.
type RootsCapability ¶
type RootsCapability interface {
ListRoots(ctx context.Context) (*mcp.ListRootsResult, error)
RegisterRootsListChangedListener(ctx context.Context, listener RootsListChangedListener) (supported bool, err error)
}
RootsCapability when present, exposes workspace roots and change notifications.
type RootsListChangedListener ¶
RootsListChangedListener is invoked when the set of workspace roots changes.
type SampleResult ¶ added in v0.6.0
type SampleResult struct {
Message sampling.Message
// Usage, Model, StopReason etc. can be added once surfaced by the engine.
Model string
StopReason string
Meta map[string]any
}
SampleResult is the ergonomic return type for SamplingCapability.CreateMessage. It intentionally mirrors the wire result shape while leaving room for future metadata without forcing callers onto protocol types.
type SamplingCapability ¶
type SamplingCapability interface {
// CreateMessage requests the client host sample a single assistant message.
// system: required system prompt string (may be empty for no system context).
// user: the current user-authored message (RoleUser; exactly one content block).
// opts: functional options configuring model preferences, history, streaming, etc.
CreateMessage(ctx context.Context, system string, user sampling.Message, opts ...sampling.Option) (*SampleResult, error)
}
SamplingCapability when present on a session, enables the sampling surface area.
type Session ¶
type Session interface {
SessionID() string
UserID() string
// ProtocolVersion is the negotiated MCP protocol version baked into the session.
ProtocolVersion() string
GetSamplingCapability() (cap SamplingCapability, ok bool)
GetRootsCapability() (cap RootsCapability, ok bool)
GetElicitationCapability() (cap ElicitationCapability, ok bool)
// PutData stores raw bytes value under key scoped to this session. The
// storage is best-effort bounded by host TTL / size constraints. Callers
// SHOULD keep values small (host-specific limits may apply). Context governs
// cancellation. Returns a backend error on failure.
PutData(ctx context.Context, key string, value []byte) error
// GetData retrieves raw bytes previously stored with PutData. It returns
// (nil, false, nil) ONLY when the key does not exist. It MUST NOT collapse
// backend/storage errors into (ok=false). Any transport, context cancellation
// or backend failure MUST surface via a non-nil err (caller should ignore ok
// when err != nil). An empty stored value returns ([]byte{}, true, nil).
GetData(ctx context.Context, key string) (value []byte, ok bool, err error)
// DeleteData removes the key if present. Missing keys are not an error.
DeleteData(ctx context.Context, key string) error
}
Session represents a negotiated MCP session and exposes optional per-session capabilities. Implementations MUST be safe for concurrent use.
type SessionData ¶ added in v0.3.0
type SessionData interface {
PutData(ctx context.Context, key string, value []byte) error
GetData(ctx context.Context, key string) ([]byte, error)
DeleteData(ctx context.Context, key string) error
}
SessionData is an optional extension for per-session key/value storage. Callers should use a type assertion: if ds, ok := sess.(SessionData); ok { ... }.
type SessionHost ¶
type SessionHost interface {
// PublishSession appends a message to the durable per-session client-facing
// event stream.
PublishSession(ctx context.Context, sessionID string, data []byte) (eventID string, err error)
// SubscribeSession registers a handler that will compete for messages from the
// per-session client-facing event stream starting after lastEventID (empty
// string means from the beginning). The passed context governs the lifetime.
// Returning a non-nil error from the handler ends the subscription with that
// error. Implementations MUST make a best-effort to avoid dropping messages,
// but at-least-once (not exactly-once) delivery is the contract.
SubscribeSession(ctx context.Context, sessionID string, lastEventID string, handler MessageHandlerFunction) error
// PublishEvent appends an event to the internal event stream scoped by topic
// and used for server-side coordination (never delivered directly to the MCP
// client). Semantics:
// * Ordering: Events are totally ordered per topic according to the order
// PublishEvent is called successfully.
// * Visibility: Subscribers only receive events published AFTER they
// subscribe (no historical replay obligation).
// * Delivery: At-least-once to every active subscriber. Implementations
// strive for exactly-once but callers must tolerate duplicates under
// rare failure / reconnect scenarios (especially in distributed hosts).
// * Fan-out: All current subscribers for the topic get every subsequent
// subsequent event (subject to at-least-once guarantee).
// * Retention: Minimal retention is sufficient; implementations may prune
// or discard already-delivered events as long as future delivery
// guarantees hold. No replay API is exposed.
// * Lifetime: Events need not survive process restarts; durability is best
// effort unless an implementation chooses otherwise. Removing a topic MAY
// discard its associated event stream.
PublishEvent(ctx context.Context, topic string, payload []byte) error
// SubscribeEvents registers a handler that will receive (in order) each
// event published after subscription time for the given topic.
// The passed context governs the lifetime. Returning a non-nil error from the
// handler ends the subscription with that error. Implementations SHOULD make
// a best-effort to avoid dropping events, but at-least-once (not exactly-once)
// delivery is the contract.
SubscribeEvents(ctx context.Context, topic string, handler EventHandlerFunction) error
// --- Session metadata lifecycle ---
CreateSession(ctx context.Context, meta *SessionMetadata) error
GetSession(ctx context.Context, sessionID string) (*SessionMetadata, error)
MutateSession(ctx context.Context, sessionID string, fn func(*SessionMetadata) error) error
TouchSession(ctx context.Context, sessionID string) error
DeleteSession(ctx context.Context, sessionID string) error // idempotent hard delete
// --- Per-session bounded KV storage ---
PutSessionData(ctx context.Context, sessionID, key string, value []byte) error
// GetSessionData returns (nil,false,nil) if the key does not exist, (v,true,nil)
// on success, and (nil,false,err) on backend / context error.
GetSessionData(ctx context.Context, sessionID, key string) (value []byte, found bool, err error)
DeleteSessionData(ctx context.Context, sessionID, key string) error
}
SessionHost defines the unified storage + messaging contract for sessions. Implementations MUST be safe for concurrent use and suitable for horizontal scaling. All methods should treat unknown session IDs as no-ops or return a sentinel error (implementation-specific) rather than panicking.
type SessionMetadata ¶ added in v0.3.0
type SessionMetadata struct {
MetaVersion int `json:"meta_version"` // For forward migration; starts at 1
SessionID string `json:"session_id"` // immutable
UserID string `json:"user_id"` // immutable
Issuer string `json:"issuer,omitempty"` // immutable (empty if not enforced)
ProtocolVersion string `json:"protocol_version,omitempty"` // immutable after creation handshake
Client MetadataClientInfo `json:"client,omitempty"` // immutable (can relax later if needed)
Capabilities CapabilitySet `json:"capabilities,omitempty"` // immutable
// State tracks whether the session is pending or open. Missing state should
// be interpreted as open for backward compatibility.
State SessionState `json:"state,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastAccess time.Time `json:"last_access"`
TTL time.Duration `json:"ttl"`
MaxLifetime time.Duration `json:"max_lifetime,omitempty"`
// OpenedAt records when the session transitioned to open.
OpenedAt time.Time `json:"opened_at,omitempty"`
Revoked bool `json:"revoked"`
}
SessionMetadata is the authoritative persisted representation of an MCP session. Invalidation and lifetime are handled via stored flags + TTL semantics in the host.
Fields marked immutable must not be changed after creation. Timestamps are wall-clock times in UTC. TTL is a sliding window: the host SHOULD expire a session if LastAccess + TTL < now (subject to debounce). If MaxLifetime > 0, the host MUST also expire the session once CreatedAt + MaxLifetime < now regardless of activity.
type SessionState ¶ added in v0.4.0
type SessionState string
SessionState represents the lifecycle phase of a session.
const ( // SessionStatePending indicates the server has responded to initialize but // has not yet received the client's notifications/initialized. While pending, // the session must not accept requests. SessionStatePending SessionState = "pending" // SessionStateOpen indicates the client has completed initialization and the // session is fully operational. SessionStateOpen SessionState = "open" )
Directories
¶
| Path | Synopsis |
|---|---|
|
Package memoryhost provides an in-memory sessions.SessionHost implementation suitable for tests, development, and single-process servers.
|
Package memoryhost provides an in-memory sessions.SessionHost implementation suitable for tests, development, and single-process servers. |
|
Package redishost implements sessions.SessionHost using Redis primitives (Streams + simple keys) to support horizontally scalable MCP deployments.
|
Package redishost implements sessions.SessionHost using Redis primitives (Streams + simple keys) to support horizontally scalable MCP deployments. |