Skip to content

Commit 2b21cd1

Browse files
committed
Fix tool name collisions across namespaces
This fixes an issue where only one tool could be registered when tools shared the same name, even if they were in different namespaces. With this change, tools in different namespaces can be registered as separate tools, so multiple tools with the same name can be registered. Fixes #197
1 parent 47c70ef commit 2b21cd1

File tree

5 files changed

+28
-12
lines changed

5 files changed

+28
-12
lines changed

lib/mcp/string_utils.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ module MCP
44
module StringUtils
55
extend self
66

7+
NAMESPACE_SEPARATOR = "/"
8+
79
def handle_from_class_name(class_name)
8-
underscore(demodulize(class_name))
10+
class_name.to_s.split("::").map do |name|
11+
underscore(name)
12+
end.join(NAMESPACE_SEPARATOR)
913
end
1014

1115
private
1216

13-
def demodulize(path)
14-
path.to_s.split("::").last || path.to_s
15-
end
16-
1717
def underscore(camel_cased_word)
1818
camel_cased_word.dup
1919
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')

test/mcp/prompt_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def template(args, server_context:)
102102

103103
prompt = DefaultNamePrompt
104104

105-
assert_equal "default_name_prompt", prompt.name_value
105+
assert_equal "mcp/prompt_test/default_name_prompt", prompt.name_value
106106
assert_equal "a mock prompt for testing", prompt.description
107107
assert_equal "test_argument", prompt.arguments.first.name
108108
end
@@ -184,7 +184,7 @@ class NoArgumentsPrompt < Prompt
184184
prompt = NoArgumentsPrompt
185185

186186
expected = {
187-
name: "no_arguments_prompt",
187+
name: "mcp/prompt_test/no_arguments_prompt",
188188
description: "No arguments prompt",
189189
}
190190

test/mcp/server_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,22 @@ def call(message:, server_context: nil)
372372
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises" })
373373
end
374374

375+
test "registers tools with the same class name in different namespaces" do
376+
module Foo
377+
class Example < Tool
378+
end
379+
end
380+
381+
class Bar
382+
class Example < Tool
383+
end
384+
end
385+
386+
server = Server.new(tools: [Foo::Example, Bar::Example])
387+
388+
assert_equal(2, server.tools.count)
389+
end
390+
375391
test "#handle_json returns error response with isError true if the tool raises an error" do
376392
request = JSON.generate({
377393
jsonrpc: "2.0",

test/mcp/string_utils_test.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ def test_handle_from_class_name_returns_the_class_name_without_the_module_for_a_
1010
end
1111

1212
def test_handle_from_class_name_returns_the_class_name_without_the_module_for_a_class_with_a_single_parent_module
13-
assert_equal("test", StringUtils.handle_from_class_name("Module::Test"))
14-
assert_equal("test_class", StringUtils.handle_from_class_name("Module::TestClass"))
13+
assert_equal("module/test", StringUtils.handle_from_class_name("Module::Test"))
14+
assert_equal("module/test_class", StringUtils.handle_from_class_name("Module::TestClass"))
1515
end
1616

1717
def test_handle_from_class_name_returns_the_class_name_without_the_module_for_a_class_with_multiple_parent_modules
18-
assert_equal("test", StringUtils.handle_from_class_name("Module::Submodule::Test"))
19-
assert_equal("test_class", StringUtils.handle_from_class_name("Module::Submodule::TestClass"))
18+
assert_equal("module/submodule/test", StringUtils.handle_from_class_name("Module::Submodule::Test"))
19+
assert_equal("module/submodule/test_class", StringUtils.handle_from_class_name("Module::Submodule::TestClass"))
2020
end
2121
end
2222
end

test/mcp/tool_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class DefaultNameTool < Tool
8989

9090
tool = DefaultNameTool
9191

92-
assert_equal "default_name_tool", tool.tool_name
92+
assert_equal "mcp/tool_test/default_name_tool", tool.tool_name
9393
end
9494

9595
test "input schema defaults to an empty hash" do

0 commit comments

Comments
 (0)