mcp-rb 0.1.0 → 0.3.0
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 +33 -0
- data/README.md +56 -18
- data/lib/mcp/app/resource.rb +57 -11
- data/lib/mcp/app/tool.rb +77 -12
- data/lib/mcp/client.rb +123 -0
- data/lib/mcp/constants.rb +1 -0
- data/lib/mcp/delegator.rb +1 -1
- data/lib/mcp/server.rb +16 -9
- data/lib/mcp/version.rb +1 -2
- data/lib/mcp.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 131c29522f8eb44f7f613f51d92db433370d99950dcfcd27340ab9917b306dd3
|
4
|
+
data.tar.gz: a676110b09851f7018205bc012b09a6f22d1c7730555ba569b9b7ae475c06009
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ebe914ec0fa9ba0ef97650f83454d0fe5bf5c924717365d5cea3bdd73bfb06b5e0ede7d49c6da2caa8d38c5a337bae3c98c22b86d5f1c25740785c66ac195509
|
7
|
+
data.tar.gz: 719f1f95950ca24c0695316af978866e1b8834b5112c962701324d8279faa9e5705151157d23a66e7f9e6572313df18f7c4acfc247037965d059f12bdcacb883
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,39 @@ 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.0] - 2025-02-19
|
9
|
+
|
10
|
+
- Allow specifying the version via DSL keyword: https://github.com/funwarioisii/mcp-rb/pull/2
|
11
|
+
- Add MCP Client: https://github.com/funwarioisii/mcp-rb/pull/3
|
12
|
+
|
13
|
+
### Breaking Changes
|
14
|
+
- `MCP::PROTOCOL_VERSION` is moved to `MCP::Constants::PROTOCOL_VERSION`
|
15
|
+
- https://github.com/funwarioisii/mcp-rb/pull/3/commits/caad65500935a8eebfe024dbd25de0d16868c44e
|
16
|
+
|
17
|
+
## [0.2.0] - 2025-02-14
|
18
|
+
|
19
|
+
### Breaking Changes
|
20
|
+
- Unified DSL to block-based style for both tools and resources
|
21
|
+
- Example of new resource style:
|
22
|
+
```ruby
|
23
|
+
resource "uri" do
|
24
|
+
name "Resource Name"
|
25
|
+
description "Description"
|
26
|
+
mime_type "text/plain"
|
27
|
+
call { "content" }
|
28
|
+
end
|
29
|
+
```
|
30
|
+
- Example of new tool style:
|
31
|
+
```ruby
|
32
|
+
tool "greet" do
|
33
|
+
description "Greet someone"
|
34
|
+
argument :name, String, required: true, description: "Name to greet"
|
35
|
+
call do |args|
|
36
|
+
"Hello, #{args[:name]}!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
8
41
|
## [0.1.0] - 2025-02-12
|
9
42
|
|
10
43
|
### Added
|
data/README.md
CHANGED
@@ -19,29 +19,40 @@ require 'mcp'
|
|
19
19
|
|
20
20
|
name "hello-world"
|
21
21
|
|
22
|
-
#
|
23
|
-
resource "hello://world"
|
24
|
-
name
|
25
|
-
description
|
26
|
-
"Hello, World!"
|
22
|
+
# Define a resource
|
23
|
+
resource "hello://world" do
|
24
|
+
name "Hello World"
|
25
|
+
description "A simple hello world message"
|
26
|
+
call { "Hello, World!" }
|
27
27
|
end
|
28
28
|
|
29
|
-
tool
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
description: "Name to greet"
|
37
|
-
}
|
38
|
-
},
|
39
|
-
required: [:name]
|
40
|
-
} do |args|
|
41
|
-
"Hello, #{args[:name]}!"
|
29
|
+
# Define a tool
|
30
|
+
tool "greet" do
|
31
|
+
description "Greet someone by name"
|
32
|
+
argument :name, String, required: true, description: "Name to greet"
|
33
|
+
call do |args|
|
34
|
+
"Hello, #{args[:name]}!"
|
35
|
+
end
|
42
36
|
end
|
43
37
|
```
|
44
38
|
|
39
|
+
## Supported specifications
|
40
|
+
|
41
|
+
Reference: [MCP 2024-11-05](https://spec.modelcontextprotocol.io/specification/2024-11-05/)
|
42
|
+
|
43
|
+
- [Base Protocol](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/)
|
44
|
+
- ping
|
45
|
+
- stdio transport
|
46
|
+
- [Server features](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/)
|
47
|
+
- Resources
|
48
|
+
- resources/read
|
49
|
+
- resources/list
|
50
|
+
- Tools
|
51
|
+
- tools/list
|
52
|
+
- tools/call
|
53
|
+
|
54
|
+
Any capabilities are not supported yet.
|
55
|
+
|
45
56
|
## Testing
|
46
57
|
|
47
58
|
```bash
|
@@ -65,3 +76,30 @@ Find broken using `hello_world.rb`
|
|
65
76
|
```bash
|
66
77
|
bundle exec standardrb --fix
|
67
78
|
```
|
79
|
+
|
80
|
+
## Release
|
81
|
+
|
82
|
+
To release a new version:
|
83
|
+
|
84
|
+
1. Update version in `lib/mcp/version.rb`
|
85
|
+
2. Update `CHANGELOG.md`
|
86
|
+
3. Create a git tag
|
87
|
+
|
88
|
+
```bash
|
89
|
+
git add .
|
90
|
+
git commit -m "Release vx.y.z"
|
91
|
+
git tag vx.y.z
|
92
|
+
git push --tags
|
93
|
+
```
|
94
|
+
|
95
|
+
1. Build and push to RubyGems
|
96
|
+
|
97
|
+
```bash
|
98
|
+
gem build mcp-rb.gemspec
|
99
|
+
gem push mcp-rb-*.gem
|
100
|
+
```
|
101
|
+
|
102
|
+
## Changelog
|
103
|
+
|
104
|
+
See [CHANGELOG.md](CHANGELOG.md)
|
105
|
+
|
data/lib/mcp/app/resource.rb
CHANGED
@@ -3,14 +3,64 @@
|
|
3
3
|
module MCP
|
4
4
|
class App
|
5
5
|
module Resource
|
6
|
-
def
|
7
|
-
|
8
|
-
|
6
|
+
def resources
|
7
|
+
@resources ||= {}
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
class ResourceBuilder
|
11
|
+
attr_reader :uri, :name, :description, :mime_type, :handler
|
12
|
+
|
13
|
+
def initialize(uri)
|
14
|
+
raise ArgumentError, "Resource URI cannot be nil or empty" if uri.nil? || uri.empty?
|
15
|
+
@uri = uri
|
16
|
+
@name = ""
|
17
|
+
@description = ""
|
18
|
+
@mime_type = "text/plain"
|
19
|
+
@handler = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
23
|
+
def name(value)
|
24
|
+
@name = value
|
25
|
+
end
|
26
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
27
|
+
|
28
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
29
|
+
def description(text)
|
30
|
+
@description = text
|
31
|
+
end
|
32
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
33
|
+
|
34
|
+
# standard:disable Lint/DuplicateMethods,Style/TrivialAccessors
|
35
|
+
def mime_type(value)
|
36
|
+
@mime_type = value
|
37
|
+
end
|
38
|
+
# standard:enable Lint/DuplicateMethods,Style/TrivialAccessors
|
39
|
+
|
40
|
+
def call(&block)
|
41
|
+
@handler = block
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_resource_hash
|
45
|
+
raise ArgumentError, "Handler must be provided" unless @handler
|
46
|
+
raise ArgumentError, "Name must be provided" if @name.empty?
|
47
|
+
|
48
|
+
{
|
49
|
+
uri: @uri,
|
50
|
+
name: @name,
|
51
|
+
mime_type: @mime_type,
|
52
|
+
description: @description,
|
53
|
+
handler: @handler
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def register_resource(uri, &block)
|
59
|
+
builder = ResourceBuilder.new(uri)
|
60
|
+
builder.instance_eval(&block)
|
61
|
+
resource_hash = builder.to_resource_hash
|
62
|
+
resources[uri] = resource_hash
|
63
|
+
resource_hash
|
14
64
|
end
|
15
65
|
|
16
66
|
def list_resources(cursor: nil, page_size: nil)
|
@@ -52,10 +102,6 @@ module MCP
|
|
52
102
|
|
53
103
|
private
|
54
104
|
|
55
|
-
def resources
|
56
|
-
@resources ||= {}
|
57
|
-
end
|
58
|
-
|
59
105
|
def format_resource(resource)
|
60
106
|
{
|
61
107
|
uri: resource[:uri],
|
data/lib/mcp/app/tool.rb
CHANGED
@@ -3,16 +3,74 @@
|
|
3
3
|
module MCP
|
4
4
|
class App
|
5
5
|
module Tool
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
def tools
|
7
|
+
@tools ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
class ToolBuilder
|
11
|
+
attr_reader :name, :description, :arguments, :handler
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
raise ArgumentError, "Tool name cannot be nil or empty" if name.nil? || name.empty?
|
15
|
+
@name = name
|
16
|
+
@description = ""
|
17
|
+
@arguments = {}
|
18
|
+
@required_arguments = []
|
19
|
+
@handler = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# standard:disable Lint/DuplicateMethods
|
23
|
+
def description(text = nil)
|
24
|
+
return @description if text.nil?
|
25
|
+
@description = text
|
26
|
+
end
|
27
|
+
# standard:enable Lint/DuplicateMethods
|
28
|
+
|
29
|
+
def argument(name, type, required: false, description: "")
|
30
|
+
@arguments[name] = {
|
31
|
+
type: ruby_type_to_schema_type(type),
|
32
|
+
description: description
|
33
|
+
}
|
34
|
+
@required_arguments << name if required
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(&block)
|
38
|
+
@handler = block if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_tool_hash
|
42
|
+
raise ArgumentError, "Handler must be provided" unless @handler
|
43
|
+
{
|
44
|
+
name: @name,
|
45
|
+
description: @description,
|
46
|
+
input_schema: {
|
47
|
+
type: :object,
|
48
|
+
properties: @arguments,
|
49
|
+
required: @required_arguments
|
50
|
+
},
|
51
|
+
handler: @handler
|
52
|
+
}
|
53
|
+
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
|
+
end
|
67
|
+
|
68
|
+
def register_tool(name, &block)
|
69
|
+
builder = ToolBuilder.new(name)
|
70
|
+
builder.instance_eval(&block)
|
71
|
+
tool_hash = builder.to_tool_hash
|
72
|
+
tools[name] = tool_hash
|
73
|
+
tool_hash
|
16
74
|
end
|
17
75
|
|
18
76
|
def list_tools(cursor: nil, page_size: 10)
|
@@ -32,6 +90,7 @@ module MCP
|
|
32
90
|
raise ArgumentError, "Tool not found: #{name}" unless tool
|
33
91
|
|
34
92
|
begin
|
93
|
+
validate_arguments(tool[:input_schema], arguments)
|
35
94
|
result = tool[:handler].call(arguments)
|
36
95
|
{
|
37
96
|
content: [
|
@@ -57,8 +116,14 @@ module MCP
|
|
57
116
|
|
58
117
|
private
|
59
118
|
|
60
|
-
def
|
61
|
-
|
119
|
+
def validate_arguments(schema, arguments)
|
120
|
+
return unless schema[:required]
|
121
|
+
|
122
|
+
schema[:required].each do |required_arg|
|
123
|
+
unless arguments.key?(required_arg)
|
124
|
+
raise ArgumentError, "missing keyword: :#{required_arg}"
|
125
|
+
end
|
126
|
+
end
|
62
127
|
end
|
63
128
|
|
64
129
|
def format_tool(tool)
|
data/lib/mcp/client.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "open3"
|
5
|
+
require "securerandom"
|
6
|
+
require_relative "constants"
|
7
|
+
|
8
|
+
module MCP
|
9
|
+
class Client
|
10
|
+
attr_reader :command, :args, :process, :stdin, :stdout, :stderr, :wait_thread
|
11
|
+
|
12
|
+
def initialize(command:, args: [], name: "mcp-client", version: VERSION)
|
13
|
+
@command = command
|
14
|
+
@args = args
|
15
|
+
@process = nil
|
16
|
+
@name = name
|
17
|
+
@version = version
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect
|
21
|
+
return if @process
|
22
|
+
|
23
|
+
start_server
|
24
|
+
initialize_connection
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def running? = !@process.nil?
|
29
|
+
|
30
|
+
def list_tools
|
31
|
+
ensure_running
|
32
|
+
send_request({
|
33
|
+
jsonrpc: Constants::JSON_RPC_VERSION,
|
34
|
+
method: Constants::RequestMethods::TOOLS_LIST,
|
35
|
+
params: {},
|
36
|
+
id: SecureRandom.uuid
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
def call_tool(name:, args: {})
|
41
|
+
ensure_running
|
42
|
+
send_request({
|
43
|
+
jsonrpc: Constants::JSON_RPC_VERSION,
|
44
|
+
method: Constants::RequestMethods::TOOLS_CALL,
|
45
|
+
params: {
|
46
|
+
name: name,
|
47
|
+
arguments: args
|
48
|
+
},
|
49
|
+
id: SecureRandom.uuid
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
return unless @process
|
55
|
+
|
56
|
+
@stdin.close
|
57
|
+
@stdout.close
|
58
|
+
@stderr.close
|
59
|
+
Process.kill("TERM", @process)
|
60
|
+
@wait_thread.join
|
61
|
+
@process = nil
|
62
|
+
rescue IOError, Errno::ESRCH
|
63
|
+
# プロセスが既に終了している場合は無視
|
64
|
+
@process = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def ensure_running
|
70
|
+
raise "Server process not running. Call #start first." unless running?
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize_connection
|
74
|
+
response = send_request({
|
75
|
+
jsonrpc: Constants::JSON_RPC_VERSION,
|
76
|
+
method: "initialize",
|
77
|
+
params: {
|
78
|
+
protocolVersion: Constants::PROTOCOL_VERSION,
|
79
|
+
client: {
|
80
|
+
name: @name,
|
81
|
+
version: @version
|
82
|
+
}
|
83
|
+
},
|
84
|
+
id: SecureRandom.uuid
|
85
|
+
})
|
86
|
+
|
87
|
+
@stdin.puts(JSON.generate({
|
88
|
+
jsonrpc: Constants::JSON_RPC_VERSION,
|
89
|
+
method: "notifications/initialized"
|
90
|
+
}))
|
91
|
+
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
def start_server
|
96
|
+
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@command, *@args)
|
97
|
+
@process = @wait_thread.pid
|
98
|
+
|
99
|
+
Thread.new do
|
100
|
+
while (line = @stderr.gets)
|
101
|
+
warn "[MCP Server] #{line}"
|
102
|
+
end
|
103
|
+
rescue IOError
|
104
|
+
# ignore when stream is closed
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def send_request(request)
|
109
|
+
@stdin.puts(JSON.generate(request))
|
110
|
+
response = @stdout.gets
|
111
|
+
raise "No response from server" unless response
|
112
|
+
|
113
|
+
result = JSON.parse(response, symbolize_names: true)
|
114
|
+
if result[:error]
|
115
|
+
raise "Server error: #{result[:error][:message]} (#{result[:error][:code]})"
|
116
|
+
end
|
117
|
+
|
118
|
+
result[:result]
|
119
|
+
rescue JSON::ParserError => e
|
120
|
+
raise "Invalid JSON response: #{e.message}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/mcp/constants.rb
CHANGED
data/lib/mcp/delegator.rb
CHANGED
data/lib/mcp/server.rb
CHANGED
@@ -3,32 +3,39 @@
|
|
3
3
|
require "json"
|
4
4
|
require "English"
|
5
5
|
require "uri"
|
6
|
+
require_relative "constants"
|
6
7
|
|
7
8
|
module MCP
|
8
9
|
class Server
|
9
|
-
|
10
|
+
attr_writer :name, :version
|
10
11
|
attr_reader :initialized
|
11
12
|
|
12
|
-
def initialize(name:, version:
|
13
|
+
def initialize(name:, version: "0.1.0")
|
13
14
|
@name = name
|
14
15
|
@version = version
|
15
16
|
@app = App.new
|
16
17
|
@initialized = false
|
17
|
-
@supported_protocol_versions = [PROTOCOL_VERSION]
|
18
|
+
@supported_protocol_versions = [Constants::PROTOCOL_VERSION]
|
18
19
|
end
|
19
20
|
|
20
|
-
def name(value = nil)
|
21
|
+
def name(value = nil)
|
21
22
|
return @name if value.nil?
|
22
23
|
|
23
24
|
@name = value
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
@
|
27
|
+
def version(value = nil)
|
28
|
+
return @version if value.nil?
|
29
|
+
|
30
|
+
@version = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def tool(name, &block)
|
34
|
+
@app.register_tool(name, &block)
|
28
35
|
end
|
29
36
|
|
30
|
-
def resource(uri,
|
31
|
-
@app.register_resource(uri,
|
37
|
+
def resource(uri, &block)
|
38
|
+
@app.register_resource(uri, &block)
|
32
39
|
end
|
33
40
|
|
34
41
|
def run
|
@@ -113,7 +120,7 @@ module MCP
|
|
113
120
|
jsonrpc: MCP::Constants::JSON_RPC_VERSION,
|
114
121
|
id: request[:id],
|
115
122
|
result: {
|
116
|
-
protocolVersion: PROTOCOL_VERSION,
|
123
|
+
protocolVersion: Constants::PROTOCOL_VERSION,
|
117
124
|
capabilities: {
|
118
125
|
logging: {},
|
119
126
|
prompts: {
|
data/lib/mcp/version.rb
CHANGED
data/lib/mcp.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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- funwarioisii
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-02-
|
10
|
+
date: 2025-02-19 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.
|
@@ -24,6 +24,7 @@ files:
|
|
24
24
|
- lib/mcp/app.rb
|
25
25
|
- lib/mcp/app/resource.rb
|
26
26
|
- lib/mcp/app/tool.rb
|
27
|
+
- lib/mcp/client.rb
|
27
28
|
- lib/mcp/constants.rb
|
28
29
|
- lib/mcp/delegator.rb
|
29
30
|
- lib/mcp/server.rb
|
@@ -49,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
rubygems_version: 3.6.
|
53
|
+
rubygems_version: 3.6.3
|
53
54
|
specification_version: 4
|
54
55
|
summary: A lightweight Ruby framework for implementing MCP (Model Context Protocol)
|
55
56
|
servers
|