model-context-protocol-rb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="