ruby-mcp-client 2.1.0 → 3.0.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/OAUTH.md +555 -0
- data/README.md +825 -48
- data/lib/mcp_client/audio_content.rb +1 -1
- data/lib/mcp_client/auth/browser_oauth.rb +131 -21
- data/lib/mcp_client/auth/oauth_provider/challenge_handling.rb +532 -0
- data/lib/mcp_client/auth/oauth_provider/client_authentication.rb +121 -0
- data/lib/mcp_client/auth/oauth_provider/pending_requests.rb +51 -0
- data/lib/mcp_client/auth/oauth_provider/registration_store.rb +486 -0
- data/lib/mcp_client/auth/oauth_provider/response_validation.rb +441 -0
- data/lib/mcp_client/auth/oauth_provider/scope_selection.rb +134 -0
- data/lib/mcp_client/auth/oauth_provider/token_store.rb +419 -0
- data/lib/mcp_client/auth/oauth_provider.rb +1354 -386
- data/lib/mcp_client/auth/peer_text.rb +174 -0
- data/lib/mcp_client/auth.rb +298 -32
- data/lib/mcp_client/cached_result.rb +145 -0
- data/lib/mcp_client/called_tool_definition.rb +138 -0
- data/lib/mcp_client/client/cache_slices.rb +195 -0
- data/lib/mcp_client/client/list_aggregation.rb +243 -0
- data/lib/mcp_client/client/notification_routing.rb +155 -0
- data/lib/mcp_client/client/sampling_validation.rb +200 -0
- data/lib/mcp_client/client/task_api.rb +531 -0
- data/lib/mcp_client/client/task_lifetimes.rb +269 -0
- data/lib/mcp_client/client/task_registry.rb +254 -0
- data/lib/mcp_client/client/task_shape.rb +102 -0
- data/lib/mcp_client/client/task_support.rb +1166 -0
- data/lib/mcp_client/client/task_updates.rb +457 -0
- data/lib/mcp_client/client/task_wait_boundaries.rb +198 -0
- data/lib/mcp_client/client/task_workers.rb +63 -0
- data/lib/mcp_client/client.rb +796 -518
- data/lib/mcp_client/deep_copy.rb +49 -0
- data/lib/mcp_client/deprecation_notices.rb +94 -0
- data/lib/mcp_client/deprecations.rb +419 -0
- data/lib/mcp_client/errors.rb +474 -7
- data/lib/mcp_client/header_params.rb +320 -0
- data/lib/mcp_client/http_transport_base/bounded_inflate.rb +41 -0
- data/lib/mcp_client/http_transport_base/cache_support.rb +694 -0
- data/lib/mcp_client/http_transport_base/era_detection.rb +134 -0
- data/lib/mcp_client/http_transport_base/listen_stream.rb +763 -0
- data/lib/mcp_client/http_transport_base/param_headers.rb +35 -0
- data/lib/mcp_client/http_transport_base/request_recovery.rb +156 -0
- data/lib/mcp_client/http_transport_base/session_recovery.rb +113 -0
- data/lib/mcp_client/http_transport_base/sse_event_scanner.rb +145 -0
- data/lib/mcp_client/http_transport_base/stream_capture.rb +160 -0
- data/lib/mcp_client/http_transport_base/stream_recovery.rb +318 -0
- data/lib/mcp_client/http_transport_base/tool_listing.rb +277 -0
- data/lib/mcp_client/http_transport_base.rb +666 -120
- data/lib/mcp_client/input_round_trips.rb +128 -0
- data/lib/mcp_client/json_rpc_common/envelopes.rb +32 -0
- data/lib/mcp_client/json_rpc_common/error_bodies.rb +105 -0
- data/lib/mcp_client/json_rpc_common/input_waits.rb +167 -0
- data/lib/mcp_client/json_rpc_common.rb +900 -13
- data/lib/mcp_client/oauth_client.rb +14 -5
- data/lib/mcp_client/prompt.rb +4 -0
- data/lib/mcp_client/request_authorization.rb +128 -0
- data/lib/mcp_client/request_meta_scope.rb +77 -0
- data/lib/mcp_client/request_metadata.rb +287 -0
- data/lib/mcp_client/resource.rb +4 -0
- data/lib/mcp_client/resource_content.rb +20 -0
- data/lib/mcp_client/resource_template.rb +4 -0
- data/lib/mcp_client/result_caching.rb +999 -0
- data/lib/mcp_client/result_completeness.rb +34 -0
- data/lib/mcp_client/root.rb +6 -0
- data/lib/mcp_client/round_trip_marker.rb +28 -0
- data/lib/mcp_client/schema_validator/annotations.rb +82 -0
- data/lib/mcp_client/schema_validator/composition.rb +86 -0
- data/lib/mcp_client/schema_validator/dialects.rb +66 -0
- data/lib/mcp_client/schema_validator/ecma_patterns.rb +567 -0
- data/lib/mcp_client/schema_validator/evaluation.rb +517 -0
- data/lib/mcp_client/schema_validator/input_requirements.rb +84 -0
- data/lib/mcp_client/schema_validator/instances.rb +449 -0
- data/lib/mcp_client/schema_validator/keyword_scan.rb +121 -0
- data/lib/mcp_client/schema_validator/normalization.rb +104 -0
- data/lib/mcp_client/schema_validator/references.rb +610 -0
- data/lib/mcp_client/schema_validator/scalars.rb +126 -0
- data/lib/mcp_client/schema_validator/shapes.rb +319 -0
- data/lib/mcp_client/schema_validator/uri_references.rb +153 -0
- data/lib/mcp_client/schema_validator.rb +882 -208
- data/lib/mcp_client/server_base.rb +233 -5
- data/lib/mcp_client/server_factory.rb +9 -3
- data/lib/mcp_client/server_http/json_rpc_transport.rb +219 -4
- data/lib/mcp_client/server_http.rb +307 -90
- data/lib/mcp_client/server_sse/json_rpc_transport.rb +113 -25
- data/lib/mcp_client/server_sse/sse_parser.rb +39 -6
- data/lib/mcp_client/server_sse.rb +227 -62
- data/lib/mcp_client/server_stdio/child_session.rb +98 -0
- data/lib/mcp_client/server_stdio/json_rpc_transport.rb +1003 -28
- data/lib/mcp_client/server_stdio.rb +772 -183
- data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +189 -25
- data/lib/mcp_client/server_streamable_http.rb +302 -115
- data/lib/mcp_client/session_pin.rb +119 -0
- data/lib/mcp_client/subscription/notification_dispatcher.rb +354 -0
- data/lib/mcp_client/subscription.rb +852 -0
- data/lib/mcp_client/subscription_support.rb +715 -0
- data/lib/mcp_client/task.rb +286 -14
- data/lib/mcp_client/tool.rb +31 -3
- data/lib/mcp_client/version.rb +21 -6
- data/lib/mcp_client.rb +108 -19
- metadata +68 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MCPClient
|
|
4
|
+
module HttpTransportBase
|
|
5
|
+
# How a probe failure is read as an era verdict: a modern server's
|
|
6
|
+
# rejection of server/discover, a legacy server's ignorance of it, or an
|
|
7
|
+
# exchange that never completed and therefore says nothing. Mixed into
|
|
8
|
+
# {MCPClient::HttpTransportBase}; every method is private there.
|
|
9
|
+
module EraDetection
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Decide what a failed server/discover probe says about the server's era,
|
|
13
|
+
# recording the verdict it settles.
|
|
14
|
+
# @param error [MCPClient::Errors::MCPError] the probe failure
|
|
15
|
+
# @param modern_confirmed [Boolean] whether the era was already settled as modern
|
|
16
|
+
# @return [Boolean] true when the server is modern despite the failure
|
|
17
|
+
# @raise [MCPClient::Errors::MCPError] when the failure settles nothing or the server is modern
|
|
18
|
+
def modern_despite_probe_failure?(error, modern_confirmed)
|
|
19
|
+
raise modern_probe_failure(error) if modern_probe_rejection?(error)
|
|
20
|
+
|
|
21
|
+
# A 404 with -32601 is a complete answer rather than a failed exchange:
|
|
22
|
+
# the server is modern and simply has no discovery support. It answers
|
|
23
|
+
# that way on every connect, so a reconnect must tolerate it exactly as
|
|
24
|
+
# the first connect did — checked before the cached modern verdict,
|
|
25
|
+
# which would otherwise turn the second identical answer into a failure.
|
|
26
|
+
if unknown_method_404?(error)
|
|
27
|
+
accept_modern_server_without_discover(error)
|
|
28
|
+
return true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if error.era_inconclusive?
|
|
32
|
+
# The exchange never completed (broken response stream, timeout, 5xx):
|
|
33
|
+
# nothing was learned, so no verdict is recorded — a cached modern
|
|
34
|
+
# verdict stays, and still rules initialize out — and the caller sees
|
|
35
|
+
# the transport failure as itself. Only a genuine rejection means
|
|
36
|
+
# legacy, or "modern but incompatible" once the server is known modern.
|
|
37
|
+
@protocol_version = nil
|
|
38
|
+
raise error
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
raise modern_probe_failure(error) if modern_confirmed
|
|
42
|
+
|
|
43
|
+
treat_probe_failure_as_legacy(error)
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Whether a probe failure is a modern server's rejection of the probe.
|
|
48
|
+
# Streamable HTTP "Backward Compatibility" recognizes the reserved
|
|
49
|
+
# errors in a **400** response; the same JSON-RPC error under 200 (a
|
|
50
|
+
# permissive legacy endpoint echoing an error object) or any other 4xx
|
|
51
|
+
# says nothing modern. The typed error is still raised for ordinary
|
|
52
|
+
# requests whatever the status — only the era verdict is status-gated.
|
|
53
|
+
# @param error [MCPClient::Errors::MCPError] the probe failure
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
def modern_probe_rejection?(error)
|
|
56
|
+
error.respond_to?(:http_status) && error.http_status == 400 && error.modern_protocol_error_for_probe?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# A modern server reports an unknown method as HTTP 404 with -32601;
|
|
60
|
+
# server/discover is mandatory, so this is a non-conforming modern server.
|
|
61
|
+
# @param error [MCPClient::Errors::MCPError] the probe failure
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def unknown_method_404?(error)
|
|
64
|
+
# Only a well-formed error object counts (from_jsonrpc types the -32601
|
|
65
|
+
# only when it carries a string message): a malformed one identifies
|
|
66
|
+
# nothing, and must not be cached as a modern verdict either.
|
|
67
|
+
error.is_a?(MCPClient::Errors::MethodNotFoundError) && error.modern_http_protocol_error?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# After UnsupportedProtocolVersionError, pick a mutually supported version
|
|
71
|
+
# from the error's advertised list and re-issue the probe.
|
|
72
|
+
# @param error [MCPClient::Errors::UnsupportedProtocolVersionError]
|
|
73
|
+
# @return [void]
|
|
74
|
+
def retry_discover_with_advertised_version(error)
|
|
75
|
+
version = select_protocol_version(error.supported)
|
|
76
|
+
unless version
|
|
77
|
+
# The rejection was well-formed, so the server is modern: the typed
|
|
78
|
+
# error stops MCPClient.connect from trying the legacy transports.
|
|
79
|
+
raise MCPClient::Errors::ModernServerError,
|
|
80
|
+
"Server rejected protocol version #{@protocol_version} and supports only " \
|
|
81
|
+
"#{error.supported.join(', ')}, none of which this client speaks"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@logger.info("Server does not support #{@protocol_version}; retrying server/discover with #{version}")
|
|
85
|
+
@protocol_version = version
|
|
86
|
+
perform_discover
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The server is modern but the connection cannot be completed. The era is
|
|
90
|
+
# cached so a later connect never falls back to initialize, and the typed
|
|
91
|
+
# error survives MCPClient's transport detector instead of sending it on
|
|
92
|
+
# to the legacy SSE transport.
|
|
93
|
+
# @param error [StandardError] a modern-era probe failure
|
|
94
|
+
# @return [MCPClient::Errors::ModernServerError]
|
|
95
|
+
def modern_probe_failure(error)
|
|
96
|
+
@protocol_version = nil
|
|
97
|
+
@confirmed_era = :modern
|
|
98
|
+
# A version rejection names what the server would accept.
|
|
99
|
+
suffix = error.respond_to?(:supported_suffix) ? error.supported_suffix : ''
|
|
100
|
+
MCPClient::Errors::ModernServerError.new("Server is modern but incompatible: #{error.message}#{suffix}")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# A 404 with -32601 is how a modern server reports an unknown method;
|
|
104
|
+
# server/discover is mandatory, so this is a non-conforming modern
|
|
105
|
+
# server. Continue with the requested version and no known capabilities.
|
|
106
|
+
# @param error [MCPClient::Errors::ServerError] the -32601 error
|
|
107
|
+
# @return [void]
|
|
108
|
+
def accept_modern_server_without_discover(error)
|
|
109
|
+
@logger.warn("Server answered server/discover with 404 -32601 (#{error.message}); treating it as a " \
|
|
110
|
+
'modern MCP server without discovery support (capabilities unknown)')
|
|
111
|
+
@supported_versions = [@protocol_version]
|
|
112
|
+
@capabilities = {}
|
|
113
|
+
@last_discover_result = nil
|
|
114
|
+
@confirmed_era = :modern
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Record that the server is legacy (the era is cached for this transport).
|
|
118
|
+
# @param error [StandardError] the non-modern probe failure
|
|
119
|
+
# @return [void]
|
|
120
|
+
# @raise [MCPClient::Errors::ConnectionError] when protocol: :modern is configured
|
|
121
|
+
def treat_probe_failure_as_legacy(error)
|
|
122
|
+
@protocol_version = nil
|
|
123
|
+
if @protocol_mode == :modern
|
|
124
|
+
raise MCPClient::Errors::ConnectionError,
|
|
125
|
+
"Server did not answer server/discover as a modern MCP server (#{error.message}); it is most likely " \
|
|
126
|
+
'a legacy server expecting the initialize handshake. Use protocol: :auto or :legacy to allow that.'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
@logger.debug("server/discover probe failed (#{error.class}); treating the server as legacy")
|
|
130
|
+
@confirmed_era = :legacy
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|