mcp-rb 0.3.0 → 0.3.1
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 +5 -0
- data/README.md +10 -1
- data/lib/mcp/app/resource.rb +25 -2
- data/lib/mcp/app/resource_template.rb +142 -0
- data/lib/mcp/app/tool.rb +1 -1
- 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 +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ff3f38b8c9fec5aac8fce9d0906f956a77f481ac0477c273e9b677948d0a8cb
|
4
|
+
data.tar.gz: 116def752685bfa0792c6d2b1dc6bd75e6b029cec03205fb2a67b00d84532cfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ed62e7606ff685a999c7f95118167e30d8ecb6a6ff35a260223b60fed2fc65599a79bd22424dfde3b0bfc4cd1b3bdf1cb46dcb6877c7720e8837021f456d8cf
|
7
|
+
data.tar.gz: 5536f506e8e2c89a73fe34acc6edc3981b444672ca8cb3730e089eea22fa9194cee33e6afe26a2442175441487283828aa29103e78b39c8387714d2f1c6a5a82
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,11 @@ 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.1] - 2025-03-05
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Add `resources/templates/list` method: https://github.com/funwarioisii/mcp-rb/pull/5
|
12
|
+
|
8
13
|
## [0.3.0] - 2025-02-19
|
9
14
|
|
10
15
|
- 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"
|
@@ -47,6 +56,7 @@ Reference: [MCP 2024-11-05](https://spec.modelcontextprotocol.io/specification/2
|
|
47
56
|
- Resources
|
48
57
|
- resources/read
|
49
58
|
- resources/list
|
59
|
+
- resources/templates/list
|
50
60
|
- Tools
|
51
61
|
- tools/list
|
52
62
|
- tools/call
|
@@ -102,4 +112,3 @@ gem push mcp-rb-*.gem
|
|
102
112
|
## Changelog
|
103
113
|
|
104
114
|
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
@@ -77,7 +77,7 @@ module MCP
|
|
77
77
|
tool_values = tools.values
|
78
78
|
start_index = cursor ? cursor.to_i : 0
|
79
79
|
paginated = tool_values[start_index, page_size]
|
80
|
-
next_cursor = (start_index + page_size < tool_values.length) ? (start_index + page_size).to_s :
|
80
|
+
next_cursor = (start_index + page_size < tool_values.length) ? (start_index + page_size).to_s : nil
|
81
81
|
|
82
82
|
{
|
83
83
|
tools: paginated.map { |t| format_tool(t) },
|
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,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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- funwarioisii
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-04 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.
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- lib/mcp.rb
|
24
24
|
- lib/mcp/app.rb
|
25
25
|
- lib/mcp/app/resource.rb
|
26
|
+
- lib/mcp/app/resource_template.rb
|
26
27
|
- lib/mcp/app/tool.rb
|
27
28
|
- lib/mcp/client.rb
|
28
29
|
- lib/mcp/constants.rb
|