Skip to content

Commit 3127bb6

Browse files
committed
Add support for _meta field on tool schema
1 parent eb0d9c0 commit 3127bb6

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
@@ -99,8 +99,8 @@ def handle_json(request)
9999
end
100100
end
101101

102-
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, &block)
103-
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, &block)
102+
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, metadata: nil, &block)
103+
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, metadata:, &block)
104104
@tools[tool.name_value] = tool
105105
end
106106

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 :metadata_value
1112

1213
def call(*args, server_context: nil)
1314
raise NotImplementedError, "Subclasses must implement call"
@@ -20,6 +21,7 @@ def to_h
2021
description: description_value,
2122
inputSchema: input_schema_value.to_h,
2223
}
24+
result[:_meta] = metadata_value if metadata_value
2325
result[:annotations] = annotations_value.to_h if annotations_value
2426
result
2527
end
@@ -31,6 +33,7 @@ def inherited(subclass)
3133
subclass.instance_variable_set(:@description_value, nil)
3234
subclass.instance_variable_set(:@input_schema_value, nil)
3335
subclass.instance_variable_set(:@annotations_value, nil)
36+
subclass.instance_variable_set(:@metadata_value, nil)
3437
end
3538

3639
def tool_name(value = NOT_SET)
@@ -77,6 +80,14 @@ def input_schema(value = NOT_SET)
7780
end
7881
end
7982

83+
def metadata(value = NOT_SET)
84+
if value == NOT_SET
85+
@metadata_value
86+
else
87+
@metadata_value = value
88+
end
89+
end
90+
8091
def annotations(hash = NOT_SET)
8192
if hash == NOT_SET
8293
@annotations_value
@@ -85,12 +96,13 @@ def annotations(hash = NOT_SET)
8596
end
8697
end
8798

88-
def define(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, &block)
99+
def define(name: nil, title: nil, description: nil, input_schema: nil, metadata: nil, annotations: nil, &block)
89100
Class.new(self) do
90101
tool_name name
91102
title title
92103
description description
93104
input_schema input_schema
105+
metadata metadata
94106
self.annotations(annotations) if annotations
95107
define_singleton_method(:call, &block) if block
96108
end

test/mcp/server_test.rb

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

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

@@ -212,6 +214,7 @@ class ServerTest < ActiveSupport::TestCase
212214
assert_equal "test_tool", result[:tools][0][:name]
213215
assert_equal "Test tool", result[:tools][0][:title]
214216
assert_equal "A test tool", result[:tools][0][:description]
217+
assert_equal({ foo: "bar" }, result[:tools][0][:_meta])
215218
end
216219

217220
test "#tools_list_handler sets the tools/list handler" do
@@ -827,6 +830,7 @@ def call(message:, server_context: nil)
827830
name: "defined_tool",
828831
description: "Defined tool",
829832
input_schema: { type: "object", properties: { message: { type: "string" } }, required: ["message"] },
833+
metadata: { foo: "bar" },
830834
) do |message:|
831835
Tool::Response.new(message)
832836
end

test/mcp/tool_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ class TestTool < Tool
1616
read_only_hint: true,
1717
title: "Test Tool",
1818
)
19+
metadata(
20+
foo: "bar",
21+
)
1922

2023
class << self
2124
def call(message:, server_context: nil)
@@ -45,6 +48,14 @@ def call(message:, server_context: nil)
4548
assert_equal expected_annotations, tool.to_h[:annotations]
4649
end
4750

51+
test "#to_h includes metadata when present" do
52+
tool = TestTool
53+
expected_metadata = {
54+
foo: "bar",
55+
}
56+
assert_equal expected_metadata, tool.to_h[:_meta]
57+
end
58+
4859
test "#call invokes the tool block and returns the response" do
4960
tool = TestTool
5061
response = tool.call(message: "test")
@@ -145,6 +156,23 @@ class InputSchemaTool < Tool
145156
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
146157
end
147158

159+
test ".define allows definition of tools with metadata" do
160+
tool = Tool.define(
161+
name: "mock_tool",
162+
title: "Mock Tool",
163+
description: "a mock tool for testing",
164+
metadata: { foo: "bar" },
165+
) do |_|
166+
Tool::Response.new([{ type: "text", content: "OK" }])
167+
end
168+
169+
assert_equal "mock_tool", tool.name_value
170+
assert_equal "Mock Tool", tool.title
171+
assert_equal "a mock tool for testing", tool.description
172+
assert_equal tool.input_schema, Tool::InputSchema.new
173+
assert_equal({ foo: "bar" }, tool.metadata_value)
174+
end
175+
148176
test "Tool class method annotations can be set and retrieved" do
149177
class AnnotationsTestTool < Tool
150178
tool_name "annotations_test"
@@ -173,6 +201,30 @@ class UpdatableAnnotationsTool < Tool
173201
assert_equal "Updated", tool.annotations_value.title
174202
end
175203

204+
test "Tool class method metadata can be set and retrieved" do
205+
class MetadataTestTool < Tool
206+
tool_name "annotations_test"
207+
metadata(foo: "bar")
208+
end
209+
210+
tool = MetadataTestTool
211+
assert_instance_of Hash, tool.metadata_value
212+
assert_equal "bar", tool.metadata_value[:foo]
213+
end
214+
215+
test "Tool class method metadata can be updated" do
216+
class UpdatableMetadataTool < Tool
217+
tool_name "updatable_metadata"
218+
end
219+
220+
tool = UpdatableMetadataTool
221+
tool.metadata(foo: "baz")
222+
assert_equal({ foo: "baz" }, tool.metadata_value)
223+
224+
tool.metadata(foo: "qux")
225+
assert_equal({ foo: "qux" }, tool.metadata_value)
226+
end
227+
176228
test "#call with Sorbet typed tools invokes the tool block and returns the response" do
177229
class TypedTestTool < Tool
178230
tool_name "test_tool"

0 commit comments

Comments
 (0)