ruby-mcp-client 2.0.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 +914 -49
- 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 +1367 -311
- data/lib/mcp_client/auth/peer_text.rb +174 -0
- data/lib/mcp_client/auth.rb +298 -36
- 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 +851 -478
- 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/elicitation_validator.rb +56 -13
- data/lib/mcp_client/errors.rb +481 -6
- 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 +669 -100
- 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 +967 -11
- 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 -2
- data/lib/mcp_client/resource_content.rb +20 -0
- data/lib/mcp_client/resource_link.rb +0 -2
- data/lib/mcp_client/resource_template.rb +4 -2
- 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 +893 -175
- data/lib/mcp_client/server_base.rb +233 -5
- data/lib/mcp_client/server_factory.rb +12 -3
- data/lib/mcp_client/server_http/json_rpc_transport.rb +220 -5
- data/lib/mcp_client/server_http.rb +307 -90
- data/lib/mcp_client/server_sse/json_rpc_transport.rb +146 -30
- data/lib/mcp_client/server_sse/origin_policy.rb +57 -0
- data/lib/mcp_client/server_sse/reconnect_monitor.rb +7 -1
- data/lib/mcp_client/server_sse/sse_parser.rb +77 -13
- data/lib/mcp_client/server_sse.rb +310 -76
- data/lib/mcp_client/server_stdio/child_session.rb +98 -0
- data/lib/mcp_client/server_stdio/json_rpc_transport.rb +1004 -29
- data/lib/mcp_client/server_stdio.rb +778 -186
- data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +262 -28
- data/lib/mcp_client/server_streamable_http.rb +547 -157
- 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 -5
- data/lib/mcp_client/version.rb +21 -6
- data/lib/mcp_client.rb +115 -19
- metadata +71 -7
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MCPClient
|
|
4
|
+
class ServerStdio
|
|
5
|
+
# The record of one server child process: when the open subscriptions were
|
|
6
|
+
# handed to it, and when its handles were torn down. One record per
|
|
7
|
+
# process, written only by that process's own lifecycle, so nothing
|
|
8
|
+
# another thread does to another process can change what this one says.
|
|
9
|
+
#
|
|
10
|
+
# It exists for the crash-loop bound. That bound used to live in flags and
|
|
11
|
+
# a timestamp on the transport, which two restarts (or a restart and a host
|
|
12
|
+
# request that re-established the process first) raced over: whichever
|
|
13
|
+
# finished last decided what the *other* process's uptime had been, and a
|
|
14
|
+
# server that exited on sight could be respawned for ever. The question a
|
|
15
|
+
# restart actually has to answer is about one particular process — "did the
|
|
16
|
+
# last process we gave these subscriptions to die straight after we gave
|
|
17
|
+
# them to it?" — and both facts it needs are recorded here, on that
|
|
18
|
+
# process, at the two moments they happen.
|
|
19
|
+
class ChildSession
|
|
20
|
+
# @return [Float, nil] monotonic time the open subscriptions were re-sent
|
|
21
|
+
# to this process, nil if it was never asked to carry any
|
|
22
|
+
attr_reader :carried_at
|
|
23
|
+
# @return [Float, nil] monotonic time this process's handles were torn
|
|
24
|
+
# down, nil while it is still the live session
|
|
25
|
+
attr_reader :ended_at
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@carried_at = nil
|
|
29
|
+
@ended_at = nil
|
|
30
|
+
@exited_unexpectedly = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Boolean] whether this process went on its own rather than
|
|
34
|
+
# being torn down by the client
|
|
35
|
+
def exited_unexpectedly?
|
|
36
|
+
@exited_unexpectedly
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Float] monotonic seconds
|
|
40
|
+
def self.now
|
|
41
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# This process has been handed the subscriptions a previous process left
|
|
45
|
+
# open. Stamped before they go out, not after: the process can exit while
|
|
46
|
+
# they are still being written, and an exit under the re-send is the
|
|
47
|
+
# clearest crash loop there is.
|
|
48
|
+
# @return [void]
|
|
49
|
+
def carrying_subscriptions
|
|
50
|
+
@carried_at = ChildSession.now
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# This process is gone (its stdio handles have been torn down). First
|
|
54
|
+
# stamp wins: a host `cleanup` racing the reader's own teardown must not
|
|
55
|
+
# move the moment the process ended.
|
|
56
|
+
# @return [void]
|
|
57
|
+
def ended
|
|
58
|
+
return if @ended_at
|
|
59
|
+
|
|
60
|
+
@ended_at = ChildSession.now
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# This process exited on its own — the reader saw EOF on a stdin the
|
|
64
|
+
# client had not closed (see
|
|
65
|
+
# {MCPClient::ServerStdio#handle_server_exit}). Recorded separately from
|
|
66
|
+
# {#ended}, which every teardown stamps, because only an exit is a crash.
|
|
67
|
+
# @return [void]
|
|
68
|
+
def exited_unexpectedly
|
|
69
|
+
@exited_unexpectedly = true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Whether this process died too soon after being given the subscriptions
|
|
73
|
+
# to be given them again — the crash loop the restart bound exists to
|
|
74
|
+
# stop. A process that was never given any is not part of that loop, and
|
|
75
|
+
# one that is still alive has not ended anything.
|
|
76
|
+
#
|
|
77
|
+
# Neither is a process the client itself shut down. A host that closes
|
|
78
|
+
# the transport and reconnects — as a `cleanup`/request cycle does, and
|
|
79
|
+
# as re-authenticating or re-configuring a server does — tears the
|
|
80
|
+
# process down whenever it likes, and reading that as the server crashing
|
|
81
|
+
# closed the very subscriptions the reconnect exists to carry across.
|
|
82
|
+
# Only an exit the reader actually watched happen counts.
|
|
83
|
+
#
|
|
84
|
+
# The interval is measured from the moment it received them, so a server
|
|
85
|
+
# that is slow to start is credited with none of its own handshake, and a
|
|
86
|
+
# process that was already gone when they were sent to it (its exit
|
|
87
|
+
# stamped before the re-send) counts as having survived no time at all.
|
|
88
|
+
# @param min_uptime [Numeric] seconds it had to last (see
|
|
89
|
+
# {MCPClient::ServerStdio::SUBSCRIPTION_RESTART_MIN_INTERVAL})
|
|
90
|
+
# @return [Boolean]
|
|
91
|
+
def died_carrying_subscriptions?(min_uptime)
|
|
92
|
+
return false unless @exited_unexpectedly && @carried_at && @ended_at
|
|
93
|
+
|
|
94
|
+
(@ended_at - @carried_at) < min_uptime
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|