transports-wg/docs/sessions-vs-sessionless-decision.md
2026-04-14 13:28:46 -06:00

9.8 KiB

Decision Document: To App Session, or Not to App Session?

Background

Sessions in MCP

Currently, the Model Context Protocol (MCP) utilizes sessions to manage client-server connections, but this concept blurs the line between two very distinct use cases:

  • Transport-Level Use Cases: Using sessions to track protocol versioning, capability negotiation (e.g., does this server support sampling?).
  • Application-Level Use Cases: Using sessions to track logical state, such as a specific user context, a continuous conversation thread, or stateful tool operations.

Why Sessions Need to Change

The current implementation of sessions is highly ambiguous, leading to the following problems/issues:

  • Inconsistent Lifecycles: Some clients create a new session for every single tool call, others create one per conversation, some use one for all conversations, and others manage them without any clear boundaries. There are no strict guarantees around what sessions provide or how long they persist.
  • Transport Divergences: On STDIO, sessions are implicitly tied to the process lifecycle. On HTTP, sessions are optional, and their absence could mean the server is stateless or that the previous state was simply lost.
  • Coupling of State to Connection: Application state, client capabilities, and protocol versions are heavily coupled to the connection. This leads to operational hazards like the "Rolling Upgrade" problem (where updating a load-balanced server drops the connection and wipes the user's logical state) and multiplexing failures.

Resolving Transport-Level Sessions

To resolve the ambiguities at the transport level, the working group is moving toward a stateless transport architecture. The need for transport-level sessions is being removed via two core SEPs:

  • SEP-1442: Moves all data to a "per request" basis, eliminating the need to store transport state between calls.
  • SEP-2322: Allows for elicitation and sampling requests without relying on sessions to align them.

The Problem Statement

With transport-level sessions resolved by SEP-1442 and SEP-2322, we are left with a critical architectural decision regarding Application-Level Sessions.

The open question the working group must decide on is: "Should the protocol support application sessions (or not)?"

Developers building agents and tools frequently need a way to track logical state across multiple turns of a conversation. However, it is currently unclear whose responsibility it is to maintain that state. We must decide whether to standardize a formal session concept at the data/application layer, or completely remove the concept of sessions from the protocol and push the responsibility of tracking state references entirely to the client via explicit state handles.

Use Cases for Application Sessions

To understand the need for application-level state, we can look at a few progressive examples of how tools currently rely on state across multiple turns of a conversation. Below are the solution-neutral logical flows for these interactions.

Simple Counter / Accumulator

The most basic form of application state is a simple accumulator where a server remembers previous interactions. For example, a count() tool that increments every time it is called by the same user or within the same conversational thread.

Logical Flow:

User: "Start counting"
tool/call: count()  -> Returns 0 
tool/call: count()  -> Returns 1 
tool/call: count()  -> Returns 2

E-Commerce / Shopping Cart

In more complex workflows, tools require a specific sequence of operations where state is built up over time. An e-commerce agent, for instance, needs to add multiple items to a shopping cart across several distinct tool calls before finally executing a checkout operation.

Logical Flow:

User: "I want to buy shoes and socks"
tool/call: add_item("shoes")
tool/call: add_item("socks")
tool/call: checkout()  -> Processes order for [shoes, socks]

Progressive Discovery of Tools

Some tools are deliberately hidden to prevent overwhelming the LLM's context window or to enforce security gates. State allows an agent to call a tool that "unlocks" deeper capabilities or different toolsets dynamically for the remainder of the interaction.

Logical Flow:

User: "Query the production database"
tool/call: list_tools()               -> Returns: [connect]
tool/call: execute_query("SELECT 1")  -> ERROR: Tool not found
tool/call: connect($DATABASE_URI)     -> Success
tool/call: list_tools()               -> Returns: [execute_query, list_tables, ...]
tool/call: list_tables()              -> Success

Object Creation / Multi-step Provisioning

When creating complex resources, an agent may need to iteratively build a configuration before executing the final creation step. This requires holding a "draft" state across multiple actions.

Logical Flow:

User: "Provision a new VM with 16GB RAM"
tool/call: init_vm("web-server")      -> Success
tool/call: set_ram(16)                -> Success
tool/call: set_cpu(4)                 -> Success
tool/call: deploy_vm()                -> Success

Proposed Solutions

The working group is split between two primary proposals for handling these application-level use cases.

Option A: Adding Data Layer Sessions

Link: Transports-WG PR #20

Outline: Instead of relying on the transport layer (HTTP/STDIO) to manage sessions, this proposal introduces explicit, standardized session constructs at the Data Layer. The protocol would explicitly define how to initialize, maintain, and terminate application contexts independent of the underlying transport connection.

Implementation Example (Implicit Tool State): With data layer sessions, the session ID is negotiated once and passed in the protocol envelope. Because the server inherently knows which session is making the request, the tool signatures themselves remain clean and rely on implicit state.

# The session is explicitly negotiated at the data layer
session_create(context="user_123") 

# 1. Simple Counter
count()                     # Returns 0
count()                     # Returns 1

# 2. Shopping Cart
add_item("shoes")           # Adds to the session's cart
add_item("socks")
checkout()

# 3. Capability Unlocking (Database)
list_tools()                # Returns: [connect]
connect($DATABASE_URI)      # State mutates silently for this session
list_tools()                # Returns: [connect, execute_query, list_tables, ...]

# 4. Object Creation (VM Provisioning)
init_vm("web-server")       # Context stored in session
set_ram(16)
set_cpu(4)
deploy_vm()

Advantages:

  • Lower implementation burden: Removing the need for the agent to manage state, meaning accuracy is programmatic vs deterministic.

Disadvantages:

  • Protocol Complexity: Retains the concept of state within the protocol definition, requiring servers to manage state lifecycles, TTLs (Time to Live), and garbage collection.

Open Questions

As we weigh the advantages and disadvantages of both proposals, multi-agent orchestration presents a significant unresolved challenge:

  • Sub-Agent Orchestration: It is currently unclear how sessions or state should be handled when a primary agent delegates work to a sub-agent. Should a sub-agent instantiate a completely new session? Should it receive a copy/fork of the parent agent's session so it has the necessary context but cannot mutate the parent's state? Or should they share the exact same session_id (which risks the sub-agent polluting the parent's context with unintended operations)?

Option B: Sessionless MCP via Explicit State Handles

Link: Transports-WG PR #25

Outline: Do not add sessions to the protocol at all. Instead, encourage a completely stateless protocol where servers return "explicit state handles" (e.g., tokens, cursors, or context IDs) in their tool responses. The client is strictly responsible for storing this handle and passing it back as an argument in subsequent related requests.

Implementation Example (Explicit State Handles): With explicit handles, there are no sessions. The client explicitly requests a state handle (like a basket ID or access token) and must manually inject it back into subsequent tool arguments.

# 1. Simple Counter
counter = create_counter()  # returns { "counter_id": "cnt_123" }
count(counter)              # Returns 0
count(counter)              # Returns 1

# 2. Shopping Cart
basket = create_basket()    # returns { "basket_id": "bsk_a1b2c3" }
add_item(basket, "shoes")
add_item(basket, "socks")
checkout(basket)

# 3. Capability Unlocking (Database)
db = connect($DATABASE_URI)               # returns { "connection_id": "db_prod_1" }
list_tables(db)
execute_sql(db)                         

# 4. Object Creation (VM Provisioning)
vm = init_vm("web-server")                # returns { "draft_id": "vm_draft_99" }
set_ram(vm, 16)
set_cpu(vm, 4)
deploy_vm(vm)

Advantages:

  • Minimal Complexity: Protocol complexity is minimal, requiring no additional changes.

Disadvantages:

  • Breaking Change: If applications are using sessions today for application state, it would require developers to update their tools and clients to use explicit state handles instead.

Decision

This decision was discussed at the Core Maintainer's Meeting on 4/1 and the decision was made to move forward with the "no-session" proposal.