ruby-sdk/test/mcp/server/capabilities_test.rb
Koichi ITO 2c62139154 Support Capability Extensions per SEP-2133
## 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.
2026-06-14 17:20:45 +09:00

71 lines
2.1 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
module MCP
class Server
class CapabilitiesTest < ActiveSupport::TestCase
test "to_h omits everything by default" do
assert_empty(Capabilities.new.to_h)
end
test "constructor accepts a capabilities hash" do
capabilities = Capabilities.new(
completions: {},
logging: {},
prompts: { listChanged: true },
resources: { listChanged: true, subscribe: true },
tools: { listChanged: true },
)
assert_equal(
{
completions: {},
logging: {},
prompts: { listChanged: true },
resources: { listChanged: true, subscribe: true },
tools: { listChanged: true },
},
capabilities.to_h,
)
end
test "constructor accepts extensions" do
capabilities = Capabilities.new(
tools: {},
extensions: { "com.example/feature" => { enabled: true } },
)
assert_equal({ "com.example/feature" => { enabled: true } }, capabilities.to_h[:extensions])
end
test "support_extensions merges repeated declarations" do
capabilities = Capabilities.new
capabilities.support_extensions("com.example/feature" => { enabled: true })
capabilities.support_extensions("io.modelcontextprotocol/tasks" => {})
assert_equal(
{ "com.example/feature" => { enabled: true }, "io.modelcontextprotocol/tasks" => {} },
capabilities.to_h[:extensions],
)
end
test "support_extensions with no arguments declares an empty extensions object" do
capabilities = Capabilities.new
capabilities.support_extensions
assert_equal({}, capabilities.to_h[:extensions])
end
test "support_extensions tolerates nil" do
capabilities = Capabilities.new(extensions: nil)
assert_equal({}, capabilities.to_h[:extensions])
end
test "to_h omits extensions when never declared" do
refute(Capabilities.new(tools: {}).to_h.key?(:extensions))
end
end
end
end