Skip to content

Commit 1b6c9ed

Browse files
committed
Implement stateless mode
1 parent 7a0d778 commit 1b6c9ed

File tree

1 file changed

+37
-11
lines changed

1 file changed

+37
-11
lines changed

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
@@ -169,10 +185,12 @@ def parse_request_body(body_string)
169185
def handle_initialization(body_string, body)
170186
session_id = SecureRandom.uuid
171187

172-
@mutex.synchronize do
173-
@sessions[session_id] = {
174-
stream: nil,
175-
}
188+
unless @stateless
189+
@mutex.synchronize do
190+
@sessions[session_id] = {
191+
stream: nil,
192+
}
193+
end
176194
end
177195

178196
response = @server.handle_json(body_string)
@@ -186,12 +204,16 @@ def handle_initialization(body_string, body)
186204
end
187205

188206
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]]
207+
unless @stateless
208+
# If session ID is provided, but not in the sessions hash, return an error
209+
if session_id && !@sessions.key?(session_id)
210+
return [400, { "Content-Type" => "application/json" }, [{ error: "Invalid session ID" }.to_json]]
211+
end
192212
end
193213

194214
response = @server.handle_json(body_string)
215+
216+
# Stream can be nil since stateless mode doesn't retain streams
195217
stream = get_session_stream(session_id) if session_id
196218

197219
if stream
@@ -222,6 +244,10 @@ def session_exists?(session_id)
222244
@mutex.synchronize { @sessions.key?(session_id) }
223245
end
224246

247+
def method_not_allowed_response
248+
[405, { "Content-Type" => "application/json" }, [{ error: "Method not allowed" }.to_json]]
249+
end
250+
225251
def missing_session_id_response
226252
[400, { "Content-Type" => "application/json" }, [{ error: "Missing session ID" }.to_json]]
227253
end

0 commit comments

Comments
 (0)