mcp-rb 0.3.0 → 0.3.2
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 +12 -0
- data/README.md +31 -1
- data/lib/mcp/app/resource.rb +25 -2
- data/lib/mcp/app/resource_template.rb +142 -0
- data/lib/mcp/app/tool.rb +126 -78
- data/lib/mcp/app.rb +2 -0
- data/lib/mcp/delegator.rb +1 -1
- data/lib/mcp/server.rb +15 -0
- data/lib/mcp/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c5b2677ddf71a4e2597cae1bb3f5806308e33beb5c5a28032741788a1c0b7b6
|
4
|
+
data.tar.gz: 63c873245f9af796df2b60072167bac6a4810f6233a3f872e9295e52e1d42b38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9672f55327ee861ba8777447b5b04724d4ffac344a268245a8dc53f9aa5715a048af67f226d8856e2066b28268aff98e7ed5b82fcb8dd59a97710f6f4e4e3ae6
|
7
|
+
data.tar.gz: 2d82cf39dd04173518e7b2365513d07536661dbc80a0db72a38b759401808651ff186f2bb0910c24ac73ecbc613a709db66c4c89c7d9422d71d93b7fd0478665
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,18 @@ 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.2] - 2025-03-07
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Add support for Nested Arguments and Array Arguments: https://github.com/funwarioisii/mcp-rb/pull/6
|
12
|
+
- Add validation for nested arguments
|
13
|
+
- Support array type arguments
|
14
|
+
|
15
|
+
## [0.3.1] - 2025-03-05
|
16
|
+
|
17
|
+
### Added
|
18
|
+
- Add `resources/templates/list` method: https://github.com/funwarioisii/mcp-rb/pull/5
|
19
|
+
|
8
20
|
## [0.3.0] - 2025-02-19
|
9
21
|
|
10
22
|
- Allow specifying the version via DSL keyword: https://github.com/funwarioisii/mcp-rb/pull/2
|
data/README.md
CHANGED
@@ -19,6 +19,8 @@ require 'mcp'
|
|
19
19
|
|
20
20
|
name "hello-world"
|
21
21
|
|
22
|
+
version "1.0.0"
|
23
|
+
|
22
24
|
# Define a resource
|
23
25
|
resource "hello://world" do
|
24
26
|
name "Hello World"
|
@@ -26,6 +28,13 @@ resource "hello://world" do
|
|
26
28
|
call { "Hello, World!" }
|
27
29
|
end
|
28
30
|
|
31
|
+
# Define a resource template
|
32
|
+
resource_template "hello://{user_name}" do
|
33
|
+
name "Hello User"
|
34
|
+
description "A simple hello user message"
|
35
|
+
call { |args| "Hello, #{args[:user_name]}!" }
|
36
|
+
end
|
37
|
+
|
29
38
|
# Define a tool
|
30
39
|
tool "greet" do
|
31
40
|
description "Greet someone by name"
|
@@ -34,6 +43,27 @@ tool "greet" do
|
|
34
43
|
"Hello, #{args[:name]}!"
|
35
44
|
end
|
36
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
|
37
67
|
```
|
38
68
|
|
39
69
|
## Supported specifications
|
@@ -47,6 +77,7 @@ Reference: [MCP 2024-11-05](https://spec.modelcontextprotocol.io/specification/2
|
|
47
77
|
- Resources
|
48
78
|
- resources/read
|
49
79
|
- resources/list
|
80
|
+
- resources/templates/list
|
50
81
|
- Tools
|
51
82
|
- tools/list
|
52
83
|
- tools/call
|
@@ -102,4 +133,3 @@ gem push mcp-rb-*.gem
|
|
102
133
|
## Changelog
|
103
134
|
|
104
135
|
See [CHANGELOG.md](CHANGELOG.md)
|
105
|
-
|
data/lib/mcp/app/resource.rb
CHANGED
@@ -69,11 +69,11 @@ module MCP
|
|
69
69
|
|
70
70
|
if page_size.nil?
|
71
71
|
paginated = values[start_index..]
|
72
|
-
next_cursor =
|
72
|
+
next_cursor = nil
|
73
73
|
else
|
74
74
|
paginated = values[start_index, page_size]
|
75
75
|
has_next = start_index + page_size < values.length
|
76
|
-
next_cursor = has_next ? (start_index + page_size).to_s :
|
76
|
+
next_cursor = has_next ? (start_index + page_size).to_s : nil
|
77
77
|
end
|
78
78
|
|
79
79
|
{
|
@@ -84,6 +84,29 @@ module MCP
|
|
84
84
|
|
85
85
|
def read_resource(uri)
|
86
86
|
resource = resources[uri]
|
87
|
+
|
88
|
+
# If no direct match, check if it matches a template
|
89
|
+
if resource.nil? && respond_to?(:find_matching_template)
|
90
|
+
template, variable_values = find_matching_template(uri)
|
91
|
+
|
92
|
+
if template
|
93
|
+
begin
|
94
|
+
# Call the template handler with the extracted variables
|
95
|
+
content = template[:handler].call(variable_values)
|
96
|
+
return {
|
97
|
+
contents: [{
|
98
|
+
uri: uri,
|
99
|
+
mimeType: template[:mime_type],
|
100
|
+
text: content
|
101
|
+
}]
|
102
|
+
}
|
103
|
+
rescue => e
|
104
|
+
raise ArgumentError, "Error reading resource from template: #{e.message}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# If we still don't have a resource, raise an error
|
87
110
|
raise ArgumentError, "Resource not found: #{uri}" unless resource
|
88
111
|
|
89
112
|
begin
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resource"
|
4
|
+
|
5
|
+
module MCP
|
6
|
+
class App
|
7
|
+
module ResourceTemplate
|
8
|
+
def resource_templates
|
9
|
+
@resource_templates ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
class ResourceTemplateBuilder
|
13
|
+
attr_reader :uri_template, :name, :description, :mime_type, :handler
|
14
|
+
|
15
|
+
def initialize(uri_template)
|
16
|
+
raise ArgumentError, "Resource URI template cannot be nil or empty" if uri_template.nil? || uri_template.empty?
|
17
|
+
@uri_template = uri_template
|
18
|
+
@name = ""
|
19
|
+
@description = ""
|
20
|
+
@mime_type = "text/plain"
|
21
|
+
@handler = nil
|
22
|
+
@variables = extract_variables(uri_template)
|
23
|
+
end
|
24
|
+
|
25
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
26
|
+
def name(value)
|
27
|
+
@name = value
|
28
|
+
end
|
29
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
30
|
+
|
31
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
32
|
+
def description(text)
|
33
|
+
@description = text
|
34
|
+
end
|
35
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
36
|
+
|
37
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
38
|
+
def mime_type(value)
|
39
|
+
@mime_type = value
|
40
|
+
end
|
41
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
42
|
+
|
43
|
+
def call(&block)
|
44
|
+
@handler = block
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_resource_template_hash
|
48
|
+
raise ArgumentError, "Name must be provided" if @name.empty?
|
49
|
+
|
50
|
+
{
|
51
|
+
uri_template: @uri_template,
|
52
|
+
name: @name,
|
53
|
+
mime_type: @mime_type,
|
54
|
+
description: @description,
|
55
|
+
handler: @handler,
|
56
|
+
variables: @variables
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Extract variables from a URI template
|
61
|
+
# e.g., "channels://{channel_id}" => ["channel_id"]
|
62
|
+
def extract_variables(uri_template)
|
63
|
+
variables = []
|
64
|
+
uri_template.scan(/\{([^}]+)\}/) do |match|
|
65
|
+
variables << match[0]&.to_sym
|
66
|
+
end
|
67
|
+
variables
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a pattern for matching URIs against this template
|
71
|
+
def to_pattern
|
72
|
+
pattern_string = Regexp.escape(@uri_template).gsub(/\\\{[^}]+\\\}/) do |match|
|
73
|
+
"([^/]+)"
|
74
|
+
end
|
75
|
+
Regexp.new("^#{pattern_string}$")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extract variable values from a concrete URI based on the template
|
79
|
+
# e.g., template: "channels://{channel_id}", uri: "channels://123" => {"channel_id" => "123"}
|
80
|
+
def extract_variable_values(uri)
|
81
|
+
pattern = to_pattern
|
82
|
+
match = pattern.match(uri)
|
83
|
+
return {} unless match
|
84
|
+
|
85
|
+
result = {}
|
86
|
+
@variables.each_with_index do |var_name, index|
|
87
|
+
result[var_name] = match[index + 1]
|
88
|
+
end
|
89
|
+
result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def register_resource_template(uri_template, &block)
|
94
|
+
builder = ResourceTemplateBuilder.new(uri_template)
|
95
|
+
builder.instance_eval(&block)
|
96
|
+
template_hash = builder.to_resource_template_hash
|
97
|
+
resource_templates[uri_template] = template_hash
|
98
|
+
template_hash
|
99
|
+
end
|
100
|
+
|
101
|
+
# Find a template that matches the given URI and extract variable values
|
102
|
+
def find_matching_template(uri)
|
103
|
+
resource_templates.each do |template_uri, template|
|
104
|
+
builder = ResourceTemplateBuilder.new(template_uri)
|
105
|
+
variable_values = builder.extract_variable_values(uri)
|
106
|
+
return [template, variable_values] unless variable_values.empty?
|
107
|
+
end
|
108
|
+
[nil, {}]
|
109
|
+
end
|
110
|
+
|
111
|
+
def list_resource_templates(cursor: nil, page_size: nil)
|
112
|
+
start_index = cursor&.to_i || 0
|
113
|
+
values = resource_templates.values
|
114
|
+
|
115
|
+
if page_size.nil?
|
116
|
+
paginated = values[start_index..]
|
117
|
+
next_cursor = nil
|
118
|
+
else
|
119
|
+
paginated = values[start_index, page_size]
|
120
|
+
has_next = start_index + page_size < values.length
|
121
|
+
next_cursor = has_next ? (start_index + page_size).to_s : nil
|
122
|
+
end
|
123
|
+
|
124
|
+
{
|
125
|
+
resourceTemplates: paginated.map { |t| format_resource_template(t) },
|
126
|
+
nextCursor: next_cursor
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def format_resource_template(template)
|
133
|
+
{
|
134
|
+
uriTemplate: template[:uri_template],
|
135
|
+
name: template[:name],
|
136
|
+
description: template[:description],
|
137
|
+
mimeType: template[:mime_type]
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
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,91 @@ 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
|
-
|
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
|
+
next_cursor = (start + page_size < tools.length) ? (start + page_size).to_s : nil
|
116
|
+
{tools: paginated.map { |t| {name: t[:name], description: t[:description], inputSchema: t[:input_schema]} }, nextCursor: next_cursor}
|
86
117
|
end
|
87
118
|
|
88
|
-
|
119
|
+
# Calls a tool with the provided arguments
|
120
|
+
def call_tool(name, **args)
|
89
121
|
tool = tools[name]
|
90
122
|
raise ArgumentError, "Tool not found: #{name}" unless tool
|
91
123
|
|
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
|
124
|
+
validate_arguments(tool[:input_schema], args)
|
125
|
+
{content: [{type: "text", text: tool[:handler].call(args).to_s}], isError: false}
|
126
|
+
rescue => e
|
127
|
+
{content: [{type: "text", text: "Error: #{e.message}"}], isError: true}
|
115
128
|
end
|
116
129
|
|
117
130
|
private
|
118
131
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
132
|
+
def validate(schema, arg, path = "")
|
133
|
+
errors = []
|
134
|
+
type = schema[:type]
|
135
|
+
|
136
|
+
if type == :object
|
137
|
+
if !arg.is_a?(Hash)
|
138
|
+
errors << (path.empty? ? "Arguments must be a hash" : "Expected object for #{path}, got #{arg.class}")
|
139
|
+
else
|
140
|
+
schema[:required]&.each do |req|
|
141
|
+
unless arg.key?(req)
|
142
|
+
errors << (path.empty? ? "Missing required param :#{req}" : "Missing required param #{path}.#{req}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
schema[:properties].each do |key, subschema|
|
146
|
+
if arg.key?(key)
|
147
|
+
sub_path = path.empty? ? key : "#{path}.#{key}"
|
148
|
+
sub_errors = validate(subschema, arg[key], sub_path)
|
149
|
+
errors.concat(sub_errors)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
elsif type == :array
|
154
|
+
if !arg.is_a?(Array)
|
155
|
+
errors << "Expected array for #{path}, got #{arg.class}"
|
156
|
+
else
|
157
|
+
arg.each_with_index do |item, index|
|
158
|
+
sub_path = "#{path}[#{index}]"
|
159
|
+
sub_errors = validate(schema[:items], item, sub_path)
|
160
|
+
errors.concat(sub_errors)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
else
|
164
|
+
valid = case type
|
165
|
+
when :string then arg.is_a?(String)
|
166
|
+
when :integer then arg.is_a?(Integer)
|
167
|
+
when :number then arg.is_a?(Float)
|
168
|
+
when :boolean then arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
|
169
|
+
else false
|
170
|
+
end
|
171
|
+
unless valid
|
172
|
+
errors << "Expected #{type} for #{path}, got #{arg.class}"
|
125
173
|
end
|
126
174
|
end
|
175
|
+
errors
|
127
176
|
end
|
128
177
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
}
|
178
|
+
def validate_arguments(schema, args)
|
179
|
+
errors = validate(schema, args, "")
|
180
|
+
unless errors.empty?
|
181
|
+
raise ArgumentError, errors.join("\n").to_s
|
182
|
+
end
|
135
183
|
end
|
136
184
|
end
|
137
185
|
end
|
data/lib/mcp/app.rb
CHANGED
data/lib/mcp/delegator.rb
CHANGED
data/lib/mcp/server.rb
CHANGED
@@ -28,6 +28,7 @@ module MCP
|
|
28
28
|
return @version if value.nil?
|
29
29
|
|
30
30
|
@version = value
|
31
|
+
@supported_protocol_versions << value
|
31
32
|
end
|
32
33
|
|
33
34
|
def tool(name, &block)
|
@@ -38,6 +39,10 @@ module MCP
|
|
38
39
|
@app.register_resource(uri, &block)
|
39
40
|
end
|
40
41
|
|
42
|
+
def resource_template(uri_template, &block)
|
43
|
+
@app.register_resource_template(uri_template, &block)
|
44
|
+
end
|
45
|
+
|
41
46
|
def run
|
42
47
|
while (input = $stdin.gets)
|
43
48
|
process_input(input)
|
@@ -56,6 +61,10 @@ module MCP
|
|
56
61
|
@app.list_resources[:resources]
|
57
62
|
end
|
58
63
|
|
64
|
+
def list_resource_templates
|
65
|
+
@app.list_resource_templates[:resourceTemplates]
|
66
|
+
end
|
67
|
+
|
59
68
|
def read_resource(uri)
|
60
69
|
@app.read_resource(uri).dig(:contents, 0, :text)
|
61
70
|
end
|
@@ -172,6 +181,12 @@ module MCP
|
|
172
181
|
success_response(request[:id], result)
|
173
182
|
end
|
174
183
|
|
184
|
+
def handle_list_resources_templates(request)
|
185
|
+
cursor = request.dig(:params, :cursor)
|
186
|
+
result = @app.list_resource_templates(cursor:)
|
187
|
+
success_response(request[:id], result)
|
188
|
+
end
|
189
|
+
|
175
190
|
def handle_read_resource(request)
|
176
191
|
uri = request.dig(:params, :uri)
|
177
192
|
result = @app.read_resource(uri)
|
data/lib/mcp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- funwarioisii
|
8
|
+
autorequire:
|
8
9
|
bindir: bin
|
9
10
|
cert_chain: []
|
10
|
-
date: 2025-
|
11
|
+
date: 2025-03-08 00:00:00.000000000 Z
|
11
12
|
dependencies: []
|
12
13
|
description: MCP-RB is a Ruby framework that provides a Sinatra-like DSL for implementing
|
13
14
|
Model Context Protocol servers.
|
@@ -23,6 +24,7 @@ files:
|
|
23
24
|
- lib/mcp.rb
|
24
25
|
- lib/mcp/app.rb
|
25
26
|
- lib/mcp/app/resource.rb
|
27
|
+
- lib/mcp/app/resource_template.rb
|
26
28
|
- lib/mcp/app/tool.rb
|
27
29
|
- lib/mcp/client.rb
|
28
30
|
- lib/mcp/constants.rb
|
@@ -36,6 +38,7 @@ metadata:
|
|
36
38
|
homepage_uri: https://github.com/funwarioisii/mcp-rb
|
37
39
|
source_code_uri: https://github.com/funwarioisii/mcp-rb
|
38
40
|
changelog_uri: https://github.com/funwarioisii/mcp-rb/blob/main/CHANGELOG.md
|
41
|
+
post_install_message:
|
39
42
|
rdoc_options: []
|
40
43
|
require_paths:
|
41
44
|
- lib
|
@@ -50,7 +53,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
53
|
- !ruby/object:Gem::Version
|
51
54
|
version: '0'
|
52
55
|
requirements: []
|
53
|
-
rubygems_version: 3.
|
56
|
+
rubygems_version: 3.4.1
|
57
|
+
signing_key:
|
54
58
|
specification_version: 4
|
55
59
|
summary: A lightweight Ruby framework for implementing MCP (Model Context Protocol)
|
56
60
|
servers
|