Skip to content

Commit fbd7e97

Browse files
committed
[WIP] refactor to configuration classes
1 parent 2e45d55 commit fbd7e97

File tree

11 files changed

+107
-216
lines changed

11 files changed

+107
-216
lines changed

lib/mcp/prompt.rb

Lines changed: 16 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,29 @@
22
# frozen_string_literal: true
33

44
module MCP
5-
class Prompt
6-
class << self
7-
NOT_SET = Object.new
8-
9-
attr_reader :description_value
10-
attr_reader :arguments_value
11-
12-
def template(args, server_context:)
13-
raise NotImplementedError, "Subclasses must implement template"
14-
end
15-
16-
def to_h
17-
{ name: name_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
18-
end
19-
20-
def inherited(subclass)
21-
super
22-
subclass.instance_variable_set(:@name_value, nil)
23-
subclass.instance_variable_set(:@description_value, nil)
24-
subclass.instance_variable_set(:@arguments_value, nil)
25-
end
26-
27-
def prompt_name(value = NOT_SET)
28-
if value == NOT_SET
29-
@name_value
30-
else
31-
@name_value = value
32-
end
33-
end
5+
module Prompt
6+
class Definition
7+
attr_reader :name, :description, :arguments, :to_h
348

35-
def name_value
36-
@name_value || StringUtils.handle_from_class_name(name)
37-
end
9+
def initialize(name:, description:, arguments:, &block)
10+
@name = name
11+
@description = description
12+
@arguments = arguments
13+
@block = block
3814

39-
def description(value = NOT_SET)
40-
if value == NOT_SET
41-
@description_value
42-
else
43-
@description_value = value
44-
end
45-
end
15+
@to_h = { name:, description:, arguments: arguments.map(&:to_h) }.compact.freeze
4616

47-
def arguments(value = NOT_SET)
48-
if value == NOT_SET
49-
@arguments_value
50-
else
51-
@arguments_value = value
52-
end
17+
freeze
5318
end
5419

55-
def define(name: nil, description: nil, arguments: [], &block)
56-
Class.new(self) do
57-
prompt_name name
58-
description description
59-
arguments arguments
60-
define_singleton_method(:template) do |args, server_context:|
61-
instance_exec(args, server_context:, &block)
62-
end
63-
end
20+
def call(args, server_context:)
21+
block.call(args, server_context:)
6422
end
23+
end
6524

66-
def validate_arguments!(args)
67-
missing = required_args - args.keys
68-
return if missing.empty?
69-
70-
raise MCP::Server::RequestHandlerError.new(
71-
"Missing required arguments: #{missing.join(", ")}", nil, error_type: :missing_required_arguments
72-
)
73-
end
74-
75-
private
76-
77-
def required_args
78-
arguments_value.filter_map { |arg| arg.name.to_sym if arg.required }
25+
class << self
26+
def define(name:, description: nil, arguments: [], &block)
27+
Definition.new(name:, description:, arguments:, &block)
7928
end
8029
end
8130
end

lib/mcp/prompt/argument.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
module MCP
55
class Prompt
66
class Argument
7-
attr_reader :name, :description, :required, :arguments
7+
attr_reader :name, :description, :required, :to_h
88

99
def initialize(name:, description: nil, required: false)
1010
@name = name
1111
@description = description
1212
@required = required
13-
@arguments = arguments
14-
end
1513

16-
def to_h
17-
{ name:, description:, required: }.compact
14+
@to_h = {
15+
name:,
16+
description:,
17+
required:,
18+
}.compact.freeze
19+
20+
freeze
1821
end
1922
end
2023
end

lib/mcp/resource/contents.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,25 @@ class TextContents < Contents
2020
attr_reader :text
2121

2222
def initialize(text:, uri:, mime_type:)
23-
super(uri: uri, mime_type: mime_type)
23+
super(uri:, mime_type:)
2424
@text = text
2525
end
2626

2727
def to_h
28-
super.merge(text: text)
28+
super.merge(text:)
2929
end
3030
end
3131

3232
class BlobContents < Contents
3333
attr_reader :data
3434

3535
def initialize(data:, uri:, mime_type:)
36-
super(uri: uri, mime_type: mime_type)
36+
super(uri:, mime_type:)
3737
@data = data
3838
end
3939

4040
def to_h
41-
super.merge(data: data)
41+
super.merge(data:)
4242
end
4343
end
4444
end

lib/mcp/resource/embedded.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class Embedded
77
attr_reader :resource, :annotations
88

99
def initialize(resource:, annotations: nil)
10+
@resource = resource
1011
@annotations = annotations
1112
end
1213

lib/mcp/resource_template.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33

44
module MCP
55
class ResourceTemplate
6-
attr_reader :uri_template, :name, :description, :mime_type
6+
attr_reader :uri_template, :name, :description, :mime_type, :to_h
77

88
def initialize(uri_template:, name:, description: nil, mime_type: nil)
99
@uri_template = uri_template
1010
@name = name
1111
@description = description
1212
@mime_type = mime_type
13-
end
1413

15-
def to_h
16-
{
14+
@to_h = {
1715
uriTemplate: @uri_template,
1816
name: @name,
1917
description: @description,
2018
mimeType: @mime_type,
21-
}.compact
19+
}.compact.freeze
20+
21+
freeze
2222
end
2323
end
2424
end

lib/mcp/server.rb

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -201,28 +201,24 @@ def call_tool(request)
201201
arguments = request[:arguments]
202202
add_instrumentation_data(tool_name:)
203203

204-
if tool.input_schema&.missing_required_arguments?(arguments)
205-
add_instrumentation_data(error: :missing_required_arguments)
206-
raise RequestHandlerError.new(
207-
"Missing required arguments: #{tool.input_schema.missing_required_arguments(arguments).join(", ")}",
208-
request,
209-
error_type: :missing_required_arguments,
210-
)
211-
end
204+
validate_tool_arguments!(tool, arguments, request)
212205

213206
begin
214-
call_params = tool_call_parameters(tool)
215-
216-
if call_params.include?(:server_context)
217-
tool.call(**arguments.transform_keys(&:to_sym), server_context:).to_h
218-
else
219-
tool.call(**arguments.transform_keys(&:to_sym)).to_h
220-
end
207+
tool.call(**arguments.transform_keys(&:to_sym), server_context:).to_h
221208
rescue => e
222209
raise RequestHandlerError.new("Internal error calling tool #{tool_name}", request, original_error: e)
223210
end
224211
end
225212

213+
def validate_tool_arguments!(tool, arguments, request)
214+
input_schema = tool.input_schema
215+
return unless input_schema
216+
217+
missing_arguments = input_schema.required - arguments.keys.map(&:to_sym)
218+
219+
missing_required_arguments!(missing_arguments, request) unless missing_arguments.empty?
220+
end
221+
226222
def list_prompts(request)
227223
add_instrumentation_data(method: Methods::PROMPTS_LIST)
228224
@prompts.map { |_, prompt| prompt.to_h }
@@ -240,9 +236,31 @@ def get_prompt(request)
240236
add_instrumentation_data(prompt_name:)
241237

242238
prompt_args = request[:arguments]
243-
prompt.validate_arguments!(prompt_args)
239+
validate_prompt_arguments!(prompt, prompt_args, request)
240+
241+
prompt.call(prompt_args, server_context:).to_h
242+
end
243+
244+
def validate_prompt_arguments!(prompt, provided_arguments, request)
245+
missing_arguments = prompt.arguments.filter_map do |configured_argument|
246+
next unless configured_argument.required?
247+
248+
key = configured_argument.name.to_sym
249+
next if provided_arguments.key?(key)
244250

245-
prompt.template(prompt_args, server_context:).to_h
251+
key
252+
end
253+
254+
missing_required_arguments!(missing_arguments, request) unless missing_arguments.empty?
255+
end
256+
257+
def missing_required_arguments!(missing_arguments, request)
258+
add_instrumentation_data(error: :missing_required_arguments)
259+
raise RequestHandlerError.new(
260+
"Missing required arguments: #{missing_arguments.join(", ")}",
261+
request,
262+
error_type: :missing_required_arguments,
263+
)
246264
end
247265

248266
def list_resources(request)
@@ -273,24 +291,5 @@ def index_resources_by_uri(resources)
273291
hash[resource.uri] = resource
274292
end
275293
end
276-
277-
def tool_call_parameters(tool)
278-
method_def = tool_call_method_def(tool)
279-
method_def.parameters.flatten
280-
end
281-
282-
def tool_call_method_def(tool)
283-
method = tool.method(:call)
284-
285-
if defined?(T::Utils) && T::Utils.respond_to?(:signature_for_method)
286-
sorbet_typed_method_definition = T::Utils.signature_for_method(method)&.method
287-
288-
# Return the Sorbet typed method definition if it exists, otherwise fallback to original method
289-
# definition if Sorbet is defined but not used by this tool.
290-
sorbet_typed_method_definition || method
291-
else
292-
method
293-
end
294-
end
295294
end
296295
end

lib/mcp/server/transports/stdio_transport.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ class Server
88
module Transports
99
class StdioTransport < Transport
1010
def initialize(server)
11-
@server = server
11+
super
1212
@open = false
1313
$stdin.set_encoding("UTF-8")
1414
$stdout.set_encoding("UTF-8")
15-
super
1615
end
1716

1817
def open

0 commit comments

Comments
 (0)