Skip to content

SEP-1359: Protocol-Level Sessions for MCP #1359

@bhosmer-ant

Description

@bhosmer-ant

SEP: Protocol-Level Sessions for MCP

Preamble

  • Title: Protocol-Level Sessions for MCP
  • Author: @bhosmer-ant
  • Status: Proposal
  • Type: Standards Track
  • Created: 2025-08-19

Abstract

This proposal establishes sessions as a first-class protocol concept in MCP, providing message ID uniqueness boundaries, state management, and continuity capabilities independent of transport mechanisms. Currently, sessions exist only in the HTTP transport specification, creating ambiguity about session boundaries and message ID uniqueness for other transports. This proposal defines logical sessions at the protocol layer while allowing transports to manage connections as needed.

Motivation

Currently, sessions exist only in the HTTP transport specification, leaving gaps:

  • No protocol-level session definition
  • Unclear session boundaries for stdio transport
  • No protocol-level termination mechanism
  • Ambiguous separation between transport connections and logical sessions

Issue #984 highlights conflicting interpretations: sessions as transport state vs. sessions as application context. This proposal resolves the ambiguity by defining logical sessions at the protocol layer while allowing transports to manage connections as needed.

Specification

Session Definition

A session is a bounded sequence of client-server interactions that:

  • Begins with successful initialization
  • Maintains a unique namespace for message IDs
  • Preserves negotiated capabilities and protocol version
  • Ends explicitly (termination) or implicitly (invalidation)

Sessions are optional - stateless servers need not implement them.

Protocol Changes

New Types

// Session identifier type
type SessionId = string;

Modified Messages

// InitializeResult - server may assign session
interface InitializeResult {
  protocolVersion: string;
  capabilities: ServerCapabilities;
  serverInfo?: ServerInfo;
  sessionId?: SessionId; // NEW: Session identifier
  sessionTimeout?: number; // NEW: Idle timeout hint in seconds
}

// Protocol messages - add optional sessionId
interface Request {
  method: string;
  params?: object;
  sessionId?: SessionId;      // NEW: Session context
}

interface Notification {
  method: string;
  params?: object;
  sessionId?: SessionId;      // NEW: Session context
}

interface Result {
  sessionId?: SessionId;      // NEW: Session context
  [key: string]: unknown;
}

Session Timeout Semantics

The sessionTimeout field in InitializeResult is advisory:

  • Indicates approximate idle time (seconds) before server MAY expire the session
  • Timer resets on each message from client (not server-initiated messages)
  • Expired sessions return invalid session error on next request
  • Servers are not required to implement timeouts

New Methods

// Session termination - returns EmptyResult on success
interface SessionTerminateRequest extends Request {
  method: "session/terminate";
  // No params - sessionId in request envelope
}

Error Handling

Invalid session errors use standard JSON-RPC error format with MCP-defined error code:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32003,
    "message": "Invalid or expired session",
    "data": {
      "sessionId": "abc123"
    }
  },
  "id": null
}

Other error conditions should use standard JSON-RPC error codes as defined in the JSON-RPC specification.

Session Lifecycle

stateDiagram
    Start --> Active: Initialize (server returns new session id)

    Active --> End: Server expires ("invalid session" response)
    Active --> End: Client terminates (session/terminate)
Loading

Operations

  1. Create: InitializeRequest → server MAY return sessionId in InitializeResult
  2. Use: Both parties include sessionId in all subsequent messages
  3. Validate: Server validates sessionId on each message, returns error if invalid
  4. Terminate:
    • Client: sends session/terminate request
    • Server: returns invalid session error
  5. Re-initialize: On invalid session error, client sends new InitializeRequest (creates new session)

Message ID Uniqueness

Message IDs must be unique within specific boundaries:

  1. Scope: Client and server maintain separate ID spaces (IDs are unique per-party, not globally)
  2. Session Boundary: New sessions start with fresh message ID namespaces
  3. Continuity: Within a session, message IDs must be unique across the session's entire lifetime
  4. Implementation: Each party (client/server) is responsible for ensuring uniqueness of IDs in messages they originate

Example:

  • Client sends requests with IDs: 1, 2, 3...
  • Server sends requests with IDs: 1, 2, 3...
  • After session ends and new one begins, both start fresh ID sequences

Transport Mapping

The protocol defines logical session semantics. Transports implement these semantics as appropriate:

HTTP Transport

  • Maps protocol sessions to the existing Mcp-Session-Id header system
  • Session validation occurs on each request via headers

Stdio Transport

  • Process lifetime typically bounds session
  • MAY persist sessions externally for continuity across process restarts

Custom Transports

  • MAY implement stateless operation (ignore session fields)
  • MUST preserve message ID uniqueness per logical session

Note: HTTP header requirements

While sessions are protocol-level concepts, HTTP transport still requires headers in specific cases:

  • GET requests (SSE streams): SessionId must be included in Mcp-Session-Id header since GET requests contain no protocol message content where sessionId could be included.

Servers supporting old protocol versions MUST include sessionId in headers for backward compatibility when communicating with clients at those protocol versions:

  • DELETE requests (session termination): SessionId included in Mcp-Session-Id header for backward compatibility with existing HTTP transport session termination mechanism.

  • POST requests: SessionId included in Mcp-Session-Id header for backward compatibility with existing HTTP transport session termination mechanism.

The situation may be analogous for future transports, where session information is needed before/after protocol messages are flowing between client and server.

Rationale

Design Decisions

Continuous validation vs explicit resumption

We considered an explicit session/resume request but decided against it because:

  • Current HTTP transport specification uses continuous validation successfully
  • Couldn't come up with compelling use cases for the extra information that an explicit 'session/resume' might carry (e.g., capability renegotiation)
    • *Note: @jonathanhefner cites client wanting to enable sampling mid-session as an example of such a use case. Decision becomes one of cost/benefit.
  • Simpler mental/state management model
  • For authorization, matches proven patterns in gRPC, cloud APIs where tokens refresh transparently

Session status queries vs implicit validation

We considered adding a session/status method to allow clients to check session validity before making requests, but decided it was sufficient for clients to simply consider a session valid until receiving an invalid/expired session error from the server (or terminating the session explicitly). This approach:

  • Reduces protocol complexity
  • Matches existing HTTP transport behavior
  • Avoids race conditions between status checks and actual requests
  • Follows the principle that the authoritative source of session validity is the server's response to actual requests

Session capability discovery

We considered adding a sessions field to ServerCapabilities but decided against it because:

  • No compelling use cases for knowing session support before connecting
  • Adds version-conditional logic complexity (servers must omit the field in older protocol versions)
  • Session support is immediately discoverable via sessionId presence in InitializeResult
  • Follows the principle of preferring immediate discovery over advance advertisement when the information isn't actionable

Protocol-level vs wire-level session placement

SessionId is placed in the protocol message types (Request, Notification, Result) rather than the JSON-RPC envelope because:

  • Sessions are a protocol concept, not a transport/wire format concern
  • Protocol handlers naturally receive sessionId with the message content
  • Transport independence - sessionId travels with protocol semantics regardless of wire format
  • Cleaner inheritance - SessionTerminateRequest naturally has sessionId via Request
  • No need for "envelope extraction" or type casting in handlers

Unified vs separate session types

Issue #984 distinguished between "transport sessions" (communication state) and "logical sessions" (shared context). We considered creating separate session types for these different concerns but decided a single unified session concept is cleaner and sufficient. Our unified sessions serve both purposes:

  • Maintain protocol negotiation state (addressing transport session needs)
  • Provide conversation context boundaries (addressing logical session needs)
  • Remain independent of transport connections while allowing transport-specific implementations

Related Work

Response to Issue #984

This proposal directly addresses the concerns raised in Issue #984:

Logical vs Transport Sessions: Rather than creating two separate session types, this proposal unifies the concepts - a single session definition serves both transport state and logical context needs while remaining independent of transport connections.

Message ID uniqueness boundaries: Sessions explicitly define message ID uniqueness boundaries with clear scoping rules.

Out-of-band requests: Server-initiated requests (elicitation, sampling) work naturally within sessions with servers including sessionId to support future multi-session scenarios.

Backward Compatibility

Protocol-level sessions are defined as part of a new protocol version, ensuring backward compatibility through MCP's standard protocol version negotiation mechanism:

New Client + Old Server: If an old server doesn't support the new protocol version, negotiation will fall back to the highest mutually supported version (without protocol-level sessions).

Old Client + New Server: If an old client doesn't understand the new protocol version, the server will negotiate down to a supported version (without protocol-level sessions).

Same Protocol Version: When both parties support the session-enabled protocol version:

  • Optional sessionId fields allow sessionless interaction for stateless servers
  • Session support discovered via sessionId presence in InitializeResult
  • Clients can ignore session features if not needed

Optional fields also provide type definition reuse convenience across protocol versions.

The optionality of sessionId serves operational purposes (enabling sessionless servers) and convenience (type definition reuse), rather than being the primary backward compatibility mechanism - that role is filled by protocol version negotiation.

Reference Implementation

A reference implementation will be provided demonstrating:

  • Protocol-level session support in client and server
  • Session timeout handling
  • Error handling for invalid sessions
  • Integration with existing HTTP transport session mechanics

Implementation Status:

  • Protocol specification changes: PR #1360
  • TypeScript SDK implementation: [PR in typescript-sdk repo to come]

Security Implications

Per the Security Best Practices specification:

Core Security Principles

  1. Sessions MUST NOT be used for authentication: Session IDs identify conversation context only. Every request with authorization requirements MUST include valid authentication credentials independently of any session ID.

  2. All requests MUST be verified: For servers that implement authorization, every inbound request must have its authorization validated, regardless of session ID.

  3. Session IDs SHOULD be bound to users: Servers should combine session IDs with user-specific information to prevent session hijacking. The user ID is derived from the authorization token, not provided by the client.

  4. Secure session ID generation: Session IDs MUST use cryptographically secure random generation to prevent guessing attacks.

  5. Authorization context changes: If the authenticated principal changes, servers MUST invalidate the session to maintain security boundaries.

Token Expiry and Re-authentication

When authorization tokens expire during an active session:

  • Clients SHOULD obtain refreshed credentials and continue using the same session ID
  • Servers MUST validate the refreshed credentials normally
  • If refreshed credentials represent the same principal, the session continues
  • If refreshed credentials represent a different principal, the session MUST be invalidated

Authorization-Session Relationship

Sessions and authorization serve complementary purposes:

  • Authorization (Transport Level): Validates identity and permissions on every request
  • Sessions (Protocol Level): Maintains conversation state and message ID boundaries

This separation ensures that session state never bypasses security requirements while enabling stateful conversation management.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SEPproposalSEP proposal without a sponsor.

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions