mcp 0.18.0 → 0.21.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.
data/lib/mcp/server.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "methods"
8
8
  require_relative "logging_message_notification"
9
9
  require_relative "progress"
10
10
  require_relative "server_context"
11
+ require_relative "server/capabilities"
11
12
  require_relative "server/pagination"
12
13
  require_relative "server/transports"
13
14
 
@@ -58,6 +59,32 @@ module MCP
58
59
  end
59
60
  end
60
61
 
62
+ # Raised when a requested resource URI does not exist. Per SEP-2164,
63
+ # resource-not-found errors use the standard JSON-RPC Invalid Params code (-32602)
64
+ # with the requested URI in the error `data` member. Raise this from
65
+ # a `resources_read_handler` block for unknown URIs:
66
+ #
67
+ # server.resources_read_handler do |params|
68
+ # raise MCP::Server::ResourceNotFoundError.new(params[:uri], params) unless known?(params[:uri])
69
+ # do_something(params[:uri])
70
+ # end
71
+ #
72
+ # https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164
73
+ class ResourceNotFoundError < RequestHandlerError
74
+ def initialize(uri, request = nil)
75
+ # The explicit `error_code` keeps the descriptive message in the JSON-RPC
76
+ # error response; `error_type: :invalid_params` alone would replace it
77
+ # with the generic "Invalid params" string.
78
+ super(
79
+ "Resource not found: #{uri}",
80
+ request,
81
+ error_type: :invalid_params,
82
+ error_code: JsonRpcHandler::ErrorCode::INVALID_PARAMS,
83
+ error_data: { uri: uri },
84
+ )
85
+ end
86
+ end
87
+
61
88
  class MethodAlreadyDefinedError < StandardError
62
89
  attr_reader :method_name
63
90
 
@@ -116,7 +143,12 @@ module MCP
116
143
 
117
144
  validate!
118
145
 
119
- @capabilities = capabilities || default_capabilities
146
+ # Accept either a plain Hash or an `MCP::Server::Capabilities` builder.
147
+ @capabilities = if capabilities.is_a?(Capabilities)
148
+ capabilities.to_h
149
+ else
150
+ capabilities || default_capabilities
151
+ end
120
152
  @client_capabilities = nil
121
153
  @logging_message_notification = nil
122
154
 
@@ -784,6 +816,10 @@ module MCP
784
816
  end
785
817
 
786
818
  def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil, related_request_id: nil, cancellation: nil)
819
+ # Transports parse incoming JSON with `symbolize_names: true`, so `arguments` already arrives symbolized
820
+ # at every nesting level. This top-level transform only guards callers that hand in string-keyed top-level arguments;
821
+ # it does not recurse, and nested object keys remain symbols. Tools therefore receive symbol keys all the way down.
822
+ # See docs/building-servers.md ("Tool argument keys").
787
823
  args = arguments&.transform_keys(&:to_sym) || {}
788
824
 
789
825
  if accepts_server_context?(tool.method(:call))
@@ -846,7 +882,7 @@ module MCP
846
882
  uri = ref[:uri]
847
883
  found = @resource_index.key?(uri) || @resource_templates.any? { |t| t.uri_template == uri }
848
884
  unless found
849
- raise RequestHandlerError.new("Resource not found: #{uri}", params, error_type: :invalid_params)
885
+ raise ResourceNotFoundError.new(uri, params)
850
886
  end
851
887
  else
852
888
  raise RequestHandlerError.new("Invalid ref type: #{ref[:type]}", params, error_type: :invalid_params)
@@ -12,9 +12,9 @@ module MCP
12
12
  end
13
13
 
14
14
  def missing_required_arguments(arguments)
15
- return [] unless schema[:required].is_a?(Array)
15
+ return [] unless @schema[:required].is_a?(Array)
16
16
 
17
- (schema[:required] - arguments.keys.map(&:to_s))
17
+ (@schema[:required] - arguments.keys.map(&:to_s))
18
18
  end
19
19
 
20
20
  def validate_arguments(arguments)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
- require "json-schema"
4
+ require "json_schemer"
5
5
 
6
6
  module MCP
7
7
  class Tool
@@ -38,11 +38,10 @@ module MCP
38
38
 
39
39
  # JSON Schema 2020-12 is the default dialect for MCP schema definitions
40
40
  # per MCP 2025-11-25 (SEP-1613). Note: emission only — runtime validation
41
- # is still performed against the JSON Schema draft-04 metaschema because
42
- # the `json-schema` gem does not yet support 2020-12.
41
+ # is still performed against the JSON Schema draft-04 metaschema.
43
42
  JSON_SCHEMA_2020_12_URI = "https://json-schema.org/draft/2020-12/schema"
44
43
 
45
- attr_reader :schema
44
+ DRAFT4_META_SCHEMA_URI = "http://json-schema.org/draft-04/schema#"
46
45
 
47
46
  def initialize(schema = {})
48
47
  @schema = JSON.parse(JSON.dump(schema), symbolize_names: true)
@@ -51,7 +50,7 @@ module MCP
51
50
  end
52
51
 
53
52
  def ==(other)
54
- other.is_a?(self.class) && schema == other.schema
53
+ other.is_a?(self.class) && @schema == other.instance_variable_get(:@schema)
55
54
  end
56
55
 
57
56
  def to_h
@@ -62,8 +61,38 @@ module MCP
62
61
 
63
62
  private
64
63
 
64
+ def stringify(obj)
65
+ case obj
66
+ when Hash
67
+ obj.each_with_object({}) { |(k, v), h| h[k.to_s] = stringify(v) }
68
+ when Array
69
+ obj.map { |v| stringify(v) }
70
+ when Symbol
71
+ obj.to_s
72
+ else
73
+ obj
74
+ end
75
+ end
76
+
77
+ # Lazily built so a cache hit in `validate_schema!` avoids the schemer construction cost.
78
+ # Memoized per Schema instance because schema content is fixed at construction,
79
+ # so the compiled schemer is reusable across many `fully_validate` calls.
80
+ #
81
+ # `format: false` preserves the legacy behavior of the previous `json-schema` based implementation,
82
+ # which did not enforce `format` keywords. `RegexpError` from a malformed `pattern` is re-raised as
83
+ # `ArgumentError` so callers see the same exception class they used to.
84
+ def schemer
85
+ @schemer ||= JSONSchemer.schema(
86
+ stringify(schema_for_validation),
87
+ meta_schema: DRAFT4_META_SCHEMA_URI,
88
+ format: false,
89
+ )
90
+ rescue RegexpError => e
91
+ raise ArgumentError, "Invalid JSON Schema: #{e.message}"
92
+ end
93
+
65
94
  def fully_validate(data)
66
- JSON::Validator.fully_validate(schema_for_validation, data)
95
+ schemer.validate(stringify(data)).map { |validation_error| validation_error.fetch("error") }
67
96
  end
68
97
 
69
98
  def validate_schema!
@@ -75,16 +104,7 @@ module MCP
75
104
  key = Digest::SHA256.hexdigest(JSON.generate(target, max_nesting: false))
76
105
  return if VALIDATION_CACHE.validated?(key)
77
106
 
78
- gem_path = File.realpath(Gem.loaded_specs["json-schema"].full_gem_path)
79
- schema_reader = JSON::Schema::Reader.new(
80
- accept_uri: false,
81
- accept_file: ->(path) { File.realpath(path.to_s).start_with?(gem_path) },
82
- )
83
- metaschema_path = Pathname.new(JSON::Validator.validator_for_name("draft4").metaschema)
84
- # Converts metaschema to a file URI for cross-platform compatibility
85
- metaschema_uri = JSON::Util::URI.file_uri(metaschema_path.expand_path.cleanpath.to_s.tr("\\", "/"))
86
- metaschema = metaschema_uri.to_s
87
- errors = JSON::Validator.fully_validate(metaschema, target, schema_reader: schema_reader)
107
+ errors = schemer.validate_schema.map { |validation_error| validation_error.fetch("error") }
88
108
  if errors.any?
89
109
  raise ArgumentError, "Invalid JSON Schema: #{errors.join(", ")}"
90
110
  end
@@ -92,9 +112,8 @@ module MCP
92
112
  VALIDATION_CACHE.store(key)
93
113
  end
94
114
 
95
- # The `json-schema` gem's draft-04 validator cannot resolve newer or unknown `$schema`
96
- # dialect URIs. Strip the top-level `$schema` before validation so a dialect URI
97
- # (whether SDK-injected by `to_h` or user-supplied) does not break the validator.
115
+ # `json_schemer` is pinned to the draft-04 metaschema, so strip top-level `$schema` before validation:
116
+ # this preserves the legacy behavior of ignoring the advertised dialect URI when the SDK validates schemas.
98
117
  def schema_for_validation
99
118
  return @schema unless @schema.key?(:"$schema")
100
119
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCP
4
+ # Reserved `_meta` keys for W3C Trace Context propagation, per SEP-414.
5
+ #
6
+ # The MCP spec reserves the un-prefixed `_meta` keys `traceparent`, `tracestate`, and `baggage`
7
+ # (an explicit exception to the reverse-DNS prefix rule for `_meta` keys) so that clients and
8
+ # servers can propagate distributed-tracing context across MCP requests.
9
+ # The SDK guarantees these keys pass through incoming request `_meta` untouched; tool, prompt,
10
+ # and resource handlers can read them from `server_context[:_meta]` and bridge them to a tracing
11
+ # system such as the `opentelemetry-ruby` gems. The SDK itself does not depend on OpenTelemetry.
12
+ #
13
+ # - https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414
14
+ # - https://www.w3.org/TR/trace-context/
15
+ # - https://www.w3.org/TR/baggage/
16
+ module TraceContext
17
+ TRACEPARENT_META_KEY = "traceparent"
18
+ TRACESTATE_META_KEY = "tracestate"
19
+ BAGGAGE_META_KEY = "baggage"
20
+
21
+ META_KEYS = [TRACEPARENT_META_KEY, TRACESTATE_META_KEY, BAGGAGE_META_KEY].freeze
22
+ end
23
+ end
data/lib/mcp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MCP
4
- VERSION = "0.18.0"
4
+ VERSION = "0.21.0"
5
5
  end
data/lib/mcp.rb CHANGED
@@ -19,6 +19,7 @@ module MCP
19
19
  autoload :Server, "mcp/server"
20
20
  autoload :ServerSession, "mcp/server_session"
21
21
  autoload :Tool, "mcp/tool"
22
+ autoload :TraceContext, "mcp/trace_context"
22
23
 
23
24
  class << self
24
25
  def configure
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.18.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Model Context Protocol
@@ -10,19 +10,19 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: json-schema
13
+ name: json_schemer
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '4.1'
18
+ version: '2.4'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '4.1'
25
+ version: '2.4'
26
26
  description: The official Ruby SDK for Model Context Protocol servers and clients
27
27
  email:
28
28
  - mcp-support@anthropic.com
@@ -42,11 +42,13 @@ files:
42
42
  - lib/mcp/client.rb
43
43
  - lib/mcp/client/http.rb
44
44
  - lib/mcp/client/oauth.rb
45
+ - lib/mcp/client/oauth/client_credentials_provider.rb
45
46
  - lib/mcp/client/oauth/discovery.rb
46
47
  - lib/mcp/client/oauth/flow.rb
47
48
  - lib/mcp/client/oauth/in_memory_storage.rb
48
49
  - lib/mcp/client/oauth/pkce.rb
49
50
  - lib/mcp/client/oauth/provider.rb
51
+ - lib/mcp/client/oauth/storage_backed_provider.rb
50
52
  - lib/mcp/client/paginated_result.rb
51
53
  - lib/mcp/client/stdio.rb
52
54
  - lib/mcp/client/tool.rb
@@ -80,6 +82,7 @@ files:
80
82
  - lib/mcp/tool/output_schema.rb
81
83
  - lib/mcp/tool/response.rb
82
84
  - lib/mcp/tool/schema.rb
85
+ - lib/mcp/trace_context.rb
83
86
  - lib/mcp/transport.rb
84
87
  - lib/mcp/version.rb
85
88
  homepage: https://ruby.sdk.modelcontextprotocol.io
@@ -87,7 +90,7 @@ licenses:
87
90
  - Apache-2.0
88
91
  metadata:
89
92
  allowed_push_host: https://rubygems.org
90
- changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.18.0
93
+ changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.21.0
91
94
  homepage_uri: https://ruby.sdk.modelcontextprotocol.io
92
95
  source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
93
96
  bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues