Skip to content

Commit 00a8329

Browse files
committed
more spec changes to support changing ports dynamically
1 parent 97a022a commit 00a8329

File tree

6 files changed

+74
-49
lines changed

6 files changed

+74
-49
lines changed

examples/tools/streamable_mcp.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
name: "streamable_mcp",
1717
transport_type: :streamable,
1818
config: {
19-
url: "http://localhost:3005/mcp"
19+
url: "http://localhost:#{ENV.fetch('PORT1', 3005)}/mcp"
2020
}
2121
)
2222

spec/fixtures/fast-mcp-ruby/lib/app.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require "rack/handler/puma"
1010

1111
is_silent = ARGV.include?("--silent")
12+
port = ENV.fetch("PORT2", 3006)
1213

1314
class NullWriter
1415
def write(*args)
@@ -107,10 +108,10 @@ def content
107108

108109
unless is_silent
109110
# Run the Rack application with Puma
110-
puts "Starting Rack application with MCP middleware on http://localhost:3006"
111+
puts "Starting Rack application with MCP middleware on http://localhost:#{port}"
111112
puts "MCP endpoints:"
112-
puts " - http://localhost:3006/mcp/sse (SSE endpoint)"
113-
puts " - http://localhost:3006/mcp/messages (JSON-RPC endpoint)"
113+
puts " - http://localhost:#{port}/mcp/sse (SSE endpoint)"
114+
puts " - http://localhost:#{port}/mcp/messages (JSON-RPC endpoint)"
114115
puts "Press Ctrl+C to stop"
115116
end
116117

@@ -124,7 +125,7 @@ def content
124125
log_writer = is_silent ? Puma::LogWriter.new(NullWriter.new, NullWriter.new) : Puma::LogWriter.stdio
125126

126127
config = Puma::Configuration.new(log_writer: log_writer) do |user_config|
127-
user_config.bind "tcp://127.0.0.1:#{ENV.fetch('PORT3', 3006)}"
128+
user_config.bind "tcp://127.0.0.1:#{port}"
128129
user_config.app app
129130
end
130131

spec/ruby_llm/mcp/transport/sse_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def client
2020
name: "fast-mcp-ruby",
2121
transport_type: :sse,
2222
config: {
23-
url: "http://127.0.0.1:3006/mcp/sse",
23+
url: "http://127.0.0.1:#{TestServerManager::PORTS[:sse]}/mcp/sse",
2424
request_timeout: 100
2525
}
2626
)

spec/ruby_llm/mcp/transport/streamable_http_spec.rb

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
transport_type: :streamable,
1010
request_timeout: 5000,
1111
config: {
12-
url: "http://localhost:3005/mcp"
12+
url: TestServerManager::HTTP_SERVER_URL
1313
}
1414
)
1515
end
1616

1717
let(:mock_coordinator) { instance_double(RubyLLM::MCP::Coordinator) }
1818
let(:transport) do
1919
RubyLLM::MCP::Transport::StreamableHTTP.new(
20-
"http://localhost:3005/mcp",
20+
TestServerManager::HTTP_SERVER_URL,
2121
request_timeout: 5000,
2222
coordinator: mock_coordinator
2323
)
@@ -166,7 +166,7 @@
166166

167167
describe "connection errors" do
168168
it "handles connection refused errors" do
169-
stub_request(:post, "http://localhost:3005/mcp")
169+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
170170
.to_raise(Errno::ECONNREFUSED)
171171

172172
expect do
@@ -175,7 +175,7 @@
175175
end
176176

177177
it "handles timeout errors" do
178-
stub_request(:post, "http://localhost:3005/mcp")
178+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
179179
.to_timeout
180180

181181
expect do
@@ -184,7 +184,7 @@
184184
end
185185

186186
it "handles network errors" do
187-
stub_request(:post, "http://localhost:3005/mcp")
187+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
188188
.to_raise(SocketError.new("Failed to open TCP connection"))
189189

190190
expect do
@@ -195,7 +195,7 @@
195195

196196
describe "HTTP status errors" do
197197
it "handles 400 Bad Request with JSON error" do
198-
stub_request(:post, "http://localhost:3005/mcp")
198+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
199199
.to_return(
200200
status: 400,
201201
headers: { "Content-Type" => "application/json" },
@@ -208,7 +208,7 @@
208208
end
209209

210210
it "handles 400 Bad Request with malformed JSON error" do
211-
stub_request(:post, "http://localhost:3005/mcp")
211+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
212212
.to_return(
213213
status: 400,
214214
headers: { "Content-Type" => "application/json" },
@@ -221,15 +221,15 @@
221221
end
222222

223223
it "handles 401 Unauthorized" do
224-
stub_request(:post, "http://localhost:3005/mcp")
224+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
225225
.to_return(status: 401)
226226

227227
result = transport.request({ "method" => "initialize", "id" => 1 }, wait_for_response: false)
228228
expect(result).to be_nil
229229
end
230230

231231
it "handles 404 Not Found (session expired)" do
232-
stub_request(:post, "http://localhost:3005/mcp")
232+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
233233
.to_return(status: 404)
234234

235235
expect do
@@ -238,15 +238,15 @@
238238
end
239239

240240
it "handles 405 Method Not Allowed" do
241-
stub_request(:post, "http://localhost:3005/mcp")
241+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
242242
.to_return(status: 405)
243243

244244
result = transport.request({ "method" => "unsupported", "id" => 1 }, wait_for_response: false)
245245
expect(result).to be_nil
246246
end
247247

248248
it "handles 500 Internal Server Error" do
249-
stub_request(:post, "http://localhost:3005/mcp")
249+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
250250
.to_return(
251251
status: 500,
252252
body: "Internal Server Error"
@@ -258,7 +258,7 @@
258258
end
259259

260260
it "handles session-related errors in error message" do
261-
stub_request(:post, "http://localhost:3005/mcp")
261+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
262262
.to_return(
263263
status: 400,
264264
headers: { "Content-Type" => "application/json" },
@@ -275,7 +275,7 @@
275275

276276
describe "response content errors" do
277277
it "handles invalid JSON in successful response" do
278-
stub_request(:post, "http://localhost:3005/mcp")
278+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
279279
.to_return(
280280
status: 200,
281281
headers: { "Content-Type" => "application/json" },
@@ -288,7 +288,7 @@
288288
end
289289

290290
it "handles unexpected content type" do
291-
stub_request(:post, "http://localhost:3005/mcp")
291+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
292292
.to_return(
293293
status: 200,
294294
headers: { "Content-Type" => "text/plain" },
@@ -303,7 +303,7 @@
303303

304304
describe "SSE (Server-Sent Events) errors" do
305305
it "handles SSE 400 errors" do
306-
stub_request(:get, "http://localhost:3005/mcp")
306+
stub_request(:get, TestServerManager::HTTP_SERVER_URL)
307307
.with(headers: { "Accept" => "text/event-stream" })
308308
.to_return(status: 400)
309309

@@ -315,7 +315,7 @@
315315
end
316316

317317
it "handles SSE 405 Method Not Allowed gracefully" do
318-
stub_request(:get, "http://localhost:3005/mcp")
318+
stub_request(:get, TestServerManager::HTTP_SERVER_URL)
319319
.with(headers: { "Accept" => "text/event-stream" })
320320
.to_return(status: 405)
321321

@@ -405,7 +405,7 @@
405405
it "handles session termination failure" do
406406
transport.instance_variable_set(:@session_id, "test-session")
407407

408-
stub_request(:delete, "http://localhost:3005/mcp")
408+
stub_request(:delete, TestServerManager::HTTP_SERVER_URL)
409409
.to_return(status: 500, body: "Server Error")
410410

411411
expect do
@@ -416,7 +416,7 @@
416416
it "handles session termination connection error" do
417417
transport.instance_variable_set(:@session_id, "test-session")
418418

419-
stub_request(:delete, "http://localhost:3005/mcp")
419+
stub_request(:delete, TestServerManager::HTTP_SERVER_URL)
420420
.to_raise(Errno::ECONNREFUSED)
421421

422422
expect do
@@ -427,7 +427,7 @@
427427
it "accepts 405 status for session termination" do
428428
transport.instance_variable_set(:@session_id, "test-session")
429429

430-
stub_request(:delete, "http://localhost:3005/mcp")
430+
stub_request(:delete, TestServerManager::HTTP_SERVER_URL)
431431
.to_return(status: 405)
432432

433433
# Should not raise an error for 405 (acceptable per spec)
@@ -442,7 +442,7 @@
442442
transport.instance_variable_set(:@session_id, nil)
443443

444444
# Should return early without making any requests
445-
expect(WebMock).not_to have_requested(:delete, "http://localhost:3005/mcp")
445+
expect(WebMock).not_to have_requested(:delete, TestServerManager::HTTP_SERVER_URL)
446446

447447
transport.send(:terminate_session)
448448
end
@@ -451,7 +451,7 @@
451451
before do
452452
transport.instance_variable_set(:@session_id, "test-session")
453453

454-
stub_request(:delete, "http://localhost:3005/mcp")
454+
stub_request(:delete, TestServerManager::HTTP_SERVER_URL)
455455
.to_return(status: 400, body: "Bad Request")
456456
end
457457

@@ -540,7 +540,7 @@
540540
describe "202 Accepted response handling" do
541541
it "starts SSE stream on initialization with 202" do
542542
allow(transport).to receive(:start_sse_stream)
543-
stub_request(:post, "http://localhost:3005/mcp")
543+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
544544
.to_return(status: 202)
545545

546546
transport.request({ "method" => "initialize", "id" => 1 }, wait_for_response: false)
@@ -550,7 +550,7 @@
550550

551551
it "does not start SSE stream on non-initialization 202" do
552552
allow(transport).to receive(:start_sse_stream)
553-
stub_request(:post, "http://localhost:3005/mcp")
553+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
554554
.to_return(status: 202)
555555

556556
result = transport.request({ "method" => "other", "id" => 1 }, wait_for_response: false)
@@ -573,7 +573,7 @@
573573

574574
let(:transport_with_options) do
575575
RubyLLM::MCP::Transport::StreamableHTTP.new(
576-
"http://localhost:3005/mcp",
576+
TestServerManager::HTTP_SERVER_URL,
577577
request_timeout: 5000,
578578
coordinator: mock_coordinator,
579579
reconnection_options: reconnection_options
@@ -592,15 +592,15 @@
592592
let(:reconnection_options) { RubyLLM::MCP::Transport::ReconnectionOptions.new(max_retries: 1) }
593593
let(:transport_with_options) do
594594
RubyLLM::MCP::Transport::StreamableHTTP.new(
595-
"http://localhost:3005/mcp",
595+
TestServerManager::HTTP_SERVER_URL,
596596
request_timeout: 1000,
597597
coordinator: mock_coordinator,
598598
reconnection_options: reconnection_options
599599
)
600600
end
601601

602602
before do
603-
stub_request(:get, "http://localhost:3005/mcp")
603+
stub_request(:get, TestServerManager::HTTP_SERVER_URL)
604604
.with(headers: { "Accept" => "text/event-stream" })
605605
.to_raise(Errno::ECONNREFUSED)
606606
end
@@ -617,7 +617,7 @@
617617
it "stops retrying when transport is closed" do
618618
transport.instance_variable_set(:@running, false)
619619

620-
stub_request(:get, "http://localhost:3005/mcp")
620+
stub_request(:get, TestServerManager::HTTP_SERVER_URL)
621621
.with(headers: { "Accept" => "text/event-stream" })
622622
.to_raise(Errno::ECONNREFUSED)
623623

@@ -628,10 +628,24 @@
628628
end.to raise_error(RubyLLM::MCP::Errors::TransportError, /Connection refused/)
629629
end
630630

631+
it "returns a 400 error if server is not running" do
632+
transport.instance_variable_set(:@running, false)
633+
634+
stub_request(:get, "http://fakeurl:4000/mcp")
635+
.with(headers: { "Accept" => "text/event-stream" })
636+
.to_raise(Errno::ECONNREFUSED)
637+
638+
options = RubyLLM::MCP::Transport::StartSSEOptions.new
639+
640+
expect do
641+
transport.send(:start_sse, options)
642+
end.to raise_error(RubyLLM::MCP::Errors::TransportError, /Failed to open SSE stream: 400/)
643+
end
644+
631645
it "stops retrying when abort controller is set" do
632646
transport.instance_variable_set(:@abort_controller, true)
633647

634-
stub_request(:get, "http://localhost:3005/mcp")
648+
stub_request(:get, TestServerManager::HTTP_SERVER_URL)
635649
.with(headers: { "Accept" => "text/event-stream" })
636650
.to_raise(Errno::ECONNREFUSED)
637651

@@ -645,7 +659,7 @@
645659

646660
describe "edge cases and boundary conditions" do
647661
it "handles bad JSON format request body gracefully" do
648-
stub_request(:post, "http://localhost:3005/mcp")
662+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
649663
.to_return(
650664
status: 200,
651665
headers: { "Content-Type" => "application/json" },
@@ -661,7 +675,7 @@
661675
it "handles request without ID gracefully" do
662676
session_id = SecureRandom.uuid
663677

664-
stub_request(:post, "http://localhost:3005/mcp")
678+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
665679
.to_return(
666680
status: 200,
667681
headers: { "Content-Type" => "application/json", "mcp-session-id" => session_id },
@@ -676,7 +690,7 @@
676690
it "handles very large response gracefully" do
677691
large_response = { "result" => { "content" => [{ "type" => "text", "value" => "x" * 10_000 }] } }
678692

679-
stub_request(:post, "http://localhost:3005/mcp")
693+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
680694
.to_return(
681695
status: 200,
682696
headers: { "Content-Type" => "application/json" },
@@ -690,7 +704,7 @@
690704

691705
it "handles response with event-stream content type" do
692706
allow(transport).to receive(:start_sse_stream)
693-
stub_request(:post, "http://localhost:3005/mcp")
707+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
694708
.to_return(
695709
status: 200,
696710
headers: { "Content-Type" => "text/event-stream" },
@@ -705,7 +719,7 @@
705719

706720
context "when handling session ID extraction from response headers" do
707721
before do
708-
stub_request(:post, "http://localhost:3005/mcp")
722+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
709723
.to_return(
710724
status: 200,
711725
headers: {
@@ -724,7 +738,7 @@
724738

725739
context "when response has malformed JSON" do
726740
before do
727-
stub_request(:post, "http://localhost:3005/mcp")
741+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
728742
.to_return(
729743
status: 200,
730744
headers: { "Content-Type" => "application/json" },
@@ -741,7 +755,7 @@
741755

742756
context "when handling HTTPX error response in main request" do
743757
before do
744-
stub_request(:post, "http://localhost:3005/mcp")
758+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
745759
.to_raise(Net::ReadTimeout.new("Connection timeout"))
746760
end
747761

@@ -754,7 +768,7 @@
754768

755769
context "when HTTPX error response has no error message" do
756770
before do
757-
stub_request(:post, "http://localhost:3005/mcp")
771+
stub_request(:post, TestServerManager::HTTP_SERVER_URL)
758772
.to_return(status: 500, body: "Internal Server Error")
759773
end
760774

0 commit comments

Comments
 (0)