model-context-protocol-rb 0.1.0 → 0.2.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/.standard.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/README.md +34 -1
- data/lib/model_context_protocol/server/router/base_map.rb +22 -0
- data/lib/model_context_protocol/server/router/prompts_map.rb +17 -0
- data/lib/model_context_protocol/server/router/protocol_map.rb +0 -0
- data/lib/model_context_protocol/server/router/resources_map.rb +17 -0
- data/lib/model_context_protocol/server/router/tools_map.rb +17 -0
- data/lib/model_context_protocol/server/router.rb +117 -0
- data/lib/model_context_protocol/server.rb +81 -0
- data/lib/model_context_protocol/version.rb +1 -1
- data/lib/model_context_protocol.rb +0 -1
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fffc016471f9e02baa6f9288860d65c7e9f4c9b573589d4abb31d824318b5ea1
|
|
4
|
+
data.tar.gz: 59b220de0291942879c34d7ca8cc47173b10d692a13e271c1458a59a7f2ff9fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abda740fa404a916e3b30aa3b8e8c06e1731bb4e810b44b3d60f91742f92298fa66d2b4165f102679a49ee2246da6223ece27ad68302e96040148c2a83d00b3d
|
|
7
|
+
data.tar.gz: bc71626a2bec6b61a4e8e3de7a6ef8a44fb64a9e8d56f163734ff8a7e4ef42d05f552b0918f16e798ed15749b145fa901cbc4747a798ca9a274ef941b8f08e44
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2025-01-13
|
|
4
|
+
|
|
5
|
+
- Added a basic, synchronous server implementation that routes requests to custom handlers.
|
|
6
|
+
|
|
3
7
|
## [0.1.0] - 2025-01-10
|
|
4
8
|
|
|
5
9
|
- Initial release
|
|
10
|
+
|
|
11
|
+
[Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...HEAD
|
|
12
|
+
[0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
|
|
13
|
+
[0.1.0]: https://github.com/dickdavis/model-context-protocol-rb/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -10,7 +10,40 @@ Include `model-context-protocol-rb` in your project.
|
|
|
10
10
|
require 'model-context-protocol-rb'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Building an MCP Server
|
|
14
|
+
|
|
15
|
+
Build a simple MCP server by routing methods to your custom handlers. Then, configure and run the server.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
server = ModelContextProtocol::Server.new do |config|
|
|
19
|
+
config.name = "My MCP Server"
|
|
20
|
+
config.router = router
|
|
21
|
+
config.version = "1.0.0"
|
|
22
|
+
config.enable_log = true
|
|
23
|
+
config.router = ModelContextProtocol::Router.new do
|
|
24
|
+
prompts do
|
|
25
|
+
list Prompt::List, broadcast_changes: true
|
|
26
|
+
get Prompt::Get
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
resources do
|
|
30
|
+
list Resource::List, broadcast_changes: true
|
|
31
|
+
read Resource::Read, allow_subscriptions: true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
tools do
|
|
35
|
+
list Tool::List, broadcast_changes: true
|
|
36
|
+
call Tool::Call
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
server.start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Messages from the MCP client will be routed to the appropriate custom handler. Your customer handler must respond to `call`; the router will pass the message to the handler as an argument.
|
|
45
|
+
|
|
46
|
+
Your handler should return a valid JSONRPC 2.0 response.
|
|
14
47
|
|
|
15
48
|
## Installation
|
|
16
49
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module ModelContextProtocol
|
|
2
|
+
class Server
|
|
3
|
+
class Router
|
|
4
|
+
##
|
|
5
|
+
# Base class for route maps.
|
|
6
|
+
class BaseMap
|
|
7
|
+
def initialize(routes)
|
|
8
|
+
@routes = routes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def register(method, handler, **options)
|
|
14
|
+
@routes[method] = {
|
|
15
|
+
handler: handler,
|
|
16
|
+
options: options
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ModelContextProtocol
|
|
2
|
+
class Server
|
|
3
|
+
class Router
|
|
4
|
+
##
|
|
5
|
+
# Maps prompt operations to handlers.
|
|
6
|
+
class PromptsMap < BaseMap
|
|
7
|
+
def list(handler, broadcast_changes: false)
|
|
8
|
+
register("prompts/list", handler, broadcast_changes:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(handler)
|
|
12
|
+
register("prompts/get", handler)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ModelContextProtocol
|
|
2
|
+
class Server
|
|
3
|
+
class Router
|
|
4
|
+
##
|
|
5
|
+
# Maps resource operations to handlers.
|
|
6
|
+
class ResourcesMap < BaseMap
|
|
7
|
+
def list(handler, broadcast_changes: false)
|
|
8
|
+
register("resources/list", handler, broadcast_changes:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def read(handler, allow_subscriptions: false)
|
|
12
|
+
register("resources/read", handler, allow_subscriptions:)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ModelContextProtocol
|
|
2
|
+
class Server
|
|
3
|
+
class Router
|
|
4
|
+
##
|
|
5
|
+
# Maps tool operations to handlers.
|
|
6
|
+
class ToolsMap < BaseMap
|
|
7
|
+
def list(handler, broadcast_changes: false)
|
|
8
|
+
register("tools/list", handler, broadcast_changes:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(handler)
|
|
12
|
+
register("tools/call", handler)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ModelContextProtocol
|
|
4
|
+
class Server
|
|
5
|
+
class Router
|
|
6
|
+
attr_accessor :server
|
|
7
|
+
|
|
8
|
+
def self.new(&block)
|
|
9
|
+
router = allocate
|
|
10
|
+
router.send(:initialize)
|
|
11
|
+
router.instance_eval(&block) if block
|
|
12
|
+
router
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@routes = {}
|
|
17
|
+
register_protocol_routes
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prompts(&block)
|
|
21
|
+
PromptsMap.new(@routes).instance_eval(&block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resources(&block)
|
|
25
|
+
ResourcesMap.new(@routes).instance_eval(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tools(&block)
|
|
29
|
+
ToolsMap.new(@routes).instance_eval(&block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def route(message)
|
|
33
|
+
handler_config = @routes[message["method"]]
|
|
34
|
+
return nil unless handler_config
|
|
35
|
+
|
|
36
|
+
handler_config[:handler].call(message)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def server_info
|
|
42
|
+
if server&.configuration
|
|
43
|
+
{
|
|
44
|
+
name: server.configuration.name,
|
|
45
|
+
version: server.configuration.version
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
{name: "mcp-server", version: "1.0.0"}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def register_protocol_routes
|
|
53
|
+
register("initialize") do |_message|
|
|
54
|
+
{
|
|
55
|
+
protocolVersion: ModelContextProtocol::Server::PROTOCOL_VERSION,
|
|
56
|
+
capabilities: build_capabilities,
|
|
57
|
+
serverInfo: server_info
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
register("notifications/initialized") do |_message|
|
|
62
|
+
nil # No-op notification handler
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
register("ping") do |_message|
|
|
66
|
+
{} # Simple pong response
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def register(method, handler = nil, **options, &block)
|
|
71
|
+
@routes[method] = {
|
|
72
|
+
handler: block || handler,
|
|
73
|
+
options: options
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_capabilities
|
|
78
|
+
{
|
|
79
|
+
prompts: has_prompt_routes? ? {broadcast_changes: prompt_broadcasts_changes?} : nil,
|
|
80
|
+
resources: has_resource_routes? ? {
|
|
81
|
+
broadcast_changes: resource_broadcasts_changes?,
|
|
82
|
+
subscribe: resource_allows_subscriptions?
|
|
83
|
+
} : nil,
|
|
84
|
+
tools: has_tool_routes? ? {broadcast_changes: tool_broadcasts_changes?} : nil
|
|
85
|
+
}.compact
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def has_prompt_routes?
|
|
89
|
+
@routes.key?("prompts/list") || @routes.key?("prompts/get")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def prompt_broadcasts_changes?
|
|
93
|
+
@routes.dig("prompts/list", :options, :broadcast_changes)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def has_resource_routes?
|
|
97
|
+
@routes.key?("resources/list") || @routes.key?("resources/read")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resource_broadcasts_changes?
|
|
101
|
+
@routes.dig("resources/list", :options, :broadcast_changes)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def resource_allows_subscriptions?
|
|
105
|
+
@routes.dig("resources/read", :options, :allow_subscriptions)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def has_tool_routes?
|
|
109
|
+
@routes.key?("tools/list") || @routes.key?("tools/call")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def tool_broadcasts_changes?
|
|
113
|
+
@routes.dig("tools/list", :options, :broadcast_changes)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module ModelContextProtocol
|
|
4
|
+
class Server
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :enable_log, :name, :router, :version
|
|
7
|
+
|
|
8
|
+
def logging_enabled?
|
|
9
|
+
enable_log || false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate!
|
|
13
|
+
raise InvalidServerNameError unless valid_name?
|
|
14
|
+
raise InvalidRouterError unless valid_router?
|
|
15
|
+
raise InvalidServerVersionError unless valid_version?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def valid_name?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def valid_router?
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def valid_version?
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
PROTOCOL_VERSION = "2024-11-05".freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :configuration
|
|
36
|
+
|
|
37
|
+
def initialize
|
|
38
|
+
@configuration = Configuration.new
|
|
39
|
+
yield(@configuration) if block_given?
|
|
40
|
+
@configuration.router.server = self if @configuration.router
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def start
|
|
44
|
+
log("Server starting")
|
|
45
|
+
|
|
46
|
+
configuration.validate!
|
|
47
|
+
|
|
48
|
+
loop do
|
|
49
|
+
line = $stdin.gets
|
|
50
|
+
break unless line
|
|
51
|
+
|
|
52
|
+
message = JSON.parse(line.chomp)
|
|
53
|
+
log("Received message: #{message.inspect}")
|
|
54
|
+
|
|
55
|
+
response = configuration.router.route(message)
|
|
56
|
+
send_response(message["id"], response) if response
|
|
57
|
+
end
|
|
58
|
+
rescue => e
|
|
59
|
+
log("Error: #{e.message}")
|
|
60
|
+
log(e.backtrace)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def log(output)
|
|
66
|
+
warn(output) if configuration.logging_enabled?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def send_response(id, result)
|
|
70
|
+
return unless result
|
|
71
|
+
|
|
72
|
+
response = {
|
|
73
|
+
jsonrpc: "2.0",
|
|
74
|
+
id: id,
|
|
75
|
+
result: result
|
|
76
|
+
}
|
|
77
|
+
$stdout.puts(JSON.generate(response))
|
|
78
|
+
$stdout.flush
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: model-context-protocol-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dick Davis
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-01-
|
|
11
|
+
date: 2025-01-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -26,6 +26,13 @@ files:
|
|
|
26
26
|
- README.md
|
|
27
27
|
- Rakefile
|
|
28
28
|
- lib/model_context_protocol.rb
|
|
29
|
+
- lib/model_context_protocol/server.rb
|
|
30
|
+
- lib/model_context_protocol/server/router.rb
|
|
31
|
+
- lib/model_context_protocol/server/router/base_map.rb
|
|
32
|
+
- lib/model_context_protocol/server/router/prompts_map.rb
|
|
33
|
+
- lib/model_context_protocol/server/router/protocol_map.rb
|
|
34
|
+
- lib/model_context_protocol/server/router/resources_map.rb
|
|
35
|
+
- lib/model_context_protocol/server/router/tools_map.rb
|
|
29
36
|
- lib/model_context_protocol/version.rb
|
|
30
37
|
homepage: https://github.com/dickdavis/model-context-protocol-rb
|
|
31
38
|
licenses:
|
|
@@ -43,7 +50,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
43
50
|
requirements:
|
|
44
51
|
- - ">="
|
|
45
52
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 2.
|
|
53
|
+
version: 3.2.4
|
|
47
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
55
|
requirements:
|
|
49
56
|
- - ">="
|