Skip to content

Commit 73cec83

Browse files
tuntisztopherbullockatesgoral
authored
Add support for _meta field on tool schema (#124)
* Add support for _meta field on tool schema * use `meta` naming for `_meta` field on tool schema * apply suggestions to fix `_meta` tests Co-authored-by: Ateş Göral <[email protected]> * Update lib/mcp/tool.rb Co-authored-by: Ateş Göral <[email protected]> * Update lib/mcp/tool.rb Co-authored-by: Ateş Göral <[email protected]> --------- Co-authored-by: Topher Bullock <[email protected]> Co-authored-by: Ateş Göral <[email protected]>
1 parent c0f7605 commit 73cec83

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

lib/mcp/server.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ def handle_json(request)
9696
end
9797
end
9898

99-
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, &block)
100-
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, &block)
99+
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, meta: nil, &block)
100+
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, meta:, &block)
101101
@tools[tool.name_value] = tool
102102

103103
validate!

lib/mcp/tool.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class << self
88
attr_reader :title_value
99
attr_reader :description_value
1010
attr_reader :annotations_value
11+
attr_reader :meta_value
1112

1213
def call(*args, server_context: nil)
1314
raise NotImplementedError, "Subclasses must implement call"
@@ -21,6 +22,7 @@ def to_h
2122
inputSchema: input_schema_value.to_h,
2223
outputSchema: @output_schema_value&.to_h,
2324
annotations: annotations_value&.to_h,
25+
_meta: meta_value,
2426
}.compact
2527
end
2628

@@ -32,6 +34,7 @@ def inherited(subclass)
3234
subclass.instance_variable_set(:@input_schema_value, nil)
3335
subclass.instance_variable_set(:@output_schema_value, nil)
3436
subclass.instance_variable_set(:@annotations_value, nil)
37+
subclass.instance_variable_set(:@meta_value, nil)
3538
end
3639

3740
def tool_name(value = NOT_SET)
@@ -90,6 +93,14 @@ def output_schema(value = NOT_SET)
9093
end
9194
end
9295

96+
def meta(value = NOT_SET)
97+
if value == NOT_SET
98+
@meta_value
99+
else
100+
@meta_value = value
101+
end
102+
end
103+
93104
def annotations(hash = NOT_SET)
94105
if hash == NOT_SET
95106
@annotations_value
@@ -98,12 +109,13 @@ def annotations(hash = NOT_SET)
98109
end
99110
end
100111

101-
def define(name: nil, title: nil, description: nil, input_schema: nil, output_schema: nil, annotations: nil, &block)
112+
def define(name: nil, title: nil, description: nil, input_schema: nil, output_schema: nil, meta: nil, annotations: nil, &block)
102113
Class.new(self) do
103114
tool_name name
104115
title title
105116
description description
106117
input_schema input_schema
118+
meta meta
107119
output_schema output_schema
108120
self.annotations(annotations) if annotations
109121
define_singleton_method(:call, &block) if block

test/mcp/server_test.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class ServerTest < ActiveSupport::TestCase
1010
name: "test_tool",
1111
title: "Test tool",
1212
description: "A test tool",
13+
meta: { foo: "bar" },
1314
)
1415

1516
@tool_that_raises = Tool.define(
@@ -195,6 +196,7 @@ class ServerTest < ActiveSupport::TestCase
195196
assert_equal "Test tool", result[:tools][0][:title]
196197
assert_equal "A test tool", result[:tools][0][:description]
197198
assert_equal({ type: "object" }, result[:tools][0][:inputSchema])
199+
assert_equal({ foo: "bar" }, result[:tools][0][:_meta])
198200
assert_instrumentation_data({ method: "tools/list" })
199201
end
200202

@@ -211,6 +213,7 @@ class ServerTest < ActiveSupport::TestCase
211213
assert_equal "test_tool", result[:tools][0][:name]
212214
assert_equal "Test tool", result[:tools][0][:title]
213215
assert_equal "A test tool", result[:tools][0][:description]
216+
assert_equal({ foo: "bar" }, result[:tools][0][:_meta])
214217
end
215218

216219
test "#tools_list_handler sets the tools/list handler" do
@@ -848,6 +851,7 @@ def call(message:, server_context: nil)
848851
name: "defined_tool",
849852
description: "Defined tool",
850853
input_schema: { type: "object", properties: { message: { type: "string" } }, required: ["message"] },
854+
meta: { foo: "bar" },
851855
) do |message:|
852856
Tool::Response.new(message)
853857
end

test/mcp/tool_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class TestTool < Tool
1515
read_only_hint: true,
1616
title: "Test Tool",
1717
)
18+
meta(
19+
foo: "bar",
20+
)
1821

1922
class << self
2023
def call(message:, server_context: nil)
@@ -52,6 +55,14 @@ def call(message:, server_context: nil)
5255
assert_equal expected_annotations, tool.to_h[:annotations]
5356
end
5457

58+
test "#to_h includes meta when present" do
59+
tool = TestTool
60+
expected_meta = {
61+
foo: "bar",
62+
}
63+
assert_equal expected_meta, tool.to_h[:_meta]
64+
end
65+
5566
test "#call invokes the tool block and returns the response" do
5667
tool = TestTool
5768
response = tool.call(message: "test")
@@ -152,6 +163,23 @@ class InputSchemaTool < Tool
152163
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
153164
end
154165

166+
test ".define allows definition of tools with meta" do
167+
tool = Tool.define(
168+
name: "mock_tool",
169+
title: "Mock Tool",
170+
description: "a mock tool for testing",
171+
meta: { foo: "bar" },
172+
) do |_|
173+
Tool::Response.new([{ type: "text", content: "OK" }])
174+
end
175+
176+
assert_equal "mock_tool", tool.name_value
177+
assert_equal "Mock Tool", tool.title
178+
assert_equal "a mock tool for testing", tool.description
179+
assert_equal tool.input_schema, Tool::InputSchema.new
180+
assert_equal({ foo: "bar" }, tool.meta_value)
181+
end
182+
155183
test "Tool class method annotations can be set and retrieved" do
156184
class AnnotationsTestTool < Tool
157185
tool_name "annotations_test"
@@ -180,6 +208,30 @@ class UpdatableAnnotationsTool < Tool
180208
assert_equal "Updated", tool.annotations_value.title
181209
end
182210

211+
test "Tool class method meta can be set and retrieved" do
212+
class MetaTestTool < Tool
213+
tool_name "meta_test"
214+
meta(foo: "bar")
215+
end
216+
217+
tool = MetaTestTool
218+
assert_instance_of Hash, tool.meta_value
219+
assert_equal "bar", tool.meta_value[:foo]
220+
end
221+
222+
test "Tool class method meta can be updated" do
223+
class UpdatableMetaTool < Tool
224+
tool_name "updatable_meta"
225+
end
226+
227+
tool = UpdatableMetaTool
228+
tool.meta(foo: "baz")
229+
assert_equal({ foo: "baz" }, tool.meta_value)
230+
231+
tool.meta(foo: "qux")
232+
assert_equal({ foo: "qux" }, tool.meta_value)
233+
end
234+
183235
test "#call with Sorbet typed tools invokes the tool block and returns the response" do
184236
class TypedTestTool < Tool
185237
tool_name "test_tool"

0 commit comments

Comments
 (0)