Merge pull request #42 from kfischer-okarin/extract-capabilities-object
Refactor: Extract Capabilities object
This commit is contained in:
commit
d2c4d3ec18
3 changed files with 303 additions and 13 deletions
|
|
@ -3,6 +3,7 @@
|
|||
require "json_rpc_handler"
|
||||
require_relative "instrumentation"
|
||||
require_relative "methods"
|
||||
require_relative "server/capabilities"
|
||||
|
||||
module MCP
|
||||
class Server
|
||||
|
|
@ -45,6 +46,10 @@ module MCP
|
|||
@resource_index = index_resources_by_uri(resources)
|
||||
@server_context = server_context
|
||||
@configuration = MCP.configuration.merge(configuration)
|
||||
@capabilities = Capabilities.new(capabilities)
|
||||
@capabilities.support_tools if tools.any?
|
||||
@capabilities.support_prompts if prompts.any?
|
||||
@capabilities.support_resources if resources.any? || resource_templates.any?
|
||||
|
||||
@handlers = {
|
||||
Methods::RESOURCES_LIST => method(:list_resources),
|
||||
|
|
@ -66,7 +71,7 @@ module MCP
|
|||
end
|
||||
|
||||
def capabilities
|
||||
@capabilities ||= determine_capabilities
|
||||
@capabilities.to_h
|
||||
end
|
||||
|
||||
def handle(request)
|
||||
|
|
@ -92,6 +97,7 @@ module MCP
|
|||
end
|
||||
|
||||
def resources_list_handler(&block)
|
||||
@capabilities.support_resources
|
||||
@handlers[Methods::RESOURCES_LIST] = block
|
||||
end
|
||||
|
||||
|
|
@ -100,10 +106,12 @@ module MCP
|
|||
end
|
||||
|
||||
def resources_templates_list_handler(&block)
|
||||
@capabilities.support_resources
|
||||
@handlers[Methods::RESOURCES_TEMPLATES_LIST] = block
|
||||
end
|
||||
|
||||
def tools_list_handler(&block)
|
||||
@capabilities.support_tools
|
||||
@handlers[Methods::TOOLS_LIST] = block
|
||||
end
|
||||
|
||||
|
|
@ -112,6 +120,7 @@ module MCP
|
|||
end
|
||||
|
||||
def prompts_list_handler(&block)
|
||||
@capabilities.support_prompts
|
||||
@handlers[Methods::PROMPTS_LIST] = block
|
||||
end
|
||||
|
||||
|
|
@ -159,18 +168,6 @@ module MCP
|
|||
}
|
||||
end
|
||||
|
||||
def determine_capabilities
|
||||
defines_prompts = @prompts.any? || @handlers[Methods::PROMPTS_LIST] != method(:list_prompts)
|
||||
defines_tools = @tools.any? || @handlers[Methods::TOOLS_LIST] != method(:list_tools)
|
||||
defines_resources = @resources.any? || @handlers[Methods::RESOURCES_LIST] != method(:list_resources)
|
||||
defines_resource_templates = @resource_templates.any? || @handlers[Methods::RESOURCES_TEMPLATES_LIST] != method(:list_resource_templates)
|
||||
{
|
||||
prompts: defines_prompts ? {} : nil,
|
||||
resources: defines_resources || defines_resource_templates ? {} : nil,
|
||||
tools: defines_tools ? {} : nil,
|
||||
}.compact
|
||||
end
|
||||
|
||||
def server_info
|
||||
@server_info ||= {
|
||||
name:,
|
||||
|
|
|
|||
96
lib/mcp/server/capabilities.rb
Normal file
96
lib/mcp/server/capabilities.rb
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MCP
|
||||
class Server
|
||||
class Capabilities
|
||||
def initialize(capabilities_hash = nil)
|
||||
@completions = nil
|
||||
@experimental = 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_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
|
||||
|
||||
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,
|
||||
logging: @logging,
|
||||
prompts: @prompts,
|
||||
resources: @resources,
|
||||
tools: @tools,
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
197
test/mcp/server/capabilities_test.rb
Normal file
197
test/mcp/server/capabilities_test.rb
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
module MCP
|
||||
class Server
|
||||
class CapabilitiesTest < ActiveSupport::TestCase
|
||||
test "can be initialized with a capabilities hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(prompts: {})
|
||||
|
||||
assert_equal({ prompts: {} }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "ignores unknown values in capabilities hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(prompts: {}, invalid: :value)
|
||||
|
||||
assert_equal({ prompts: {} }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "support prompts" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_prompts
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :prompts
|
||||
end
|
||||
|
||||
test "support resources" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_resources
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :resources
|
||||
end
|
||||
|
||||
test "support tools" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_tools
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :tools
|
||||
end
|
||||
|
||||
test "support completions" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_completions
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :completions
|
||||
assert_empty(capabilities.to_h[:completions])
|
||||
end
|
||||
|
||||
test "support logging" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_logging
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :logging
|
||||
assert_empty(capabilities.to_h[:logging])
|
||||
end
|
||||
|
||||
test "support experimental with custom config" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_experimental({ myFeature: { enabled: true } })
|
||||
|
||||
assert_operator capabilities.to_h, :key?, :experimental
|
||||
assert_equal({ myFeature: { enabled: true } }, capabilities.to_h[:experimental])
|
||||
end
|
||||
|
||||
test "support prompts list changed" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_prompts_list_changed
|
||||
|
||||
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "support resources list changed" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_resources_list_changed
|
||||
|
||||
assert_equal({ resources: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "support resources subscribe" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_resources_subscribe
|
||||
|
||||
assert_equal({ resources: { subscribe: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "support resources with both list changed and subscribe" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_resources_list_changed
|
||||
capabilities.support_resources_subscribe
|
||||
|
||||
assert_equal({ resources: { listChanged: true, subscribe: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "support tools list changed" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_tools_list_changed
|
||||
|
||||
assert_equal({ tools: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with prompts list changed from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(prompts: { listChanged: true })
|
||||
|
||||
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with resources list changed and subscribe from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(resources: { listChanged: true, subscribe: true })
|
||||
|
||||
assert_equal({ resources: { listChanged: true, subscribe: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with tools list changed from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(tools: { listChanged: true })
|
||||
|
||||
assert_equal({ tools: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with completions from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(completions: {})
|
||||
|
||||
assert_equal({ completions: {} }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with logging from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(logging: {})
|
||||
|
||||
assert_equal({ logging: {} }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with experimental config from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(experimental: { feature1: { enabled: true }, feature2: "config" })
|
||||
|
||||
assert_equal({ experimental: { feature1: { enabled: true }, feature2: "config" } }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "initializes with all capabilities from hash" do
|
||||
capabilities = MCP::Server::Capabilities.new(
|
||||
completions: {},
|
||||
experimental: { customFeature: true },
|
||||
logging: {},
|
||||
prompts: { listChanged: true },
|
||||
resources: { listChanged: true, subscribe: true },
|
||||
tools: { listChanged: true },
|
||||
)
|
||||
|
||||
expected = {
|
||||
completions: {},
|
||||
experimental: { customFeature: true },
|
||||
logging: {},
|
||||
prompts: { listChanged: true },
|
||||
resources: { listChanged: true, subscribe: true },
|
||||
tools: { listChanged: true },
|
||||
}
|
||||
|
||||
assert_equal(expected, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "handles nil values in capabilities hash gracefully" do
|
||||
capabilities = MCP::Server::Capabilities.new(
|
||||
prompts: nil,
|
||||
resources: nil,
|
||||
tools: nil,
|
||||
)
|
||||
|
||||
assert_equal({ prompts: {}, resources: {}, tools: {} }, capabilities.to_h)
|
||||
end
|
||||
|
||||
test "to_h returns empty hash when no capabilities are set" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
assert_empty(capabilities.to_h)
|
||||
end
|
||||
|
||||
test "calling support methods multiple times does not duplicate entries" do
|
||||
capabilities = MCP::Server::Capabilities.new
|
||||
|
||||
capabilities.support_prompts
|
||||
capabilities.support_prompts
|
||||
capabilities.support_prompts_list_changed
|
||||
|
||||
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue