Support POST response SSE streams for server-to-client messages
## Motivation and Context The MCP Streamable HTTP specification defines that servers can return POST responses as SSE streams and send server-to-client JSON-RPC requests and notifications through them: > If the input is a JSON-RPC request, the server MUST either return > `Content-Type: text/event-stream`, to initiate an SSE stream, or > `Content-Type: application/json`, to return one JSON object. If the server initiates an SSE stream: > The server MAY send JSON-RPC requests and notifications before > sending the JSON-RPC response. These messages SHOULD relate to the > originating client request. See: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server Previously, when a GET SSE stream was connected, the Ruby SDK sent POST response bodies through the GET stream and returned a 202 HTTP status for the POST itself. When no GET SSE stream was connected, the SDK correctly returned `application/json` responses. The 202 behavior with GET SSE was non-compliant with the specification, which requires POST requests to return `text/event-stream` or `application/json`. Additionally, since GET SSE is optional per the specification, clients that did not establish a GET SSE connection could not receive server-to-client messages (e.g., `sampling/createMessage`, log notifications) during request processing. With this change, `handle_regular_request` always returns the POST response as an SSE stream for stateful sessions. Each POST response stream is stored in `session[:post_request_streams]` keyed by `related_request_id` (the JSON-RPC request ID), enabling correct routing when multiple POST requests are processed concurrently on the same session. Server-to-client messages with a `related_request_id` are routed to the originating POST response stream only; when `related_request_id` is nil, the GET SSE stream is used. The TypeScript and Python SDKs already support this pattern. ### Internal Changes `JsonRpcHandler.handle` and `JsonRpcHandler.handle_json` now pass both `method_name` and `request_id` to the method finder block. This allows `Server#handle_request` to receive `related_request_id` directly from the protocol layer. Without this, `related_request_id` would need to be relayed as a keyword argument through `Server#handle_json`, `ServerSession#handle_json`, and `dispatch_handle_json`, unnecessarily exposing it on public method signatures. This follows the same design as the TypeScript and Python SDKs, where the protocol layer extracts the request ID and propagates it to the handler context. ## How Has This Been Tested? Added tests for POST response stream: - `send_request` via POST response stream (sampling with and without GET SSE) - `send_notification` via POST response stream (logging without GET SSE) - `progress` notification via POST response stream (without GET SSE) - POST request returns SSE response even with GET SSE connected - Session-scoped notifications (log, progress) are sent to POST response stream, not GET SSE stream - `active_stream` does not fall back to GET SSE when `related_request_id` is given but request stream is missing Updated existing tests to handle SSE response format where applicable. ## Breaking Changes (spec compliance fix) POST responses for JSON-RPC requests in stateful sessions now return `Content-Type: text/event-stream` instead of being sent through the GET SSE stream with a 202 HTTP status. Clients that relied on receiving responses via the GET SSE stream will need to read the POST response body instead. This is a spec compliance fix: the MCP specification requires POST requests to return `text/event-stream` or `application/json`, not 202 with the response on a separate GET stream.
This commit is contained in:
parent
0d700d7d7d
commit
8278ca6586
6 changed files with 671 additions and 154 deletions
|
|
@ -92,7 +92,7 @@ module JsonRpcHandler
|
|||
end
|
||||
|
||||
begin
|
||||
method = method_finder.call(method_name)
|
||||
method = method_finder.call(method_name, id)
|
||||
|
||||
if method.nil?
|
||||
return error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
module MCP
|
||||
class Progress
|
||||
def initialize(notification_target:, progress_token:)
|
||||
def initialize(notification_target:, progress_token:, related_request_id: nil)
|
||||
@notification_target = notification_target
|
||||
@progress_token = progress_token
|
||||
@related_request_id = related_request_id
|
||||
end
|
||||
|
||||
def report(progress, total: nil, message: nil)
|
||||
|
|
@ -16,6 +17,7 @@ module MCP
|
|||
progress: progress,
|
||||
total: total,
|
||||
message: message,
|
||||
related_request_id: @related_request_id,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -127,8 +127,8 @@ module MCP
|
|||
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
||||
# @return [Hash, nil] The JSON-RPC response, or `nil` for notifications.
|
||||
def handle(request, session: nil)
|
||||
JsonRpcHandler.handle(request) do |method|
|
||||
handle_request(request, method, session: session)
|
||||
JsonRpcHandler.handle(request) do |method, request_id|
|
||||
handle_request(request, method, session: session, related_request_id: request_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -140,8 +140,8 @@ module MCP
|
|||
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
||||
# @return [String, nil] The JSON-RPC response as JSON, or `nil` for notifications.
|
||||
def handle_json(request, session: nil)
|
||||
JsonRpcHandler.handle_json(request) do |method|
|
||||
handle_request(request, method, session: session)
|
||||
JsonRpcHandler.handle_json(request) do |method, request_id|
|
||||
handle_request(request, method, session: session, related_request_id: request_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -220,7 +220,8 @@ module MCP
|
|||
stop_sequences: nil,
|
||||
metadata: nil,
|
||||
tools: nil,
|
||||
tool_choice: nil
|
||||
tool_choice: nil,
|
||||
related_request_id: nil
|
||||
)
|
||||
unless @transport
|
||||
raise "Cannot send sampling request without a transport."
|
||||
|
|
@ -371,7 +372,7 @@ module MCP
|
|||
end
|
||||
end
|
||||
|
||||
def handle_request(request, method, session: nil)
|
||||
def handle_request(request, method, session: nil, related_request_id: nil)
|
||||
handler = @handlers[method]
|
||||
unless handler
|
||||
instrument_call("unsupported_method") do
|
||||
|
|
@ -399,7 +400,7 @@ module MCP
|
|||
when Methods::RESOURCES_TEMPLATES_LIST
|
||||
{ resourceTemplates: @handlers[Methods::RESOURCES_TEMPLATES_LIST].call(params) }
|
||||
when Methods::TOOLS_CALL
|
||||
call_tool(params, session: session)
|
||||
call_tool(params, session: session, related_request_id: related_request_id)
|
||||
when Methods::COMPLETION_COMPLETE
|
||||
complete(params)
|
||||
when Methods::LOGGING_SET_LEVEL
|
||||
|
|
@ -499,7 +500,7 @@ module MCP
|
|||
@tools.values.map(&:to_h)
|
||||
end
|
||||
|
||||
def call_tool(request, session: nil)
|
||||
def call_tool(request, session: nil, related_request_id: nil)
|
||||
tool_name = request[:name]
|
||||
|
||||
tool = tools[tool_name]
|
||||
|
|
@ -531,7 +532,7 @@ module MCP
|
|||
|
||||
progress_token = request.dig(:_meta, :progressToken)
|
||||
|
||||
call_tool_with_args(tool, arguments, server_context_with_meta(request), progress_token: progress_token, session: session)
|
||||
call_tool_with_args(tool, arguments, server_context_with_meta(request), progress_token: progress_token, session: session, related_request_id: related_request_id)
|
||||
rescue RequestHandlerError
|
||||
raise
|
||||
rescue => e
|
||||
|
|
@ -611,12 +612,12 @@ module MCP
|
|||
parameters.any? { |type, name| type == :keyrest || name == :server_context }
|
||||
end
|
||||
|
||||
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil)
|
||||
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil, related_request_id: nil)
|
||||
args = arguments&.transform_keys(&:to_sym) || {}
|
||||
|
||||
if accepts_server_context?(tool.method(:call))
|
||||
progress = Progress.new(notification_target: session, progress_token: progress_token)
|
||||
server_context = ServerContext.new(context, progress: progress, notification_target: session)
|
||||
progress = Progress.new(notification_target: session, progress_token: progress_token, related_request_id: related_request_id)
|
||||
server_context = ServerContext.new(context, progress: progress, notification_target: session, related_request_id: related_request_id)
|
||||
tool.call(**args, server_context: server_context).to_h
|
||||
else
|
||||
tool.call(**args).to_h
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ module MCP
|
|||
class Server
|
||||
module Transports
|
||||
class StreamableHTTPTransport < Transport
|
||||
SSE_HEADERS = {
|
||||
"Content-Type" => "text/event-stream",
|
||||
"Cache-Control" => "no-cache",
|
||||
"Connection" => "keep-alive",
|
||||
}.freeze
|
||||
|
||||
def initialize(server, stateless: false, session_idle_timeout: nil)
|
||||
super(server)
|
||||
# Maps `session_id` to `{ stream: stream_object, server_session: ServerSession, last_active_at: float_from_monotonic_clock }`.
|
||||
|
|
@ -56,10 +62,11 @@ module MCP
|
|||
|
||||
removed_sessions.each do |session|
|
||||
close_stream_safely(session[:stream])
|
||||
close_post_request_streams(session)
|
||||
end
|
||||
end
|
||||
|
||||
def send_notification(method, params = nil, session_id: nil)
|
||||
def send_notification(method, params = nil, session_id: nil, related_request_id: nil)
|
||||
# Stateless mode doesn't support notifications
|
||||
raise "Stateless mode does not support notifications" if @stateless
|
||||
|
||||
|
|
@ -74,8 +81,10 @@ module MCP
|
|||
result = @mutex.synchronize do
|
||||
if session_id
|
||||
# Send to specific session
|
||||
session = @sessions[session_id]
|
||||
next false unless session && session[:stream]
|
||||
if (session = @sessions[session_id])
|
||||
stream = active_stream(session, related_request_id: related_request_id)
|
||||
end
|
||||
next false unless stream
|
||||
|
||||
if session_expired?(session)
|
||||
cleanup_and_collect_stream(session_id, streams_to_close)
|
||||
|
|
@ -83,14 +92,19 @@ module MCP
|
|||
end
|
||||
|
||||
begin
|
||||
send_to_stream(session[:stream], notification)
|
||||
send_to_stream(stream, notification)
|
||||
true
|
||||
rescue *STREAM_WRITE_ERRORS => e
|
||||
MCP.configuration.exception_reporter.call(
|
||||
e,
|
||||
{ session_id: session_id, error: "Failed to send notification" },
|
||||
)
|
||||
cleanup_and_collect_stream(session_id, streams_to_close)
|
||||
if related_request_id && session[:post_request_streams]&.key?(related_request_id)
|
||||
session[:post_request_streams].delete(related_request_id)
|
||||
streams_to_close << stream
|
||||
else
|
||||
cleanup_and_collect_stream(session_id, streams_to_close)
|
||||
end
|
||||
false
|
||||
end
|
||||
else
|
||||
|
|
@ -99,7 +113,7 @@ module MCP
|
|||
failed_sessions = []
|
||||
|
||||
@sessions.each do |sid, session|
|
||||
next unless session[:stream]
|
||||
next unless (stream = session[:stream])
|
||||
|
||||
if session_expired?(session)
|
||||
failed_sessions << sid
|
||||
|
|
@ -107,7 +121,7 @@ module MCP
|
|||
end
|
||||
|
||||
begin
|
||||
send_to_stream(session[:stream], notification)
|
||||
send_to_stream(stream, notification)
|
||||
sent_count += 1
|
||||
rescue *STREAM_WRITE_ERRORS => e
|
||||
MCP.configuration.exception_reporter.call(
|
||||
|
|
@ -139,7 +153,7 @@ module MCP
|
|||
# sends the request via SSE stream, then blocks on `queue.pop`.
|
||||
# When the client POSTs a response, `handle_response` matches it by `request_id`
|
||||
# and pushes the result onto the queue, unblocking this thread.
|
||||
def send_request(method, params = nil, session_id: nil)
|
||||
def send_request(method, params = nil, session_id: nil, related_request_id: nil)
|
||||
if @stateless
|
||||
raise "Stateless mode does not support server-to-client requests."
|
||||
end
|
||||
|
|
@ -163,12 +177,17 @@ module MCP
|
|||
|
||||
@pending_responses[request_id] = { queue: queue, session_id: session_id }
|
||||
|
||||
if (stream = session[:stream])
|
||||
if (stream = active_stream(session, related_request_id: related_request_id))
|
||||
begin
|
||||
send_to_stream(stream, request)
|
||||
sent = true
|
||||
rescue *STREAM_WRITE_ERRORS
|
||||
cleanup_session_unsafe(session_id)
|
||||
if related_request_id && session[:post_request_streams]&.key?(related_request_id)
|
||||
session[:post_request_streams].delete(related_request_id)
|
||||
close_stream_safely(stream)
|
||||
else
|
||||
cleanup_session_unsafe(session_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -181,7 +200,7 @@ module MCP
|
|||
# The TypeScript and Python SDKs buffer messages and replay on reconnect.
|
||||
# Until then, raise to prevent queue.pop from blocking indefinitely.
|
||||
unless sent
|
||||
raise "No active SSE stream for #{method} request."
|
||||
raise "No active stream for #{method} request."
|
||||
end
|
||||
|
||||
response = queue.pop
|
||||
|
|
@ -229,6 +248,7 @@ module MCP
|
|||
|
||||
removed_sessions.each do |session|
|
||||
close_stream_safely(session[:stream])
|
||||
close_post_request_streams(session)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -265,7 +285,7 @@ module MCP
|
|||
|
||||
handle_response(body, session_id: session_id)
|
||||
else
|
||||
handle_regular_request(body_string, session_id)
|
||||
handle_regular_request(body_string, session_id, related_request_id: body[:id])
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
|
|
@ -313,7 +333,10 @@ module MCP
|
|||
cleanup_session_unsafe(session_id)
|
||||
end
|
||||
|
||||
close_stream_safely(session[:stream]) if session
|
||||
if session
|
||||
close_stream_safely(session[:stream])
|
||||
close_post_request_streams(session)
|
||||
end
|
||||
end
|
||||
|
||||
# Removes a session from `@sessions` and returns it. Does not close the stream.
|
||||
|
|
@ -336,6 +359,7 @@ module MCP
|
|||
return unless (removed = cleanup_session_unsafe(session_id))
|
||||
|
||||
streams_to_close << removed[:stream]
|
||||
removed[:post_request_streams]&.each_value { |stream| streams_to_close << stream }
|
||||
end
|
||||
|
||||
def close_stream_safely(stream)
|
||||
|
|
@ -344,6 +368,14 @@ module MCP
|
|||
# Ignore close-related errors from already closed/broken streams.
|
||||
end
|
||||
|
||||
def close_post_request_streams(session)
|
||||
return unless (post_request_streams = session[:post_request_streams])
|
||||
|
||||
post_request_streams.each_value do |stream|
|
||||
close_stream_safely(stream)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_session_id(request)
|
||||
request.env["HTTP_MCP_SESSION_ID"]
|
||||
end
|
||||
|
|
@ -443,9 +475,8 @@ module MCP
|
|||
[202, {}, []]
|
||||
end
|
||||
|
||||
def handle_regular_request(body_string, session_id)
|
||||
def handle_regular_request(body_string, session_id, related_request_id: nil)
|
||||
server_session = nil
|
||||
stream = nil
|
||||
|
||||
unless @stateless
|
||||
if session_id
|
||||
|
|
@ -455,22 +486,73 @@ module MCP
|
|||
@mutex.synchronize do
|
||||
session = @sessions[session_id]
|
||||
server_session = session[:server_session] if session
|
||||
stream = session[:stream] if session
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response = if server_session
|
||||
if session_id && !@stateless
|
||||
handle_request_with_sse_response(body_string, session_id, server_session, related_request_id: related_request_id)
|
||||
else
|
||||
response = dispatch_handle_json(body_string, server_session)
|
||||
[200, { "Content-Type" => "application/json" }, [response]]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the POST response as an SSE stream so the server can send
|
||||
# JSON-RPC requests and notifications during request processing.
|
||||
# https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
|
||||
def handle_request_with_sse_response(body_string, session_id, server_session, related_request_id: nil)
|
||||
body = proc do |stream|
|
||||
@mutex.synchronize do
|
||||
session = @sessions[session_id]
|
||||
if session && related_request_id
|
||||
session[:post_request_streams] ||= {}
|
||||
session[:post_request_streams][related_request_id] = stream
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
response = dispatch_handle_json(body_string, server_session)
|
||||
|
||||
send_to_stream(stream, response) if response
|
||||
ensure
|
||||
if related_request_id
|
||||
@mutex.synchronize do
|
||||
session = @sessions[session_id]
|
||||
session[:post_request_streams]&.delete(related_request_id) if session
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
stream.close
|
||||
rescue StandardError
|
||||
# Ignore close-related errors from already closed/broken streams.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
[200, SSE_HEADERS, body]
|
||||
end
|
||||
|
||||
# Returns the SSE stream available for server-to-client messages.
|
||||
# When `related_request_id` is given, returns only the POST response
|
||||
# stream for that request (no fallback to GET SSE). This prevents
|
||||
# request-scoped messages from leaking to the wrong stream.
|
||||
# When `related_request_id` is nil, returns the GET SSE stream.
|
||||
def active_stream(session, related_request_id: nil)
|
||||
if related_request_id
|
||||
session.dig(:post_request_streams, related_request_id)
|
||||
else
|
||||
session[:stream]
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch_handle_json(body_string, server_session)
|
||||
if server_session
|
||||
server_session.handle_json(body_string)
|
||||
else
|
||||
@server.handle_json(body_string)
|
||||
end
|
||||
|
||||
if stream
|
||||
send_response_to_stream(stream, response, session_id)
|
||||
else
|
||||
[200, { "Content-Type" => "application/json" }, [response]]
|
||||
end
|
||||
end
|
||||
|
||||
def validate_and_touch_session(session_id)
|
||||
|
|
@ -489,7 +571,13 @@ module MCP
|
|||
nil
|
||||
end
|
||||
|
||||
close_stream_safely(removed[:stream]) if removed
|
||||
if removed
|
||||
close_stream_safely(removed[:stream])
|
||||
|
||||
removed[:post_request_streams]&.each_value do |stream|
|
||||
close_stream_safely(stream)
|
||||
end
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
|
@ -498,19 +586,6 @@ module MCP
|
|||
@mutex.synchronize { @sessions[session_id]&.fetch(:stream, nil) }
|
||||
end
|
||||
|
||||
def send_response_to_stream(stream, response, session_id)
|
||||
message = JSON.parse(response)
|
||||
send_to_stream(stream, message)
|
||||
handle_accepted
|
||||
rescue *STREAM_WRITE_ERRORS => e
|
||||
MCP.configuration.exception_reporter.call(
|
||||
e,
|
||||
{ session_id: session_id, error: "Stream closed during response" },
|
||||
)
|
||||
cleanup_session(session_id)
|
||||
[200, { "Content-Type" => "application/json" }, [response]]
|
||||
end
|
||||
|
||||
def session_exists?(session_id)
|
||||
@mutex.synchronize { @sessions.key?(session_id) }
|
||||
end
|
||||
|
|
@ -538,13 +613,7 @@ module MCP
|
|||
def setup_sse_stream(session_id)
|
||||
body = create_sse_body(session_id)
|
||||
|
||||
headers = {
|
||||
"Content-Type" => "text/event-stream",
|
||||
"Cache-Control" => "no-cache",
|
||||
"Connection" => "keep-alive",
|
||||
}
|
||||
|
||||
[200, headers, body]
|
||||
[200, SSE_HEADERS, body]
|
||||
end
|
||||
|
||||
def create_sse_body(session_id)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
module MCP
|
||||
class ServerContext
|
||||
def initialize(context, progress:, notification_target:)
|
||||
def initialize(context, progress:, notification_target:, related_request_id: nil)
|
||||
@context = context
|
||||
@progress = progress
|
||||
@notification_target = notification_target
|
||||
@related_request_id = related_request_id
|
||||
end
|
||||
|
||||
# Reports progress for the current tool operation.
|
||||
|
|
@ -26,7 +27,7 @@ module MCP
|
|||
def notify_log_message(data:, level:, logger: nil)
|
||||
return unless @notification_target
|
||||
|
||||
@notification_target.notify_log_message(data: data, level: level, logger: logger)
|
||||
@notification_target.notify_log_message(data: data, level: level, logger: logger, related_request_id: @related_request_id)
|
||||
end
|
||||
|
||||
# Delegates to the session so the request is scoped to the originating client.
|
||||
|
|
@ -34,9 +35,9 @@ module MCP
|
|||
# does not support sampling.
|
||||
def create_sampling_message(**kwargs)
|
||||
if @notification_target.respond_to?(:create_sampling_message)
|
||||
@notification_target.create_sampling_message(**kwargs)
|
||||
@notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
||||
elsif @context.respond_to?(:create_sampling_message)
|
||||
@context.create_sampling_message(**kwargs)
|
||||
@context.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
||||
else
|
||||
raise NoMethodError, "undefined method 'create_sampling_message' for #{self}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ module MCP
|
|||
end
|
||||
|
||||
# Sends a `sampling/createMessage` request scoped to this session.
|
||||
def create_sampling_message(**kwargs)
|
||||
def create_sampling_message(related_request_id: nil, **kwargs)
|
||||
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
||||
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params)
|
||||
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
|
||||
end
|
||||
|
||||
# Sends a progress notification to this session only.
|
||||
def notify_progress(progress_token:, progress:, total: nil, message: nil)
|
||||
def notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil)
|
||||
params = {
|
||||
"progressToken" => progress_token,
|
||||
"progress" => progress,
|
||||
|
|
@ -56,20 +56,20 @@ module MCP
|
|||
"message" => message,
|
||||
}.compact
|
||||
|
||||
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params)
|
||||
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params, related_request_id: related_request_id)
|
||||
rescue => e
|
||||
@server.report_exception(e, notification: "progress")
|
||||
end
|
||||
|
||||
# Sends a log message notification to this session only.
|
||||
def notify_log_message(data:, level:, logger: nil)
|
||||
def notify_log_message(data:, level:, logger: nil, related_request_id: nil)
|
||||
effective_logging = @logging_message_notification || @server.logging_message_notification
|
||||
return unless effective_logging&.should_notify?(level)
|
||||
|
||||
params = { "data" => data, "level" => level }
|
||||
params["logger"] = logger if logger
|
||||
|
||||
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params)
|
||||
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params, related_request_id: related_request_id)
|
||||
rescue => e
|
||||
@server.report_exception(e, { notification: "log_message" })
|
||||
end
|
||||
|
|
@ -82,9 +82,9 @@ module MCP
|
|||
# TODO: When Ruby 2.7 support is dropped, replace with a direct call:
|
||||
# `@transport.send_notification(method, params, session_id: @session_id)` and
|
||||
# add `**` to `Transport#send_notification` and `StdioTransport#send_notification`.
|
||||
def send_to_transport(method, params)
|
||||
def send_to_transport(method, params, related_request_id: nil)
|
||||
if @session_id
|
||||
@transport.send_notification(method, params, session_id: @session_id)
|
||||
@transport.send_notification(method, params, session_id: @session_id, related_request_id: related_request_id)
|
||||
else
|
||||
@transport.send_notification(method, params)
|
||||
end
|
||||
|
|
@ -96,9 +96,9 @@ module MCP
|
|||
# TODO: When Ruby 2.7 support is dropped, replace with a direct call:
|
||||
# `@transport.send_request(method, params, session_id: @session_id)` and
|
||||
# add `**` to `Transport#send_request` and `StdioTransport#send_request`.
|
||||
def send_to_transport_request(method, params)
|
||||
def send_to_transport_request(method, params, related_request_id: nil)
|
||||
if @session_id
|
||||
@transport.send_request(method, params, session_id: @session_id)
|
||||
@transport.send_request(method, params, session_id: @session_id, related_request_id: related_request_id)
|
||||
else
|
||||
@transport.send_request(method, params)
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue