model-context-protocol-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9da483d392c30f83fc52293db51b1d807bc054889e6cd29d54d1dd96e9043ea6
4
- data.tar.gz: 07f07aa7d469b674d57718ce3cb4d7abfffc64f1f0b4a159613dd6bfb13b4957
3
+ metadata.gz: 0127f55c18cd5b2886b93b9f939b2b1358b14d2700e1f9194b26e5f43935101f
4
+ data.tar.gz: c0ce5f9bc0891e5b20fb3b30acee6c6dee0ebc2d412fac31078839fcf94f0010
5
5
  SHA512:
6
- metadata.gz: 37f34d68b99ebf423c8500def91c848662820bdf3c8b295e443e8aaecd588ed030b2b76838e8e94b42f5e488e5bf85c4b9f5390cfd910676993309b6152344cb
7
- data.tar.gz: 22b094b711874327767cddbf77c95a8f0ab35d23710656925cc8bcadab0aa6fcf01d602dedeb90d05d3fb7320b034150f85590e54539d4045392722426d2e967
6
+ metadata.gz: f17f3371c5c07b264b2a495fbc8b3e47fc8d18993cc3bd64f9fe91f4475720dde9ec536f266af1f22e7c7eb30ffd0bc4e5680f0a53f8a39c08d2eff034206e9f
7
+ data.tar.gz: 564433c1386b063560c17502b7346ae7db2cc523c47ab4ecd62fb986c51fba178432331a4ceed699ec9469c8d63e719acddce669c03d68969b8fc7086a85e640
data/.solargraph.yml ADDED
@@ -0,0 +1,13 @@
1
+ include:
2
+ - "**/*.rb"
3
+ exclude:
4
+ - spec/**/*
5
+ - ".bundle/**"
6
+ require: []
7
+ domains: []
8
+ require_paths: []
9
+ plugins:
10
+ - solargraph-standardrb
11
+ reporters:
12
+ - standardrb
13
+ max_files: 5000
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,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-03-11
4
+
5
+ - (Breaking) Replaced router initialization in favor of registry initialization during server configuration. The server now relies on the registry for auto-discovery of prompts, resources, and tools; this requires the use of SDK-provided builders to facilitate.
6
+ - (Breaking) Implemented the use of `Data` objects across the implementation. As a result, responses from custom handlers must now respond with an object that responds to `serialized`.
7
+ - Refactored the implementation to maintain separation of concerns and improve testability/maintainability.
8
+ - Improved test coverage.
9
+ - Improved development tooling.
10
+
11
+ ## [0.2.0] - 2025-01-13
12
+
13
+ - Added a basic, synchronous server implementation that routes requests to custom handlers.
14
+
3
15
  ## [0.1.0] - 2025-01-10
4
16
 
5
17
  - Initial release
18
+
19
+ [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...HEAD
20
+ [0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
21
+ [0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
22
+ [0.1.0]: https://github.com/dickdavis/model-context-protocol-rb/releases/tag/v0.1.0
data/README.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/) in Ruby.
4
4
 
5
+ This SDK is experimental and subject to change. The initial focus is to implement MCP server support with the goal of providing a stable API by version `0.4`. MCP client support will follow.
6
+
7
+ You are welcome to contribute.
8
+
9
+ TODO's:
10
+
11
+ * [Completion](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/completion/)
12
+ * [Logging](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging/)
13
+ * [Pagination](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/pagination/)
14
+ * [Prompt list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/#list-changed-notification)
15
+ * [Resource list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#list-changed-notification)
16
+ * [Resource subscriptions](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#subscriptions)
17
+ * [Resource templates](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#resource-templates)
18
+ * [Tool list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/#list-changed-notification)
19
+
5
20
  ## Usage
6
21
 
7
22
  Include `model-context-protocol-rb` in your project.
@@ -10,7 +25,124 @@ Include `model-context-protocol-rb` in your project.
10
25
  require 'model-context-protocol-rb'
11
26
  ```
12
27
 
13
- TODO: Write usage instructions here once implementation lands
28
+ ### Building an MCP Server
29
+
30
+ Build a simple MCP server by routing methods to your custom handlers. Then, configure and run the server.
31
+
32
+ ```ruby
33
+ server = ModelContextProtocol::Server.new do |config|
34
+ config.name = "MCP Development Server"
35
+ config.version = "1.0.0"
36
+ config.enable_log = true
37
+ config.registry = ModelContextProtocol::Server::Registry.new do
38
+ prompts list_changed: true do
39
+ register TestPrompt
40
+ end
41
+
42
+ resources list_changed: true, subscribe: true do
43
+ register TestResource
44
+ end
45
+
46
+ tools list_changed: true do
47
+ register TestTool
48
+ end
49
+ end
50
+ end
51
+
52
+ server.start
53
+ ```
54
+
55
+ Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
56
+
57
+ #### ModelContextProtocol::Server::Prompt
58
+
59
+ The `Prompt` class is used to define a prompt that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/) in the `with_metadata` block, and then implement the call method to build your prompt. The `call` method should return a `Response` data object.
60
+
61
+ ```ruby
62
+ class TestPrompt < ModelContextProtocol::Server::Prompt
63
+ with_metadata do
64
+ {
65
+ name: "Test Prompt",
66
+ description: "A test prompt",
67
+ arguments: [
68
+ {
69
+ name: "message",
70
+ description: "The thing to do",
71
+ required: true
72
+ },
73
+ {
74
+ name: "other",
75
+ description: "Another thing to do",
76
+ required: false
77
+ }
78
+ ]
79
+ }
80
+ end
81
+
82
+ def call
83
+ messages = [
84
+ {
85
+ role: "user",
86
+ content: {
87
+ type: "text",
88
+ text: "Do this: #{params["message"]}"
89
+ }
90
+ }
91
+ ]
92
+
93
+ Response[messages:, prompt: self]
94
+ end
95
+ end
96
+ ```
97
+
98
+ #### ModelContextProtocol::Server::Resource
99
+
100
+ The `Resource` class is used to define a resource that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/) in the `with_metadata` block, and then implement the 'call' method to build your prompt. The `call` method should return a `TextResponse` or a `BinaryResponse` data object.
101
+
102
+ ```ruby
103
+ class TestResource < ModelContextProtocol::Server::Resource
104
+ with_metadata do
105
+ {
106
+ name: "Test Resource",
107
+ description: "A test resource",
108
+ mime_type: "text/plain",
109
+ uri: "resource://test-resource"
110
+ }
111
+ end
112
+
113
+ def call
114
+ TextResponse[resource: self, text: "Here's the data"]
115
+ end
116
+ end
117
+ ```
118
+
119
+ #### ModelContextProtocol::Server::Tool
120
+
121
+ The `Tool` class is used to define a tool that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/) in the `with_metadata` block, and then implement the `call` method to build your prompt. The `call` method should return a `TextResponse`, `ImageResponse`, `ResourceResponse`, or `ToolErrorResponse` data object.
122
+
123
+ ```ruby
124
+ class TestTool < ModelContextProtocol::Server::Tool
125
+ with_metadata do
126
+ {
127
+ name: "test-tool",
128
+ description: "A test tool",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ message: {
133
+ type: "string"
134
+ }
135
+ },
136
+ required: ["message"]
137
+ }
138
+ }
139
+ end
140
+
141
+ def call
142
+ TextResponse[text: "You said: #{params["message"]}"]
143
+ end
144
+ end
145
+ ```
14
146
 
15
147
  ## Installation
16
148
 
@@ -0,0 +1,38 @@
1
+ module ModelContextProtocol
2
+ class Server::Configuration
3
+ # Raised when configured with invalid name.
4
+ class InvalidServerNameError < StandardError; end
5
+
6
+ # Raised when configured with invalid version.
7
+ class InvalidServerVersionError < StandardError; end
8
+
9
+ # Raised when configured with invalid registry.
10
+ class InvalidRegistryError < StandardError; end
11
+
12
+ attr_accessor :enable_log, :name, :registry, :version
13
+
14
+ def logging_enabled?
15
+ enable_log || false
16
+ end
17
+
18
+ def validate!
19
+ raise InvalidServerNameError unless valid_name?
20
+ raise InvalidRegistryError unless valid_registry?
21
+ raise InvalidServerVersionError unless valid_version?
22
+ end
23
+
24
+ private
25
+
26
+ def valid_name?
27
+ name&.is_a?(String)
28
+ end
29
+
30
+ def valid_registry?
31
+ registry&.is_a?(ModelContextProtocol::Server::Registry)
32
+ end
33
+
34
+ def valid_version?
35
+ version&.is_a?(String)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ module ModelContextProtocol
2
+ class Server::Prompt
3
+ attr_reader :params, :description
4
+
5
+ Response = Data.define(:messages, :prompt) do
6
+ def serialized
7
+ {description: prompt.description, messages:}
8
+ end
9
+ end
10
+
11
+ def initialize(params)
12
+ validate!(params)
13
+ @description = self.class.description
14
+ @params = params
15
+ end
16
+
17
+ def call
18
+ raise NotImplementedError, "Subclasses must implement the call method"
19
+ end
20
+
21
+ private def validate!(params = {})
22
+ arguments = self.class.arguments || []
23
+ required_args = arguments.select { |arg| arg[:required] }.map { |arg| arg[:name] }
24
+ valid_arg_names = arguments.map { |arg| arg[:name] }
25
+
26
+ missing_args = required_args - params.keys
27
+ unless missing_args.empty?
28
+ missing_args_list = missing_args.join(", ")
29
+ raise ArgumentError, "Missing required arguments: #{missing_args_list}"
30
+ end
31
+
32
+ extra_args = params.keys - valid_arg_names
33
+ unless extra_args.empty?
34
+ extra_args_list = extra_args.join(", ")
35
+ raise ArgumentError, "Unexpected arguments: #{extra_args_list}"
36
+ end
37
+ end
38
+
39
+ class << self
40
+ attr_reader :name, :description, :arguments
41
+
42
+ def with_metadata(&block)
43
+ metadata = instance_eval(&block)
44
+
45
+ @name = metadata[:name]
46
+ @description = metadata[:description]
47
+ @arguments = metadata[:arguments]
48
+ end
49
+
50
+ def inherited(subclass)
51
+ subclass.instance_variable_set(:@name, @name)
52
+ subclass.instance_variable_set(:@description, @description)
53
+ subclass.instance_variable_set(:@arguments, @arguments)
54
+ end
55
+
56
+ def call(params)
57
+ new(params).call
58
+ rescue ArgumentError => error
59
+ raise ModelContextProtocol::Server::ParameterValidationError, error.message
60
+ end
61
+
62
+ def metadata
63
+ {name: @name, description: @description, arguments: @arguments}
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,102 @@
1
+ module ModelContextProtocol
2
+ class Server::Registry
3
+ attr_reader :prompts_options, :resources_options, :tools_options
4
+
5
+ def self.new(&block)
6
+ registry = allocate
7
+ registry.send(:initialize)
8
+ registry.instance_eval(&block) if block
9
+ registry
10
+ end
11
+
12
+ def initialize
13
+ @prompts = []
14
+ @resources = []
15
+ @tools = []
16
+ @prompts_options = {}
17
+ @resources_options = {}
18
+ @tools_options = {}
19
+ end
20
+
21
+ def prompts(options = {}, &block)
22
+ @prompts_options = options
23
+ instance_eval(&block) if block
24
+ end
25
+
26
+ def resources(options = {}, &block)
27
+ @resources_options = options
28
+ instance_eval(&block) if block
29
+ end
30
+
31
+ def tools(options = {}, &block)
32
+ @tools_options = options
33
+ instance_eval(&block) if block
34
+ end
35
+
36
+ def register(klass)
37
+ metadata = klass.metadata
38
+ entry = {klass: klass}.merge(metadata)
39
+
40
+ case klass.ancestors
41
+ when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::Prompt) }
42
+ @prompts << entry
43
+ when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::Resource) }
44
+ @resources << entry
45
+ when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::Tool) }
46
+ @tools << entry
47
+ else
48
+ raise ArgumentError, "Unknown class type: #{klass}"
49
+ end
50
+ end
51
+
52
+ def find_prompt(name)
53
+ find_by_name(@prompts, name)
54
+ end
55
+
56
+ def find_resource(uri)
57
+ entry = @resources.find { |r| r[:uri] == uri }
58
+ entry ? entry[:klass] : nil
59
+ end
60
+
61
+ def find_tool(name)
62
+ find_by_name(@tools, name)
63
+ end
64
+
65
+ def prompts_data
66
+ PromptsData[prompts: @prompts.map { |entry| entry.except(:klass) }]
67
+ end
68
+
69
+ def resources_data
70
+ ResourcesData[resources: @resources.map { |entry| entry.except(:klass) }]
71
+ end
72
+
73
+ def tools_data
74
+ ToolsData[tools: @tools.map { |entry| entry.except(:klass) }]
75
+ end
76
+
77
+ private
78
+
79
+ PromptsData = Data.define(:prompts) do
80
+ def serialized
81
+ {prompts:}
82
+ end
83
+ end
84
+
85
+ ResourcesData = Data.define(:resources) do
86
+ def serialized
87
+ {resources:}
88
+ end
89
+ end
90
+
91
+ ToolsData = Data.define(:tools) do
92
+ def serialized
93
+ {tools:}
94
+ end
95
+ end
96
+
97
+ def find_by_name(collection, name)
98
+ entry = collection.find { |item| item[:name] == name }
99
+ entry ? entry[:klass] : nil
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,54 @@
1
+ module ModelContextProtocol
2
+ class Server::Resource
3
+ attr_reader :mime_type, :uri
4
+
5
+ TextResponse = Data.define(:resource, :text) do
6
+ def serialized
7
+ {contents: [{mimeType: resource.mime_type, text:, uri: resource.uri}]}
8
+ end
9
+ end
10
+
11
+ BinaryResponse = Data.define(:blob, :resource) do
12
+ def serialized
13
+ {contents: [{blob:, mimeType: resource.mime_type, uri: resource.uri}]}
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @mime_type = self.class.mime_type
19
+ @uri = self.class.uri
20
+ end
21
+
22
+ def call
23
+ raise NotImplementedError, "Subclasses must implement the call method"
24
+ end
25
+
26
+ class << self
27
+ attr_reader :name, :description, :mime_type, :uri
28
+
29
+ def with_metadata(&block)
30
+ metadata = instance_eval(&block)
31
+
32
+ @name = metadata[:name]
33
+ @description = metadata[:description]
34
+ @mime_type = metadata[:mime_type]
35
+ @uri = metadata[:uri]
36
+ end
37
+
38
+ def inherited(subclass)
39
+ subclass.instance_variable_set(:@name, @name)
40
+ subclass.instance_variable_set(:@description, @description)
41
+ subclass.instance_variable_set(:@mime_type, @mime_type)
42
+ subclass.instance_variable_set(:@uri, @uri)
43
+ end
44
+
45
+ def call
46
+ new.call
47
+ end
48
+
49
+ def metadata
50
+ {name: @name, description: @description, mime_type: @mime_type, uri: @uri}
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ module ModelContextProtocol
2
+ class Server::Router
3
+ # Raised when an invalid method is provided.
4
+ class MethodNotFoundError < StandardError; end
5
+
6
+ def initialize
7
+ @handlers = {}
8
+ end
9
+
10
+ def map(method, &handler)
11
+ @handlers[method] = handler
12
+ end
13
+
14
+ def route(message)
15
+ method = message["method"]
16
+ handler = @handlers[method]
17
+ raise MethodNotFoundError, "Method not found: #{method}" unless handler
18
+
19
+ handler.call(message)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,65 @@
1
+ module ModelContextProtocol
2
+ class Server::StdioTransport
3
+ Response = Data.define(:id, :result) do
4
+ def serialized
5
+ {jsonrpc: "2.0", id:, result:}
6
+ end
7
+ end
8
+
9
+ ErrorResponse = Data.define(:id, :error) do
10
+ def serialized
11
+ {jsonrpc: "2.0", id:, error:}
12
+ end
13
+ end
14
+
15
+ attr_reader :logger, :router
16
+
17
+ def initialize(logger:, router:)
18
+ @logger = logger
19
+ @router = router
20
+ end
21
+
22
+ def begin
23
+ loop do
24
+ line = $stdin.gets
25
+ break unless line
26
+
27
+ begin
28
+ message = JSON.parse(line.chomp)
29
+ next if message["method"].start_with?("notifications")
30
+
31
+ result = router.route(message)
32
+ send_message(Response[id: message["id"], result: result.serialized])
33
+ rescue ModelContextProtocol::Server::ParameterValidationError => validation_error
34
+ log("Validation error: #{validation_error.message}")
35
+ send_message(
36
+ ErrorResponse[id: message["id"], error: {code: -32602, message: validation_error.message}]
37
+ )
38
+ rescue JSON::ParserError => parser_error
39
+ log("Parser error: #{parser_error.message}")
40
+ send_message(
41
+ ErrorResponse[id: "", error: {code: -32700, message: parser_error.message}]
42
+ )
43
+ rescue => error
44
+ log("Internal error: #{error.message}")
45
+ log(error.backtrace)
46
+ send_message(
47
+ ErrorResponse[id: message["id"], error: {code: -32603, message: error.message}]
48
+ )
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def log(output, level = :error)
56
+ logger.send(level.to_sym, output)
57
+ end
58
+
59
+ def send_message(message)
60
+ message_json = JSON.generate(message.serialized)
61
+ $stdout.puts(message_json)
62
+ $stdout.flush
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,82 @@
1
+ require "json-schema"
2
+
3
+ module ModelContextProtocol
4
+ class Server::Tool
5
+ attr_reader :params
6
+
7
+ TextResponse = Data.define(:text) do
8
+ def serialized
9
+ {content: [{type: "text", text:}], isError: false}
10
+ end
11
+ end
12
+
13
+ ImageResponse = Data.define(:data, :mime_type) do
14
+ def initialize(data:, mime_type: "image/png")
15
+ super
16
+ end
17
+
18
+ def serialized
19
+ {content: [{type: "image", data:, mimeType: mime_type}], isError: false}
20
+ end
21
+ end
22
+
23
+ ResourceResponse = Data.define(:uri, :text, :mime_type) do
24
+ def initialize(uri:, text:, mime_type: "text/plain")
25
+ super
26
+ end
27
+
28
+ def serialized
29
+ {content: [{type: "resource", resource: {uri:, mimeType: mime_type, text:}}], isError: false}
30
+ end
31
+ end
32
+
33
+ ToolErrorResponse = Data.define(:text) do
34
+ def serialized
35
+ {content: [{type: "text", text:}], isError: true}
36
+ end
37
+ end
38
+
39
+ def initialize(params)
40
+ validate!(params)
41
+ @params = params
42
+ end
43
+
44
+ def call
45
+ raise NotImplementedError, "Subclasses must implement the call method"
46
+ end
47
+
48
+ private def validate!(params)
49
+ JSON::Validator.validate!(self.class.input_schema, params)
50
+ end
51
+
52
+ class << self
53
+ attr_reader :name, :description, :input_schema
54
+
55
+ def with_metadata(&block)
56
+ metadata = instance_eval(&block)
57
+
58
+ @name = metadata[:name]
59
+ @description = metadata[:description]
60
+ @input_schema = metadata[:inputSchema]
61
+ end
62
+
63
+ def inherited(subclass)
64
+ subclass.instance_variable_set(:@name, @name)
65
+ subclass.instance_variable_set(:@description, @description)
66
+ subclass.instance_variable_set(:@input_schema, @input_schema)
67
+ end
68
+
69
+ def call(params)
70
+ new(params).call
71
+ rescue JSON::Schema::ValidationError => error
72
+ raise ModelContextProtocol::Server::ParameterValidationError, error.message
73
+ rescue => error
74
+ ToolErrorResponse[text: error.message]
75
+ end
76
+
77
+ def metadata
78
+ {name: @name, description: @description, inputSchema: @input_schema}
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,112 @@
1
+ require "logger"
2
+
3
+ module ModelContextProtocol
4
+ class Server
5
+ # Raised when invalid parameters are provided.
6
+ class ParameterValidationError < StandardError; end
7
+
8
+ attr_reader :configuration, :router
9
+
10
+ def initialize
11
+ @configuration = Configuration.new
12
+ yield(@configuration) if block_given?
13
+ @router = Router.new
14
+ map_handlers
15
+ end
16
+
17
+ def start
18
+ configuration.validate!
19
+ logdev = configuration.logging_enabled? ? $stderr : File::NULL
20
+ StdioTransport.new(logger: Logger.new(logdev), router:).begin
21
+ end
22
+
23
+ private
24
+
25
+ PROTOCOL_VERSION = "2024-11-05".freeze
26
+ private_constant :PROTOCOL_VERSION
27
+
28
+ InitializeResponse = Data.define(:protocol_version, :capabilities, :server_info) do
29
+ def serialized
30
+ {
31
+ protocolVersion: protocol_version,
32
+ capabilities: capabilities,
33
+ serverInfo: server_info
34
+ }
35
+ end
36
+ end
37
+
38
+ PingResponse = Data.define do
39
+ def serialized
40
+ {}
41
+ end
42
+ end
43
+
44
+ def map_handlers
45
+ router.map("initialize") do |_message|
46
+ InitializeResponse[
47
+ protocol_version: PROTOCOL_VERSION,
48
+ capabilities: build_capabilities,
49
+ server_info: {
50
+ name: configuration.name,
51
+ version: configuration.version
52
+ }
53
+ ]
54
+ end
55
+
56
+ router.map("ping") do
57
+ PingResponse[]
58
+ end
59
+
60
+ router.map("resources/list") do
61
+ configuration.registry.resources_data
62
+ end
63
+
64
+ router.map("resources/read") do |message|
65
+ configuration.registry.find_resource(message["params"]["uri"]).call
66
+ end
67
+
68
+ router.map("prompts/list") do
69
+ configuration.registry.prompts_data
70
+ end
71
+
72
+ router.map("prompts/get") do |message|
73
+ configuration.registry.find_prompt(message["params"]["name"]).call(message["params"]["arguments"])
74
+ end
75
+
76
+ router.map("tools/list") do
77
+ configuration.registry.tools_data
78
+ end
79
+
80
+ router.map("tools/call") do |message|
81
+ configuration.registry.find_tool(message["params"]["name"]).call(message["params"]["arguments"])
82
+ end
83
+ end
84
+
85
+ def build_capabilities
86
+ {}.tap do |capabilities|
87
+ capabilities[:logging] = {} if configuration.logging_enabled?
88
+
89
+ registry = configuration.registry
90
+
91
+ if registry.prompts_options.any? && !registry.instance_variable_get(:@prompts).empty?
92
+ capabilities[:prompts] = {
93
+ listChanged: registry.prompts_options[:list_changed]
94
+ }.compact
95
+ end
96
+
97
+ if registry.resources_options.any? && !registry.instance_variable_get(:@resources).empty?
98
+ capabilities[:resources] = {
99
+ subscribe: registry.resources_options[:subscribe],
100
+ listChanged: registry.resources_options[:list_changed]
101
+ }.compact
102
+ end
103
+
104
+ if registry.tools_options.any? && !registry.instance_variable_get(:@tools).empty?
105
+ capabilities[:tools] = {
106
+ listChanged: registry.tools_options[:list_changed]
107
+ }.compact
108
+ end
109
+ end
110
+ end
111
+ end
112
+ 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.3.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,15 +1,29 @@
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.3.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
12
- dependencies: []
11
+ date: 2025-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json-schema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.1'
13
27
  description:
14
28
  email:
15
29
  - dick@hey.com
@@ -19,6 +33,7 @@ extra_rdoc_files: []
19
33
  files:
20
34
  - ".reek.yml"
21
35
  - ".rspec"
36
+ - ".solargraph.yml"
22
37
  - ".standard.yml"
23
38
  - ".tool-versions"
24
39
  - CHANGELOG.md
@@ -26,6 +41,14 @@ files:
26
41
  - README.md
27
42
  - Rakefile
28
43
  - lib/model_context_protocol.rb
44
+ - lib/model_context_protocol/server.rb
45
+ - lib/model_context_protocol/server/configuration.rb
46
+ - lib/model_context_protocol/server/prompt.rb
47
+ - lib/model_context_protocol/server/registry.rb
48
+ - lib/model_context_protocol/server/resource.rb
49
+ - lib/model_context_protocol/server/router.rb
50
+ - lib/model_context_protocol/server/stdio_transport.rb
51
+ - lib/model_context_protocol/server/tool.rb
29
52
  - lib/model_context_protocol/version.rb
30
53
  homepage: https://github.com/dickdavis/model-context-protocol-rb
31
54
  licenses:
@@ -43,7 +66,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
66
  requirements:
44
67
  - - ">="
45
68
  - !ruby/object:Gem::Version
46
- version: 2.6.0
69
+ version: 3.2.4
47
70
  required_rubygems_version: !ruby/object:Gem::Requirement
48
71
  requirements:
49
72
  - - ">="