mcp 0.10.0 → 0.12.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 +577 -355
- data/lib/json_rpc_handler.rb +1 -1
- data/lib/mcp/client/http.rb +4 -1
- data/lib/mcp/client.rb +62 -48
- data/lib/mcp/progress.rb +3 -1
- data/lib/mcp/server/transports/stdio_transport.rb +35 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +289 -83
- data/lib/mcp/server.rb +181 -23
- data/lib/mcp/server_context.rb +16 -2
- data/lib/mcp/server_session.rb +35 -7
- data/lib/mcp/tool/schema.rb +1 -14
- data/lib/mcp/transport.rb +14 -0
- data/lib/mcp/version.rb +1 -1
- metadata +5 -3
data/lib/mcp/server.rb
CHANGED
|
@@ -24,6 +24,12 @@ module MCP
|
|
|
24
24
|
UNSUPPORTED_PROPERTIES_UNTIL_2025_06_18 = [:description, :icons].freeze
|
|
25
25
|
UNSUPPORTED_PROPERTIES_UNTIL_2025_03_26 = [:title, :websiteUrl].freeze
|
|
26
26
|
|
|
27
|
+
DEFAULT_COMPLETION_RESULT = { completion: { values: [], hasMore: false } }.freeze
|
|
28
|
+
|
|
29
|
+
# Servers return an array of completion values ranked by relevance, with maximum 100 items per response.
|
|
30
|
+
# https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion#completion-results
|
|
31
|
+
MAX_COMPLETION_VALUES = 100
|
|
32
|
+
|
|
27
33
|
class RequestHandlerError < StandardError
|
|
28
34
|
attr_reader :error_type
|
|
29
35
|
attr_reader :original_error
|
|
@@ -48,6 +54,7 @@ module MCP
|
|
|
48
54
|
include Instrumentation
|
|
49
55
|
|
|
50
56
|
attr_accessor :description, :icons, :name, :title, :version, :website_url, :instructions, :tools, :prompts, :resources, :server_context, :configuration, :capabilities, :transport, :logging_message_notification
|
|
57
|
+
attr_reader :client_capabilities
|
|
51
58
|
|
|
52
59
|
def initialize(
|
|
53
60
|
description: nil,
|
|
@@ -86,6 +93,7 @@ module MCP
|
|
|
86
93
|
validate!
|
|
87
94
|
|
|
88
95
|
@capabilities = capabilities || default_capabilities
|
|
96
|
+
@client_capabilities = nil
|
|
89
97
|
@logging_message_notification = nil
|
|
90
98
|
|
|
91
99
|
@handlers = {
|
|
@@ -100,12 +108,12 @@ module MCP
|
|
|
100
108
|
Methods::PING => ->(_) { {} },
|
|
101
109
|
Methods::NOTIFICATIONS_INITIALIZED => ->(_) {},
|
|
102
110
|
Methods::NOTIFICATIONS_PROGRESS => ->(_) {},
|
|
111
|
+
Methods::COMPLETION_COMPLETE => ->(_) { DEFAULT_COMPLETION_RESULT },
|
|
103
112
|
Methods::LOGGING_SET_LEVEL => method(:configure_logging_level),
|
|
104
113
|
|
|
105
114
|
# No op handlers for currently unsupported methods
|
|
106
115
|
Methods::RESOURCES_SUBSCRIBE => ->(_) { {} },
|
|
107
116
|
Methods::RESOURCES_UNSUBSCRIBE => ->(_) { {} },
|
|
108
|
-
Methods::COMPLETION_COMPLETE => ->(_) { { completion: { values: [], hasMore: false } } },
|
|
109
117
|
Methods::ELICITATION_CREATE => ->(_) {},
|
|
110
118
|
}
|
|
111
119
|
@transport = transport
|
|
@@ -119,8 +127,8 @@ module MCP
|
|
|
119
127
|
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
|
120
128
|
# @return [Hash, nil] The JSON-RPC response, or `nil` for notifications.
|
|
121
129
|
def handle(request, session: nil)
|
|
122
|
-
JsonRpcHandler.handle(request) do |method|
|
|
123
|
-
handle_request(request, method, session: session)
|
|
130
|
+
JsonRpcHandler.handle(request) do |method, request_id|
|
|
131
|
+
handle_request(request, method, session: session, related_request_id: request_id)
|
|
124
132
|
end
|
|
125
133
|
end
|
|
126
134
|
|
|
@@ -132,8 +140,8 @@ module MCP
|
|
|
132
140
|
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
|
133
141
|
# @return [String, nil] The JSON-RPC response as JSON, or `nil` for notifications.
|
|
134
142
|
def handle_json(request, session: nil)
|
|
135
|
-
JsonRpcHandler.handle_json(request) do |method|
|
|
136
|
-
handle_request(request, method, session: session)
|
|
143
|
+
JsonRpcHandler.handle_json(request) do |method, request_id|
|
|
144
|
+
handle_request(request, method, session: session, related_request_id: request_id)
|
|
137
145
|
end
|
|
138
146
|
end
|
|
139
147
|
|
|
@@ -198,6 +206,44 @@ module MCP
|
|
|
198
206
|
report_exception(e, { notification: "log_message" })
|
|
199
207
|
end
|
|
200
208
|
|
|
209
|
+
# Sends a `sampling/createMessage` request to the client.
|
|
210
|
+
# For single-client transports (e.g., `StdioTransport`). For multi-client transports
|
|
211
|
+
# (e.g., `StreamableHTTPTransport`), use `ServerSession#create_sampling_message` instead
|
|
212
|
+
# to ensure the request is routed to the correct client.
|
|
213
|
+
def create_sampling_message(
|
|
214
|
+
messages:,
|
|
215
|
+
max_tokens:,
|
|
216
|
+
system_prompt: nil,
|
|
217
|
+
model_preferences: nil,
|
|
218
|
+
include_context: nil,
|
|
219
|
+
temperature: nil,
|
|
220
|
+
stop_sequences: nil,
|
|
221
|
+
metadata: nil,
|
|
222
|
+
tools: nil,
|
|
223
|
+
tool_choice: nil,
|
|
224
|
+
related_request_id: nil
|
|
225
|
+
)
|
|
226
|
+
unless @transport
|
|
227
|
+
raise "Cannot send sampling request without a transport."
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
params = build_sampling_params(
|
|
231
|
+
@client_capabilities,
|
|
232
|
+
messages: messages,
|
|
233
|
+
max_tokens: max_tokens,
|
|
234
|
+
system_prompt: system_prompt,
|
|
235
|
+
model_preferences: model_preferences,
|
|
236
|
+
include_context: include_context,
|
|
237
|
+
temperature: temperature,
|
|
238
|
+
stop_sequences: stop_sequences,
|
|
239
|
+
metadata: metadata,
|
|
240
|
+
tools: tools,
|
|
241
|
+
tool_choice: tool_choice,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@transport.send_request(Methods::SAMPLING_CREATE_MESSAGE, params)
|
|
245
|
+
end
|
|
246
|
+
|
|
201
247
|
# Sets a custom handler for `resources/read` requests.
|
|
202
248
|
# The block receives the parsed request params and should return resource
|
|
203
249
|
# contents. The return value is set as the `contents` field of the response.
|
|
@@ -208,6 +254,54 @@ module MCP
|
|
|
208
254
|
@handlers[Methods::RESOURCES_READ] = block
|
|
209
255
|
end
|
|
210
256
|
|
|
257
|
+
# Sets a custom handler for `completion/complete` requests.
|
|
258
|
+
# The block receives the parsed request params and should return completion values.
|
|
259
|
+
#
|
|
260
|
+
# @yield [params] The request params containing `:ref`, `:argument`, and optionally `:context`.
|
|
261
|
+
# @yieldreturn [Hash] A hash with `:completion` key containing `:values`, optional `:total`, and `:hasMore`.
|
|
262
|
+
def completion_handler(&block)
|
|
263
|
+
@handlers[Methods::COMPLETION_COMPLETE] = block
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def build_sampling_params(
|
|
267
|
+
capabilities,
|
|
268
|
+
messages:,
|
|
269
|
+
max_tokens:,
|
|
270
|
+
system_prompt: nil,
|
|
271
|
+
model_preferences: nil,
|
|
272
|
+
include_context: nil,
|
|
273
|
+
temperature: nil,
|
|
274
|
+
stop_sequences: nil,
|
|
275
|
+
metadata: nil,
|
|
276
|
+
tools: nil,
|
|
277
|
+
tool_choice: nil
|
|
278
|
+
)
|
|
279
|
+
unless capabilities&.dig(:sampling)
|
|
280
|
+
raise "Client does not support sampling."
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if tools && !capabilities.dig(:sampling, :tools)
|
|
284
|
+
raise "Client does not support sampling with tools."
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
if tool_choice && !capabilities.dig(:sampling, :tools)
|
|
288
|
+
raise "Client does not support sampling with tool_choice."
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
{
|
|
292
|
+
messages: messages,
|
|
293
|
+
maxTokens: max_tokens,
|
|
294
|
+
systemPrompt: system_prompt,
|
|
295
|
+
modelPreferences: model_preferences,
|
|
296
|
+
includeContext: include_context,
|
|
297
|
+
temperature: temperature,
|
|
298
|
+
stopSequences: stop_sequences,
|
|
299
|
+
metadata: metadata,
|
|
300
|
+
tools: tools,
|
|
301
|
+
toolChoice: tool_choice,
|
|
302
|
+
}.compact
|
|
303
|
+
end
|
|
304
|
+
|
|
211
305
|
private
|
|
212
306
|
|
|
213
307
|
def validate!
|
|
@@ -278,7 +372,7 @@ module MCP
|
|
|
278
372
|
end
|
|
279
373
|
end
|
|
280
374
|
|
|
281
|
-
def handle_request(request, method, session: nil)
|
|
375
|
+
def handle_request(request, method, session: nil, related_request_id: nil)
|
|
282
376
|
handler = @handlers[method]
|
|
283
377
|
unless handler
|
|
284
378
|
instrument_call("unsupported_method") do
|
|
@@ -306,7 +400,9 @@ module MCP
|
|
|
306
400
|
when Methods::RESOURCES_TEMPLATES_LIST
|
|
307
401
|
{ resourceTemplates: @handlers[Methods::RESOURCES_TEMPLATES_LIST].call(params) }
|
|
308
402
|
when Methods::TOOLS_CALL
|
|
309
|
-
call_tool(params, session: session)
|
|
403
|
+
call_tool(params, session: session, related_request_id: related_request_id)
|
|
404
|
+
when Methods::COMPLETION_COMPLETE
|
|
405
|
+
complete(params)
|
|
310
406
|
when Methods::LOGGING_SET_LEVEL
|
|
311
407
|
configure_logging_level(params, session: session)
|
|
312
408
|
else
|
|
@@ -316,13 +412,12 @@ module MCP
|
|
|
316
412
|
add_instrumentation_data(client: client) if client
|
|
317
413
|
|
|
318
414
|
result
|
|
415
|
+
rescue RequestHandlerError => e
|
|
416
|
+
report_exception(e.original_error || e, { request: request })
|
|
417
|
+
add_instrumentation_data(error: e.error_type)
|
|
418
|
+
raise e
|
|
319
419
|
rescue => e
|
|
320
420
|
report_exception(e, { request: request })
|
|
321
|
-
if e.is_a?(RequestHandlerError)
|
|
322
|
-
add_instrumentation_data(error: e.error_type)
|
|
323
|
-
raise e
|
|
324
|
-
end
|
|
325
|
-
|
|
326
421
|
add_instrumentation_data(error: :internal_error)
|
|
327
422
|
raise RequestHandlerError.new("Internal error handling #{method} request", request, original_error: e)
|
|
328
423
|
end
|
|
@@ -355,10 +450,11 @@ module MCP
|
|
|
355
450
|
session.store_client_info(client: params[:clientInfo], capabilities: params[:capabilities])
|
|
356
451
|
else
|
|
357
452
|
@client = params[:clientInfo]
|
|
453
|
+
@client_capabilities = params[:capabilities]
|
|
358
454
|
end
|
|
455
|
+
protocol_version = params[:protocolVersion]
|
|
359
456
|
end
|
|
360
457
|
|
|
361
|
-
protocol_version = params[:protocolVersion] if params
|
|
362
458
|
negotiated_version = if Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(protocol_version)
|
|
363
459
|
protocol_version
|
|
364
460
|
else
|
|
@@ -404,7 +500,7 @@ module MCP
|
|
|
404
500
|
@tools.values.map(&:to_h)
|
|
405
501
|
end
|
|
406
502
|
|
|
407
|
-
def call_tool(request, session: nil)
|
|
503
|
+
def call_tool(request, session: nil, related_request_id: nil)
|
|
408
504
|
tool_name = request[:name]
|
|
409
505
|
|
|
410
506
|
tool = tools[tool_name]
|
|
@@ -421,7 +517,7 @@ module MCP
|
|
|
421
517
|
add_instrumentation_data(error: :missing_required_arguments)
|
|
422
518
|
|
|
423
519
|
missing = tool.input_schema.missing_required_arguments(arguments).join(", ")
|
|
424
|
-
|
|
520
|
+
raise RequestHandlerError.new("Missing required arguments: #{missing}", request, error_type: :invalid_params)
|
|
425
521
|
end
|
|
426
522
|
|
|
427
523
|
if configuration.validate_tool_call_arguments && tool.input_schema
|
|
@@ -430,19 +526,22 @@ module MCP
|
|
|
430
526
|
rescue Tool::InputSchema::ValidationError => e
|
|
431
527
|
add_instrumentation_data(error: :invalid_schema)
|
|
432
528
|
|
|
433
|
-
|
|
529
|
+
raise RequestHandlerError.new(e.message, request, error_type: :invalid_params)
|
|
434
530
|
end
|
|
435
531
|
end
|
|
436
532
|
|
|
437
533
|
progress_token = request.dig(:_meta, :progressToken)
|
|
438
534
|
|
|
439
|
-
call_tool_with_args(tool, arguments, server_context_with_meta(request), progress_token: progress_token, session: session)
|
|
535
|
+
call_tool_with_args(tool, arguments, server_context_with_meta(request), progress_token: progress_token, session: session, related_request_id: related_request_id)
|
|
440
536
|
rescue RequestHandlerError
|
|
441
537
|
raise
|
|
442
538
|
rescue => e
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
539
|
+
raise RequestHandlerError.new(
|
|
540
|
+
"Internal error calling tool #{tool_name}: #{e.message}",
|
|
541
|
+
request,
|
|
542
|
+
error_type: :internal_error,
|
|
543
|
+
original_error: e,
|
|
544
|
+
)
|
|
446
545
|
end
|
|
447
546
|
|
|
448
547
|
def list_prompts(request)
|
|
@@ -479,6 +578,14 @@ module MCP
|
|
|
479
578
|
@resource_templates.map(&:to_h)
|
|
480
579
|
end
|
|
481
580
|
|
|
581
|
+
def complete(params)
|
|
582
|
+
validate_completion_params!(params)
|
|
583
|
+
|
|
584
|
+
result = @handlers[Methods::COMPLETION_COMPLETE].call(params)
|
|
585
|
+
|
|
586
|
+
normalize_completion_result(result)
|
|
587
|
+
end
|
|
588
|
+
|
|
482
589
|
def report_exception(exception, server_context = {})
|
|
483
590
|
configuration.exception_reporter.call(exception, server_context)
|
|
484
591
|
end
|
|
@@ -505,12 +612,12 @@ module MCP
|
|
|
505
612
|
parameters.any? { |type, name| type == :keyrest || name == :server_context }
|
|
506
613
|
end
|
|
507
614
|
|
|
508
|
-
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil)
|
|
615
|
+
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil, related_request_id: nil)
|
|
509
616
|
args = arguments&.transform_keys(&:to_sym) || {}
|
|
510
617
|
|
|
511
618
|
if accepts_server_context?(tool.method(:call))
|
|
512
|
-
progress = Progress.new(notification_target: session, progress_token: progress_token)
|
|
513
|
-
server_context = ServerContext.new(context, progress: progress, notification_target: session)
|
|
619
|
+
progress = Progress.new(notification_target: session, progress_token: progress_token, related_request_id: related_request_id)
|
|
620
|
+
server_context = ServerContext.new(context, progress: progress, notification_target: session, related_request_id: related_request_id)
|
|
514
621
|
tool.call(**args, server_context: server_context).to_h
|
|
515
622
|
else
|
|
516
623
|
tool.call(**args).to_h
|
|
@@ -537,5 +644,56 @@ module MCP
|
|
|
537
644
|
server_context
|
|
538
645
|
end
|
|
539
646
|
end
|
|
647
|
+
|
|
648
|
+
def validate_completion_params!(params)
|
|
649
|
+
unless params.is_a?(Hash)
|
|
650
|
+
raise RequestHandlerError.new("Invalid params", params, error_type: :invalid_params)
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
ref = params[:ref]
|
|
654
|
+
if ref.nil? || ref[:type].nil?
|
|
655
|
+
raise RequestHandlerError.new("Missing or invalid ref", params, error_type: :invalid_params)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
argument = params[:argument]
|
|
659
|
+
if argument.nil? || argument[:name].nil? || !argument.key?(:value)
|
|
660
|
+
raise RequestHandlerError.new("Missing argument name or value", params, error_type: :invalid_params)
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
case ref[:type]
|
|
664
|
+
when "ref/prompt"
|
|
665
|
+
unless @prompts[ref[:name]]
|
|
666
|
+
raise RequestHandlerError.new("Prompt not found: #{ref[:name]}", params, error_type: :invalid_params)
|
|
667
|
+
end
|
|
668
|
+
when "ref/resource"
|
|
669
|
+
uri = ref[:uri]
|
|
670
|
+
found = @resource_index.key?(uri) || @resource_templates.any? { |t| t.uri_template == uri }
|
|
671
|
+
unless found
|
|
672
|
+
raise RequestHandlerError.new("Resource not found: #{uri}", params, error_type: :invalid_params)
|
|
673
|
+
end
|
|
674
|
+
else
|
|
675
|
+
raise RequestHandlerError.new("Invalid ref type: #{ref[:type]}", params, error_type: :invalid_params)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def normalize_completion_result(result)
|
|
680
|
+
return DEFAULT_COMPLETION_RESULT unless result.is_a?(Hash)
|
|
681
|
+
|
|
682
|
+
completion = result[:completion] || result["completion"]
|
|
683
|
+
return DEFAULT_COMPLETION_RESULT unless completion.is_a?(Hash)
|
|
684
|
+
|
|
685
|
+
values = completion[:values] || completion["values"] || []
|
|
686
|
+
total = completion[:total] || completion["total"]
|
|
687
|
+
has_more = completion[:hasMore] || completion["hasMore"] || false
|
|
688
|
+
|
|
689
|
+
count = values.length
|
|
690
|
+
if count > MAX_COMPLETION_VALUES
|
|
691
|
+
has_more = true
|
|
692
|
+
total ||= count
|
|
693
|
+
values = values.first(MAX_COMPLETION_VALUES)
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
{ completion: { values: values, total: total, hasMore: has_more }.compact }
|
|
697
|
+
end
|
|
540
698
|
end
|
|
541
699
|
end
|
data/lib/mcp/server_context.rb
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module MCP
|
|
4
4
|
class ServerContext
|
|
5
|
-
def initialize(context, progress:, notification_target:)
|
|
5
|
+
def initialize(context, progress:, notification_target:, related_request_id: nil)
|
|
6
6
|
@context = context
|
|
7
7
|
@progress = progress
|
|
8
8
|
@notification_target = notification_target
|
|
9
|
+
@related_request_id = related_request_id
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
# Reports progress for the current tool operation.
|
|
@@ -26,7 +27,20 @@ module MCP
|
|
|
26
27
|
def notify_log_message(data:, level:, logger: nil)
|
|
27
28
|
return unless @notification_target
|
|
28
29
|
|
|
29
|
-
@notification_target.notify_log_message(data: data, level: level, logger: logger)
|
|
30
|
+
@notification_target.notify_log_message(data: data, level: level, logger: logger, related_request_id: @related_request_id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Delegates to the session so the request is scoped to the originating client.
|
|
34
|
+
# Falls back to `@context` (via `method_missing`) when `@notification_target`
|
|
35
|
+
# does not support sampling.
|
|
36
|
+
def create_sampling_message(**kwargs)
|
|
37
|
+
if @notification_target.respond_to?(:create_sampling_message)
|
|
38
|
+
@notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
|
39
|
+
elsif @context.respond_to?(:create_sampling_message)
|
|
40
|
+
@context.create_sampling_message(**kwargs, related_request_id: @related_request_id)
|
|
41
|
+
else
|
|
42
|
+
raise NoMethodError, "undefined method 'create_sampling_message' for #{self}"
|
|
43
|
+
end
|
|
30
44
|
end
|
|
31
45
|
|
|
32
46
|
def method_missing(name, ...)
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -13,7 +13,7 @@ module MCP
|
|
|
13
13
|
@transport = transport
|
|
14
14
|
@session_id = session_id
|
|
15
15
|
@client = nil
|
|
16
|
-
@client_capabilities = nil
|
|
16
|
+
@client_capabilities = nil
|
|
17
17
|
@logging_message_notification = nil
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -36,8 +36,19 @@ module MCP
|
|
|
36
36
|
@logging_message_notification = logging_message_notification
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Returns per-session client capabilities, falling back to global.
|
|
40
|
+
def client_capabilities
|
|
41
|
+
@client_capabilities || @server.client_capabilities
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sends a `sampling/createMessage` request scoped to this session.
|
|
45
|
+
def create_sampling_message(related_request_id: nil, **kwargs)
|
|
46
|
+
params = @server.build_sampling_params(client_capabilities, **kwargs)
|
|
47
|
+
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
# Sends a progress notification to this session only.
|
|
40
|
-
def notify_progress(progress_token:, progress:, total: nil, message: nil)
|
|
51
|
+
def notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil)
|
|
41
52
|
params = {
|
|
42
53
|
"progressToken" => progress_token,
|
|
43
54
|
"progress" => progress,
|
|
@@ -45,35 +56,52 @@ module MCP
|
|
|
45
56
|
"message" => message,
|
|
46
57
|
}.compact
|
|
47
58
|
|
|
48
|
-
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params)
|
|
59
|
+
send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params, related_request_id: related_request_id)
|
|
49
60
|
rescue => e
|
|
50
61
|
@server.report_exception(e, notification: "progress")
|
|
51
62
|
end
|
|
52
63
|
|
|
53
64
|
# Sends a log message notification to this session only.
|
|
54
|
-
def notify_log_message(data:, level:, logger: nil)
|
|
65
|
+
def notify_log_message(data:, level:, logger: nil, related_request_id: nil)
|
|
55
66
|
effective_logging = @logging_message_notification || @server.logging_message_notification
|
|
56
67
|
return unless effective_logging&.should_notify?(level)
|
|
57
68
|
|
|
58
69
|
params = { "data" => data, "level" => level }
|
|
59
70
|
params["logger"] = logger if logger
|
|
60
71
|
|
|
61
|
-
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params)
|
|
72
|
+
send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params, related_request_id: related_request_id)
|
|
62
73
|
rescue => e
|
|
63
74
|
@server.report_exception(e, { notification: "log_message" })
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
private
|
|
67
78
|
|
|
79
|
+
# Branches on `@session_id` because `StdioTransport` creates a `ServerSession` without
|
|
80
|
+
# a `session_id` (`session_id: nil`), while `StreamableHTTPTransport` always provides one.
|
|
81
|
+
#
|
|
68
82
|
# TODO: When Ruby 2.7 support is dropped, replace with a direct call:
|
|
69
83
|
# `@transport.send_notification(method, params, session_id: @session_id)` and
|
|
70
84
|
# add `**` to `Transport#send_notification` and `StdioTransport#send_notification`.
|
|
71
|
-
def send_to_transport(method, params)
|
|
85
|
+
def send_to_transport(method, params, related_request_id: nil)
|
|
72
86
|
if @session_id
|
|
73
|
-
@transport.send_notification(method, params, session_id: @session_id)
|
|
87
|
+
@transport.send_notification(method, params, session_id: @session_id, related_request_id: related_request_id)
|
|
74
88
|
else
|
|
75
89
|
@transport.send_notification(method, params)
|
|
76
90
|
end
|
|
77
91
|
end
|
|
92
|
+
|
|
93
|
+
# Branches on `@session_id` because `StdioTransport` creates a `ServerSession` without
|
|
94
|
+
# a `session_id` (`session_id: nil`), while `StreamableHTTPTransport` always provides one.
|
|
95
|
+
#
|
|
96
|
+
# TODO: When Ruby 2.7 support is dropped, replace with a direct call:
|
|
97
|
+
# `@transport.send_request(method, params, session_id: @session_id)` and
|
|
98
|
+
# add `**` to `Transport#send_request` and `StdioTransport#send_request`.
|
|
99
|
+
def send_to_transport_request(method, params, related_request_id: nil)
|
|
100
|
+
if @session_id
|
|
101
|
+
@transport.send_request(method, params, session_id: @session_id, related_request_id: related_request_id)
|
|
102
|
+
else
|
|
103
|
+
@transport.send_request(method, params)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
78
106
|
end
|
|
79
107
|
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
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.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -28,7 +28,9 @@ 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
|
|
@@ -76,7 +78,7 @@ licenses:
|
|
|
76
78
|
- Apache-2.0
|
|
77
79
|
metadata:
|
|
78
80
|
allowed_push_host: https://rubygems.org
|
|
79
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.
|
|
81
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.12.0
|
|
80
82
|
homepage_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
81
83
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
82
84
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|