@@ -8,11 +8,13 @@ module MCP
88 class Server
99 module Transports
1010 class StreamableHTTPTransport < Transport
11- def initialize ( server )
12- super
11+ def initialize ( server , stateless : false )
12+ super ( server )
1313 # { session_id => { stream: stream_object }
1414 @sessions = { }
1515 @mutex = Mutex . new
16+
17+ @stateless = stateless
1618 end
1719
1820 def handle_request ( request )
@@ -24,7 +26,7 @@ def handle_request(request)
2426 when "DELETE"
2527 handle_delete ( request )
2628 else
27- [ 405 , { "Content-Type" => "application/json" } , [ { error : "Method not allowed" } . to_json ] ]
29+ method_not_allowed_response
2830 end
2931 end
3032
@@ -35,6 +37,9 @@ def close
3537 end
3638
3739 def send_notification ( method , params = nil , session_id : nil )
40+ # Stateless mode doesn't support notifications
41+ raise "Stateless mode does not support notifications" if @stateless
42+
3843 notification = {
3944 jsonrpc : "2.0" ,
4045 method :,
@@ -117,6 +122,10 @@ def handle_post(request)
117122 end
118123
119124 def handle_get ( request )
125+ if @stateless
126+ return method_not_allowed_response
127+ end
128+
120129 session_id = extract_session_id ( request )
121130
122131 return missing_session_id_response unless session_id
@@ -126,6 +135,13 @@ def handle_get(request)
126135 end
127136
128137 def handle_delete ( request )
138+ success_response = [ 200 , { "Content-Type" => "application/json" } , [ { success : true } . to_json ] ]
139+
140+ if @stateless
141+ # Stateless mode doesn't support sessions, so we can just return a success response
142+ return success_response
143+ end
144+
129145 session_id = request . env [ "HTTP_MCP_SESSION_ID" ]
130146
131147 return [
@@ -135,7 +151,7 @@ def handle_delete(request)
135151 ] unless session_id
136152
137153 cleanup_session ( session_id )
138- [ 200 , { "Content-Type" => "application/json" } , [ { success : true } . to_json ] ]
154+ success_response
139155 end
140156
141157 def cleanup_session ( session_id )
@@ -167,31 +183,40 @@ def parse_request_body(body_string)
167183 end
168184
169185 def handle_initialization ( body_string , body )
170- session_id = SecureRandom . uuid
186+ session_id = nil
171187
172- @mutex . synchronize do
173- @sessions [ session_id ] = {
174- stream : nil ,
175- }
188+ unless @stateless
189+ session_id = SecureRandom . uuid
190+
191+ @mutex . synchronize do
192+ @sessions [ session_id ] = {
193+ stream : nil ,
194+ }
195+ end
176196 end
177197
178198 response = @server . handle_json ( body_string )
179199
180200 headers = {
181201 "Content-Type" => "application/json" ,
182- "Mcp-Session-Id" => session_id ,
183202 }
184203
204+ headers [ "Mcp-Session-Id" ] = session_id if session_id
205+
185206 [ 200 , headers , [ response ] ]
186207 end
187208
188209 def handle_regular_request ( body_string , session_id )
189- # If session ID is provided, but not in the sessions hash, return an error
190- if session_id && !@sessions . key? ( session_id )
191- return [ 400 , { "Content-Type" => "application/json" } , [ { error : "Invalid session ID" } . to_json ] ]
210+ unless @stateless
211+ # If session ID is provided, but not in the sessions hash, return an error
212+ if session_id && !@sessions . key? ( session_id )
213+ return [ 400 , { "Content-Type" => "application/json" } , [ { error : "Invalid session ID" } . to_json ] ]
214+ end
192215 end
193216
194217 response = @server . handle_json ( body_string )
218+
219+ # Stream can be nil since stateless mode doesn't retain streams
195220 stream = get_session_stream ( session_id ) if session_id
196221
197222 if stream
@@ -222,6 +247,10 @@ def session_exists?(session_id)
222247 @mutex . synchronize { @sessions . key? ( session_id ) }
223248 end
224249
250+ def method_not_allowed_response
251+ [ 405 , { "Content-Type" => "application/json" } , [ { error : "Method not allowed" } . to_json ] ]
252+ end
253+
225254 def missing_session_id_response
226255 [ 400 , { "Content-Type" => "application/json" } , [ { error : "Missing session ID" } . to_json ] ]
227256 end
0 commit comments