mcp 0.19.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.
- checksums.yaml +4 -4
- data/README.md +117 -3
- data/lib/json_rpc_handler.rb +7 -2
- data/lib/mcp/annotations.rb +2 -0
- data/lib/mcp/client/http.rb +68 -7
- data/lib/mcp/client/oauth/client_credentials_provider.rb +89 -0
- data/lib/mcp/client/oauth/discovery.rb +42 -1
- data/lib/mcp/client/oauth/flow.rb +160 -25
- data/lib/mcp/client/oauth/provider.rb +15 -25
- data/lib/mcp/client/oauth/storage_backed_provider.rb +43 -0
- data/lib/mcp/client/oauth.rb +3 -1
- data/lib/mcp/client.rb +49 -20
- data/lib/mcp/configuration.rb +1 -0
- data/lib/mcp/resource.rb +4 -2
- data/lib/mcp/resource_template.rb +4 -2
- data/lib/mcp/server/capabilities.rb +14 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +78 -50
- data/lib/mcp/server.rb +38 -2
- data/lib/mcp/tool/input_schema.rb +2 -2
- data/lib/mcp/tool/schema.rb +38 -19
- data/lib/mcp/trace_context.rb +23 -0
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +1 -0
- metadata +8 -5
|
@@ -6,6 +6,7 @@ module MCP
|
|
|
6
6
|
def initialize(capabilities_hash = nil)
|
|
7
7
|
@completions = nil
|
|
8
8
|
@experimental = nil
|
|
9
|
+
@extensions = nil
|
|
9
10
|
@logging = nil
|
|
10
11
|
@prompts = nil
|
|
11
12
|
@resources = nil
|
|
@@ -14,6 +15,7 @@ module MCP
|
|
|
14
15
|
if capabilities_hash
|
|
15
16
|
support_completions if capabilities_hash.key?(:completions)
|
|
16
17
|
support_experimental(capabilities_hash[:experimental]) if capabilities_hash.key?(:experimental)
|
|
18
|
+
support_extensions(capabilities_hash[:extensions]) if capabilities_hash.key?(:extensions)
|
|
17
19
|
support_logging if capabilities_hash.key?(:logging)
|
|
18
20
|
|
|
19
21
|
if capabilities_hash.key?(:prompts)
|
|
@@ -45,6 +47,17 @@ module MCP
|
|
|
45
47
|
@experimental = config || {}
|
|
46
48
|
end
|
|
47
49
|
|
|
50
|
+
# Declares support for capability extensions per SEP-2133. Keys are
|
|
51
|
+
# extension identifiers using the reverse-DNS prefix convention
|
|
52
|
+
# (e.g. `"io.modelcontextprotocol/tasks"`, `"com.example/feature"`);
|
|
53
|
+
# values are arbitrary extension-defined configuration objects,
|
|
54
|
+
# with an empty hash meaning "supported with no settings".
|
|
55
|
+
# Repeated calls merge, so several extensions can be declared independently.
|
|
56
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
|
|
57
|
+
def support_extensions(extensions = {})
|
|
58
|
+
@extensions = (@extensions || {}).merge(extensions || {})
|
|
59
|
+
end
|
|
60
|
+
|
|
48
61
|
def support_logging
|
|
49
62
|
@logging ||= {}
|
|
50
63
|
end
|
|
@@ -85,6 +98,7 @@ module MCP
|
|
|
85
98
|
{
|
|
86
99
|
completions: @completions,
|
|
87
100
|
experimental: @experimental,
|
|
101
|
+
extensions: @extensions,
|
|
88
102
|
logging: @logging,
|
|
89
103
|
prompts: @prompts,
|
|
90
104
|
resources: @resources,
|
|
@@ -85,8 +85,10 @@ module MCP
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def send_notification(method, params = nil, session_id: nil, related_request_id: nil)
|
|
88
|
-
# Stateless mode
|
|
89
|
-
|
|
88
|
+
# Stateless mode has no streams to deliver notifications on. Report non-delivery instead of raising
|
|
89
|
+
# so the ephemeral per-request session's notify_* helpers (e.g. progress or log notifications from
|
|
90
|
+
# a tool handler) degrade gracefully rather than spamming the exception reporter on every call.
|
|
91
|
+
return false if @stateless
|
|
90
92
|
|
|
91
93
|
notification = {
|
|
92
94
|
jsonrpc: "2.0",
|
|
@@ -389,7 +391,11 @@ module MCP
|
|
|
389
391
|
end
|
|
390
392
|
rescue StandardError => e
|
|
391
393
|
MCP.configuration.exception_reporter.call(e, { request: body_string })
|
|
392
|
-
|
|
394
|
+
json_rpc_error_response(
|
|
395
|
+
status: 500,
|
|
396
|
+
code: JsonRpcHandler::ErrorCode::INTERNAL_ERROR,
|
|
397
|
+
message: "Internal server error",
|
|
398
|
+
)
|
|
393
399
|
end
|
|
394
400
|
|
|
395
401
|
def handle_get(request)
|
|
@@ -513,19 +519,19 @@ module MCP
|
|
|
513
519
|
media_type = content_type&.split(";")&.first&.strip&.downcase
|
|
514
520
|
return if media_type == "application/json"
|
|
515
521
|
|
|
516
|
-
|
|
517
|
-
415,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
json_rpc_error_response(
|
|
523
|
+
status: 415,
|
|
524
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
525
|
+
message: "Unsupported Media Type: Content-Type must be application/json",
|
|
526
|
+
)
|
|
521
527
|
end
|
|
522
528
|
|
|
523
529
|
def not_acceptable_response(required_types)
|
|
524
|
-
|
|
525
|
-
406,
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
530
|
+
json_rpc_error_response(
|
|
531
|
+
status: 406,
|
|
532
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
533
|
+
message: "Not Acceptable: Accept header must include #{required_types.join(" and ")}",
|
|
534
|
+
)
|
|
529
535
|
end
|
|
530
536
|
|
|
531
537
|
def parse_request_body(body_string)
|
|
@@ -535,7 +541,11 @@ module MCP
|
|
|
535
541
|
end
|
|
536
542
|
|
|
537
543
|
def invalid_json_response
|
|
538
|
-
|
|
544
|
+
json_rpc_error_response(
|
|
545
|
+
status: 400,
|
|
546
|
+
code: JsonRpcHandler::ErrorCode::PARSE_ERROR,
|
|
547
|
+
message: "Parse error: Invalid JSON",
|
|
548
|
+
)
|
|
539
549
|
end
|
|
540
550
|
|
|
541
551
|
def initialize_request?(body)
|
|
@@ -543,20 +553,20 @@ module MCP
|
|
|
543
553
|
end
|
|
544
554
|
|
|
545
555
|
def validate_protocol_version_header(request)
|
|
546
|
-
header_value = request.env["HTTP_MCP_PROTOCOL_VERSION"]
|
|
547
|
-
return if header_value.nil?
|
|
556
|
+
header_value = request.env["HTTP_MCP_PROTOCOL_VERSION"] || MCP::Configuration::DEFAULT_NEGOTIATED_PROTOCOL_VERSION
|
|
548
557
|
return if MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(header_value)
|
|
549
558
|
|
|
550
559
|
supported = MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.join(", ")
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
+
json_rpc_error_response(
|
|
561
|
+
status: 400,
|
|
562
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
563
|
+
message: "Bad Request: Unsupported protocol version: #{header_value}. Supported versions: #{supported}",
|
|
564
|
+
)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def json_rpc_error_response(status:, code:, message:)
|
|
568
|
+
body = { jsonrpc: "2.0", id: nil, error: { code: code, message: message } }
|
|
569
|
+
[status, { "Content-Type" => "application/json" }, [body.to_json]]
|
|
560
570
|
end
|
|
561
571
|
|
|
562
572
|
def notification?(body)
|
|
@@ -567,7 +577,9 @@ module MCP
|
|
|
567
577
|
# `notifications/initialized`) through the server so it can update session state.
|
|
568
578
|
def dispatch_notification(body_string, session_id)
|
|
569
579
|
server_session = nil
|
|
570
|
-
if
|
|
580
|
+
if @stateless
|
|
581
|
+
server_session = ephemeral_session
|
|
582
|
+
elsif session_id
|
|
571
583
|
@mutex.synchronize do
|
|
572
584
|
session = @sessions[session_id]
|
|
573
585
|
server_session = session[:server_session] if session
|
|
@@ -603,9 +615,10 @@ module MCP
|
|
|
603
615
|
|
|
604
616
|
def handle_initialization(body_string, body)
|
|
605
617
|
session_id = nil
|
|
606
|
-
server_session = nil
|
|
607
618
|
|
|
608
|
-
|
|
619
|
+
if @stateless
|
|
620
|
+
server_session = ephemeral_session
|
|
621
|
+
else
|
|
609
622
|
session_id = SecureRandom.uuid
|
|
610
623
|
server_session = ServerSession.new(server: @server, transport: self, session_id: session_id)
|
|
611
624
|
|
|
@@ -618,17 +631,13 @@ module MCP
|
|
|
618
631
|
end
|
|
619
632
|
end
|
|
620
633
|
|
|
621
|
-
response =
|
|
622
|
-
server_session.handle_json(body_string)
|
|
623
|
-
else
|
|
624
|
-
@server.handle_json(body_string)
|
|
625
|
-
end
|
|
634
|
+
response = server_session.handle_json(body_string)
|
|
626
635
|
|
|
627
636
|
# If `Server#init` produced an error response (e.g., malformed JSON-RPC envelope),
|
|
628
637
|
# `mark_initialized!` was never called. Discard the orphaned session and omit
|
|
629
638
|
# the `Mcp-Session-Id` header so the client retries from a clean state instead of
|
|
630
639
|
# reusing a never-initialized ID that would later look like a duplicate `initialize`.
|
|
631
|
-
if
|
|
640
|
+
if session_id && !server_session.initialized?
|
|
632
641
|
cleanup_session(session_id)
|
|
633
642
|
session_id = nil
|
|
634
643
|
end
|
|
@@ -649,15 +658,15 @@ module MCP
|
|
|
649
658
|
def handle_regular_request(body_string, session_id, related_request_id: nil)
|
|
650
659
|
server_session = nil
|
|
651
660
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
661
|
+
if @stateless
|
|
662
|
+
server_session = ephemeral_session
|
|
663
|
+
elsif session_id
|
|
664
|
+
error_response = validate_and_touch_session(session_id)
|
|
665
|
+
return error_response if error_response
|
|
656
666
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
end
|
|
667
|
+
@mutex.synchronize do
|
|
668
|
+
session = @sessions[session_id]
|
|
669
|
+
server_session = session[:server_session] if session
|
|
661
670
|
end
|
|
662
671
|
end
|
|
663
672
|
|
|
@@ -767,6 +776,13 @@ module MCP
|
|
|
767
776
|
@mutex.synchronize { @sessions.key?(session_id) }
|
|
768
777
|
end
|
|
769
778
|
|
|
779
|
+
# Each stateless POST is self-contained (SEP-2567): handlers run against an ephemeral per-request `ServerSession`
|
|
780
|
+
# so client info, logging level, and initialized state never leak onto the shared `Server` instance or across concurrent requests.
|
|
781
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567
|
|
782
|
+
def ephemeral_session
|
|
783
|
+
ServerSession.new(server: @server, transport: self, session_id: nil)
|
|
784
|
+
end
|
|
785
|
+
|
|
770
786
|
# Returns true iff a session exists and is not past its idle timeout. Expired sessions
|
|
771
787
|
# are evicted as a side effect so a live request never observes a zombie session that
|
|
772
788
|
# the reaper hasn't yet pruned. Does NOT update `last_active_at`; callers that are
|
|
@@ -793,15 +809,27 @@ module MCP
|
|
|
793
809
|
end
|
|
794
810
|
|
|
795
811
|
def method_not_allowed_response
|
|
796
|
-
|
|
812
|
+
json_rpc_error_response(
|
|
813
|
+
status: 405,
|
|
814
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
815
|
+
message: "Method not allowed",
|
|
816
|
+
)
|
|
797
817
|
end
|
|
798
818
|
|
|
799
819
|
def missing_session_id_response
|
|
800
|
-
|
|
820
|
+
json_rpc_error_response(
|
|
821
|
+
status: 400,
|
|
822
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
823
|
+
message: "Missing session ID",
|
|
824
|
+
)
|
|
801
825
|
end
|
|
802
826
|
|
|
803
827
|
def session_not_found_response
|
|
804
|
-
|
|
828
|
+
json_rpc_error_response(
|
|
829
|
+
status: 404,
|
|
830
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
831
|
+
message: "Session not found",
|
|
832
|
+
)
|
|
805
833
|
end
|
|
806
834
|
|
|
807
835
|
def already_initialized_response(request_id)
|
|
@@ -821,11 +849,11 @@ module MCP
|
|
|
821
849
|
end
|
|
822
850
|
|
|
823
851
|
def session_already_connected_response
|
|
824
|
-
|
|
825
|
-
409,
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
852
|
+
json_rpc_error_response(
|
|
853
|
+
status: 409,
|
|
854
|
+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
|
|
855
|
+
message: "Conflict: Only one SSE stream is allowed per session",
|
|
856
|
+
)
|
|
829
857
|
end
|
|
830
858
|
|
|
831
859
|
def setup_sse_stream(session_id)
|
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
|
-
|
|
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
|
|
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)
|
data/lib/mcp/tool/schema.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
|
-
require "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
96
|
-
#
|
|
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
data/lib/mcp.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.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:
|
|
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
|
|
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
|
|
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.
|
|
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
|