sessions

package
v0.8.4 Latest Latest
Warning

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

Go to latest
Published: Nov 17, 2025 License: MIT Imports: 5 Imported by: 1

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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

type ClientInfo struct {
	Name    string
	Version string
}

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

type ElicitConfig struct {
	Strict bool
	RawDst *map[string]any
}

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:

  1. target MUST be a non-nil pointer to a struct; exported fields become schema properties.
  2. 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.
  3. Pointer fields are treated as optional. Non-pointer fields present in the schema's required set are enforced.
  4. On success the target struct is populated in-place and the user action is returned.
  5. 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

type EventHandlerFunction func(ctx context.Context, payload []byte) error

EventHandlerFunction handles server-internal events for a specific topic. Returning an error will terminate the subscription with that error.

type MessageHandlerFunction

type MessageHandlerFunction func(ctx context.Context, msgID string, msg []byte) error

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

type RootsListChangedListener func(ctx context.Context) error

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.

Jump to

Keyboard shortcuts

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