Skip to content

Commit 137887a

Browse files
committed
client/http: add Server-Sent Events (SSE) response parsing
- Detect text/event-stream content-type in responses - Parse SSE format (data: lines) and extract JSON - Update Faraday middleware to handle both JSON and SSE responses - Add Accept header for application/json and text/event-stream - Add parse_sse_response method for SSE parsing
1 parent c89ee14 commit 137887a

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

lib/mcp/client/http.rb

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ def initialize(url:, headers: {})
1111
@session_id = nil
1212
end
1313

14+
# Sends a JSON-RPC request and returns the parsed response.
15+
# Supports both application/json and text/event-stream responses.
16+
#
17+
# @param request [Hash] The JSON-RPC request to send
18+
# @return [Hash] The parsed JSON-RPC response
1419
def send_request(request:)
1520
method = request[:method] || request["method"]
1621
params = request[:params] || request["params"]
@@ -25,7 +30,18 @@ def send_request(request:)
2530
@session_id = response.headers["Mcp-Session-Id"]
2631
end
2732

28-
response.body
33+
# Handle different response types based on content-type
34+
content_type = response.headers["content-type"]
35+
36+
parsed_body = if content_type&.include?("text/event-stream")
37+
# Parse SSE response
38+
parse_sse_response(response.body)
39+
else
40+
# Standard JSON response (Faraday already parsed it)
41+
response.body
42+
end
43+
44+
parsed_body
2945
rescue Faraday::BadRequestError => e
3046
raise RequestHandlerError.new(
3147
"The #{method} request is invalid",
@@ -78,12 +94,26 @@ def client
7894
require_faraday!
7995
@client ||= Faraday.new(url) do |faraday|
8096
faraday.request(:json)
81-
faraday.response(:json)
97+
# Don't automatically parse JSON responses - we need to handle SSE too
8298
faraday.response(:raise_error)
8399

100+
# Add Accept header to support both JSON and SSE
101+
faraday.headers["Accept"] = "application/json, text/event-stream"
102+
84103
headers.each do |key, value|
85104
faraday.headers[key] = value
86105
end
106+
107+
# Use a middleware that doesn't auto-parse to handle both content types
108+
faraday.response do |env|
109+
content_type = env.response_headers["content-type"]
110+
111+
# Only auto-parse JSON, leave SSE as raw text
112+
if content_type&.include?("application/json")
113+
require "json"
114+
env[:body] = JSON.parse(env[:body]) if env[:body].is_a?(String) && !env[:body].empty?
115+
end
116+
end
87117
end
88118
end
89119

@@ -105,6 +135,27 @@ def require_faraday!
105135
"Add it to your Gemfile: gem 'faraday', '>= 2.0'" \
106136
"See https://rubygems.org/gems/faraday for more details."
107137
end
138+
139+
# Parses Server-Sent Events (SSE) response
140+
# Looks for the message event and parses its data as JSON
141+
def parse_sse_response(body)
142+
require "json"
143+
result = nil
144+
145+
body.split("\n").each do |line|
146+
# SSE format: "event: message\ndata: {...}\n\n"
147+
if line.start_with?("data: ")
148+
data = line[6..-1] # Remove "data: " prefix
149+
result = JSON.parse(data)
150+
end
151+
end
152+
153+
result || raise(RequestHandlerError.new(
154+
"No data found in SSE response",
155+
{},
156+
error_type: :internal_error,
157+
))
158+
end
108159
end
109160
end
110161
end

0 commit comments

Comments
 (0)