Skip to content

SEP-1364: Elevating MCP Sessions #1364

@jonathanhefner

Description

@jonathanhefner

Authors: Shaun Smith (@evalstate), Kurtis Van Gent (@kurtisvg), Jonathan Hefner (@jonathanhefner)
Status: Draft
Type: Standards Track
Created: 2025-08-19

Abstract

This SEP proposes:

  1. Sessions be elevated from the transport layer (or, more specifically, the Streamable HTTP transport) to the data layer.
  2. Clients may maintain multiple (logical) sessions per server. For example, one session per context window.

Motivation

The current specification for Model Context Protocol (MCP) sessions is ambiguous, leading to confusion and inconsistent implementations across clients and servers. As highlighted in issue #984, the intended purpose and use-case for sessions are not clearly defined, creating a paradox for developers. This proposal aims to resolve this ambiguity by elevating the role of sessions within the MCP, providing a clear and robust framework for stateful interactions.

The Problem with Sessions

The core of the problem lies in the dual and often conflicting roles that sessions are expected to play. Issue #984 categorizes these into two main use-cases:

  • Transport (or Tracking) Sessions: These sessions are tied to the communication state between a client and a server, including the negotiated protocol version and capabilities.
  • Logical (or ‘User’ or ‘Contextual’) Sessions: These sessions are intended to group multiple requests within a single logical interaction, as defined by the application (e.g., per LLM context window, per user, or per task).

In addition, we want to solve the following problems highlighted in #984:

  • Decouple logical sessions from the transport layer session
  • Provide a clear way to a client to initialize multiple logical sessions per transport session
  • Allow a server to more clearly indicate if it supports sessions (or not)

Specification

The Transports Working Group is currently debating two alternative designs, and is seeking feedback.

Both designs propose the following changes:

  • Add an optional sessionId field to each RPC request and response so that they can be associated with a session.

     export interface Request {
       method: string;
       params?: {
         /* ... */
       };
    +  sessionId?: string;
     }
    
     export interface Notification {
       method: string;
       params?: {
         /* ... */
       };
    +  sessionId?: string;
     }
    
    +export interface Response {
    +  sessionId?: string;
    +}
    
    -export interface JSONRPCResponse {
    +export interface JSONRPCResponse extends Response {
       jsonrpc: typeof JSONRPC_VERSION;
       id: RequestId;
       result: Result;
     }
  • When using the Streamable HTTP transport, require the Mcp-Session-Id HTTP header to mirror the value of the sessionId field.

However, the alternative designs differ in how sessions are started by the client.

Alternative 1: Add a sessions/create Endpoint

Alternative 1 splits sessions into "transport sessions" and "logical sessions". It proposes the following:

  1. Use initialize to mark the start of the transport session. The client should call this method once.

  2. Add a sessions server capability indicating the server's ability to manage logical sessions:

     export interface ServerCapabilities {
    +  /**
    +   * If present, the server supports multiple sessions.
    +   */
    +  sessions?: {
    +    /**
    +     * If `true`, the Server requires valid Session IDs on Requests and
    +     * Notifications. If `false`, Requests and Notifications can be sent
    +     * with an empty (null) Session ID.
    +     */
    +    required: boolean;
    +  }
    
        /* ... */
     }
  3. Add a sessions/create method, allowing the client to start a logical session after calling initialize. The client may create as many or as few logical sessions as needed.

    • If the server does not support sessions (i.e., the sessions capability is not present), the client must not call this method.
    • If the server supports sessions and the sessions.required server capability is true, the client must call this method after initialize, before calling any other methods.
    export interface SessionCreateRequest extends Request {
      method: "session/create";
      params: {
        /**
         * Unique identifier, such as a UUID.
         *
         * If omitted, the server will create a new session and return the new
         * session ID in its response.
         *
         * If given, the server will try to update its information about the
         * client. If the session ID is invalid, the server will return an error.
         */
        sessionId?: string;
    
        capabilities: ClientCapabilities;
      };
    }
    
    export interface SessionCreateResult extends Result {
      sessionId: string;
    }
  4. Remove capabilities: ClientCapabilities from InitializeRequest in favor of adding it to SessionCreateRequest.

     export interface InitializeRequest extends Request {
       method: "initialize";
       params: {
         protocolVersion: string;
    -    capabilities: ClientCapabilities;
         clientInfo: Implementation;
       };
     }
  5. Add a sessions/delete method, allowing the client to explicitly end a session.

    export interface SessionDeleteRequest extends Request {
      method: "session/delete";
      params: {
        sessionId: string;
      };
    }
    
    export interface SessionDeleteResult extends Result {
    }
sequenceDiagram
    participant Client
    participant Server

    Note over Client,Server: Start transport session
    Client->>+Server: InitializeRequest
    Server-->>-Client: InitializeResult<br>{ capabilities: { sessions: { required: true } } }
    Client->>Server: InitializedNotification

    loop
        Note over Client,Server: Start logical session
        Client->>+Server: SessionCreateRequest<br>{ sessionId: null, params: { capabilities: { ... } } }
        Server-->>-Client: SessionCreateResult<br>{ sessionId: "123" }

        rect rgb(200, 220, 250)
            Note over Client,Server: Session-associated operations
            Client->>+Server: ListToolsRequest<br>{ sessionId: "123" }
            Server-->>-Client: ListToolsResponse<br>{ sessionId: "123", result: { ... } }
        end

        opt
            Note over Client,Server: End logical session
            Client->>+Server: SessionDeleteRequest<br>{ sessionId: "123" }
            Server-->>-Client: SessionDeleteResult
        end
    end
Loading

Alternative 2: Use initialize method for session creation

Alternative 2 has a unified concept of a "data layer sessions". It proposes the following:

  1. Use initialize to the mark the start of a data layer session. The client may call initialize one or more times.

    • If the server does not use sessions and the client calls initialize without a sessionId, the server must respond without a sessionId.
    • If the server does not use sessions and the client calls initialize with a sessionId, the server must respond with an error.
    • If the server uses sessions and the client calls initialize without a sessionId, the server must respond with a new sessionId.
    • If the server uses sessions and the client calls initialize with a sessionId, the server must check whether that sessionId is valid for the client. If the sessionId is valid, the server must respond with that same sessionId. Otherwise, the server must respond with an error.

    Summary table:

    sessionId from Client Response from sessionless server Response from sessionful server
    null null sessionId new sessionId
    valid error same sessionId
    invalid error error
  2. Make notifications/initialized optional, contingent upon whether the protocol version was immediately agreed upon versus negotiated. In other words, the client may skip sending notifications/initialized if the initialize request and response both have the same value for protocolVersion.

  3. Add a terminate method, allowing the client to explicitly end a session.

    export interface TerminateRequest extends Request {
      method: "terminate";
      params: {
        sessionId: string;
      };
    }
    
    export interface TerminateResult extends Result {
    }
sequenceDiagram
    participant Client
    participant Server

    loop
        Note over Client,Server: Start data layer session
        Client->>+Server: InitializeRequest<br>{ sessionId: null, params: { protocolVersion: "456" } }
        Server-->>-Client: InitializeResponse<br>{ sessionId: "123", result: { protocolVersion: "456" } }

        rect rgb(200, 220, 250)
            Note over Client,Server: Session-associated operations
            Client->>+Server: ListToolsRequest<br>{ sessionId: "123" }
            Server-->>-Client: ListToolsResponse<br>{ sessionId: "123", result: { ... } }
        end

        opt
            Note over Client,Server: End data layer session
            Client->>+Server: TerminateRequest<br>{ sessionId: "123" }
            Server-->>-Client: TerminateResult
        end
    end
Loading

Alternative 2 also suggests a future change: an initializeNotRequired: boolean field in server manifests (e.g., server.json). This field would allow a server to declare a priori that it does not depend on initialize-related features such as sessions. If a client knows initializeNotRequired == true, it may skip calling initialize and instead rely on alternative mechanisms, such as reading server capabilities from the manifest.

Rationale

Rationale for Alternative 1: Add a sessions/create Endpoint

  1. Separation of concerns: By separating the session creation from the other aspects of the initialize phase, we don’t need to worry about confusion caused by the re-negotiating other aspects of initialize (such as protocol version or capabilities negotiation)
  2. Indicates sessions are optional: Session creation as a separate RPC call both more clearly communicates that sessions are not mandatory (leaving servers to optionally provide support if warranted) and allows servers to advertise their support for it (or not).

Rationale for Alternative 2: Use initialize method for session creation

  • Having a unified data-layer session does not require taking a stance on which pieces of state belong to which type of session.
  • Using initialize means that a session begins as soon as the client and server begin interacting at the data layer. There is no possibility of errant calls between initialize and the start of the session.
  • Using initialize eliminates a round trip in the case where both the initial handshake and a session are required.

Backward Compatibility

WIP

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions