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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9da483d392c30f83fc52293db51b1d807bc054889e6cd29d54d1dd96e9043ea6
4
- data.tar.gz: 07f07aa7d469b674d57718ce3cb4d7abfffc64f1f0b4a159613dd6bfb13b4957
3
+ metadata.gz: fffc016471f9e02baa6f9288860d65c7e9f4c9b573589d4abb31d824318b5ea1
4
+ data.tar.gz: 59b220de0291942879c34d7ca8cc47173b10d692a13e271c1458a59a7f2ff9fb
5
5
  SHA512:
6
- metadata.gz: 37f34d68b99ebf423c8500def91c848662820bdf3c8b295e443e8aaecd588ed030b2b76838e8e94b42f5e488e5bf85c4b9f5390cfd910676993309b6152344cb
7
- data.tar.gz: 22b094b711874327767cddbf77c95a8f0ab35d23710656925cc8bcadab0aa6fcf01d602dedeb90d05d3fb7320b034150f85590e54539d4045392722426d2e967
6
+ metadata.gz: abda740fa404a916e3b30aa3b8e8c06e1731bb4e810b44b3d60f91742f92298fa66d2b4165f102679a49ee2246da6223ece27ad68302e96040148c2a83d00b3d
7
+ data.tar.gz: bc71626a2bec6b61a4e8e3de7a6ef8a44fb64a9e8d56f163734ff8a7e4ef42d05f552b0918f16e798ed15749b145fa901cbc4747a798ca9a274ef941b8f08e44
data/.standard.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/testdouble/standard
3
- ruby_version: 2.6
3
+ ruby_version: 3.2
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
- TODO: Write usage instructions here once implementation lands
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModelContextProtocol
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -5,5 +5,4 @@ Dir[File.join(__dir__, "model_context_protocol/", "**", "*.rb")].sort.each { |fi
5
5
  ##
6
6
  # Top-level namespace
7
7
  module ModelContextProtocol
8
- # TODO: everything
9
8
  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.1.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 00:00:00.000000000 Z
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.6.0
53
+ version: 3.2.4
47
54
  required_rubygems_version: !ruby/object:Gem::Requirement
48
55
  requirements:
49
56
  - - ">="