Skip to content

Commit 2eaa240

Browse files
committed
Spec passes for tools manifest
1 parent 12c78a0 commit 2eaa240

2 files changed

Lines changed: 42 additions & 26 deletions

File tree

lib/modelcontextprotocol.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,24 @@ def camelize_hash_keys(obj)
2121
obj.map { |item| camelize_hash_keys(item) }
2222
when Hash
2323
obj.each_with_object({}) do |(key, value), new_hash|
24-
new_key = camelize(key.to_s)
24+
new_key = modify_key(key.to_s)
2525
new_hash[new_key] = camelize_hash_keys(value)
2626
end
2727
else
2828
obj
2929
end
3030
end
3131

32+
def modify_key(key)
33+
# In some cases, we want to emit keys that don't get snake cased, so for that
34+
# we freeze strings and leave them alone.
35+
if key.is_a?(String) and key.frozen?
36+
key
37+
else
38+
camelize key
39+
end
40+
end
41+
3242
def camelize(snake_str)
3343
parts = snake_str.split('_')
3444
parts[0] + parts[1..-1].map(&:capitalize).join
@@ -54,10 +64,8 @@ def initialize(name:, type:, description:, required: false)
5464

5565
def to_h
5666
{
57-
name: name,
5867
type: type,
59-
description: description,
60-
required: required
68+
description: description
6169
}
6270
end
6371
end
@@ -86,10 +94,17 @@ def property(...)
8694
self
8795
end
8896

97+
def properties_hash
98+
properties.each_with_object Hash.new do |prop, hash|
99+
hash[prop.name.freeze] = prop.to_h
100+
end
101+
end
102+
89103
def to_h
90104
{
91105
type: type,
92-
properties: properties.map(&:to_h)
106+
properties: properties_hash,
107+
required: properties.select(&:required).map(&:name)
93108
}
94109
end
95110
end

spec/modelcontextprotocol_spec.rb

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
input_schema = tool.to_h[:input_schema]
2323
expect(input_schema[:type]).to eq("object")
24-
expect(input_schema[:properties].first[:name]).to eq("test_property")
25-
expect(input_schema[:properties].first[:required]).to eq(true)
24+
expect(input_schema[:properties]["test_property"][:type]).to eq("string")
25+
expect(input_schema[:properties]["test_property"][:description]).to eq("A test property")
2626
end
2727

2828
it "serializes to camelCase JSON" do
@@ -43,8 +43,8 @@
4343
expect(parsed).to have_key("inputSchema")
4444
expect(parsed["inputSchema"]).to have_key("type")
4545
expect(parsed["inputSchema"]).to have_key("properties")
46-
expect(parsed["inputSchema"]["properties"].first).to have_key("name")
47-
expect(parsed["inputSchema"]["properties"].first).to have_key("required")
46+
expect(parsed["inputSchema"]["properties"]["test_property"]).to have_key("type")
47+
expect(parsed["inputSchema"]["properties"]["test_property"]).to have_key("description")
4848
end
4949
end
5050

@@ -82,30 +82,31 @@
8282
tools.add_tool(tool)
8383

8484
# Expected output hash
85-
expected_hash = {
86-
"jsonrpc" => "2.0",
87-
"id" => 1,
88-
"result" => {
89-
"tools" => [
85+
expected_hash = JSON.parse <<~JSON
86+
{
87+
"jsonrpc": "2.0",
88+
"id": 1,
89+
"result": {
90+
"tools": [
9091
{
91-
"name" => "get_weather",
92-
"description" => "Get current weather information for a location",
93-
"inputSchema" => {
94-
"type" => "object",
95-
"properties" => [
96-
{
97-
"name" => "location",
98-
"type" => "string",
99-
"description" => "City name or zip code",
100-
"required" => true
92+
"name": "get_weather",
93+
"description": "Get current weather information for a location",
94+
"inputSchema": {
95+
"type": "object",
96+
"properties": {
97+
"location": {
98+
"type": "string",
99+
"description": "City name or zip code"
101100
}
102-
]
101+
},
102+
"required": ["location"]
103103
}
104104
}
105105
],
106-
"nextCursor" => "next-page-cursor"
106+
"nextCursor": "next-page-cursor"
107107
}
108108
}
109+
JSON
109110

110111
json_output = tools.to_json
111112
parsed_output = JSON.parse(json_output)

0 commit comments

Comments
 (0)