Class: MCP::ServerContext

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp/server_context.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, progress:, notification_target:, related_request_id: nil, cancellation: nil, envelope: nil, input_responses: nil, request_state: nil) ⇒ ServerContext

Returns a new instance of ServerContext.



18
19
20
21
22
23
24
25
26
27
28
# File 'lib/mcp/server_context.rb', line 18

def initialize(context, progress:, notification_target:, related_request_id: nil, cancellation: nil, envelope: nil,
  input_responses: nil, request_state: nil)
  @context = context
  @progress = progress
  @notification_target = notification_target
  @related_request_id = related_request_id
  @cancellation = cancellation
  @envelope = envelope
  @input_responses = input_responses
  @request_state = request_state
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, **kwargs, &block) ⇒ Object

Forward arguments explicitly with *args, **kwargs, &block rather than the ... forwarding syntax. The gem supports Ruby 2.7.0 (see required_ruby_version), but RuboCop's Parser backend only runs on Ruby 2.7.8, so leading-argument forwarding like def method_missing(name, ...) is allowed by the linter even though it raises a SyntaxError on Ruby 2.7.0 through 2.7.2 (it was added in Ruby 2.7.3). Explicit forwarding keeps this method loadable on Ruby 2.7.0.



237
238
239
240
241
242
243
# File 'lib/mcp/server_context.rb', line 237

def method_missing(name, *args, **kwargs, &block)
  if @context.respond_to?(name)
    @context.public_send(name, *args, **kwargs, &block)
  else
    super
  end
end

Instance Attribute Details

#cancellationObject (readonly)

Returns the value of attribute cancellation.



5
6
7
# File 'lib/mcp/server_context.rb', line 5

def cancellation
  @cancellation
end

#envelopeObject (readonly)

The SEP-2575 per-request envelope (MCP::RequestEnvelope) when the request was classified as modern; nil on legacy requests.



9
10
11
# File 'lib/mcp/server_context.rb', line 9

def envelope
  @envelope
end

#input_responsesObject (readonly)

SEP-2322 multi round-trip retry fields, present when the client re-issued the request after an input_required result: input_responses maps the keys of the earlier inputRequests to the client's answers, and request_state is the opaque continuation string echoed back byte-exactly. Both are nil on a first-round request. Only handlers that opt in to server_context: can read them (the same access model as the envelope readers).



16
17
18
# File 'lib/mcp/server_context.rb', line 16

def input_responses
  @input_responses
end

#request_stateObject (readonly)

SEP-2322 multi round-trip retry fields, present when the client re-issued the request after an input_required result: input_responses maps the keys of the earlier inputRequests to the client's answers, and request_state is the opaque continuation string echoed back byte-exactly. Both are nil on a first-round request. Only handlers that opt in to server_context: can read them (the same access model as the envelope readers).



16
17
18
# File 'lib/mcp/server_context.rb', line 16

def request_state
  @request_state
end

Instance Method Details

#cancelled?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'lib/mcp/server_context.rb', line 38

def cancelled?
  !!@cancellation&.cancelled?
end

#client_capabilitiesObject

Client capabilities for the current request, with the same envelope-first resolution as #client_info.



61
62
63
64
65
# File 'lib/mcp/server_context.rb', line 61

def client_capabilities
  return @envelope.client_capabilities if @envelope

  @notification_target.client_capabilities if @notification_target.respond_to?(:client_capabilities)
end

#client_infoObject

Client identity for the current request. Modern requests carry it in the _meta envelope; legacy sessions fall back to the state stored by initialize. The envelope always wins because servers MUST NOT infer identity from prior requests.



54
55
56
57
58
# File 'lib/mcp/server_context.rb', line 54

def client_info
  return @envelope.client_info if @envelope

  @notification_target.client if @notification_target.respond_to?(:client)
end

#create_form_elicitation(**kwargs) ⇒ Object

Delegates to the session so the request is scoped to the originating client. Falls back to @context (via method_missing) when @notification_target does not support elicitation. The originating request id is stamped as a non-overridable related_request_id, as with create_sampling_message (SEP-2260).



197
198
199
200
201
202
203
204
205
# File 'lib/mcp/server_context.rb', line 197

def create_form_elicitation(**kwargs)
  if @notification_target.respond_to?(:create_form_elicitation)
    @notification_target.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
  elsif @context.respond_to?(:create_form_elicitation)
    @context.create_form_elicitation(**kwargs, related_request_id: @related_request_id)
  else
    raise NoMethodError, "undefined method 'create_form_elicitation' for #{self}"
  end
end

#create_sampling_message(**kwargs) ⇒ Object

Deprecated.

MCP Sampling (sampling/createMessage) is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider APIs instead.

Delegates to the session so the request is scoped to the originating client. Falls back to @context (via method_missing) when @notification_target does not support sampling.

The originating request id is stamped as related_request_id and cannot be overridden by callers (the literal keyword after **kwargs wins), satisfying SEP-2260's requirement that server-to-client requests be associated with the client request being processed.



182
183
184
185
186
187
188
189
190
# File 'lib/mcp/server_context.rb', line 182

def create_sampling_message(**kwargs)
  if @notification_target.respond_to?(:create_sampling_message)
    @notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
  elsif @context.respond_to?(:create_sampling_message)
    @context.create_sampling_message(**kwargs, related_request_id: @related_request_id)
  else
    raise NoMethodError, "undefined method 'create_sampling_message' for #{self}"
  end
end

#create_url_elicitation(**kwargs) ⇒ Object

Delegates to the session so the request is scoped to the originating client. Falls back to @context when @notification_target does not support URL mode elicitation. The originating request id is stamped as a non-overridable related_request_id, as with create_sampling_message (SEP-2260).



211
212
213
214
215
216
217
218
219
# File 'lib/mcp/server_context.rb', line 211

def create_url_elicitation(**kwargs)
  if @notification_target.respond_to?(:create_url_elicitation)
    @notification_target.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
  elsif @context.respond_to?(:create_url_elicitation)
    @context.create_url_elicitation(**kwargs, related_request_id: @related_request_id)
  else
    raise NoMethodError, "undefined method 'create_url_elicitation' for #{self}"
  end
end

#input_response(key) ⇒ Object

Reads one entry of #input_responses by its inputRequests key, tolerating symbol or string keys.



31
32
33
34
35
36
# File 'lib/mcp/server_context.rb', line 31

def input_response(key)
  return unless @input_responses.is_a?(Hash)

  value = @input_responses[key.to_sym]
  value.nil? ? @input_responses[key.to_s] : value
end

#list_roots(timeout: nil) ⇒ Object

Deprecated.

MCP Roots (roots/list and notifications/roots/list_changed) is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs, server configuration, or environment variables instead.

Delegates to the session so the request is scoped to the originating client. The originating request id is stamped as related_request_id, satisfying SEP-2260's requirement that server-to-client requests be associated with the client request being processed.



141
142
143
144
145
146
147
# File 'lib/mcp/server_context.rb', line 141

def list_roots(timeout: nil)
  if @notification_target.respond_to?(:list_roots)
    @notification_target.list_roots(related_request_id: @related_request_id, **timeout_kwarg(timeout))
  else
    raise NoMethodError, "undefined method 'list_roots' for #{self}"
  end
end

#modern?Boolean

Whether the current request follows the stateless modern lifecycle (SEP-2575).

Returns:

  • (Boolean)


47
48
49
# File 'lib/mcp/server_context.rb', line 47

def modern?
  !@envelope.nil?
end

#notify_elicitation_complete(**kwargs) ⇒ Object

Delegates to the session so the notification is scoped to the originating client.



222
223
224
225
226
227
228
229
230
# File 'lib/mcp/server_context.rb', line 222

def notify_elicitation_complete(**kwargs)
  if @notification_target.respond_to?(:notify_elicitation_complete)
    @notification_target.notify_elicitation_complete(**kwargs)
  elsif @context.respond_to?(:notify_elicitation_complete)
    @context.notify_elicitation_complete(**kwargs)
  else
    raise NoMethodError, "undefined method 'notify_elicitation_complete' for #{self}"
  end
end

#notify_log_message(data:, level:, logger: nil) ⇒ Object

Deprecated.

MCP Logging (logging/setLevel and notifications/message) is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). Use stderr or OpenTelemetry instead.

Sends a log message notification scoped to the originating session.

Parameters:

  • data (Object)

    The log data to send.

  • level (String)

    Log level (e.g., "debug", "info", "error").

  • logger (String, nil) (defaults to: nil)

    Logger name.



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/mcp/server_context.rb', line 110

def notify_log_message(data:, level:, logger: nil)
  return unless @notification_target

  # Modern requests opt in to logging per request (SEP-2575): without `io.modelcontextprotocol/logLevel` in `_meta`,
  # the server MUST NOT send any `notifications/message` for the request, and an insufficient level drops
  # the message the same way. Session- or server-level gating still applies downstream on delegation.
  if @envelope
    threshold = @envelope.log_level && LoggingMessageNotification.new(level: @envelope.log_level)
    return unless threshold&.valid_level? && threshold.should_notify?(level)
  end

  @notification_target.notify_log_message(data: data, level: level, logger: logger, related_request_id: @related_request_id)
end

#notify_resources_updated(uri:) ⇒ Object

Sends a resource updated notification scoped to the originating session.

Parameters:

  • uri (String)

    The URI of the updated resource.



127
128
129
130
131
# File 'lib/mcp/server_context.rb', line 127

def notify_resources_updated(uri:)
  return unless @notification_target

  @notification_target.notify_resources_updated(uri: uri)
end

#ping(timeout: nil) ⇒ Hash

Sends a ping request to the originating client to verify it is still responsive. Per the MCP spec, the client MUST respond promptly with an empty result.

Examples:

def self.call(server_context:)
  server_context.ping # => {}
  # ...
end

Returns:

  • (Hash)

    An empty hash on success.

Raises:

  • (Server::ValidationError)

    If the response result is not a Hash.

  • (NoMethodError)

    If the session does not support sending pings.

See Also:



163
164
165
166
167
168
169
# File 'lib/mcp/server_context.rb', line 163

def ping(timeout: nil)
  if @notification_target.respond_to?(:ping)
    @notification_target.ping(related_request_id: @related_request_id, **timeout_kwarg(timeout))
  else
    raise NoMethodError, "undefined method 'ping' for #{self}"
  end
end

#protocol_versionObject

The protocol version the current request was made with. nil on legacy requests, where the version is a session-level negotiation result rather than per-request data.



69
70
71
# File 'lib/mcp/server_context.rb', line 69

def protocol_version
  @envelope&.protocol_version
end

#raise_if_cancelled!Object



42
43
44
# File 'lib/mcp/server_context.rb', line 42

def raise_if_cancelled!
  @cancellation&.raise_if_cancelled!
end

#report_progress(progress, total: nil, message: nil) ⇒ Object

Reports progress for the current tool operation. The notification is automatically scoped to the originating session.

Parameters:

  • progress (Numeric)

    Current progress value.

  • total (Numeric, nil) (defaults to: nil)

    Total expected value.

  • message (String, nil) (defaults to: nil)

    Human-readable status message.



98
99
100
# File 'lib/mcp/server_context.rb', line 98

def report_progress(progress, total: nil, message: nil)
  @progress.report(progress, total: total, message: message)
end

#require_client_capability!(*path) ⇒ Object

Guards the current request on a declared client capability (SEP-2575). path names nested capability keys, e.g. require_client_capability!(:elicitation, :form). Raises Server::MissingRequiredClientCapabilityError (JSON-RPC error -32021 with data: { requiredCapabilities: ... }) when the capability was not declared.

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mcp/server_context.rb', line 76

def require_client_capability!(*path)
  raise ArgumentError, "at least one capability key is required" if path.empty?

  declared = client_capabilities
  value = path.reduce(declared) do |acc, key|
    break unless acc.is_a?(Hash)

    symbol_value = acc[key.to_sym]
    symbol_value.nil? ? acc[key.to_s] : symbol_value
  end
  return unless value.nil?

  required = path.reverse.inject({}) { |acc, key| { key.to_sym => acc } }
  raise Server::MissingRequiredClientCapabilityError, required
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/mcp/server_context.rb', line 245

def respond_to_missing?(name, include_private = false)
  @context.respond_to?(name) || super
end