mcp 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +935 -927
- data/lib/mcp/client/http.rb +4 -1
- data/lib/mcp/server/transports/streamable_http_transport.rb +30 -15
- data/lib/mcp/transport.rb +1 -0
- data/lib/mcp/version.rb +1 -1
- metadata +2 -2
data/lib/mcp/client/http.rb
CHANGED
|
@@ -7,9 +7,10 @@ module MCP
|
|
|
7
7
|
|
|
8
8
|
attr_reader :url
|
|
9
9
|
|
|
10
|
-
def initialize(url:, headers: {})
|
|
10
|
+
def initialize(url:, headers: {}, &block)
|
|
11
11
|
@url = url
|
|
12
12
|
@headers = headers
|
|
13
|
+
@faraday_customizer = block
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def send_request(request:)
|
|
@@ -78,6 +79,8 @@ module MCP
|
|
|
78
79
|
headers.each do |key, value|
|
|
79
80
|
faraday.headers[key] = value
|
|
80
81
|
end
|
|
82
|
+
|
|
83
|
+
@faraday_customizer&.call(faraday)
|
|
81
84
|
end
|
|
82
85
|
end
|
|
83
86
|
|
|
@@ -15,7 +15,7 @@ module MCP
|
|
|
15
15
|
|
|
16
16
|
def initialize(server, stateless: false, session_idle_timeout: nil)
|
|
17
17
|
super(server)
|
|
18
|
-
# Maps `session_id` to `{
|
|
18
|
+
# Maps `session_id` to `{ get_sse_stream: stream_object, server_session: ServerSession, last_active_at: float_from_monotonic_clock }`.
|
|
19
19
|
@sessions = {}
|
|
20
20
|
@mutex = Mutex.new
|
|
21
21
|
|
|
@@ -61,7 +61,7 @@ module MCP
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
removed_sessions.each do |session|
|
|
64
|
-
close_stream_safely(session[:
|
|
64
|
+
close_stream_safely(session[:get_sse_stream])
|
|
65
65
|
close_post_request_streams(session)
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -113,7 +113,7 @@ module MCP
|
|
|
113
113
|
failed_sessions = []
|
|
114
114
|
|
|
115
115
|
@sessions.each do |sid, session|
|
|
116
|
-
next unless (stream = session[:
|
|
116
|
+
next unless (stream = session[:get_sse_stream])
|
|
117
117
|
|
|
118
118
|
if session_expired?(session)
|
|
119
119
|
failed_sessions << sid
|
|
@@ -247,7 +247,7 @@ module MCP
|
|
|
247
247
|
end
|
|
248
248
|
|
|
249
249
|
removed_sessions.each do |session|
|
|
250
|
-
close_stream_safely(session[:
|
|
250
|
+
close_stream_safely(session[:get_sse_stream])
|
|
251
251
|
close_post_request_streams(session)
|
|
252
252
|
end
|
|
253
253
|
end
|
|
@@ -267,6 +267,9 @@ module MCP
|
|
|
267
267
|
accept_error = validate_accept_header(request, REQUIRED_POST_ACCEPT_TYPES)
|
|
268
268
|
return accept_error if accept_error
|
|
269
269
|
|
|
270
|
+
content_type_error = validate_content_type(request)
|
|
271
|
+
return content_type_error if content_type_error
|
|
272
|
+
|
|
270
273
|
body_string = request.body.read
|
|
271
274
|
session_id = extract_session_id(request)
|
|
272
275
|
|
|
@@ -334,7 +337,7 @@ module MCP
|
|
|
334
337
|
end
|
|
335
338
|
|
|
336
339
|
if session
|
|
337
|
-
close_stream_safely(session[:
|
|
340
|
+
close_stream_safely(session[:get_sse_stream])
|
|
338
341
|
close_post_request_streams(session)
|
|
339
342
|
end
|
|
340
343
|
end
|
|
@@ -358,7 +361,7 @@ module MCP
|
|
|
358
361
|
def cleanup_and_collect_stream(session_id, streams_to_close)
|
|
359
362
|
return unless (removed = cleanup_session_unsafe(session_id))
|
|
360
363
|
|
|
361
|
-
streams_to_close << removed[:
|
|
364
|
+
streams_to_close << removed[:get_sse_stream]
|
|
362
365
|
removed[:post_request_streams]&.each_value { |stream| streams_to_close << stream }
|
|
363
366
|
end
|
|
364
367
|
|
|
@@ -399,6 +402,18 @@ module MCP
|
|
|
399
402
|
end
|
|
400
403
|
end
|
|
401
404
|
|
|
405
|
+
def validate_content_type(request)
|
|
406
|
+
content_type = request.env["CONTENT_TYPE"]
|
|
407
|
+
media_type = content_type&.split(";")&.first&.strip&.downcase
|
|
408
|
+
return if media_type == "application/json"
|
|
409
|
+
|
|
410
|
+
[
|
|
411
|
+
415,
|
|
412
|
+
{ "Content-Type" => "application/json" },
|
|
413
|
+
[{ error: "Unsupported Media Type: Content-Type must be application/json" }.to_json],
|
|
414
|
+
]
|
|
415
|
+
end
|
|
416
|
+
|
|
402
417
|
def not_acceptable_response(required_types)
|
|
403
418
|
[
|
|
404
419
|
406,
|
|
@@ -449,7 +464,7 @@ module MCP
|
|
|
449
464
|
|
|
450
465
|
@mutex.synchronize do
|
|
451
466
|
@sessions[session_id] = {
|
|
452
|
-
|
|
467
|
+
get_sse_stream: nil,
|
|
453
468
|
server_session: server_session,
|
|
454
469
|
last_active_at: Process.clock_gettime(Process::CLOCK_MONOTONIC),
|
|
455
470
|
}
|
|
@@ -543,7 +558,7 @@ module MCP
|
|
|
543
558
|
if related_request_id
|
|
544
559
|
session.dig(:post_request_streams, related_request_id)
|
|
545
560
|
else
|
|
546
|
-
session[:
|
|
561
|
+
session[:get_sse_stream]
|
|
547
562
|
end
|
|
548
563
|
end
|
|
549
564
|
|
|
@@ -572,7 +587,7 @@ module MCP
|
|
|
572
587
|
end
|
|
573
588
|
|
|
574
589
|
if removed
|
|
575
|
-
close_stream_safely(removed[:
|
|
590
|
+
close_stream_safely(removed[:get_sse_stream])
|
|
576
591
|
|
|
577
592
|
removed[:post_request_streams]&.each_value do |stream|
|
|
578
593
|
close_stream_safely(stream)
|
|
@@ -583,7 +598,7 @@ module MCP
|
|
|
583
598
|
end
|
|
584
599
|
|
|
585
600
|
def get_session_stream(session_id)
|
|
586
|
-
@mutex.synchronize { @sessions[session_id]&.fetch(:
|
|
601
|
+
@mutex.synchronize { @sessions[session_id]&.fetch(:get_sse_stream, nil) }
|
|
587
602
|
end
|
|
588
603
|
|
|
589
604
|
def session_exists?(session_id)
|
|
@@ -626,8 +641,8 @@ module MCP
|
|
|
626
641
|
def store_stream_for_session(session_id, stream)
|
|
627
642
|
@mutex.synchronize do
|
|
628
643
|
session = @sessions[session_id]
|
|
629
|
-
if session && !session[:
|
|
630
|
-
session[:
|
|
644
|
+
if session && !session[:get_sse_stream]
|
|
645
|
+
session[:get_sse_stream] = stream
|
|
631
646
|
else
|
|
632
647
|
# Either session was removed, or another request already established a stream.
|
|
633
648
|
stream.close
|
|
@@ -652,13 +667,13 @@ module MCP
|
|
|
652
667
|
end
|
|
653
668
|
|
|
654
669
|
def session_active_with_stream?(session_id)
|
|
655
|
-
@mutex.synchronize { @sessions.key?(session_id) && @sessions[session_id][:
|
|
670
|
+
@mutex.synchronize { @sessions.key?(session_id) && @sessions[session_id][:get_sse_stream] }
|
|
656
671
|
end
|
|
657
672
|
|
|
658
673
|
def send_keepalive_ping(session_id)
|
|
659
674
|
@mutex.synchronize do
|
|
660
|
-
if @sessions[session_id] && @sessions[session_id][:
|
|
661
|
-
send_ping_to_stream(@sessions[session_id][:
|
|
675
|
+
if @sessions[session_id] && @sessions[session_id][:get_sse_stream]
|
|
676
|
+
send_ping_to_stream(@sessions[session_id][:get_sse_stream])
|
|
662
677
|
end
|
|
663
678
|
end
|
|
664
679
|
rescue *STREAM_WRITE_ERRORS => e
|
data/lib/mcp/transport.rb
CHANGED
data/lib/mcp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -78,7 +78,7 @@ licenses:
|
|
|
78
78
|
- Apache-2.0
|
|
79
79
|
metadata:
|
|
80
80
|
allowed_push_host: https://rubygems.org
|
|
81
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.
|
|
81
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.12.0
|
|
82
82
|
homepage_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
83
83
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
84
84
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|