mcp 1.1.0 → 1.3.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 +50 -2725
- data/lib/json_rpc_handler.rb +3 -2
- data/lib/mcp/client/http.rb +338 -63
- data/lib/mcp/client/mcp_param_headers.rb +242 -0
- data/lib/mcp/client/modern_envelope.rb +32 -0
- data/lib/mcp/client/oauth/bounded_body.rb +67 -0
- data/lib/mcp/client/oauth/discovery.rb +108 -14
- data/lib/mcp/client/oauth/flow.rb +139 -17
- data/lib/mcp/client/oauth/id_jag_token_exchange.rb +12 -1
- data/lib/mcp/client/oauth.rb +1 -0
- data/lib/mcp/client/stdio.rb +243 -66
- data/lib/mcp/client.rb +363 -41
- data/lib/mcp/configuration.rb +72 -10
- data/lib/mcp/elicitation/enum_schema.rb +121 -0
- data/lib/mcp/elicitation.rb +10 -0
- data/lib/mcp/instrumentation.rb +6 -0
- data/lib/mcp/methods.rb +21 -0
- data/lib/mcp/prompt.rb +8 -1
- data/lib/mcp/protocol_deprecations.rb +61 -0
- data/lib/mcp/request_envelope.rb +39 -17
- data/lib/mcp/server/input_required_result.rb +163 -0
- data/lib/mcp/server/pending_response.rb +62 -0
- data/lib/mcp/server/request_state_security.rb +131 -0
- data/lib/mcp/server/transports/stdio_transport.rb +39 -2
- data/lib/mcp/server/transports/streamable_http_transport.rb +745 -24
- data/lib/mcp/server.rb +574 -48
- data/lib/mcp/server_context.rb +92 -5
- data/lib/mcp/server_session.rb +104 -20
- data/lib/mcp/transport.rb +7 -0
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +2 -0
- metadata +11 -2
data/lib/mcp/server_context.rb
CHANGED
|
@@ -4,12 +4,35 @@ module MCP
|
|
|
4
4
|
class ServerContext
|
|
5
5
|
attr_reader :cancellation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# The SEP-2575 per-request envelope (`MCP::RequestEnvelope`) when the request was classified as modern;
|
|
8
|
+
# `nil` on legacy requests.
|
|
9
|
+
attr_reader :envelope
|
|
10
|
+
|
|
11
|
+
# SEP-2322 multi round-trip retry fields, present when the client re-issued the request after
|
|
12
|
+
# an `input_required` result: `input_responses` maps the keys of the earlier `inputRequests` to
|
|
13
|
+
# the client's answers, and `request_state` is the opaque continuation string echoed back byte-exactly.
|
|
14
|
+
# Both are `nil` on a first-round request. Only handlers that opt in to `server_context:` can read them
|
|
15
|
+
# (the same access model as the envelope readers).
|
|
16
|
+
attr_reader :input_responses, :request_state
|
|
17
|
+
|
|
18
|
+
def initialize(context, progress:, notification_target:, related_request_id: nil, cancellation: nil, envelope: nil,
|
|
19
|
+
input_responses: nil, request_state: nil)
|
|
8
20
|
@context = context
|
|
9
21
|
@progress = progress
|
|
10
22
|
@notification_target = notification_target
|
|
11
23
|
@related_request_id = related_request_id
|
|
12
24
|
@cancellation = cancellation
|
|
25
|
+
@envelope = envelope
|
|
26
|
+
@input_responses = input_responses
|
|
27
|
+
@request_state = request_state
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Reads one entry of {#input_responses} by its `inputRequests` key, tolerating symbol or string keys.
|
|
31
|
+
def input_response(key)
|
|
32
|
+
return unless @input_responses.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
value = @input_responses[key.to_sym]
|
|
35
|
+
value.nil? ? @input_responses[key.to_s] : value
|
|
13
36
|
end
|
|
14
37
|
|
|
15
38
|
def cancelled?
|
|
@@ -20,6 +43,52 @@ module MCP
|
|
|
20
43
|
@cancellation&.raise_if_cancelled!
|
|
21
44
|
end
|
|
22
45
|
|
|
46
|
+
# Whether the current request follows the stateless modern lifecycle (SEP-2575).
|
|
47
|
+
def modern?
|
|
48
|
+
!@envelope.nil?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Client identity for the current request. Modern requests carry it in the `_meta` envelope;
|
|
52
|
+
# legacy sessions fall back to the state stored by `initialize`. The envelope always wins
|
|
53
|
+
# because servers MUST NOT infer identity from prior requests.
|
|
54
|
+
def client_info
|
|
55
|
+
return @envelope.client_info if @envelope
|
|
56
|
+
|
|
57
|
+
@notification_target.client if @notification_target.respond_to?(:client)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Client capabilities for the current request, with the same envelope-first resolution as {#client_info}.
|
|
61
|
+
def client_capabilities
|
|
62
|
+
return @envelope.client_capabilities if @envelope
|
|
63
|
+
|
|
64
|
+
@notification_target.client_capabilities if @notification_target.respond_to?(:client_capabilities)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The protocol version the current request was made with. `nil` on legacy requests,
|
|
68
|
+
# where the version is a session-level negotiation result rather than per-request data.
|
|
69
|
+
def protocol_version
|
|
70
|
+
@envelope&.protocol_version
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Guards the current request on a declared client capability (SEP-2575). `path` names nested capability keys,
|
|
74
|
+
# e.g. `require_client_capability!(:elicitation, :form)`. Raises `Server::MissingRequiredClientCapabilityError`
|
|
75
|
+
# (JSON-RPC error `-32021` with `data: { requiredCapabilities: ... }`) when the capability was not declared.
|
|
76
|
+
def require_client_capability!(*path)
|
|
77
|
+
raise ArgumentError, "at least one capability key is required" if path.empty?
|
|
78
|
+
|
|
79
|
+
declared = client_capabilities
|
|
80
|
+
value = path.reduce(declared) do |acc, key|
|
|
81
|
+
break unless acc.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
symbol_value = acc[key.to_sym]
|
|
84
|
+
symbol_value.nil? ? acc[key.to_s] : symbol_value
|
|
85
|
+
end
|
|
86
|
+
return unless value.nil?
|
|
87
|
+
|
|
88
|
+
required = path.reverse.inject({}) { |acc, key| { key.to_sym => acc } }
|
|
89
|
+
raise Server::MissingRequiredClientCapabilityError, required
|
|
90
|
+
end
|
|
91
|
+
|
|
23
92
|
# Reports progress for the current tool operation.
|
|
24
93
|
# The notification is automatically scoped to the originating session.
|
|
25
94
|
#
|
|
@@ -41,6 +110,14 @@ module MCP
|
|
|
41
110
|
def notify_log_message(data:, level:, logger: nil)
|
|
42
111
|
return unless @notification_target
|
|
43
112
|
|
|
113
|
+
# Modern requests opt in to logging per request (SEP-2575): without `io.modelcontextprotocol/logLevel` in `_meta`,
|
|
114
|
+
# the server MUST NOT send any `notifications/message` for the request, and an insufficient level drops
|
|
115
|
+
# the message the same way. Session- or server-level gating still applies downstream on delegation.
|
|
116
|
+
if @envelope
|
|
117
|
+
threshold = @envelope.log_level && LoggingMessageNotification.new(level: @envelope.log_level)
|
|
118
|
+
return unless threshold&.valid_level? && threshold.should_notify?(level)
|
|
119
|
+
end
|
|
120
|
+
|
|
44
121
|
@notification_target.notify_log_message(data: data, level: level, logger: logger, related_request_id: @related_request_id)
|
|
45
122
|
end
|
|
46
123
|
|
|
@@ -61,9 +138,9 @@ module MCP
|
|
|
61
138
|
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
62
139
|
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
63
140
|
# server configuration, or environment variables instead.
|
|
64
|
-
def list_roots
|
|
141
|
+
def list_roots(timeout: nil)
|
|
65
142
|
if @notification_target.respond_to?(:list_roots)
|
|
66
|
-
@notification_target.list_roots(related_request_id: @related_request_id)
|
|
143
|
+
@notification_target.list_roots(related_request_id: @related_request_id, **timeout_kwarg(timeout))
|
|
67
144
|
else
|
|
68
145
|
raise NoMethodError, "undefined method 'list_roots' for #{self}"
|
|
69
146
|
end
|
|
@@ -83,9 +160,9 @@ module MCP
|
|
|
83
160
|
# end
|
|
84
161
|
#
|
|
85
162
|
# @see https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping
|
|
86
|
-
def ping
|
|
163
|
+
def ping(timeout: nil)
|
|
87
164
|
if @notification_target.respond_to?(:ping)
|
|
88
|
-
@notification_target.ping(related_request_id: @related_request_id)
|
|
165
|
+
@notification_target.ping(related_request_id: @related_request_id, **timeout_kwarg(timeout))
|
|
89
166
|
else
|
|
90
167
|
raise NoMethodError, "undefined method 'ping' for #{self}"
|
|
91
168
|
end
|
|
@@ -168,5 +245,15 @@ module MCP
|
|
|
168
245
|
def respond_to_missing?(name, include_private = false)
|
|
169
246
|
@context.respond_to?(name) || super
|
|
170
247
|
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# An omitted `timeout:` is not forwarded at all, so the delegated call keeps the shape it had
|
|
252
|
+
# before per-request timeouts existed. A notification target that predates the keyword
|
|
253
|
+
# (a custom object standing in for a session) keeps working until a caller actually asks for a timeout,
|
|
254
|
+
# and the transport applies its own default in that case.
|
|
255
|
+
def timeout_kwarg(timeout)
|
|
256
|
+
timeout.nil? ? {} : { timeout: timeout }
|
|
257
|
+
end
|
|
171
258
|
end
|
|
172
259
|
end
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -7,15 +7,26 @@ module MCP
|
|
|
7
7
|
# Holds per-connection state for a single client session.
|
|
8
8
|
# Created by the transport layer; delegates request handling to the shared `Server`.
|
|
9
9
|
class ServerSession
|
|
10
|
-
|
|
10
|
+
ERAS = [:legacy, :modern].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :session_id, :client, :logging_message_notification, :protocol_version
|
|
13
|
+
|
|
14
|
+
# Connection-era lock of the dual-era serving model (SEP-2575): `nil` until the first era-distinctive message succeeds,
|
|
15
|
+
# then `:legacy` or `:modern` for the connection's lifetime. Modern-era transports construct their per-request sessions
|
|
16
|
+
# with `era: :modern` up front.
|
|
17
|
+
attr_reader :era
|
|
18
|
+
|
|
19
|
+
def initialize(server:, transport:, session_id: nil, era: nil)
|
|
20
|
+
validate_era!(era) if era
|
|
11
21
|
|
|
12
|
-
def initialize(server:, transport:, session_id: nil)
|
|
13
22
|
@server = server
|
|
14
23
|
@transport = transport
|
|
15
24
|
@session_id = session_id
|
|
25
|
+
@era = era
|
|
16
26
|
@client = nil
|
|
17
27
|
@client_capabilities = nil
|
|
18
28
|
@logging_message_notification = nil
|
|
29
|
+
@protocol_version = nil
|
|
19
30
|
@in_flight = {}
|
|
20
31
|
@in_flight_mutex = Mutex.new
|
|
21
32
|
@initialized = false
|
|
@@ -29,23 +40,59 @@ module MCP
|
|
|
29
40
|
# Called by `Server#init` after a successful `initialize` response, so subsequent
|
|
30
41
|
# `initialize` requests on the same session can be rejected per MCP spec
|
|
31
42
|
# (the initialization phase MUST be the first interaction).
|
|
32
|
-
def mark_initialized!
|
|
43
|
+
def mark_initialized!(protocol_version: nil)
|
|
33
44
|
@initialized = true
|
|
45
|
+
@protocol_version = protocol_version
|
|
46
|
+
# A successful `initialize` is the legacy-distinctive message of the dual-era serving model (SEP-2575),
|
|
47
|
+
# so it also locks the connection era.
|
|
48
|
+
@era ||= :legacy
|
|
34
49
|
end
|
|
35
50
|
|
|
36
|
-
#
|
|
51
|
+
# One-shot era lock. Locking the already-locked era is a no-op; flipping an established era raises,
|
|
52
|
+
# because a connection can never change eras.
|
|
53
|
+
def lock_era!(era)
|
|
54
|
+
validate_era!(era)
|
|
55
|
+
return if @era == era
|
|
56
|
+
raise "Session era already locked to #{@era}" if @era
|
|
57
|
+
|
|
58
|
+
@era = era
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Registers a `Cancellation` token for an in-flight request, or returns `nil` when `request_id` is already in flight.
|
|
62
|
+
# The request id is the only key that routes request-scoped notifications, server-to-client requests,
|
|
63
|
+
# and `notifications/cancelled` back to the request that caused them, so a second live request under the same id
|
|
64
|
+
# has no destination of its own. Rather than let the newcomer displace the registration, report the collision
|
|
65
|
+
# and leave the first request intact; the caller turns that into an Invalid Request.
|
|
37
66
|
def register_in_flight(request_id)
|
|
38
67
|
return if request_id.nil?
|
|
39
68
|
|
|
40
69
|
cancellation = Cancellation.new(request_id: request_id)
|
|
41
|
-
@in_flight_mutex.synchronize
|
|
42
|
-
|
|
70
|
+
registered = @in_flight_mutex.synchronize do
|
|
71
|
+
next false if @in_flight.key?(request_id)
|
|
72
|
+
|
|
73
|
+
@in_flight[request_id] = cancellation
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
registered ? cancellation : nil
|
|
43
78
|
end
|
|
44
79
|
|
|
45
|
-
|
|
80
|
+
# Removes an in-flight registration. Passing the `Cancellation` that `register_in_flight` returned removes
|
|
81
|
+
# the entry only while it is still that one, so a request can never evict a registration it does not own.
|
|
82
|
+
def unregister_in_flight(request_id, cancellation: nil)
|
|
46
83
|
return if request_id.nil?
|
|
47
84
|
|
|
48
|
-
@in_flight_mutex.synchronize
|
|
85
|
+
@in_flight_mutex.synchronize do
|
|
86
|
+
next if cancellation && !@in_flight[request_id].equal?(cancellation)
|
|
87
|
+
|
|
88
|
+
@in_flight.delete(request_id)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Whether `request_id` is currently in flight, so a transport can refuse a colliding request
|
|
93
|
+
# before registering any state of its own for it.
|
|
94
|
+
def in_flight?(request_id)
|
|
95
|
+
!lookup_in_flight(request_id).nil?
|
|
49
96
|
end
|
|
50
97
|
|
|
51
98
|
def lookup_in_flight(request_id)
|
|
@@ -106,19 +153,19 @@ module MCP
|
|
|
106
153
|
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
107
154
|
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
108
155
|
# server configuration, or environment variables instead.
|
|
109
|
-
def list_roots(related_request_id: nil)
|
|
156
|
+
def list_roots(related_request_id: nil, timeout: nil)
|
|
110
157
|
warn_unassociated_request(__method__, related_request_id)
|
|
111
158
|
|
|
112
159
|
unless client_capabilities&.dig(:roots)
|
|
113
160
|
raise "Client does not support roots."
|
|
114
161
|
end
|
|
115
162
|
|
|
116
|
-
send_to_transport_request(Methods::ROOTS_LIST, nil, related_request_id: related_request_id)
|
|
163
|
+
send_to_transport_request(Methods::ROOTS_LIST, nil, related_request_id: related_request_id, timeout: timeout)
|
|
117
164
|
end
|
|
118
165
|
|
|
119
166
|
# Sends a `ping` request scoped to this session.
|
|
120
|
-
def ping(related_request_id: nil)
|
|
121
|
-
result = send_to_transport_request(Methods::PING, nil, related_request_id: related_request_id)
|
|
167
|
+
def ping(related_request_id: nil, timeout: nil)
|
|
168
|
+
result = send_to_transport_request(Methods::PING, nil, related_request_id: related_request_id, timeout: timeout)
|
|
122
169
|
raise Server::ValidationError, "Response validation failed: invalid `result`" unless result.is_a?(Hash)
|
|
123
170
|
|
|
124
171
|
result
|
|
@@ -132,11 +179,16 @@ module MCP
|
|
|
132
179
|
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
|
|
133
180
|
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
|
|
134
181
|
# APIs instead.
|
|
135
|
-
def create_sampling_message(related_request_id: nil, **kwargs)
|
|
182
|
+
def create_sampling_message(related_request_id: nil, timeout: nil, **kwargs)
|
|
136
183
|
warn_unassociated_request(__method__, related_request_id)
|
|
137
184
|
|
|
138
185
|
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
|
139
|
-
send_to_transport_request(
|
|
186
|
+
send_to_transport_request(
|
|
187
|
+
Methods::SAMPLING_CREATE_MESSAGE,
|
|
188
|
+
params,
|
|
189
|
+
related_request_id: related_request_id,
|
|
190
|
+
timeout: timeout,
|
|
191
|
+
)
|
|
140
192
|
end
|
|
141
193
|
|
|
142
194
|
# Sends an `elicitation/create` request (form mode) scoped to this session.
|
|
@@ -144,7 +196,7 @@ module MCP
|
|
|
144
196
|
# Per SEP-2260, the request must be associated with an originating client
|
|
145
197
|
# request; prefer `server_context.create_form_elicitation` inside a handler,
|
|
146
198
|
# which stamps the association automatically.
|
|
147
|
-
def create_form_elicitation(message:, requested_schema:, related_request_id: nil)
|
|
199
|
+
def create_form_elicitation(message:, requested_schema:, related_request_id: nil, timeout: nil)
|
|
148
200
|
warn_unassociated_request(__method__, related_request_id)
|
|
149
201
|
|
|
150
202
|
unless client_capabilities&.dig(:elicitation)
|
|
@@ -153,7 +205,12 @@ module MCP
|
|
|
153
205
|
end
|
|
154
206
|
|
|
155
207
|
params = { mode: "form", message: message, requestedSchema: requested_schema }
|
|
156
|
-
send_to_transport_request(
|
|
208
|
+
send_to_transport_request(
|
|
209
|
+
Methods::ELICITATION_CREATE,
|
|
210
|
+
params,
|
|
211
|
+
related_request_id: related_request_id,
|
|
212
|
+
timeout: timeout,
|
|
213
|
+
)
|
|
157
214
|
end
|
|
158
215
|
|
|
159
216
|
# Sends an `elicitation/create` request (URL mode) scoped to this session.
|
|
@@ -161,7 +218,7 @@ module MCP
|
|
|
161
218
|
# Per SEP-2260, the request must be associated with an originating client
|
|
162
219
|
# request; prefer `server_context.create_url_elicitation` inside a handler,
|
|
163
220
|
# which stamps the association automatically.
|
|
164
|
-
def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil)
|
|
221
|
+
def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil, timeout: nil)
|
|
165
222
|
warn_unassociated_request(__method__, related_request_id)
|
|
166
223
|
|
|
167
224
|
unless client_capabilities&.dig(:elicitation, :url)
|
|
@@ -170,7 +227,21 @@ module MCP
|
|
|
170
227
|
end
|
|
171
228
|
|
|
172
229
|
params = { mode: "url", message: message, url: url, elicitationId: elicitation_id }
|
|
173
|
-
send_to_transport_request(
|
|
230
|
+
send_to_transport_request(
|
|
231
|
+
Methods::ELICITATION_CREATE,
|
|
232
|
+
params,
|
|
233
|
+
related_request_id: related_request_id,
|
|
234
|
+
timeout: timeout,
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Sends an embedded SEP-2322 `inputRequests` entry as a real server-to-client request on the legacy wire,
|
|
239
|
+
# for the server's dual-era fulfilment shim.
|
|
240
|
+
# The entry is forwarded verbatim - per the spec, clients treat each entry exactly like the equivalent
|
|
241
|
+
# standalone request - and stays associated with the originating client request per SEP-2260.
|
|
242
|
+
# Returns the client's result.
|
|
243
|
+
def fulfill_input_request(method, params, related_request_id:)
|
|
244
|
+
send_to_transport_request(method, params, related_request_id: related_request_id)
|
|
174
245
|
end
|
|
175
246
|
|
|
176
247
|
# Sends `notifications/cancelled` to the peer for a nested server-to-client request
|
|
@@ -223,7 +294,15 @@ module MCP
|
|
|
223
294
|
# is deprecated as of MCP protocol version 2026-07-28 (SEP-2577).
|
|
224
295
|
# Use stderr or OpenTelemetry instead.
|
|
225
296
|
def notify_log_message(data:, level:, logger: nil, related_request_id: nil)
|
|
226
|
-
|
|
297
|
+
# In the modern lifecycle, log delivery is authorized per request through the `_meta` envelope's `logLevel` member
|
|
298
|
+
# (applied via `configure_logging`); without it no `notifications/message` is sent, and the server-wide level
|
|
299
|
+
# does not apply (SEP-2575).
|
|
300
|
+
effective_logging = if @era == :modern
|
|
301
|
+
@logging_message_notification
|
|
302
|
+
else
|
|
303
|
+
@logging_message_notification || @server.logging_message_notification
|
|
304
|
+
end
|
|
305
|
+
|
|
227
306
|
return unless effective_logging&.should_notify?(level)
|
|
228
307
|
|
|
229
308
|
params = { "data" => data, "level" => level }
|
|
@@ -236,6 +315,10 @@ module MCP
|
|
|
236
315
|
|
|
237
316
|
private
|
|
238
317
|
|
|
318
|
+
def validate_era!(era)
|
|
319
|
+
raise ArgumentError, "era must be one of #{ERAS.inspect}" unless ERAS.include?(era)
|
|
320
|
+
end
|
|
321
|
+
|
|
239
322
|
# Forwards `send_notification` to the transport with only the kwargs the transport's method signature
|
|
240
323
|
# actually accepts. Custom transports that implement the abstract `send_notification(method, params = nil)`
|
|
241
324
|
# contract continue to work unchanged; bundled transports that declare `session_id:` / `related_request_id:`
|
|
@@ -255,7 +338,7 @@ module MCP
|
|
|
255
338
|
# `parent_cancellation:` / `server_session:` receive the nested-cancellation plumbing.
|
|
256
339
|
# When `related_request_id` names an in-flight request, its `Cancellation` token is looked up
|
|
257
340
|
# so that cancelling the parent also cancels this nested server-to-client request.
|
|
258
|
-
def send_to_transport_request(method, params, related_request_id: nil)
|
|
341
|
+
def send_to_transport_request(method, params, related_request_id: nil, timeout: nil)
|
|
259
342
|
parent_cancellation = related_request_id ? lookup_in_flight(related_request_id) : nil
|
|
260
343
|
|
|
261
344
|
kwargs = {
|
|
@@ -263,6 +346,7 @@ module MCP
|
|
|
263
346
|
related_request_id: related_request_id,
|
|
264
347
|
parent_cancellation: parent_cancellation,
|
|
265
348
|
server_session: self,
|
|
349
|
+
timeout: timeout,
|
|
266
350
|
}.compact
|
|
267
351
|
|
|
268
352
|
forward_to_transport(@transport.method(:send_request), method, params, kwargs)
|
data/lib/mcp/transport.rb
CHANGED
|
@@ -50,6 +50,13 @@ module MCP
|
|
|
50
50
|
raise NotImplementedError, "Subclasses must implement send_request"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Whether the transport serves the `subscriptions/listen` notification stream (MCP 2026-07-28, SEP-2575).
|
|
54
|
+
# `Server#discover` strips the `listChanged`/`subscribe` capability flags when the transport cannot deliver
|
|
55
|
+
# those notifications in the modern lifecycle.
|
|
56
|
+
def serves_subscriptions_listen?
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
53
60
|
private
|
|
54
61
|
|
|
55
62
|
def generate_request_id
|
data/lib/mcp/version.rb
CHANGED
data/lib/mcp.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "json_rpc_handler"
|
|
4
4
|
require_relative "mcp/configuration"
|
|
5
|
+
require_relative "mcp/protocol_deprecations"
|
|
5
6
|
require_relative "mcp/string_utils"
|
|
6
7
|
require_relative "mcp/transport"
|
|
7
8
|
require_relative "mcp/version"
|
|
@@ -13,6 +14,7 @@ module MCP
|
|
|
13
14
|
autoload :CancelledError, "mcp/cancelled_error"
|
|
14
15
|
autoload :Client, "mcp/client"
|
|
15
16
|
autoload :Content, "mcp/content"
|
|
17
|
+
autoload :Elicitation, "mcp/elicitation"
|
|
16
18
|
autoload :ErrorCodes, "mcp/error_codes"
|
|
17
19
|
autoload :Icon, "mcp/icon"
|
|
18
20
|
autoload :Prompt, "mcp/prompt"
|
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: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -43,7 +43,10 @@ files:
|
|
|
43
43
|
- lib/mcp/client.rb
|
|
44
44
|
- lib/mcp/client/elicitation.rb
|
|
45
45
|
- lib/mcp/client/http.rb
|
|
46
|
+
- lib/mcp/client/mcp_param_headers.rb
|
|
47
|
+
- lib/mcp/client/modern_envelope.rb
|
|
46
48
|
- lib/mcp/client/oauth.rb
|
|
49
|
+
- lib/mcp/client/oauth/bounded_body.rb
|
|
47
50
|
- lib/mcp/client/oauth/client_credentials_provider.rb
|
|
48
51
|
- lib/mcp/client/oauth/cross_app_access_provider.rb
|
|
49
52
|
- lib/mcp/client/oauth/discovery.rb
|
|
@@ -59,6 +62,8 @@ files:
|
|
|
59
62
|
- lib/mcp/client/tool.rb
|
|
60
63
|
- lib/mcp/configuration.rb
|
|
61
64
|
- lib/mcp/content.rb
|
|
65
|
+
- lib/mcp/elicitation.rb
|
|
66
|
+
- lib/mcp/elicitation/enum_schema.rb
|
|
62
67
|
- lib/mcp/error_codes.rb
|
|
63
68
|
- lib/mcp/icon.rb
|
|
64
69
|
- lib/mcp/instrumentation.rb
|
|
@@ -69,6 +74,7 @@ files:
|
|
|
69
74
|
- lib/mcp/prompt/argument.rb
|
|
70
75
|
- lib/mcp/prompt/message.rb
|
|
71
76
|
- lib/mcp/prompt/result.rb
|
|
77
|
+
- lib/mcp/protocol_deprecations.rb
|
|
72
78
|
- lib/mcp/request_envelope.rb
|
|
73
79
|
- lib/mcp/resource.rb
|
|
74
80
|
- lib/mcp/resource/contents.rb
|
|
@@ -77,7 +83,10 @@ files:
|
|
|
77
83
|
- lib/mcp/result_type.rb
|
|
78
84
|
- lib/mcp/server.rb
|
|
79
85
|
- lib/mcp/server/capabilities.rb
|
|
86
|
+
- lib/mcp/server/input_required_result.rb
|
|
80
87
|
- lib/mcp/server/pagination.rb
|
|
88
|
+
- lib/mcp/server/pending_response.rb
|
|
89
|
+
- lib/mcp/server/request_state_security.rb
|
|
81
90
|
- lib/mcp/server/transports.rb
|
|
82
91
|
- lib/mcp/server/transports/stdio_transport.rb
|
|
83
92
|
- lib/mcp/server/transports/streamable_http_transport.rb
|
|
@@ -98,7 +107,7 @@ licenses:
|
|
|
98
107
|
- Apache-2.0
|
|
99
108
|
metadata:
|
|
100
109
|
allowed_push_host: https://rubygems.org
|
|
101
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v1.
|
|
110
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v1.3.0
|
|
102
111
|
homepage_uri: https://ruby.sdk.modelcontextprotocol.io
|
|
103
112
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
104
113
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|