Skip to content

Commit 4968d9c

Browse files
authored
Merge pull request #204 from koic/make_tool_names_stricter
Make tool names stricter
2 parents ab4f080 + 84ff1fc commit 4968d9c

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

lib/mcp/tool.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module MCP
44
class Tool
55
class << self
66
NOT_SET = Object.new
7+
MAX_LENGTH_OF_NAME = 128
78

89
attr_reader :title_value
910
attr_reader :description_value
@@ -45,11 +46,13 @@ def tool_name(value = NOT_SET)
4546
name_value
4647
else
4748
@name_value = value
49+
50+
validate!
4851
end
4952
end
5053

5154
def name_value
52-
@name_value || StringUtils.handle_from_class_name(name)
55+
@name_value || (name.nil? ? nil : StringUtils.handle_from_class_name(name))
5356
end
5457

5558
def input_schema_value
@@ -129,6 +132,22 @@ def define(name: nil, title: nil, description: nil, icons: [], input_schema: nil
129132
output_schema output_schema
130133
self.annotations(annotations) if annotations
131134
define_singleton_method(:call, &block) if block
135+
end.tap(&:validate!)
136+
end
137+
138+
# It complies with the following tool name specification:
139+
# https://modelcontextprotocol.io/specification/latest/server/tools#tool-names
140+
def validate!
141+
return true unless tool_name
142+
143+
if tool_name.empty? || tool_name.length > MAX_LENGTH_OF_NAME
144+
raise ArgumentError, "Tool names should be between 1 and 128 characters in length (inclusive)."
145+
end
146+
147+
unless tool_name.match?(/\A[A-Za-z\d_\-\.]+\z/)
148+
raise ArgumentError, <<~MESSAGE
149+
Tool names only allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.).
150+
MESSAGE
132151
end
133152
end
134153
end

test/mcp/tool_test.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,5 +447,46 @@ def call(message:, server_context: nil)
447447
expected_output = { type: "object", properties: { result: { type: "string" }, success: { type: "boolean" } }, required: ["result", "success"] }
448448
assert_equal expected_output, tool.output_schema.to_h
449449
end
450+
451+
test "accepts valid tool names" do
452+
assert Tool.define(name: "getUser")
453+
assert Tool.define(name: "DATA_EXPORT_v2")
454+
assert Tool.define(name: "admin.tools.list")
455+
assert Tool.define(name: "a" * 128)
456+
end
457+
458+
test "raises an error when tool name is empty in class definition" do
459+
error = assert_raises(ArgumentError) do
460+
class EmptyTitleNameTool < Tool
461+
tool_name ""
462+
end
463+
end
464+
assert_equal("Tool names should be between 1 and 128 characters in length (inclusive).", error.message)
465+
end
466+
467+
test "allows nil tool name in class definition" do
468+
assert_nothing_raised do
469+
class EmptyTitleNameTool < Tool
470+
tool_name nil
471+
end
472+
end
473+
end
474+
475+
test "raises an error when tool name is empty" do
476+
error = assert_raises(ArgumentError) { Tool.define(name: "") }
477+
assert_equal("Tool names should be between 1 and 128 characters in length (inclusive).", error.message)
478+
end
479+
480+
test "raises an error when tool name exceeds 128 characters" do
481+
error = assert_raises(ArgumentError) { Tool.define(name: "a" * 129) }
482+
assert_equal("Tool names should be between 1 and 128 characters in length (inclusive).", error.message)
483+
end
484+
485+
test "raises an error when tool name includes invalid characters (e.g., spaces)" do
486+
error = assert_raises(ArgumentError) { Tool.define(name: "foo bar") }
487+
assert_equal(<<~MESSAGE, error.message)
488+
Tool names only allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.).
489+
MESSAGE
490+
end
450491
end
451492
end

0 commit comments

Comments
 (0)