Skip to content

Commit d2c4d3e

Browse files
authored
Merge pull request #42 from kfischer-okarin/extract-capabilities-object
Refactor: Extract Capabilities object
2 parents 5649f03 + 4a666ed commit d2c4d3e

File tree

3 files changed

+303
-13
lines changed

3 files changed

+303
-13
lines changed

lib/mcp/server.rb

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "json_rpc_handler"
44
require_relative "instrumentation"
55
require_relative "methods"
6+
require_relative "server/capabilities"
67

78
module MCP
89
class Server
@@ -45,6 +46,10 @@ def initialize(
4546
@resource_index = index_resources_by_uri(resources)
4647
@server_context = server_context
4748
@configuration = MCP.configuration.merge(configuration)
49+
@capabilities = Capabilities.new(capabilities)
50+
@capabilities.support_tools if tools.any?
51+
@capabilities.support_prompts if prompts.any?
52+
@capabilities.support_resources if resources.any? || resource_templates.any?
4853

4954
@handlers = {
5055
Methods::RESOURCES_LIST => method(:list_resources),
@@ -66,7 +71,7 @@ def initialize(
6671
end
6772

6873
def capabilities
69-
@capabilities ||= determine_capabilities
74+
@capabilities.to_h
7075
end
7176

7277
def handle(request)
@@ -92,6 +97,7 @@ def define_prompt(name: nil, description: nil, arguments: [], &block)
9297
end
9398

9499
def resources_list_handler(&block)
100+
@capabilities.support_resources
95101
@handlers[Methods::RESOURCES_LIST] = block
96102
end
97103

@@ -100,10 +106,12 @@ def resources_read_handler(&block)
100106
end
101107

102108
def resources_templates_list_handler(&block)
109+
@capabilities.support_resources
103110
@handlers[Methods::RESOURCES_TEMPLATES_LIST] = block
104111
end
105112

106113
def tools_list_handler(&block)
114+
@capabilities.support_tools
107115
@handlers[Methods::TOOLS_LIST] = block
108116
end
109117

@@ -112,6 +120,7 @@ def tools_call_handler(&block)
112120
end
113121

114122
def prompts_list_handler(&block)
123+
@capabilities.support_prompts
115124
@handlers[Methods::PROMPTS_LIST] = block
116125
end
117126

@@ -159,18 +168,6 @@ def handle_request(request, method)
159168
}
160169
end
161170

162-
def determine_capabilities
163-
defines_prompts = @prompts.any? || @handlers[Methods::PROMPTS_LIST] != method(:list_prompts)
164-
defines_tools = @tools.any? || @handlers[Methods::TOOLS_LIST] != method(:list_tools)
165-
defines_resources = @resources.any? || @handlers[Methods::RESOURCES_LIST] != method(:list_resources)
166-
defines_resource_templates = @resource_templates.any? || @handlers[Methods::RESOURCES_TEMPLATES_LIST] != method(:list_resource_templates)
167-
{
168-
prompts: defines_prompts ? {} : nil,
169-
resources: defines_resources || defines_resource_templates ? {} : nil,
170-
tools: defines_tools ? {} : nil,
171-
}.compact
172-
end
173-
174171
def server_info
175172
@server_info ||= {
176173
name:,

lib/mcp/server/capabilities.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# frozen_string_literal: true
2+
3+
module MCP
4+
class Server
5+
class Capabilities
6+
def initialize(capabilities_hash = nil)
7+
@completions = nil
8+
@experimental = nil
9+
@logging = nil
10+
@prompts = nil
11+
@resources = nil
12+
@tools = nil
13+
14+
if capabilities_hash
15+
support_completions if capabilities_hash.key?(:completions)
16+
support_experimental(capabilities_hash[:experimental]) if capabilities_hash.key?(:experimental)
17+
support_logging if capabilities_hash.key?(:logging)
18+
19+
if capabilities_hash.key?(:prompts)
20+
support_prompts
21+
prompts_config = capabilities_hash[:prompts] || {}
22+
support_prompts_list_changed if prompts_config[:listChanged]
23+
end
24+
25+
if capabilities_hash.key?(:resources)
26+
support_resources
27+
resources_config = capabilities_hash[:resources] || {}
28+
support_resources_list_changed if resources_config[:listChanged]
29+
support_resources_subscribe if resources_config[:subscribe]
30+
end
31+
32+
if capabilities_hash.key?(:tools)
33+
support_tools
34+
tools_config = capabilities_hash[:tools] || {}
35+
support_tools_list_changed if tools_config[:listChanged]
36+
end
37+
end
38+
end
39+
40+
def support_completions
41+
@completions ||= {}
42+
end
43+
44+
def support_experimental(config = {})
45+
@experimental = config || {}
46+
end
47+
48+
def support_logging
49+
@logging ||= {}
50+
end
51+
52+
def support_prompts
53+
@prompts ||= {}
54+
end
55+
56+
def support_prompts_list_changed
57+
support_prompts
58+
@prompts[:listChanged] = true
59+
end
60+
61+
def support_resources
62+
@resources ||= {}
63+
end
64+
65+
def support_resources_list_changed
66+
support_resources
67+
@resources[:listChanged] = true
68+
end
69+
70+
def support_resources_subscribe
71+
support_resources
72+
@resources[:subscribe] = true
73+
end
74+
75+
def support_tools
76+
@tools ||= {}
77+
end
78+
79+
def support_tools_list_changed
80+
support_tools
81+
@tools[:listChanged] = true
82+
end
83+
84+
def to_h
85+
{
86+
completions: @completions,
87+
experimental: @experimental,
88+
logging: @logging,
89+
prompts: @prompts,
90+
resources: @resources,
91+
tools: @tools,
92+
}.compact
93+
end
94+
end
95+
end
96+
end

test/mcp/server/capabilities_test.rb

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
module MCP
6+
class Server
7+
class CapabilitiesTest < ActiveSupport::TestCase
8+
test "can be initialized with a capabilities hash" do
9+
capabilities = MCP::Server::Capabilities.new(prompts: {})
10+
11+
assert_equal({ prompts: {} }, capabilities.to_h)
12+
end
13+
14+
test "ignores unknown values in capabilities hash" do
15+
capabilities = MCP::Server::Capabilities.new(prompts: {}, invalid: :value)
16+
17+
assert_equal({ prompts: {} }, capabilities.to_h)
18+
end
19+
20+
test "support prompts" do
21+
capabilities = MCP::Server::Capabilities.new
22+
23+
capabilities.support_prompts
24+
25+
assert_operator capabilities.to_h, :key?, :prompts
26+
end
27+
28+
test "support resources" do
29+
capabilities = MCP::Server::Capabilities.new
30+
31+
capabilities.support_resources
32+
33+
assert_operator capabilities.to_h, :key?, :resources
34+
end
35+
36+
test "support tools" do
37+
capabilities = MCP::Server::Capabilities.new
38+
39+
capabilities.support_tools
40+
41+
assert_operator capabilities.to_h, :key?, :tools
42+
end
43+
44+
test "support completions" do
45+
capabilities = MCP::Server::Capabilities.new
46+
47+
capabilities.support_completions
48+
49+
assert_operator capabilities.to_h, :key?, :completions
50+
assert_empty(capabilities.to_h[:completions])
51+
end
52+
53+
test "support logging" do
54+
capabilities = MCP::Server::Capabilities.new
55+
56+
capabilities.support_logging
57+
58+
assert_operator capabilities.to_h, :key?, :logging
59+
assert_empty(capabilities.to_h[:logging])
60+
end
61+
62+
test "support experimental with custom config" do
63+
capabilities = MCP::Server::Capabilities.new
64+
65+
capabilities.support_experimental({ myFeature: { enabled: true } })
66+
67+
assert_operator capabilities.to_h, :key?, :experimental
68+
assert_equal({ myFeature: { enabled: true } }, capabilities.to_h[:experimental])
69+
end
70+
71+
test "support prompts list changed" do
72+
capabilities = MCP::Server::Capabilities.new
73+
74+
capabilities.support_prompts_list_changed
75+
76+
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
77+
end
78+
79+
test "support resources list changed" do
80+
capabilities = MCP::Server::Capabilities.new
81+
82+
capabilities.support_resources_list_changed
83+
84+
assert_equal({ resources: { listChanged: true } }, capabilities.to_h)
85+
end
86+
87+
test "support resources subscribe" do
88+
capabilities = MCP::Server::Capabilities.new
89+
90+
capabilities.support_resources_subscribe
91+
92+
assert_equal({ resources: { subscribe: true } }, capabilities.to_h)
93+
end
94+
95+
test "support resources with both list changed and subscribe" do
96+
capabilities = MCP::Server::Capabilities.new
97+
98+
capabilities.support_resources_list_changed
99+
capabilities.support_resources_subscribe
100+
101+
assert_equal({ resources: { listChanged: true, subscribe: true } }, capabilities.to_h)
102+
end
103+
104+
test "support tools list changed" do
105+
capabilities = MCP::Server::Capabilities.new
106+
107+
capabilities.support_tools_list_changed
108+
109+
assert_equal({ tools: { listChanged: true } }, capabilities.to_h)
110+
end
111+
112+
test "initializes with prompts list changed from hash" do
113+
capabilities = MCP::Server::Capabilities.new(prompts: { listChanged: true })
114+
115+
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
116+
end
117+
118+
test "initializes with resources list changed and subscribe from hash" do
119+
capabilities = MCP::Server::Capabilities.new(resources: { listChanged: true, subscribe: true })
120+
121+
assert_equal({ resources: { listChanged: true, subscribe: true } }, capabilities.to_h)
122+
end
123+
124+
test "initializes with tools list changed from hash" do
125+
capabilities = MCP::Server::Capabilities.new(tools: { listChanged: true })
126+
127+
assert_equal({ tools: { listChanged: true } }, capabilities.to_h)
128+
end
129+
130+
test "initializes with completions from hash" do
131+
capabilities = MCP::Server::Capabilities.new(completions: {})
132+
133+
assert_equal({ completions: {} }, capabilities.to_h)
134+
end
135+
136+
test "initializes with logging from hash" do
137+
capabilities = MCP::Server::Capabilities.new(logging: {})
138+
139+
assert_equal({ logging: {} }, capabilities.to_h)
140+
end
141+
142+
test "initializes with experimental config from hash" do
143+
capabilities = MCP::Server::Capabilities.new(experimental: { feature1: { enabled: true }, feature2: "config" })
144+
145+
assert_equal({ experimental: { feature1: { enabled: true }, feature2: "config" } }, capabilities.to_h)
146+
end
147+
148+
test "initializes with all capabilities from hash" do
149+
capabilities = MCP::Server::Capabilities.new(
150+
completions: {},
151+
experimental: { customFeature: true },
152+
logging: {},
153+
prompts: { listChanged: true },
154+
resources: { listChanged: true, subscribe: true },
155+
tools: { listChanged: true },
156+
)
157+
158+
expected = {
159+
completions: {},
160+
experimental: { customFeature: true },
161+
logging: {},
162+
prompts: { listChanged: true },
163+
resources: { listChanged: true, subscribe: true },
164+
tools: { listChanged: true },
165+
}
166+
167+
assert_equal(expected, capabilities.to_h)
168+
end
169+
170+
test "handles nil values in capabilities hash gracefully" do
171+
capabilities = MCP::Server::Capabilities.new(
172+
prompts: nil,
173+
resources: nil,
174+
tools: nil,
175+
)
176+
177+
assert_equal({ prompts: {}, resources: {}, tools: {} }, capabilities.to_h)
178+
end
179+
180+
test "to_h returns empty hash when no capabilities are set" do
181+
capabilities = MCP::Server::Capabilities.new
182+
183+
assert_empty(capabilities.to_h)
184+
end
185+
186+
test "calling support methods multiple times does not duplicate entries" do
187+
capabilities = MCP::Server::Capabilities.new
188+
189+
capabilities.support_prompts
190+
capabilities.support_prompts
191+
capabilities.support_prompts_list_changed
192+
193+
assert_equal({ prompts: { listChanged: true } }, capabilities.to_h)
194+
end
195+
end
196+
end
197+
end

0 commit comments

Comments
 (0)