mcp-rb 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +21 -0
- data/lib/mcp/app/resource.rb +1 -1
- data/lib/mcp/app/resource_template.rb +1 -1
- data/lib/mcp/app/tool.rb +127 -78
- data/lib/mcp/server.rb +5 -1
- data/lib/mcp/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89a4d1cb8ae8957cb8a1d3a867e93218215dc4224eb2e03ab81baf81c0a1c6ba
|
4
|
+
data.tar.gz: 5f16964ddf22038d610967d3ae8781fcf7a7f91b17d0b7b749a5c1d38251a967
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f743d921b897ae532ee48343ab3b318c0688e740a90b7b8a2e23fda1a3996669b5385ab49f59027c1c9d5333d57c65c54baa577c71970d6e5fa32e7ca209c75
|
7
|
+
data.tar.gz: 441afb473c07c345647a036ae778dd7e36dad5cab6ad3bbad010a97d1ecc863d28bca0b4f5c6898cc86750566f8e0aabe99c93409f3f93c43d68b194c3aea973
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.3.3] - 2025-03-12
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Fix error response style: https://github.com/funwarioisii/mcp-rb/pull/12
|
12
|
+
- Follow the return style of `handle_*` methods
|
13
|
+
- Omit `nextCursor` key when no additional pages exist: https://github.com/funwarioisii/mcp-rb/pull/13
|
14
|
+
|
15
|
+
## [0.3.2] - 2025-03-07
|
16
|
+
|
17
|
+
### Added
|
18
|
+
- Add support for Nested Arguments and Array Arguments: https://github.com/funwarioisii/mcp-rb/pull/6
|
19
|
+
- Add validation for nested arguments
|
20
|
+
- Support array type arguments
|
21
|
+
|
8
22
|
## [0.3.1] - 2025-03-05
|
9
23
|
|
10
24
|
### Added
|
data/README.md
CHANGED
@@ -43,6 +43,27 @@ tool "greet" do
|
|
43
43
|
"Hello, #{args[:name]}!"
|
44
44
|
end
|
45
45
|
end
|
46
|
+
|
47
|
+
# Define a tool with nested arguments
|
48
|
+
tool "greet_full_name" do
|
49
|
+
description "Greet someone by their full name"
|
50
|
+
argument :person, required: true, description: "Person to greet" do
|
51
|
+
argument :first_name, String, required: false, description: "First name"
|
52
|
+
argument :last_name, String, required: false, description: "Last name"
|
53
|
+
end
|
54
|
+
call do |args|
|
55
|
+
"Hello, First: #{args[:person][:first_name]} Last: #{args[:person][:last_name]}!"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a tool with an Array argument
|
60
|
+
tool "group_greeting" do
|
61
|
+
description "Greet multiple people at once"
|
62
|
+
argument :people, Array, required: true, items: String, description: "People to greet"
|
63
|
+
call do |args|
|
64
|
+
args[:people].map { |person| "Hello, #{person}!" }.join(", ")
|
65
|
+
end
|
66
|
+
end
|
46
67
|
```
|
47
68
|
|
48
69
|
## Supported specifications
|
data/lib/mcp/app/resource.rb
CHANGED
data/lib/mcp/app/tool.rb
CHANGED
@@ -7,31 +7,83 @@ module MCP
|
|
7
7
|
@tools ||= {}
|
8
8
|
end
|
9
9
|
|
10
|
+
# Builds schemas for arguments, supporting simple types, nested objects, and arrays
|
11
|
+
class SchemaBuilder
|
12
|
+
def initialize
|
13
|
+
@schema = nil
|
14
|
+
@properties = {}
|
15
|
+
@required = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def argument(name, type = nil, required: false, description: "", items: nil, &block)
|
19
|
+
if type == Array
|
20
|
+
if block_given?
|
21
|
+
sub_builder = SchemaBuilder.new
|
22
|
+
sub_builder.instance_eval(&block)
|
23
|
+
item_schema = sub_builder.to_schema
|
24
|
+
elsif items
|
25
|
+
item_schema = {type: ruby_type_to_schema_type(items)}
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Must provide items or a block for array type"
|
28
|
+
end
|
29
|
+
@properties[name] = {type: :array, description: description, items: item_schema}
|
30
|
+
elsif block_given?
|
31
|
+
raise ArgumentError, "Type not allowed with block for objects" if type
|
32
|
+
sub_builder = SchemaBuilder.new
|
33
|
+
sub_builder.instance_eval(&block)
|
34
|
+
@properties[name] = sub_builder.to_schema.merge(description: description)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Type required for simple arguments" if type.nil?
|
37
|
+
@properties[name] = {type: ruby_type_to_schema_type(type), description: description}
|
38
|
+
end
|
39
|
+
@required << name if required
|
40
|
+
end
|
41
|
+
|
42
|
+
def type(t)
|
43
|
+
@schema = {type: ruby_type_to_schema_type(t)}
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_schema
|
47
|
+
@schema || {type: :object, properties: @properties, required: @required}
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def ruby_type_to_schema_type(type)
|
53
|
+
if type == String
|
54
|
+
:string
|
55
|
+
elsif type == Integer
|
56
|
+
:integer
|
57
|
+
elsif type == Float
|
58
|
+
:number
|
59
|
+
elsif type == TrueClass || type == FalseClass
|
60
|
+
:boolean
|
61
|
+
elsif type == Array
|
62
|
+
:array
|
63
|
+
else
|
64
|
+
raise ArgumentError, "Unsupported type: #{type}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Constructs tool definitions with enhanced schema support
|
10
70
|
class ToolBuilder
|
11
|
-
attr_reader :name, :
|
71
|
+
attr_reader :name, :arguments, :handler
|
12
72
|
|
13
73
|
def initialize(name)
|
14
74
|
raise ArgumentError, "Tool name cannot be nil or empty" if name.nil? || name.empty?
|
15
75
|
@name = name
|
16
76
|
@description = ""
|
17
|
-
@
|
18
|
-
@required_arguments = []
|
77
|
+
@schema_builder = SchemaBuilder.new
|
19
78
|
@handler = nil
|
20
79
|
end
|
21
80
|
|
22
|
-
# standard:disable Lint/DuplicateMethods
|
23
81
|
def description(text = nil)
|
24
|
-
|
25
|
-
@description = text
|
82
|
+
text ? @description = text : @description
|
26
83
|
end
|
27
|
-
# standard:enable Lint/DuplicateMethods
|
28
84
|
|
29
|
-
def argument(
|
30
|
-
@
|
31
|
-
type: ruby_type_to_schema_type(type),
|
32
|
-
description: description
|
33
|
-
}
|
34
|
-
@required_arguments << name if required
|
85
|
+
def argument(*args, **kwargs, &block)
|
86
|
+
@schema_builder.argument(*args, **kwargs, &block)
|
35
87
|
end
|
36
88
|
|
37
89
|
def call(&block)
|
@@ -43,95 +95,92 @@ module MCP
|
|
43
95
|
{
|
44
96
|
name: @name,
|
45
97
|
description: @description,
|
46
|
-
input_schema:
|
47
|
-
type: :object,
|
48
|
-
properties: @arguments,
|
49
|
-
required: @required_arguments
|
50
|
-
},
|
98
|
+
input_schema: @schema_builder.to_schema,
|
51
99
|
handler: @handler
|
52
100
|
}
|
53
101
|
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def ruby_type_to_schema_type(type)
|
58
|
-
case type.to_s
|
59
|
-
when "String" then :string
|
60
|
-
when "Integer" then :integer
|
61
|
-
when "Float" then :number
|
62
|
-
when "TrueClass", "FalseClass", "Boolean" then :boolean
|
63
|
-
else :object
|
64
|
-
end
|
65
|
-
end
|
66
102
|
end
|
67
103
|
|
104
|
+
# Registers a tool with the given name and block
|
68
105
|
def register_tool(name, &block)
|
69
106
|
builder = ToolBuilder.new(name)
|
70
107
|
builder.instance_eval(&block)
|
71
|
-
|
72
|
-
tools[name] = tool_hash
|
73
|
-
tool_hash
|
108
|
+
tools[name] = builder.to_tool_hash
|
74
109
|
end
|
75
110
|
|
111
|
+
# Lists tools with pagination
|
76
112
|
def list_tools(cursor: nil, page_size: 10)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
next_cursor = (
|
81
|
-
|
82
|
-
{
|
83
|
-
tools: paginated.map { |t| format_tool(t) },
|
84
|
-
nextCursor: next_cursor
|
85
|
-
}
|
113
|
+
start = cursor ? cursor.to_i : 0
|
114
|
+
paginated = tools.values[start, page_size]
|
115
|
+
|
116
|
+
next_cursor = (start + page_size < tools.length) ? (start + page_size).to_s : nil
|
117
|
+
{tools: paginated.map { |t| {name: t[:name], description: t[:description], inputSchema: t[:input_schema]} }, nextCursor: next_cursor}.compact
|
86
118
|
end
|
87
119
|
|
88
|
-
|
120
|
+
# Calls a tool with the provided arguments
|
121
|
+
def call_tool(name, **args)
|
89
122
|
tool = tools[name]
|
90
123
|
raise ArgumentError, "Tool not found: #{name}" unless tool
|
91
124
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
content: [
|
97
|
-
{
|
98
|
-
type: "text",
|
99
|
-
text: result.to_s
|
100
|
-
}
|
101
|
-
],
|
102
|
-
isError: false
|
103
|
-
}
|
104
|
-
rescue => e
|
105
|
-
{
|
106
|
-
content: [
|
107
|
-
{
|
108
|
-
type: "text",
|
109
|
-
text: "Error: #{e.message}"
|
110
|
-
}
|
111
|
-
],
|
112
|
-
isError: true
|
113
|
-
}
|
114
|
-
end
|
125
|
+
validate_arguments(tool[:input_schema], args)
|
126
|
+
{content: [{type: "text", text: tool[:handler].call(args).to_s}], isError: false}
|
127
|
+
rescue => e
|
128
|
+
{content: [{type: "text", text: "Error: #{e.message}"}], isError: true}
|
115
129
|
end
|
116
130
|
|
117
131
|
private
|
118
132
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
133
|
+
def validate(schema, arg, path = "")
|
134
|
+
errors = []
|
135
|
+
type = schema[:type]
|
136
|
+
|
137
|
+
if type == :object
|
138
|
+
if !arg.is_a?(Hash)
|
139
|
+
errors << (path.empty? ? "Arguments must be a hash" : "Expected object for #{path}, got #{arg.class}")
|
140
|
+
else
|
141
|
+
schema[:required]&.each do |req|
|
142
|
+
unless arg.key?(req)
|
143
|
+
errors << (path.empty? ? "Missing required param :#{req}" : "Missing required param #{path}.#{req}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
schema[:properties].each do |key, subschema|
|
147
|
+
if arg.key?(key)
|
148
|
+
sub_path = path.empty? ? key : "#{path}.#{key}"
|
149
|
+
sub_errors = validate(subschema, arg[key], sub_path)
|
150
|
+
errors.concat(sub_errors)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
elsif type == :array
|
155
|
+
if !arg.is_a?(Array)
|
156
|
+
errors << "Expected array for #{path}, got #{arg.class}"
|
157
|
+
else
|
158
|
+
arg.each_with_index do |item, index|
|
159
|
+
sub_path = "#{path}[#{index}]"
|
160
|
+
sub_errors = validate(schema[:items], item, sub_path)
|
161
|
+
errors.concat(sub_errors)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
else
|
165
|
+
valid = case type
|
166
|
+
when :string then arg.is_a?(String)
|
167
|
+
when :integer then arg.is_a?(Integer)
|
168
|
+
when :number then arg.is_a?(Float)
|
169
|
+
when :boolean then arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
|
170
|
+
else false
|
171
|
+
end
|
172
|
+
unless valid
|
173
|
+
errors << "Expected #{type} for #{path}, got #{arg.class}"
|
125
174
|
end
|
126
175
|
end
|
176
|
+
errors
|
127
177
|
end
|
128
178
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
}
|
179
|
+
def validate_arguments(schema, args)
|
180
|
+
errors = validate(schema, args, "")
|
181
|
+
unless errors.empty?
|
182
|
+
raise ArgumentError, errors.join("\n").to_s
|
183
|
+
end
|
135
184
|
end
|
136
185
|
end
|
137
186
|
end
|
data/lib/mcp/server.rb
CHANGED
@@ -169,7 +169,11 @@ module MCP
|
|
169
169
|
arguments = request.dig(:params, :arguments)
|
170
170
|
begin
|
171
171
|
result = @app.call_tool(name, **arguments.transform_keys(&:to_sym))
|
172
|
-
|
172
|
+
if result[:isError]
|
173
|
+
error_response(request[:id], Constants::ErrorCodes::INVALID_REQUEST, result[:content].first[:text])
|
174
|
+
else
|
175
|
+
success_response(request[:id], result)
|
176
|
+
end
|
173
177
|
rescue ArgumentError => e
|
174
178
|
error_response(request[:id], Constants::ErrorCodes::INVALID_REQUEST, e.message)
|
175
179
|
end
|
data/lib/mcp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mcp-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- funwarioisii
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-03-
|
10
|
+
date: 2025-03-12 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: MCP-RB is a Ruby framework that provides a Sinatra-like DSL for implementing
|
13
13
|
Model Context Protocol servers.
|
@@ -44,14 +44,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.
|
47
|
+
version: 3.3.0
|
48
48
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
49
|
requirements:
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '0'
|
53
53
|
requirements: []
|
54
|
-
rubygems_version: 3.6.
|
54
|
+
rubygems_version: 3.6.2
|
55
55
|
specification_version: 4
|
56
56
|
summary: A lightweight Ruby framework for implementing MCP (Model Context Protocol)
|
57
57
|
servers
|