mcp 0.23.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 +250 -6
- 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 +3 -1
- data/lib/mcp/client.rb +89 -1
- data/lib/mcp/error_codes.rb +21 -0
- data/lib/mcp/methods.rb +3 -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/streamable_http_transport.rb +113 -78
- data/lib/mcp/server.rb +191 -20
- data/lib/mcp/server_context.rb +12 -0
- data/lib/mcp/server_session.rb +40 -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
|
|
|
@@ -472,8 +529,9 @@ module MCP
|
|
|
472
529
|
when Methods::INITIALIZE
|
|
473
530
|
init(params, session: session)
|
|
474
531
|
when Methods::RESOURCES_READ
|
|
475
|
-
|
|
532
|
+
build_read_resource_result(read_resource_contents(params, session: session, related_request_id: related_request_id, cancellation: cancellation))
|
|
476
533
|
when Methods::RESOURCES_SUBSCRIBE, Methods::RESOURCES_UNSUBSCRIBE
|
|
534
|
+
validate_resource_subscription_params!(params)
|
|
477
535
|
dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation)
|
|
478
536
|
{}
|
|
479
537
|
when Methods::TOOLS_CALL
|
|
@@ -548,6 +606,8 @@ module MCP
|
|
|
548
606
|
end
|
|
549
607
|
|
|
550
608
|
def init(params, session: nil)
|
|
609
|
+
validate_initialize_params!(params)
|
|
610
|
+
|
|
551
611
|
# MCP spec: the initialization phase MUST be the first interaction between client and server.
|
|
552
612
|
# Reject duplicate `initialize` on an already-initialized session so the negotiated
|
|
553
613
|
# client identity and capabilities cannot be silently overwritten.
|
|
@@ -555,15 +615,13 @@ module MCP
|
|
|
555
615
|
raise RequestHandlerError.new("Invalid Request: Server already initialized", params, error_type: :invalid_request)
|
|
556
616
|
end
|
|
557
617
|
|
|
558
|
-
if
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
@client_capabilities = params[:capabilities]
|
|
564
|
-
end
|
|
565
|
-
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]
|
|
566
623
|
end
|
|
624
|
+
protocol_version = params[:protocolVersion]
|
|
567
625
|
|
|
568
626
|
negotiated_version = if Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(protocol_version)
|
|
569
627
|
protocol_version
|
|
@@ -592,6 +650,47 @@ module MCP
|
|
|
592
650
|
}.compact
|
|
593
651
|
end
|
|
594
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
|
+
|
|
595
694
|
def configure_logging_level(request, session: nil)
|
|
596
695
|
if capabilities[:logging].nil?
|
|
597
696
|
raise RequestHandlerError.new("Server does not support logging", request, error_type: :internal_error)
|
|
@@ -611,7 +710,7 @@ module MCP
|
|
|
611
710
|
def list_tools(request)
|
|
612
711
|
page = paginate(@tools.values, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
613
712
|
|
|
614
|
-
{ tools: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
713
|
+
apply_cache_metadata({ tools: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
615
714
|
end
|
|
616
715
|
|
|
617
716
|
def call_tool(request, session: nil, related_request_id: nil, cancellation: nil)
|
|
@@ -667,7 +766,7 @@ module MCP
|
|
|
667
766
|
def list_prompts(request)
|
|
668
767
|
page = paginate(@prompts.values, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
669
768
|
|
|
670
|
-
{ prompts: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
769
|
+
apply_cache_metadata({ prompts: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
671
770
|
end
|
|
672
771
|
|
|
673
772
|
def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil)
|
|
@@ -696,19 +795,91 @@ module MCP
|
|
|
696
795
|
def list_resources(request)
|
|
697
796
|
page = paginate(@resources, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
698
797
|
|
|
699
|
-
{ resources: page[:items], nextCursor: page[:next_cursor] }.compact
|
|
798
|
+
apply_cache_metadata({ resources: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
799
|
+
end
|
|
800
|
+
|
|
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)
|
|
700
826
|
end
|
|
701
827
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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) }
|
|
706
852
|
end
|
|
707
853
|
|
|
708
854
|
def list_resource_templates(request)
|
|
709
855
|
page = paginate(@resource_templates, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h)
|
|
710
856
|
|
|
711
|
-
{ 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)
|
|
712
883
|
end
|
|
713
884
|
|
|
714
885
|
def complete(params, session: nil, related_request_id: nil, cancellation: nil)
|
data/lib/mcp/server_context.rb
CHANGED
|
@@ -54,6 +54,9 @@ module MCP
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
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.
|
|
57
60
|
# @deprecated MCP Roots (`roots/list` and
|
|
58
61
|
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
59
62
|
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
@@ -91,6 +94,11 @@ module MCP
|
|
|
91
94
|
# Delegates to the session so the request is scoped to the originating client.
|
|
92
95
|
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
93
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.
|
|
94
102
|
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
|
|
95
103
|
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
|
|
96
104
|
# APIs instead.
|
|
@@ -107,6 +115,8 @@ module MCP
|
|
|
107
115
|
# Delegates to the session so the request is scoped to the originating client.
|
|
108
116
|
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
109
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).
|
|
110
120
|
def create_form_elicitation(**kwargs)
|
|
111
121
|
if @notification_target.respond_to?(:create_form_elicitation)
|
|
112
122
|
@notification_target.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
@@ -119,6 +129,8 @@ module MCP
|
|
|
119
129
|
|
|
120
130
|
# Delegates to the session so the request is scoped to the originating client.
|
|
121
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).
|
|
122
134
|
def create_url_elicitation(**kwargs)
|
|
123
135
|
if @notification_target.respond_to?(:create_url_elicitation)
|
|
124
136
|
@notification_target.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -98,11 +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.
|
|
101
105
|
# @deprecated MCP Roots (`roots/list` and
|
|
102
106
|
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
|
|
103
107
|
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
|
|
104
108
|
# server configuration, or environment variables instead.
|
|
105
109
|
def list_roots(related_request_id: nil)
|
|
110
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
111
|
+
|
|
106
112
|
unless client_capabilities&.dig(:roots)
|
|
107
113
|
raise "Client does not support roots."
|
|
108
114
|
end
|
|
@@ -119,16 +125,28 @@ module MCP
|
|
|
119
125
|
end
|
|
120
126
|
|
|
121
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.
|
|
122
132
|
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
|
|
123
133
|
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
|
|
124
134
|
# APIs instead.
|
|
125
135
|
def create_sampling_message(related_request_id: nil, **kwargs)
|
|
136
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
137
|
+
|
|
126
138
|
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
|
127
139
|
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
|
|
128
140
|
end
|
|
129
141
|
|
|
130
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.
|
|
131
147
|
def create_form_elicitation(message:, requested_schema:, related_request_id: nil)
|
|
148
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
149
|
+
|
|
132
150
|
unless client_capabilities&.dig(:elicitation)
|
|
133
151
|
raise "Client does not support elicitation. " \
|
|
134
152
|
"The client must declare the `elicitation` capability during initialization."
|
|
@@ -139,7 +157,13 @@ module MCP
|
|
|
139
157
|
end
|
|
140
158
|
|
|
141
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.
|
|
142
164
|
def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil)
|
|
165
|
+
warn_unassociated_request(__method__, related_request_id)
|
|
166
|
+
|
|
143
167
|
unless client_capabilities&.dig(:elicitation, :url)
|
|
144
168
|
raise "Client does not support URL mode elicitation. " \
|
|
145
169
|
"The client must declare the `elicitation.url` capability during initialization."
|
|
@@ -264,5 +288,21 @@ module MCP
|
|
|
264
288
|
# The explicit splat suppresses that conversion and is a no-op when `supported` is empty.
|
|
265
289
|
transport_method.call(method, params, **supported)
|
|
266
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
|
|
267
307
|
end
|
|
268
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
|