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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/OAUTH.md +555 -0
  3. data/README.md +825 -48
  4. data/lib/mcp_client/audio_content.rb +1 -1
  5. data/lib/mcp_client/auth/browser_oauth.rb +131 -21
  6. data/lib/mcp_client/auth/oauth_provider/challenge_handling.rb +532 -0
  7. data/lib/mcp_client/auth/oauth_provider/client_authentication.rb +121 -0
  8. data/lib/mcp_client/auth/oauth_provider/pending_requests.rb +51 -0
  9. data/lib/mcp_client/auth/oauth_provider/registration_store.rb +486 -0
  10. data/lib/mcp_client/auth/oauth_provider/response_validation.rb +441 -0
  11. data/lib/mcp_client/auth/oauth_provider/scope_selection.rb +134 -0
  12. data/lib/mcp_client/auth/oauth_provider/token_store.rb +419 -0
  13. data/lib/mcp_client/auth/oauth_provider.rb +1354 -386
  14. data/lib/mcp_client/auth/peer_text.rb +174 -0
  15. data/lib/mcp_client/auth.rb +298 -32
  16. data/lib/mcp_client/cached_result.rb +145 -0
  17. data/lib/mcp_client/called_tool_definition.rb +138 -0
  18. data/lib/mcp_client/client/cache_slices.rb +195 -0
  19. data/lib/mcp_client/client/list_aggregation.rb +243 -0
  20. data/lib/mcp_client/client/notification_routing.rb +155 -0
  21. data/lib/mcp_client/client/sampling_validation.rb +200 -0
  22. data/lib/mcp_client/client/task_api.rb +531 -0
  23. data/lib/mcp_client/client/task_lifetimes.rb +269 -0
  24. data/lib/mcp_client/client/task_registry.rb +254 -0
  25. data/lib/mcp_client/client/task_shape.rb +102 -0
  26. data/lib/mcp_client/client/task_support.rb +1166 -0
  27. data/lib/mcp_client/client/task_updates.rb +457 -0
  28. data/lib/mcp_client/client/task_wait_boundaries.rb +198 -0
  29. data/lib/mcp_client/client/task_workers.rb +63 -0
  30. data/lib/mcp_client/client.rb +796 -518
  31. data/lib/mcp_client/deep_copy.rb +49 -0
  32. data/lib/mcp_client/deprecation_notices.rb +94 -0
  33. data/lib/mcp_client/deprecations.rb +419 -0
  34. data/lib/mcp_client/errors.rb +474 -7
  35. data/lib/mcp_client/header_params.rb +320 -0
  36. data/lib/mcp_client/http_transport_base/bounded_inflate.rb +41 -0
  37. data/lib/mcp_client/http_transport_base/cache_support.rb +694 -0
  38. data/lib/mcp_client/http_transport_base/era_detection.rb +134 -0
  39. data/lib/mcp_client/http_transport_base/listen_stream.rb +763 -0
  40. data/lib/mcp_client/http_transport_base/param_headers.rb +35 -0
  41. data/lib/mcp_client/http_transport_base/request_recovery.rb +156 -0
  42. data/lib/mcp_client/http_transport_base/session_recovery.rb +113 -0
  43. data/lib/mcp_client/http_transport_base/sse_event_scanner.rb +145 -0
  44. data/lib/mcp_client/http_transport_base/stream_capture.rb +160 -0
  45. data/lib/mcp_client/http_transport_base/stream_recovery.rb +318 -0
  46. data/lib/mcp_client/http_transport_base/tool_listing.rb +277 -0
  47. data/lib/mcp_client/http_transport_base.rb +666 -120
  48. data/lib/mcp_client/input_round_trips.rb +128 -0
  49. data/lib/mcp_client/json_rpc_common/envelopes.rb +32 -0
  50. data/lib/mcp_client/json_rpc_common/error_bodies.rb +105 -0
  51. data/lib/mcp_client/json_rpc_common/input_waits.rb +167 -0
  52. data/lib/mcp_client/json_rpc_common.rb +900 -13
  53. data/lib/mcp_client/oauth_client.rb +14 -5
  54. data/lib/mcp_client/prompt.rb +4 -0
  55. data/lib/mcp_client/request_authorization.rb +128 -0
  56. data/lib/mcp_client/request_meta_scope.rb +77 -0
  57. data/lib/mcp_client/request_metadata.rb +287 -0
  58. data/lib/mcp_client/resource.rb +4 -0
  59. data/lib/mcp_client/resource_content.rb +20 -0
  60. data/lib/mcp_client/resource_template.rb +4 -0
  61. data/lib/mcp_client/result_caching.rb +999 -0
  62. data/lib/mcp_client/result_completeness.rb +34 -0
  63. data/lib/mcp_client/root.rb +6 -0
  64. data/lib/mcp_client/round_trip_marker.rb +28 -0
  65. data/lib/mcp_client/schema_validator/annotations.rb +82 -0
  66. data/lib/mcp_client/schema_validator/composition.rb +86 -0
  67. data/lib/mcp_client/schema_validator/dialects.rb +66 -0
  68. data/lib/mcp_client/schema_validator/ecma_patterns.rb +567 -0
  69. data/lib/mcp_client/schema_validator/evaluation.rb +517 -0
  70. data/lib/mcp_client/schema_validator/input_requirements.rb +84 -0
  71. data/lib/mcp_client/schema_validator/instances.rb +449 -0
  72. data/lib/mcp_client/schema_validator/keyword_scan.rb +121 -0
  73. data/lib/mcp_client/schema_validator/normalization.rb +104 -0
  74. data/lib/mcp_client/schema_validator/references.rb +610 -0
  75. data/lib/mcp_client/schema_validator/scalars.rb +126 -0
  76. data/lib/mcp_client/schema_validator/shapes.rb +319 -0
  77. data/lib/mcp_client/schema_validator/uri_references.rb +153 -0
  78. data/lib/mcp_client/schema_validator.rb +882 -208
  79. data/lib/mcp_client/server_base.rb +233 -5
  80. data/lib/mcp_client/server_factory.rb +9 -3
  81. data/lib/mcp_client/server_http/json_rpc_transport.rb +219 -4
  82. data/lib/mcp_client/server_http.rb +307 -90
  83. data/lib/mcp_client/server_sse/json_rpc_transport.rb +113 -25
  84. data/lib/mcp_client/server_sse/sse_parser.rb +39 -6
  85. data/lib/mcp_client/server_sse.rb +227 -62
  86. data/lib/mcp_client/server_stdio/child_session.rb +98 -0
  87. data/lib/mcp_client/server_stdio/json_rpc_transport.rb +1003 -28
  88. data/lib/mcp_client/server_stdio.rb +772 -183
  89. data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +189 -25
  90. data/lib/mcp_client/server_streamable_http.rb +302 -115
  91. data/lib/mcp_client/session_pin.rb +119 -0
  92. data/lib/mcp_client/subscription/notification_dispatcher.rb +354 -0
  93. data/lib/mcp_client/subscription.rb +852 -0
  94. data/lib/mcp_client/subscription_support.rb +715 -0
  95. data/lib/mcp_client/task.rb +286 -14
  96. data/lib/mcp_client/tool.rb +31 -3
  97. data/lib/mcp_client/version.rb +21 -6
  98. data/lib/mcp_client.rb +108 -19
  99. 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