## Motivation and Context SEP-2133 (modelcontextprotocol/modelcontextprotocol#2133, merged for the 2026-07-28 spec release) establishes the extensions framework for MCP: both `ClientCapabilities` and `ServerCapabilities` gain an optional `extensions` member keyed by reverse-DNS extension identifiers (e.g. `"io.modelcontextprotocol/tasks"`, `"com.example/feature"`), with arbitrary extension-defined configuration objects as values and an empty object meaning "supported with no settings". Official extensions such as MCP Apps (SEP-1865) and the Tasks extension (SEP-2663) are layered on this field. This mirrors the shape the TypeScript SDK merged (typescript-sdk#1630): plain declaration and passthrough, no registrar API and no key-format validation (the naming convention is documented instead). - `MCP::Server::Capabilities` gains `support_extensions` (repeated calls merge), accepts an `:extensions` key in its constructor hash, and includes `extensions` in `to_h`. The class was previously dead code: it was never required and `Server.new` only took plain hashes. It is now required by `lib/mcp/server.rb`, and `Server.new(capabilities:)` accepts either a plain Hash (as before) or a `Capabilities` instance. - Server-declared extensions appear in the `initialize` result; extensions the client declares during `initialize` were already stored verbatim and are readable via `Server#client_capabilities[:extensions]` and `ServerSession#client_capabilities[:extensions]` (now covered by tests). - `MCP::Client#connect(capabilities:)` already passes capabilities through verbatim; its documentation now covers the `extensions` member. Resolves #378. ## How Has This Been Tested? - New `test/mcp/server/capabilities_test.rb` covers the previously untested `Capabilities` class: empty default, constructor round-trip, extensions via constructor and `support_extensions`, merge semantics, nil tolerance, and omission from `to_h` when never declared. - New tests in `test/mcp/server_test.rb` assert that the `initialize` result carries declared extensions (from both a plain hash and a `Capabilities` instance) and that client-declared extensions are readable via `Server#client_capabilities` and `ServerSession#client_capabilities`. ## Breaking Changes None. `extensions` is additive and appears on the wire only when declared; servers constructed with plain capability hashes behave exactly as before.
110 lines
3.2 KiB
Ruby
110 lines
3.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module MCP
|
|
class Server
|
|
class Capabilities
|
|
def initialize(capabilities_hash = nil)
|
|
@completions = nil
|
|
@experimental = nil
|
|
@extensions = nil
|
|
@logging = nil
|
|
@prompts = nil
|
|
@resources = nil
|
|
@tools = nil
|
|
|
|
if capabilities_hash
|
|
support_completions if capabilities_hash.key?(:completions)
|
|
support_experimental(capabilities_hash[:experimental]) if capabilities_hash.key?(:experimental)
|
|
support_extensions(capabilities_hash[:extensions]) if capabilities_hash.key?(:extensions)
|
|
support_logging if capabilities_hash.key?(:logging)
|
|
|
|
if capabilities_hash.key?(:prompts)
|
|
support_prompts
|
|
prompts_config = capabilities_hash[:prompts] || {}
|
|
support_prompts_list_changed if prompts_config[:listChanged]
|
|
end
|
|
|
|
if capabilities_hash.key?(:resources)
|
|
support_resources
|
|
resources_config = capabilities_hash[:resources] || {}
|
|
support_resources_list_changed if resources_config[:listChanged]
|
|
support_resources_subscribe if resources_config[:subscribe]
|
|
end
|
|
|
|
if capabilities_hash.key?(:tools)
|
|
support_tools
|
|
tools_config = capabilities_hash[:tools] || {}
|
|
support_tools_list_changed if tools_config[:listChanged]
|
|
end
|
|
end
|
|
end
|
|
|
|
def support_completions
|
|
@completions ||= {}
|
|
end
|
|
|
|
def support_experimental(config = {})
|
|
@experimental = config || {}
|
|
end
|
|
|
|
# Declares support for capability extensions per SEP-2133. Keys are
|
|
# extension identifiers using the reverse-DNS prefix convention
|
|
# (e.g. `"io.modelcontextprotocol/tasks"`, `"com.example/feature"`);
|
|
# values are arbitrary extension-defined configuration objects,
|
|
# with an empty hash meaning "supported with no settings".
|
|
# Repeated calls merge, so several extensions can be declared independently.
|
|
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
|
|
def support_extensions(extensions = {})
|
|
@extensions = (@extensions || {}).merge(extensions || {})
|
|
end
|
|
|
|
def support_logging
|
|
@logging ||= {}
|
|
end
|
|
|
|
def support_prompts
|
|
@prompts ||= {}
|
|
end
|
|
|
|
def support_prompts_list_changed
|
|
support_prompts
|
|
@prompts[:listChanged] = true
|
|
end
|
|
|
|
def support_resources
|
|
@resources ||= {}
|
|
end
|
|
|
|
def support_resources_list_changed
|
|
support_resources
|
|
@resources[:listChanged] = true
|
|
end
|
|
|
|
def support_resources_subscribe
|
|
support_resources
|
|
@resources[:subscribe] = true
|
|
end
|
|
|
|
def support_tools
|
|
@tools ||= {}
|
|
end
|
|
|
|
def support_tools_list_changed
|
|
support_tools
|
|
@tools[:listChanged] = true
|
|
end
|
|
|
|
def to_h
|
|
{
|
|
completions: @completions,
|
|
experimental: @experimental,
|
|
extensions: @extensions,
|
|
logging: @logging,
|
|
prompts: @prompts,
|
|
resources: @resources,
|
|
tools: @tools,
|
|
}.compact
|
|
end
|
|
end
|
|
end
|
|
end
|