mcp 0.11.0 → 0.13.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.
data/lib/mcp/server.rb CHANGED
@@ -31,14 +31,27 @@ module MCP
31
31
  MAX_COMPLETION_VALUES = 100
32
32
 
33
33
  class RequestHandlerError < StandardError
34
- attr_reader :error_type
35
- attr_reader :original_error
34
+ attr_reader :error_type, :original_error, :error_code, :error_data
36
35
 
37
- def initialize(message, request, error_type: :internal_error, original_error: nil)
36
+ def initialize(message, request, error_type: :internal_error, original_error: nil, error_code: nil, error_data: nil)
38
37
  super(message)
39
38
  @request = request
40
39
  @error_type = error_type
41
40
  @original_error = original_error
41
+ @error_code = error_code
42
+ @error_data = error_data
43
+ end
44
+ end
45
+
46
+ class URLElicitationRequiredError < RequestHandlerError
47
+ def initialize(elicitations)
48
+ super(
49
+ "URL elicitation required",
50
+ nil,
51
+ error_type: :url_elicitation_required,
52
+ error_code: -32042,
53
+ error_data: { elicitations: elicitations },
54
+ )
42
55
  end
43
56
  end
44
57
 
@@ -114,7 +127,6 @@ module MCP
114
127
  # No op handlers for currently unsupported methods
115
128
  Methods::RESOURCES_SUBSCRIBE => ->(_) { {} },
116
129
  Methods::RESOURCES_UNSUBSCRIBE => ->(_) { {} },
117
- Methods::ELICITATION_CREATE => ->(_) {},
118
130
  }
119
131
  @transport = transport
120
132
  end
@@ -206,44 +218,6 @@ module MCP
206
218
  report_exception(e, { notification: "log_message" })
207
219
  end
208
220
 
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
-
247
221
  # Sets a custom handler for `resources/read` requests.
248
222
  # The block receives the parsed request params and should return resource
249
223
  # contents. The return value is set as the `contents` field of the response.
@@ -375,7 +349,7 @@ module MCP
375
349
  def handle_request(request, method, session: nil, related_request_id: nil)
376
350
  handler = @handlers[method]
377
351
  unless handler
378
- instrument_call("unsupported_method") do
352
+ instrument_call("unsupported_method", server_context: { request: request }) do
379
353
  client = session&.client || @client
380
354
  add_instrumentation_data(client: client) if client
381
355
  end
@@ -385,7 +359,12 @@ module MCP
385
359
  Methods.ensure_capability!(method, capabilities)
386
360
 
387
361
  ->(params) {
388
- instrument_call(method) do
362
+ reported_exception = nil
363
+ instrument_call(
364
+ method,
365
+ server_context: { request: request },
366
+ exception_already_reported: ->(e) { reported_exception.equal?(e) },
367
+ ) do
389
368
  result = case method
390
369
  when Methods::INITIALIZE
391
370
  init(params, session: session)
@@ -415,11 +394,14 @@ module MCP
415
394
  rescue RequestHandlerError => e
416
395
  report_exception(e.original_error || e, { request: request })
417
396
  add_instrumentation_data(error: e.error_type)
397
+ reported_exception = e
418
398
  raise e
419
399
  rescue => e
420
400
  report_exception(e, { request: request })
421
401
  add_instrumentation_data(error: :internal_error)
422
- raise RequestHandlerError.new("Internal error handling #{method} request", request, original_error: e)
402
+ wrapped = RequestHandlerError.new("Internal error handling #{method} request", request, original_error: e)
403
+ reported_exception = wrapped
404
+ raise wrapped
423
405
  end
424
406
  }
425
407
  end
@@ -43,6 +43,42 @@ module MCP
43
43
  end
44
44
  end
45
45
 
46
+ # Delegates to the session so the request is scoped to the originating client.
47
+ # Falls back to `@context` (via `method_missing`) when `@notification_target`
48
+ # does not support elicitation.
49
+ def create_form_elicitation(**kwargs)
50
+ if @notification_target.respond_to?(:create_form_elicitation)
51
+ @notification_target.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
52
+ elsif @context.respond_to?(:create_form_elicitation)
53
+ @context.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
54
+ else
55
+ raise NoMethodError, "undefined method 'create_form_elicitation' for #{self}"
56
+ end
57
+ end
58
+
59
+ # Delegates to the session so the request is scoped to the originating client.
60
+ # Falls back to `@context` when `@notification_target` does not support URL mode elicitation.
61
+ def create_url_elicitation(**kwargs)
62
+ if @notification_target.respond_to?(:create_url_elicitation)
63
+ @notification_target.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
64
+ elsif @context.respond_to?(:create_url_elicitation)
65
+ @context.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
66
+ else
67
+ raise NoMethodError, "undefined method 'create_url_elicitation' for #{self}"
68
+ end
69
+ end
70
+
71
+ # Delegates to the session so the notification is scoped to the originating client.
72
+ def notify_elicitation_complete(**kwargs)
73
+ if @notification_target.respond_to?(:notify_elicitation_complete)
74
+ @notification_target.notify_elicitation_complete(**kwargs)
75
+ elsif @context.respond_to?(:notify_elicitation_complete)
76
+ @context.notify_elicitation_complete(**kwargs)
77
+ else
78
+ raise NoMethodError, "undefined method 'notify_elicitation_complete' for #{self}"
79
+ end
80
+ end
81
+
46
82
  def method_missing(name, ...)
47
83
  if @context.respond_to?(name)
48
84
  @context.public_send(name, ...)
@@ -47,6 +47,35 @@ module MCP
47
47
  send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
48
48
  end
49
49
 
50
+ # Sends an `elicitation/create` request (form mode) scoped to this session.
51
+ def create_form_elicitation(message:, requested_schema:, related_request_id: nil)
52
+ unless client_capabilities&.dig(:elicitation)
53
+ raise "Client does not support elicitation. " \
54
+ "The client must declare the `elicitation` capability during initialization."
55
+ end
56
+
57
+ params = { mode: "form", message: message, requestedSchema: requested_schema }
58
+ send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: related_request_id)
59
+ end
60
+
61
+ # Sends an `elicitation/create` request (URL mode) scoped to this session.
62
+ def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil)
63
+ unless client_capabilities&.dig(:elicitation, :url)
64
+ raise "Client does not support URL mode elicitation. " \
65
+ "The client must declare the `elicitation.url` capability during initialization."
66
+ end
67
+
68
+ params = { mode: "url", message: message, url: url, elicitationId: elicitation_id }
69
+ send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: related_request_id)
70
+ end
71
+
72
+ # Sends an elicitation complete notification scoped to this session.
73
+ def notify_elicitation_complete(elicitation_id:)
74
+ send_to_transport(Methods::NOTIFICATIONS_ELICITATION_COMPLETE, { elicitationId: elicitation_id })
75
+ rescue => e
76
+ @server.report_exception(e, notification: "elicitation_complete")
77
+ end
78
+
50
79
  # Sends a progress notification to this session only.
51
80
  def notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil)
52
81
  params = {
@@ -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/transport.rb CHANGED
@@ -7,6 +7,7 @@ module MCP
7
7
  # Initialize the transport with the server instance
8
8
  def initialize(server)
9
9
  @server = server
10
+ server.transport = self
10
11
  end
11
12
 
12
13
  # Send a response to the client
data/lib/mcp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MCP
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
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.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Model Context Protocol
@@ -78,7 +78,7 @@ licenses:
78
78
  - Apache-2.0
79
79
  metadata:
80
80
  allowed_push_host: https://rubygems.org
81
- changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.11.0
81
+ changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.13.0
82
82
  homepage_uri: https://github.com/modelcontextprotocol/ruby-sdk
83
83
  source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
84
84
  bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues