mcp 0.22.0 → 0.24.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 +324 -10
- data/lib/json_rpc_handler.rb +17 -0
- data/lib/mcp/apps.rb +109 -0
- data/lib/mcp/client/elicitation.rb +51 -0
- data/lib/mcp/client/http.rb +326 -31
- data/lib/mcp/client/oauth/client_credentials_provider.rb +72 -14
- data/lib/mcp/client/oauth/flow.rb +148 -5
- data/lib/mcp/client/oauth/in_memory_storage.rb +3 -1
- data/lib/mcp/client/oauth/jwt_client_assertion.rb +128 -0
- data/lib/mcp/client/oauth/provider.rb +11 -3
- data/lib/mcp/client/oauth.rb +1 -0
- data/lib/mcp/client/paginated_result.rb +7 -5
- data/lib/mcp/client/stdio.rb +47 -11
- data/lib/mcp/client.rb +89 -1
- data/lib/mcp/error_codes.rb +21 -0
- data/lib/mcp/methods.rb +7 -1
- data/lib/mcp/resource.rb +135 -0
- data/lib/mcp/resource_template.rb +149 -0
- data/lib/mcp/result_type.rb +18 -0
- data/lib/mcp/server/transports/stdio_transport.rb +51 -3
- data/lib/mcp/server/transports/streamable_http_transport.rb +385 -94
- data/lib/mcp/server.rb +205 -20
- data/lib/mcp/server_context.rb +22 -0
- data/lib/mcp/server_session.rb +50 -0
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +3 -0
- metadata +7 -2
data/lib/mcp/server.rb
CHANGED
|
@@ -102,8 +102,11 @@ module MCP
|
|
|
102
102
|
include Instrumentation
|
|
103
103
|
include Pagination
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
# Allowed values for the SEP-2549 `cacheScope` cache hint.
|
|
106
|
+
CACHE_SCOPES = ["public", "private"].freeze
|
|
107
|
+
|
|
108
|
+
attr_accessor :description, :icons, :name, :title, :version, :website_url, :instructions, :tools, :prompts, :resource_templates, :server_context, :configuration, :capabilities, :transport, :logging_message_notification
|
|
109
|
+
attr_reader :resources, :page_size, :client_capabilities, :ttl_ms, :cache_scope
|
|
107
110
|
|
|
108
111
|
def initialize(
|
|
109
112
|
description: nil,
|
|
@@ -121,6 +124,8 @@ module MCP
|
|
|
121
124
|
configuration: nil,
|
|
122
125
|
capabilities: nil,
|
|
123
126
|
page_size: nil,
|
|
127
|
+
ttl_ms: nil,
|
|
128
|
+
cache_scope: nil,
|
|
124
129
|
transport: nil
|
|
125
130
|
)
|
|
126
131
|
@description = description
|
|
@@ -138,6 +143,8 @@ module MCP
|
|
|
138
143
|
@resource_index = index_resources_by_uri(resources)
|
|
139
144
|
@server_context = server_context
|
|
140
145
|
self.page_size = page_size
|
|
146
|
+
self.ttl_ms = ttl_ms
|
|
147
|
+
self.cache_scope = cache_scope
|
|
141
148
|
@configuration = MCP.configuration.merge(configuration)
|
|
142
149
|
@client = nil
|
|
143
150
|
|
|
@@ -154,7 +161,7 @@ module MCP
|
|
|
154
161
|
|
|
155
162
|
@handlers = {
|
|
156
163
|
Methods::RESOURCES_LIST => method(:list_resources),
|
|
157
|
-
Methods::RESOURCES_READ => method(:
|
|
164
|
+
Methods::RESOURCES_READ => method(:read_resource),
|
|
158
165
|
Methods::RESOURCES_TEMPLATES_LIST => method(:list_resource_templates),
|
|
159
166
|
Methods::RESOURCES_SUBSCRIBE => ->(_) { {} },
|
|
160
167
|
Methods::RESOURCES_UNSUBSCRIBE => ->(_) { {} },
|
|
@@ -163,6 +170,7 @@ module MCP
|
|
|
163
170
|
Methods::PROMPTS_LIST => method(:list_prompts),
|
|
164
171
|
Methods::PROMPTS_GET => method(:get_prompt),
|
|
165
172
|
Methods::INITIALIZE => method(:init),
|
|
173
|
+
Methods::SERVER_DISCOVER => method(:discover),
|
|
166
174
|
Methods::PING => ->(_) { {} },
|
|
167
175
|
Methods::NOTIFICATIONS_INITIALIZED => ->(_) {},
|
|
168
176
|
Methods::NOTIFICATIONS_PROGRESS => ->(_) {},
|
|
@@ -216,6 +224,34 @@ module MCP
|
|
|
216
224
|
validate!
|
|
217
225
|
end
|
|
218
226
|
|
|
227
|
+
def define_resource(uri: nil, name: nil, title: nil, description: nil, icons: [], mime_type: nil, annotations: nil, size: nil, meta: nil, &block)
|
|
228
|
+
resource = Resource.define(
|
|
229
|
+
uri: uri, name: name, title: title, description: description, icons: icons,
|
|
230
|
+
mime_type: mime_type, annotations: annotations, size: size, meta: meta,
|
|
231
|
+
&block
|
|
232
|
+
)
|
|
233
|
+
@resources << resource
|
|
234
|
+
@resource_index[resource.uri] = resource
|
|
235
|
+
|
|
236
|
+
validate!
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def define_resource_template(uri_template: nil, name: nil, title: nil, description: nil, icons: [], mime_type: nil, annotations: nil, meta: nil, &block)
|
|
240
|
+
resource_template = ResourceTemplate.define(
|
|
241
|
+
uri_template: uri_template, name: name, title: title, description: description, icons: icons,
|
|
242
|
+
mime_type: mime_type, annotations: annotations, meta: meta,
|
|
243
|
+
&block
|
|
244
|
+
)
|
|
245
|
+
@resource_templates << resource_template
|
|
246
|
+
|
|
247
|
+
validate!
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def resources=(resources)
|
|
251
|
+
@resources = resources
|
|
252
|
+
@resource_index = index_resources_by_uri(resources)
|
|
253
|
+
end
|
|
254
|
+
|
|
219
255
|
def define_custom_method(method_name:, &block)
|
|
220
256
|
if @handlers.key?(method_name)
|
|
221
257
|
raise MethodAlreadyDefinedError, method_name
|
|
@@ -232,6 +268,27 @@ module MCP
|
|
|
232
268
|
@page_size = page_size
|
|
233
269
|
end
|
|
234
270
|
|
|
271
|
+
# SEP-2549 cache hint: freshness lifetime in milliseconds for list and read results
|
|
272
|
+
# (max-age semantics; 0 means do not cache). Emission is opt-in: when both `ttl_ms`
|
|
273
|
+
# and `cache_scope` are nil, results are serialized exactly as before.
|
|
274
|
+
def ttl_ms=(ttl_ms)
|
|
275
|
+
unless ttl_ms.nil? || (ttl_ms.is_a?(Integer) && ttl_ms >= 0)
|
|
276
|
+
raise ArgumentError, "ttl_ms must be nil or a non-negative integer"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
@ttl_ms = ttl_ms
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# SEP-2549 cache hint: whether shared intermediaries may cache the result ("public")
|
|
283
|
+
# or only the requesting client ("private").
|
|
284
|
+
def cache_scope=(cache_scope)
|
|
285
|
+
unless cache_scope.nil? || CACHE_SCOPES.include?(cache_scope)
|
|
286
|
+
raise ArgumentError, "cache_scope must be nil, \"public\", or \"private\""
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
@cache_scope = cache_scope
|
|
290
|
+
end
|
|
291
|
+
|
|
235
292
|
def notify_tools_list_changed
|
|
236
293
|
return unless @transport
|
|
237
294
|
|
|
@@ -256,6 +313,9 @@ module MCP
|
|
|
256
313
|
report_exception(e, { notification: "resources_list_changed" })
|
|
257
314
|
end
|
|
258
315
|
|
|
316
|
+
# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
|
|
317
|
+
# is deprecated as of MCP protocol version 2026-07-28 (SEP-2577).
|
|
318
|
+
# Use stderr or OpenTelemetry instead.
|
|
259
319
|
def notify_log_message(data:, level:, logger: nil)
|
|
260
320
|
return unless @transport
|
|
261
321
|
return unless logging_message_notification&.should_notify?(level)
|
|
@@ -272,6 +332,10 @@ module MCP
|
|
|
272
332
|
# Called when a client notifies the server that its filesystem roots have changed.
|
|
273
333
|
#
|
|
274
334
|
# @yield [params] The notification params (typically `nil`).
|
|
335
|
+
# @deprecated MCP Roots (`roots/list` and
|
|
336
|
+
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
337
|
+
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
338
|
+
# server configuration, or environment variables instead.
|
|
275
339
|
def roots_list_changed_handler(&block)
|
|
276
340
|
@handlers[Methods::NOTIFICATIONS_ROOTS_LIST_CHANGED] = block
|
|
277
341
|
end
|
|
@@ -423,6 +487,13 @@ module MCP
|
|
|
423
487
|
end
|
|
424
488
|
|
|
425
489
|
def handle_request(request, method, session: nil, related_request_id: nil)
|
|
490
|
+
# A well-formed notification carries no JSON-RPC id and receives no response.
|
|
491
|
+
# If a client erroneously sends a notification-only method with an id, the message
|
|
492
|
+
# is framed as a request; since notification methods have no request handler,
|
|
493
|
+
# returning `nil` here makes `JsonRpcHandler` report "Method not found", matching
|
|
494
|
+
# the TypeScript and Python SDKs rather than emitting a spurious `result: null`.
|
|
495
|
+
return if Methods.notification?(method) && !related_request_id.nil?
|
|
496
|
+
|
|
426
497
|
# `notifications/cancelled` is dispatched directly: it is a notification (no JSON-RPC id)
|
|
427
498
|
# and intentionally bypasses the `@handlers` lookup, capability check, in-flight registry,
|
|
428
499
|
# and rescue blocks below.
|
|
@@ -458,8 +529,9 @@ module MCP
|
|
|
458
529
|
when Methods::INITIALIZE
|
|
459
530
|
init(params, session: session)
|
|
460
531
|
when Methods::RESOURCES_READ
|
|
461
|
-
|
|
532
|
+
build_read_resource_result(read_resource_contents(params, session: session, related_request_id: related_request_id, cancellation: cancellation))
|
|
462
533
|
when Methods::RESOURCES_SUBSCRIBE, Methods::RESOURCES_UNSUBSCRIBE
|
|
534
|
+
validate_resource_subscription_params!(params)
|
|
463
535
|
dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation)
|
|
464
536
|
{}
|
|
465
537
|
when Methods::TOOLS_CALL
|
|
@@ -534,6 +606,8 @@ module MCP
|
|
|
534
606
|
end
|
|
535
607
|
|
|
536
608
|
def init(params, session: nil)
|
|
609
|
+
validate_initialize_params!(params)
|
|
610
|
+
|
|
537
611
|
# MCP spec: the initialization phase MUST be the first interaction between client and server.
|
|
538
612
|
# Reject duplicate `initialize` on an already-initialized session so the negotiated
|
|
539
613
|
# client identity and capabilities cannot be silently overwritten.
|
|
@@ -541,15 +615,13 @@ module MCP
|
|
|
541
615
|
raise RequestHandlerError.new("Invalid Request: Server already initialized", params, error_type: :invalid_request)
|
|
542
616
|
end
|
|
543
617
|
|
|
544
|
-
if
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
@client_capabilities = params[:capabilities]
|
|
550
|
-
end
|
|
551
|
-
protocol_version = params[:protocolVersion]
|
|
618
|
+
if session
|
|
619
|
+
session.store_client_info(client: params[:clientInfo], capabilities: params[:capabilities])
|
|
620
|
+
else
|
|
621
|
+
@client = params[:clientInfo]
|
|
622
|
+
@client_capabilities = params[:capabilities]
|
|
552
623
|
end
|
|
624
|
+
protocol_version = params[:protocolVersion]
|
|
553
625
|
|
|
554
626
|
negotiated_version = if Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(protocol_version)
|
|
555
627
|
protocol_version
|
|
@@ -578,6 +650,47 @@ module MCP
|
|
|
578
650
|
}.compact
|
|
579
651
|
end
|
|
580
652
|
|
|
653
|
+
def validate_resource_subscription_params!(params)
|
|
654
|
+
unless params.is_a?(Hash) && params[:uri].is_a?(String)
|
|
655
|
+
raise RequestHandlerError.new("Missing or invalid uri", params, error_type: :invalid_params)
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def validate_initialize_params!(params)
|
|
660
|
+
unless params.is_a?(Hash)
|
|
661
|
+
raise RequestHandlerError.new("Invalid params", params, error_type: :invalid_params)
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
unless params[:protocolVersion].is_a?(String)
|
|
665
|
+
raise RequestHandlerError.new("Missing or invalid protocolVersion", params, error_type: :invalid_params)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
unless params[:capabilities].is_a?(Hash)
|
|
669
|
+
raise RequestHandlerError.new("Missing or invalid capabilities", params, error_type: :invalid_params)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
client_info = params[:clientInfo]
|
|
673
|
+
if !client_info.is_a?(Hash) || client_info[:name].nil? || client_info[:version].nil?
|
|
674
|
+
raise RequestHandlerError.new("Missing or invalid clientInfo", params, error_type: :invalid_params)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
# Handles `server/discover` (MCP 2026-07-28 draft, SEP-2575): sessionless capability discovery.
|
|
679
|
+
# Unlike `init`, this is state-free and idempotent: it stores no client info, does not mark
|
|
680
|
+
# the session initialized, and responds regardless of capability declarations or initialization state,
|
|
681
|
+
# so clients can probe a server before (or instead of) `initialize`. `serverInfo` is returned unfiltered
|
|
682
|
+
# because discovery happens before version negotiation. The draft's `ttlMs`/`cacheScope` cache hints
|
|
683
|
+
# are not included here yet.
|
|
684
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
685
|
+
def discover(_request)
|
|
686
|
+
{
|
|
687
|
+
supportedVersions: Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS,
|
|
688
|
+
capabilities: capabilities,
|
|
689
|
+
serverInfo: server_info,
|
|
690
|
+
instructions: instructions,
|
|
691
|
+
}.compact
|
|
692
|
+
end
|
|
693
|
+
|
|
581
694
|
def configure_logging_level(request, session: nil)
|
|
582
695
|
if capabilities[:logging].nil?
|
|
583
696
|
raise RequestHandlerError.new("Server does not support logging", request, error_type: :internal_error)
|
|
@@ -597,7 +710,7 @@ module MCP
|
|
|
597
710
|
def list_tools(request)
|
|
598
711
|
page = paginate(@tools.values, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
599
712
|
|
|
600
|
-
{ tools: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
713
|
+
apply_cache_metadata({ tools: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
601
714
|
end
|
|
602
715
|
|
|
603
716
|
def call_tool(request, session: nil, related_request_id: nil, cancellation: nil)
|
|
@@ -653,7 +766,7 @@ module MCP
|
|
|
653
766
|
def list_prompts(request)
|
|
654
767
|
page = paginate(@prompts.values, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
655
768
|
|
|
656
|
-
{ prompts: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
769
|
+
apply_cache_metadata({ prompts: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
657
770
|
end
|
|
658
771
|
|
|
659
772
|
def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil)
|
|
@@ -682,19 +795,91 @@ module MCP
|
|
|
682
795
|
def list_resources(request)
|
|
683
796
|
page = paginate(@resources, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
684
797
|
|
|
685
|
-
{ resources: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
798
|
+
apply_cache_metadata({ resources: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
686
799
|
end
|
|
687
800
|
|
|
688
|
-
#
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
801
|
+
# Default `resources/read` handler: routes to class-based resources and resource templates.
|
|
802
|
+
# Fully replaced when `resources_read_handler` is set. When no class-based resource or template is registered,
|
|
803
|
+
# unknown URIs keep the historical no-op `[]` response instead of raising.
|
|
804
|
+
def read_resource(request, server_context: nil)
|
|
805
|
+
uri = request[:uri]
|
|
806
|
+
add_instrumentation_data(resource_uri: uri)
|
|
807
|
+
|
|
808
|
+
resource = @resource_index[uri]
|
|
809
|
+
if resource.is_a?(Class)
|
|
810
|
+
return normalize_resource_contents(call_resource_contents(resource, server_context))
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
@resource_templates.each do |template|
|
|
814
|
+
next unless template.is_a?(Class)
|
|
815
|
+
|
|
816
|
+
params = template.match_uri(uri)
|
|
817
|
+
next unless params
|
|
818
|
+
|
|
819
|
+
add_instrumentation_data(resource_template: template.uri_template)
|
|
820
|
+
return normalize_resource_contents(call_template_contents(template, params, server_context))
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
return [] unless class_based_resources_registered?
|
|
824
|
+
|
|
825
|
+
raise ResourceNotFoundError.new(uri, request)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
def call_resource_contents(resource, server_context)
|
|
829
|
+
if accepts_server_context?(resource.method(:contents))
|
|
830
|
+
resource.contents(server_context: server_context)
|
|
831
|
+
else
|
|
832
|
+
resource.contents
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
def call_template_contents(template, params, server_context)
|
|
837
|
+
if accepts_server_context?(template.method(:contents))
|
|
838
|
+
template.contents(**params, server_context: server_context)
|
|
839
|
+
else
|
|
840
|
+
template.contents(**params)
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def normalize_resource_contents(result)
|
|
845
|
+
contents = result.is_a?(Array) ? result : [result]
|
|
846
|
+
contents.map(&:to_h)
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
def class_based_resources_registered?
|
|
850
|
+
@resources.any? { |resource| resource.is_a?(Class) } ||
|
|
851
|
+
@resource_templates.any? { |template| template.is_a?(Class) }
|
|
692
852
|
end
|
|
693
853
|
|
|
694
854
|
def list_resource_templates(request)
|
|
695
855
|
page = paginate(@resource_templates, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
696
856
|
|
|
697
|
-
{ resourceTemplates: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
857
|
+
apply_cache_metadata({ resourceTemplates: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Builds the `resources/read` result. The documented handler contract is "return value becomes `contents`";
|
|
861
|
+
# a Hash with a `:contents` key is also accepted as a full result so a handler can override the server-level
|
|
862
|
+
# `ttlMs`/`cacheScope` cache hints per result (SEP-2549).
|
|
863
|
+
def build_read_resource_result(handler_result)
|
|
864
|
+
result = if handler_result.is_a?(Hash) && handler_result.key?(:contents)
|
|
865
|
+
handler_result
|
|
866
|
+
else
|
|
867
|
+
{ contents: handler_result }
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
apply_cache_metadata(result)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# Adds the SEP-2549 cache hints (`ttlMs`, `cacheScope`) to a result. Emission is opt-in: nothing is added
|
|
874
|
+
# unless the server was configured with `ttl_ms`/`cache_scope` or the result already carries one of the fields, in
|
|
875
|
+
# which case the missing one is filled with the spec defaults (`ttlMs: 0` = do not cache, `cacheScope: "public"`).
|
|
876
|
+
# Values already in the result win, enabling per-result overrides.
|
|
877
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549
|
|
878
|
+
def apply_cache_metadata(result)
|
|
879
|
+
explicit = result.key?(:ttlMs) || result.key?(:cacheScope)
|
|
880
|
+
return result if @ttl_ms.nil? && @cache_scope.nil? && !explicit
|
|
881
|
+
|
|
882
|
+
{ ttlMs: @ttl_ms || 0, cacheScope: @cache_scope || "public" }.merge(result)
|
|
698
883
|
end
|
|
699
884
|
|
|
700
885
|
def complete(params, session: nil, related_request_id: nil, cancellation: nil)
|
data/lib/mcp/server_context.rb
CHANGED
|
@@ -35,6 +35,9 @@ module MCP
|
|
|
35
35
|
# @param data [Object] The log data to send.
|
|
36
36
|
# @param level [String] Log level (e.g., `"debug"`, `"info"`, `"error"`).
|
|
37
37
|
# @param logger [String, nil] Logger name.
|
|
38
|
+
# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
|
|
39
|
+
# is deprecated as of MCP protocol version 2026-07-28 (SEP-2577).
|
|
40
|
+
# Use stderr or OpenTelemetry instead.
|
|
38
41
|
def notify_log_message(data:, level:, logger: nil)
|
|
39
42
|
return unless @notification_target
|
|
40
43
|
|
|
@@ -51,6 +54,13 @@ module MCP
|
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
# Delegates to the session so the request is scoped to the originating client.
|
|
57
|
+
# The originating request id is stamped as `related_request_id`, satisfying
|
|
58
|
+
# SEP-2260's requirement that server-to-client requests be associated with
|
|
59
|
+
# the client request being processed.
|
|
60
|
+
# @deprecated MCP Roots (`roots/list` and
|
|
61
|
+
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
62
|
+
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
63
|
+
# server configuration, or environment variables instead.
|
|
54
64
|
def list_roots
|
|
55
65
|
if @notification_target.respond_to?(:list_roots)
|
|
56
66
|
@notification_target.list_roots(related_request_id: @related_request_id)
|
|
@@ -84,6 +94,14 @@ module MCP
|
|
|
84
94
|
# Delegates to the session so the request is scoped to the originating client.
|
|
85
95
|
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
86
96
|
# does not support sampling.
|
|
97
|
+
#
|
|
98
|
+
# The originating request id is stamped as `related_request_id` and cannot be
|
|
99
|
+
# overridden by callers (the literal keyword after `**kwargs` wins),
|
|
100
|
+
# satisfying SEP-2260's requirement that server-to-client requests be
|
|
101
|
+
# associated with the client request being processed.
|
|
102
|
+
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
|
|
103
|
+
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
|
|
104
|
+
# APIs instead.
|
|
87
105
|
def create_sampling_message(**kwargs)
|
|
88
106
|
if @notification_target.respond_to?(:create_sampling_message)
|
|
89
107
|
@notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
|
@@ -97,6 +115,8 @@ module MCP
|
|
|
97
115
|
# Delegates to the session so the request is scoped to the originating client.
|
|
98
116
|
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
99
117
|
# does not support elicitation.
|
|
118
|
+
# The originating request id is stamped as a non-overridable
|
|
119
|
+
# `related_request_id`, as with `create_sampling_message` (SEP-2260).
|
|
100
120
|
def create_form_elicitation(**kwargs)
|
|
101
121
|
if @notification_target.respond_to?(:create_form_elicitation)
|
|
102
122
|
@notification_target.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
@@ -109,6 +129,8 @@ module MCP
|
|
|
109
129
|
|
|
110
130
|
# Delegates to the session so the request is scoped to the originating client.
|
|
111
131
|
# Falls back to `@context` when `@notification_target` does not support URL mode elicitation.
|
|
132
|
+
# The originating request id is stamped as a non-overridable `related_request_id`,
|
|
133
|
+
# as with `create_sampling_message` (SEP-2260).
|
|
112
134
|
def create_url_elicitation(**kwargs)
|
|
113
135
|
if @notification_target.respond_to?(:create_url_elicitation)
|
|
114
136
|
@notification_target.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -98,7 +98,17 @@ module MCP
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
# Sends a `roots/list` request scoped to this session.
|
|
101
|
+
#
|
|
102
|
+
# Per SEP-2260, the request must be associated with an originating client
|
|
103
|
+
# request; prefer `server_context.list_roots` inside a handler, which stamps
|
|
104
|
+
# the association automatically.
|
|
105
|
+
# @deprecated MCP Roots (`roots/list` and
|
|
106
|
+
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
107
|
+
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
108
|
+
# server configuration, or environment variables instead.
|
|
101
109
|
def list_roots(related_request_id: nil)
|
|
110
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
111
|
+
|
|
102
112
|
unless client_capabilities&.dig(:roots)
|
|
103
113
|
raise "Client does not support roots."
|
|
104
114
|
end
|
|
@@ -115,13 +125,28 @@ module MCP
|
|
|
115
125
|
end
|
|
116
126
|
|
|
117
127
|
# Sends a `sampling/createMessage` request scoped to this session.
|
|
128
|
+
#
|
|
129
|
+
# Per SEP-2260, the request must be associated with an originating client
|
|
130
|
+
# request; prefer `server_context.create_sampling_message` inside a handler,
|
|
131
|
+
# which stamps the association automatically.
|
|
132
|
+
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
|
|
133
|
+
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
|
|
134
|
+
# APIs instead.
|
|
118
135
|
def create_sampling_message(related_request_id: nil, **kwargs)
|
|
136
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
137
|
+
|
|
119
138
|
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
|
120
139
|
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
|
|
121
140
|
end
|
|
122
141
|
|
|
123
142
|
# Sends an `elicitation/create` request (form mode) scoped to this session.
|
|
143
|
+
#
|
|
144
|
+
# Per SEP-2260, the request must be associated with an originating client
|
|
145
|
+
# request; prefer `server_context.create_form_elicitation` inside a handler,
|
|
146
|
+
# which stamps the association automatically.
|
|
124
147
|
def create_form_elicitation(message:, requested_schema:, related_request_id: nil)
|
|
148
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
149
|
+
|
|
125
150
|
unless client_capabilities&.dig(:elicitation)
|
|
126
151
|
raise "Client does not support elicitation. " \
|
|
127
152
|
"The client must declare the `elicitation` capability during initialization."
|
|
@@ -132,7 +157,13 @@ module MCP
|
|
|
132
157
|
end
|
|
133
158
|
|
|
134
159
|
# Sends an `elicitation/create` request (URL mode) scoped to this session.
|
|
160
|
+
#
|
|
161
|
+
# Per SEP-2260, the request must be associated with an originating client
|
|
162
|
+
# request; prefer `server_context.create_url_elicitation` inside a handler,
|
|
163
|
+
# which stamps the association automatically.
|
|
135
164
|
def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil)
|
|
165
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
166
|
+
|
|
136
167
|
unless client_capabilities&.dig(:elicitation, :url)
|
|
137
168
|
raise "Client does not support URL mode elicitation. " \
|
|
138
169
|
"The client must declare the `elicitation.url` capability during initialization."
|
|
@@ -188,6 +219,9 @@ module MCP
|
|
|
188
219
|
end
|
|
189
220
|
|
|
190
221
|
# Sends a log message notification to this session only.
|
|
222
|
+
# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
|
|
223
|
+
# is deprecated as of MCP protocol version 2026-07-28 (SEP-2577).
|
|
224
|
+
# Use stderr or OpenTelemetry instead.
|
|
191
225
|
def notify_log_message(data:, level:, logger: nil, related_request_id: nil)
|
|
192
226
|
effective_logging = @logging_message_notification || @server.logging_message_notification
|
|
193
227
|
return unless effective_logging&.should_notify?(level)
|
|
@@ -254,5 +288,21 @@ module MCP
|
|
|
254
288
|
# The explicit splat suppresses that conversion and is a no-op when `supported` is empty.
|
|
255
289
|
transport_method.call(method, params, **supported)
|
|
256
290
|
end
|
|
291
|
+
|
|
292
|
+
# Per SEP-2260, servers MUST send `roots/list`, `sampling/createMessage`, and `elicitation/create` only
|
|
293
|
+
# in association with an originating client request (`ping` is exempt). A request without `related_request_id:` is
|
|
294
|
+
# delivered on the standalone GET stream by the Streamable HTTP transport, which the 2026-07-28 spec forbids;
|
|
295
|
+
# warn so callers migrate to the `server_context` helpers before this becomes an error.
|
|
296
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260
|
|
297
|
+
def warn_unassociated_request(method_name, related_request_id)
|
|
298
|
+
return if related_request_id
|
|
299
|
+
|
|
300
|
+
warn(
|
|
301
|
+
"Calling `ServerSession##{method_name}` without `related_request_id:` is deprecated (SEP-2260): " \
|
|
302
|
+
"server-to-client #{method_name} requests must be associated with an originating client request. " \
|
|
303
|
+
"Use `server_context.#{method_name}` inside a handler, or pass `related_request_id:` explicitly.",
|
|
304
|
+
uplevel: 2,
|
|
305
|
+
)
|
|
306
|
+
end
|
|
257
307
|
end
|
|
258
308
|
end
|
data/lib/mcp/version.rb
CHANGED
data/lib/mcp.rb
CHANGED
|
@@ -8,14 +8,17 @@ require_relative "mcp/version"
|
|
|
8
8
|
|
|
9
9
|
module MCP
|
|
10
10
|
autoload :Annotations, "mcp/annotations"
|
|
11
|
+
autoload :Apps, "mcp/apps"
|
|
11
12
|
autoload :Cancellation, "mcp/cancellation"
|
|
12
13
|
autoload :CancelledError, "mcp/cancelled_error"
|
|
13
14
|
autoload :Client, "mcp/client"
|
|
14
15
|
autoload :Content, "mcp/content"
|
|
16
|
+
autoload :ErrorCodes, "mcp/error_codes"
|
|
15
17
|
autoload :Icon, "mcp/icon"
|
|
16
18
|
autoload :Prompt, "mcp/prompt"
|
|
17
19
|
autoload :Resource, "mcp/resource"
|
|
18
20
|
autoload :ResourceTemplate, "mcp/resource_template"
|
|
21
|
+
autoload :ResultType, "mcp/result_type"
|
|
19
22
|
autoload :Server, "mcp/server"
|
|
20
23
|
autoload :ServerSession, "mcp/server_session"
|
|
21
24
|
autoload :Tool, "mcp/tool"
|
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.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -37,15 +37,18 @@ files:
|
|
|
37
37
|
- lib/json_rpc_handler.rb
|
|
38
38
|
- lib/mcp.rb
|
|
39
39
|
- lib/mcp/annotations.rb
|
|
40
|
+
- lib/mcp/apps.rb
|
|
40
41
|
- lib/mcp/cancellation.rb
|
|
41
42
|
- lib/mcp/cancelled_error.rb
|
|
42
43
|
- lib/mcp/client.rb
|
|
44
|
+
- lib/mcp/client/elicitation.rb
|
|
43
45
|
- lib/mcp/client/http.rb
|
|
44
46
|
- lib/mcp/client/oauth.rb
|
|
45
47
|
- lib/mcp/client/oauth/client_credentials_provider.rb
|
|
46
48
|
- lib/mcp/client/oauth/discovery.rb
|
|
47
49
|
- lib/mcp/client/oauth/flow.rb
|
|
48
50
|
- lib/mcp/client/oauth/in_memory_storage.rb
|
|
51
|
+
- lib/mcp/client/oauth/jwt_client_assertion.rb
|
|
49
52
|
- lib/mcp/client/oauth/pkce.rb
|
|
50
53
|
- lib/mcp/client/oauth/provider.rb
|
|
51
54
|
- lib/mcp/client/oauth/storage_backed_provider.rb
|
|
@@ -54,6 +57,7 @@ files:
|
|
|
54
57
|
- lib/mcp/client/tool.rb
|
|
55
58
|
- lib/mcp/configuration.rb
|
|
56
59
|
- lib/mcp/content.rb
|
|
60
|
+
- lib/mcp/error_codes.rb
|
|
57
61
|
- lib/mcp/icon.rb
|
|
58
62
|
- lib/mcp/instrumentation.rb
|
|
59
63
|
- lib/mcp/logging_message_notification.rb
|
|
@@ -67,6 +71,7 @@ files:
|
|
|
67
71
|
- lib/mcp/resource/contents.rb
|
|
68
72
|
- lib/mcp/resource/embedded.rb
|
|
69
73
|
- lib/mcp/resource_template.rb
|
|
74
|
+
- lib/mcp/result_type.rb
|
|
70
75
|
- lib/mcp/server.rb
|
|
71
76
|
- lib/mcp/server/capabilities.rb
|
|
72
77
|
- lib/mcp/server/pagination.rb
|
|
@@ -90,7 +95,7 @@ licenses:
|
|
|
90
95
|
- Apache-2.0
|
|
91
96
|
metadata:
|
|
92
97
|
allowed_push_host: https://rubygems.org
|
|
93
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.
|
|
98
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.24.0
|
|
94
99
|
homepage_uri: https://ruby.sdk.modelcontextprotocol.io
|
|
95
100
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
96
101
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|