-
Notifications
You must be signed in to change notification settings - Fork 908
Description
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)
Operations
- Create: InitializeRequest → server MAY return sessionId in InitializeResult
- Use: Both parties include sessionId in all subsequent messages
- Validate: Server validates sessionId on each message, returns error if invalid
- Terminate:
- Client: sends session/terminate request
- Server: returns invalid session error
- Re-initialize: On invalid session error, client sends new InitializeRequest (creates new session)
Message ID Uniqueness
Message IDs must be unique within specific boundaries:
- Scope: Client and server maintain separate ID spaces (IDs are unique per-party, not globally)
- Session Boundary: New sessions start with fresh message ID namespaces
- Continuity: Within a session, message IDs must be unique across the session's entire lifetime
- 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
- HTTP transport session handling in MCP
- MCP Authorization specification
- OAuth token refresh patterns
- gRPC connection and auth handling
- Kubernetes API client session management
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
-
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.
-
All requests MUST be verified: For servers that implement authorization, every inbound request must have its authorization validated, regardless of session ID.
-
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.
-
Secure session ID generation: Session IDs MUST use cryptographically secure random generation to prevent guessing attacks.
-
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
Labels
Type
Projects
Status