mcp 0.10.0 → 0.15.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 +1384 -691
- data/lib/json_rpc_handler.rb +23 -10
- data/lib/mcp/cancellation.rb +72 -0
- data/lib/mcp/cancelled_error.rb +13 -0
- data/lib/mcp/client/http.rb +234 -15
- data/lib/mcp/client/paginated_result.rb +13 -0
- data/lib/mcp/client/stdio.rb +100 -49
- data/lib/mcp/client.rb +293 -66
- data/lib/mcp/configuration.rb +38 -2
- data/lib/mcp/content.rb +16 -12
- data/lib/mcp/instrumentation.rb +23 -2
- data/lib/mcp/methods.rb +4 -5
- data/lib/mcp/progress.rb +3 -1
- data/lib/mcp/prompt/result.rb +4 -3
- data/lib/mcp/resource/contents.rb +8 -7
- data/lib/mcp/resource.rb +4 -2
- data/lib/mcp/resource_template.rb +4 -2
- data/lib/mcp/server/pagination.rb +42 -0
- data/lib/mcp/server/transports/stdio_transport.rb +42 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +383 -88
- data/lib/mcp/server.rb +361 -50
- data/lib/mcp/server_context.rb +81 -2
- data/lib/mcp/server_session.rb +170 -12
- data/lib/mcp/tool/response.rb +4 -3
- data/lib/mcp/tool/schema.rb +1 -14
- data/lib/mcp/transport.rb +14 -0
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +2 -0
- metadata +11 -6
- data/lib/mcp/transports/stdio.rb +0 -15
data/lib/mcp/server_context.rb
CHANGED
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module MCP
|
|
4
4
|
class ServerContext
|
|
5
|
-
|
|
5
|
+
attr_reader :cancellation
|
|
6
|
+
|
|
7
|
+
def initialize(context, progress:, notification_target:, related_request_id: nil, cancellation: nil)
|
|
6
8
|
@context = context
|
|
7
9
|
@progress = progress
|
|
8
10
|
@notification_target = notification_target
|
|
11
|
+
@related_request_id = related_request_id
|
|
12
|
+
@cancellation = cancellation
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cancelled?
|
|
16
|
+
!!@cancellation&.cancelled?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def raise_if_cancelled!
|
|
20
|
+
@cancellation&.raise_if_cancelled!
|
|
9
21
|
end
|
|
10
22
|
|
|
11
23
|
# Reports progress for the current tool operation.
|
|
@@ -26,7 +38,74 @@ module MCP
|
|
|
26
38
|
def notify_log_message(data:, level:, logger: nil)
|
|
27
39
|
return unless @notification_target
|
|
28
40
|
|
|
29
|
-
@notification_target.notify_log_message(data: data, level: level, logger: logger)
|
|
41
|
+
@notification_target.notify_log_message(data: data, level: level, logger: logger, related_request_id: @related_request_id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sends a resource updated notification scoped to the originating session.
|
|
45
|
+
#
|
|
46
|
+
# @param uri [String] The URI of the updated resource.
|
|
47
|
+
def notify_resources_updated(uri:)
|
|
48
|
+
return unless @notification_target
|
|
49
|
+
|
|
50
|
+
@notification_target.notify_resources_updated(uri: uri)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Delegates to the session so the request is scoped to the originating client.
|
|
54
|
+
def list_roots
|
|
55
|
+
if @notification_target.respond_to?(:list_roots)
|
|
56
|
+
@notification_target.list_roots(related_request_id: @related_request_id)
|
|
57
|
+
else
|
|
58
|
+
raise NoMethodError, "undefined method 'list_roots' for #{self}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Delegates to the session so the request is scoped to the originating client.
|
|
63
|
+
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
64
|
+
# does not support sampling.
|
|
65
|
+
def create_sampling_message(**kwargs)
|
|
66
|
+
if @notification_target.respond_to?(:create_sampling_message)
|
|
67
|
+
@notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
|
68
|
+
elsif @context.respond_to?(:create_sampling_message)
|
|
69
|
+
@context.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
|
70
|
+
else
|
|
71
|
+
raise NoMethodError, "undefined method 'create_sampling_message' for #{self}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Delegates to the session so the request is scoped to the originating client.
|
|
76
|
+
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
77
|
+
# does not support elicitation.
|
|
78
|
+
def create_form_elicitation(**kwargs)
|
|
79
|
+
if @notification_target.respond_to?(:create_form_elicitation)
|
|
80
|
+
@notification_target.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
81
|
+
elsif @context.respond_to?(:create_form_elicitation)
|
|
82
|
+
@context.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
83
|
+
else
|
|
84
|
+
raise NoMethodError, "undefined method 'create_form_elicitation' for #{self}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Delegates to the session so the request is scoped to the originating client.
|
|
89
|
+
# Falls back to `@context` when `@notification_target` does not support URL mode elicitation.
|
|
90
|
+
def create_url_elicitation(**kwargs)
|
|
91
|
+
if @notification_target.respond_to?(:create_url_elicitation)
|
|
92
|
+
@notification_target.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
93
|
+
elsif @context.respond_to?(:create_url_elicitation)
|
|
94
|
+
@context.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
|
|
95
|
+
else
|
|
96
|
+
raise NoMethodError, "undefined method 'create_url_elicitation' for #{self}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Delegates to the session so the notification is scoped to the originating client.
|
|
101
|
+
def notify_elicitation_complete(**kwargs)
|
|
102
|
+
if @notification_target.respond_to?(:notify_elicitation_complete)
|
|
103
|
+
@notification_target.notify_elicitation_complete(**kwargs)
|
|
104
|
+
elsif @context.respond_to?(:notify_elicitation_complete)
|
|
105
|
+
@context.notify_elicitation_complete(**kwargs)
|
|
106
|
+
else
|
|
107
|
+
raise NoMethodError, "undefined method 'notify_elicitation_complete' for #{self}"
|
|
108
|
+
end
|
|
30
109
|
end
|
|
31
110
|
|
|
32
111
|
def method_missing(name, ...)
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "cancellation"
|
|
3
4
|
require_relative "methods"
|
|
4
5
|
|
|
5
6
|
module MCP
|
|
@@ -13,8 +14,50 @@ module MCP
|
|
|
13
14
|
@transport = transport
|
|
14
15
|
@session_id = session_id
|
|
15
16
|
@client = nil
|
|
16
|
-
@client_capabilities = nil
|
|
17
|
+
@client_capabilities = nil
|
|
17
18
|
@logging_message_notification = nil
|
|
19
|
+
@in_flight = {}
|
|
20
|
+
@in_flight_mutex = Mutex.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Registers a `Cancellation` token for an in-flight request.
|
|
24
|
+
def register_in_flight(request_id)
|
|
25
|
+
return if request_id.nil?
|
|
26
|
+
|
|
27
|
+
cancellation = Cancellation.new(request_id: request_id)
|
|
28
|
+
@in_flight_mutex.synchronize { @in_flight[request_id] = cancellation }
|
|
29
|
+
cancellation
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def unregister_in_flight(request_id)
|
|
33
|
+
return if request_id.nil?
|
|
34
|
+
|
|
35
|
+
@in_flight_mutex.synchronize { @in_flight.delete(request_id) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def lookup_in_flight(request_id)
|
|
39
|
+
@in_flight_mutex.synchronize { @in_flight[request_id] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Flips the `Cancellation` for a matching in-flight request received from the peer.
|
|
43
|
+
# Silently ignores unknown IDs per MCP spec (cancellation utilities, item 5).
|
|
44
|
+
def cancel_incoming(request_id:, reason: nil)
|
|
45
|
+
cancellation = lookup_in_flight(request_id)
|
|
46
|
+
cancellation&.cancel(reason: reason)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Sends `notifications/cancelled` to the peer for a previously-issued request.
|
|
50
|
+
# Also unblocks any transport-level `send_request` waiting on a response for `request_id`.
|
|
51
|
+
def cancel_request(request_id:, reason: nil)
|
|
52
|
+
params = { requestId: request_id }
|
|
53
|
+
params[:reason] = reason if reason
|
|
54
|
+
send_to_transport(Methods::NOTIFICATIONS_CANCELLED, params)
|
|
55
|
+
|
|
56
|
+
if @transport.respond_to?(:cancel_pending_request)
|
|
57
|
+
@transport.cancel_pending_request(request_id, reason: reason)
|
|
58
|
+
end
|
|
59
|
+
rescue => e
|
|
60
|
+
MCP.configuration.exception_reporter.call(e, { notification: "cancelled", request_id: request_id })
|
|
18
61
|
end
|
|
19
62
|
|
|
20
63
|
def handle(request)
|
|
@@ -36,8 +79,81 @@ module MCP
|
|
|
36
79
|
@logging_message_notification = logging_message_notification
|
|
37
80
|
end
|
|
38
81
|
|
|
82
|
+
# Returns per-session client capabilities, falling back to global.
|
|
83
|
+
def client_capabilities
|
|
84
|
+
@client_capabilities || @server.client_capabilities
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Sends a `roots/list` request scoped to this session.
|
|
88
|
+
def list_roots(related_request_id: nil)
|
|
89
|
+
unless client_capabilities&.dig(:roots)
|
|
90
|
+
raise "Client does not support roots."
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
send_to_transport_request(Methods::ROOTS_LIST, nil, related_request_id: related_request_id)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Sends a `sampling/createMessage` request scoped to this session.
|
|
97
|
+
def create_sampling_message(related_request_id: nil, **kwargs)
|
|
98
|
+
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
|
99
|
+
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Sends an `elicitation/create` request (form mode) scoped to this session.
|
|
103
|
+
def create_form_elicitation(message:, requested_schema:, related_request_id: nil)
|
|
104
|
+
unless client_capabilities&.dig(:elicitation)
|
|
105
|
+
raise "Client does not support elicitation. " \
|
|
106
|
+
"The client must declare the `elicitation` capability during initialization."
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
params = { mode: "form", message: message, requestedSchema: requested_schema }
|
|
110
|
+
send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: related_request_id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Sends an `elicitation/create` request (URL mode) scoped to this session.
|
|
114
|
+
def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil)
|
|
115
|
+
unless client_capabilities&.dig(:elicitation, :url)
|
|
116
|
+
raise "Client does not support URL mode elicitation. " \
|
|
117
|
+
"The client must declare the `elicitation.url` capability during initialization."
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
params = { mode: "url", message: message, url: url, elicitationId: elicitation_id }
|
|
121
|
+
send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: related_request_id)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Sends `notifications/cancelled` to the peer for a nested server-to-client request
|
|
125
|
+
# that was started inside a now-cancelled parent request. `related_request_id`
|
|
126
|
+
# is the parent request id so the notification is routed to the same stream
|
|
127
|
+
# (e.g. the parent's POST response stream on `StreamableHTTPTransport`) rather than
|
|
128
|
+
# the GET SSE stream.
|
|
129
|
+
def send_peer_cancellation(nested_request_id:, related_request_id: nil, reason: nil)
|
|
130
|
+
params = { requestId: nested_request_id }
|
|
131
|
+
params[:reason] = reason if reason
|
|
132
|
+
send_to_transport(Methods::NOTIFICATIONS_CANCELLED, params, related_request_id: related_request_id)
|
|
133
|
+
|
|
134
|
+
if @transport.respond_to?(:cancel_pending_request)
|
|
135
|
+
@transport.cancel_pending_request(nested_request_id, reason: reason)
|
|
136
|
+
end
|
|
137
|
+
rescue => e
|
|
138
|
+
MCP.configuration.exception_reporter.call(e, { notification: "cancelled", request_id: nested_request_id })
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Sends an elicitation complete notification scoped to this session.
|
|
142
|
+
def notify_elicitation_complete(elicitation_id:)
|
|
143
|
+
send_to_transport(Methods::NOTIFICATIONS_ELICITATION_COMPLETE, { elicitationId: elicitation_id })
|
|
144
|
+
rescue => e
|
|
145
|
+
@server.report_exception(e, notification: "elicitation_complete")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Sends a resource updated notification to this session only.
|
|
149
|
+
def notify_resources_updated(uri:)
|
|
150
|
+
send_to_transport(Methods::NOTIFICATIONS_RESOURCES_UPDATED, { "uri" => uri })
|
|
151
|
+
rescue => e
|
|
152
|
+
@server.report_exception(e, notification: "resources_updated")
|
|
153
|
+
end
|
|
154
|
+
|
|
39
155
|
# Sends a progress notification to this session only.
|
|
40
|
-
def notify_progress(progress_token:, progress:, total: nil, message: nil)
|
|
156
|
+
def notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil)
|
|
41
157
|
params = {
|
|
42
158
|
"progressToken" => progress_token,
|
|
43
159
|
"progress" => progress,
|
|
@@ -45,35 +161,77 @@ module MCP
|
|
|
45
161
|
"message" => message,
|
|
46
162
|
}.compact
|
|
47
163
|
|
|
48
|
-
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params)
|
|
164
|
+
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params, related_request_id: related_request_id)
|
|
49
165
|
rescue => e
|
|
50
166
|
@server.report_exception(e, notification: "progress")
|
|
51
167
|
end
|
|
52
168
|
|
|
53
169
|
# Sends a log message notification to this session only.
|
|
54
|
-
def notify_log_message(data:, level:, logger: nil)
|
|
170
|
+
def notify_log_message(data:, level:, logger: nil, related_request_id: nil)
|
|
55
171
|
effective_logging = @logging_message_notification || @server.logging_message_notification
|
|
56
172
|
return unless effective_logging&.should_notify?(level)
|
|
57
173
|
|
|
58
174
|
params = { "data" => data, "level" => level }
|
|
59
175
|
params["logger"] = logger if logger
|
|
60
176
|
|
|
61
|
-
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params)
|
|
177
|
+
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params, related_request_id: related_request_id)
|
|
62
178
|
rescue => e
|
|
63
179
|
@server.report_exception(e, { notification: "log_message" })
|
|
64
180
|
end
|
|
65
181
|
|
|
66
182
|
private
|
|
67
183
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
184
|
+
# Forwards `send_notification` to the transport with only the kwargs the transport's method signature
|
|
185
|
+
# actually accepts. Custom transports that implement the abstract `send_notification(method, params = nil)`
|
|
186
|
+
# contract continue to work unchanged; bundled transports that declare `session_id:` / `related_request_id:`
|
|
187
|
+
# receive the session-scoped routing information.
|
|
188
|
+
def send_to_transport(method, params, related_request_id: nil)
|
|
189
|
+
kwargs = {
|
|
190
|
+
session_id: @session_id,
|
|
191
|
+
related_request_id: related_request_id,
|
|
192
|
+
}.compact
|
|
193
|
+
|
|
194
|
+
forward_to_transport(@transport.method(:send_notification), method, params, kwargs)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Forwards `send_request` to the transport with only the kwargs the transport's method signature
|
|
198
|
+
# actually accepts. Custom transports that implement the abstract `send_request(method, params = nil)`
|
|
199
|
+
# contract continue to work; bundled transports that declare `session_id:` / `related_request_id:` /
|
|
200
|
+
# `parent_cancellation:` / `server_session:` receive the nested-cancellation plumbing.
|
|
201
|
+
# When `related_request_id` names an in-flight request, its `Cancellation` token is looked up
|
|
202
|
+
# so that cancelling the parent also cancels this nested server-to-client request.
|
|
203
|
+
def send_to_transport_request(method, params, related_request_id: nil)
|
|
204
|
+
parent_cancellation = related_request_id ? lookup_in_flight(related_request_id) : nil
|
|
205
|
+
|
|
206
|
+
kwargs = {
|
|
207
|
+
session_id: @session_id,
|
|
208
|
+
related_request_id: related_request_id,
|
|
209
|
+
parent_cancellation: parent_cancellation,
|
|
210
|
+
server_session: self,
|
|
211
|
+
}.compact
|
|
212
|
+
|
|
213
|
+
forward_to_transport(@transport.method(:send_request), method, params, kwargs)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Calls `transport_method(method, params, **supported)` where `supported` contains only the keys
|
|
217
|
+
# the transport's method signature accepts. This keeps bundled transports (which declare the new kwargs)
|
|
218
|
+
# working while preserving compatibility with custom transports that implement only the abstract
|
|
219
|
+
# `(method, params = nil)` contract.
|
|
220
|
+
def forward_to_transport(transport_method, method, params, kwargs)
|
|
221
|
+
parameters = transport_method.parameters
|
|
222
|
+
accepts_keyrest = parameters.any? { |type, _| type == :keyrest }
|
|
223
|
+
supported = if accepts_keyrest
|
|
224
|
+
kwargs
|
|
74
225
|
else
|
|
75
|
-
|
|
226
|
+
allowed = parameters.filter_map { |type, name| name if type == :key || type == :keyreq }
|
|
227
|
+
kwargs.slice(*allowed)
|
|
76
228
|
end
|
|
229
|
+
|
|
230
|
+
# Always splat `**supported` even when empty: on Ruby 2.7 the bare `transport_method.call(method, params)`
|
|
231
|
+
# form would let the trailing `params` Hash be auto-promoted to keyword arguments when the receiver
|
|
232
|
+
# accepts `**kwargs`, breaking handlers that rely on `params` arriving as a positional Hash.
|
|
233
|
+
# The explicit splat suppresses that conversion and is a no-op when `supported` is empty.
|
|
234
|
+
transport_method.call(method, params, **supported)
|
|
77
235
|
end
|
|
78
236
|
end
|
|
79
237
|
end
|
data/lib/mcp/tool/response.rb
CHANGED
|
@@ -5,9 +5,9 @@ module MCP
|
|
|
5
5
|
class Response
|
|
6
6
|
NOT_GIVEN = Object.new.freeze
|
|
7
7
|
|
|
8
|
-
attr_reader :content, :structured_content
|
|
8
|
+
attr_reader :content, :structured_content, :meta
|
|
9
9
|
|
|
10
|
-
def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: nil)
|
|
10
|
+
def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: nil, meta: nil)
|
|
11
11
|
if deprecated_error != NOT_GIVEN
|
|
12
12
|
warn("Passing `error` with the 2nd argument of `Response.new` is deprecated. Use keyword argument like `Response.new(content, error: error)` instead.", uplevel: 1)
|
|
13
13
|
error = deprecated_error
|
|
@@ -16,6 +16,7 @@ module MCP
|
|
|
16
16
|
@content = content || []
|
|
17
17
|
@error = error
|
|
18
18
|
@structured_content = structured_content
|
|
19
|
+
@meta = meta
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def error?
|
|
@@ -23,7 +24,7 @@ module MCP
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def to_h
|
|
26
|
-
{ content: content, isError: error?, structuredContent: @structured_content }.compact
|
|
27
|
+
{ content: content, isError: error?, structuredContent: @structured_content, _meta: meta }.compact
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
end
|
data/lib/mcp/tool/schema.rb
CHANGED
|
@@ -8,7 +8,7 @@ module MCP
|
|
|
8
8
|
attr_reader :schema
|
|
9
9
|
|
|
10
10
|
def initialize(schema = {})
|
|
11
|
-
@schema =
|
|
11
|
+
@schema = JSON.parse(JSON.dump(schema), symbolize_names: true)
|
|
12
12
|
@schema[:type] ||= "object"
|
|
13
13
|
validate_schema!
|
|
14
14
|
end
|
|
@@ -27,19 +27,6 @@ module MCP
|
|
|
27
27
|
JSON::Validator.fully_validate(to_h, data)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def deep_transform_keys(schema, &block)
|
|
31
|
-
case schema
|
|
32
|
-
when Hash
|
|
33
|
-
schema.each_with_object({}) do |(key, value), result|
|
|
34
|
-
result[yield(key)] = deep_transform_keys(value, &block)
|
|
35
|
-
end
|
|
36
|
-
when Array
|
|
37
|
-
schema.map { |e| deep_transform_keys(e, &block) }
|
|
38
|
-
else
|
|
39
|
-
schema
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
30
|
def validate_schema!
|
|
44
31
|
schema = to_h
|
|
45
32
|
gem_path = File.realpath(Gem.loaded_specs["json-schema"].full_gem_path)
|
data/lib/mcp/transport.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
3
5
|
module MCP
|
|
4
6
|
class Transport
|
|
5
7
|
# Initialize the transport with the server instance
|
|
6
8
|
def initialize(server)
|
|
7
9
|
@server = server
|
|
10
|
+
server.transport = self
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
# Send a response to the client
|
|
@@ -41,5 +44,16 @@ module MCP
|
|
|
41
44
|
def send_notification(method, params = nil)
|
|
42
45
|
raise NotImplementedError, "Subclasses must implement send_notification"
|
|
43
46
|
end
|
|
47
|
+
|
|
48
|
+
# Send a JSON-RPC request to the client and wait for a response.
|
|
49
|
+
def send_request(method, params = nil)
|
|
50
|
+
raise NotImplementedError, "Subclasses must implement send_request"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def generate_request_id
|
|
56
|
+
SecureRandom.uuid
|
|
57
|
+
end
|
|
44
58
|
end
|
|
45
59
|
end
|
data/lib/mcp/version.rb
CHANGED
data/lib/mcp.rb
CHANGED
|
@@ -8,6 +8,8 @@ require_relative "mcp/version"
|
|
|
8
8
|
|
|
9
9
|
module MCP
|
|
10
10
|
autoload :Annotations, "mcp/annotations"
|
|
11
|
+
autoload :Cancellation, "mcp/cancellation"
|
|
12
|
+
autoload :CancelledError, "mcp/cancelled_error"
|
|
11
13
|
autoload :Client, "mcp/client"
|
|
12
14
|
autoload :Content, "mcp/content"
|
|
13
15
|
autoload :Icon, "mcp/icon"
|
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.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -28,15 +28,20 @@ email:
|
|
|
28
28
|
- mcp-support@anthropic.com
|
|
29
29
|
executables: []
|
|
30
30
|
extensions: []
|
|
31
|
-
extra_rdoc_files:
|
|
31
|
+
extra_rdoc_files:
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
32
34
|
files:
|
|
33
35
|
- LICENSE
|
|
34
36
|
- README.md
|
|
35
37
|
- lib/json_rpc_handler.rb
|
|
36
38
|
- lib/mcp.rb
|
|
37
39
|
- lib/mcp/annotations.rb
|
|
40
|
+
- lib/mcp/cancellation.rb
|
|
41
|
+
- lib/mcp/cancelled_error.rb
|
|
38
42
|
- lib/mcp/client.rb
|
|
39
43
|
- lib/mcp/client/http.rb
|
|
44
|
+
- lib/mcp/client/paginated_result.rb
|
|
40
45
|
- lib/mcp/client/stdio.rb
|
|
41
46
|
- lib/mcp/client/tool.rb
|
|
42
47
|
- lib/mcp/configuration.rb
|
|
@@ -56,6 +61,7 @@ files:
|
|
|
56
61
|
- lib/mcp/resource_template.rb
|
|
57
62
|
- lib/mcp/server.rb
|
|
58
63
|
- lib/mcp/server/capabilities.rb
|
|
64
|
+
- lib/mcp/server/pagination.rb
|
|
59
65
|
- lib/mcp/server/transports.rb
|
|
60
66
|
- lib/mcp/server/transports/stdio_transport.rb
|
|
61
67
|
- lib/mcp/server/transports/streamable_http_transport.rb
|
|
@@ -69,15 +75,14 @@ files:
|
|
|
69
75
|
- lib/mcp/tool/response.rb
|
|
70
76
|
- lib/mcp/tool/schema.rb
|
|
71
77
|
- lib/mcp/transport.rb
|
|
72
|
-
- lib/mcp/transports/stdio.rb
|
|
73
78
|
- lib/mcp/version.rb
|
|
74
|
-
homepage: https://
|
|
79
|
+
homepage: https://ruby.sdk.modelcontextprotocol.io
|
|
75
80
|
licenses:
|
|
76
81
|
- Apache-2.0
|
|
77
82
|
metadata:
|
|
78
83
|
allowed_push_host: https://rubygems.org
|
|
79
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.
|
|
80
|
-
homepage_uri: https://
|
|
84
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.15.0
|
|
85
|
+
homepage_uri: https://ruby.sdk.modelcontextprotocol.io
|
|
81
86
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
82
87
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|
|
83
88
|
documentation_uri: https://rubydoc.info/gems/mcp
|
data/lib/mcp/transports/stdio.rb
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../server/transports/stdio_transport"
|
|
4
|
-
|
|
5
|
-
warn <<~MESSAGE, uplevel: 3
|
|
6
|
-
Use `require "mcp/server/transports/stdio_transport"` instead of `require "mcp/transports/stdio"`.
|
|
7
|
-
Also use `MCP::Server::Transports::StdioTransport` instead of `MCP::Transports::StdioTransport`.
|
|
8
|
-
This API is deprecated and will be removed in a future release.
|
|
9
|
-
MESSAGE
|
|
10
|
-
|
|
11
|
-
module MCP
|
|
12
|
-
module Transports
|
|
13
|
-
StdioTransport = Server::Transports::StdioTransport
|
|
14
|
-
end
|
|
15
|
-
end
|