mcp_lite 2.0.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.
@@ -0,0 +1,200 @@
1
+ require "json"
2
+
3
+ module McpLite
4
+ class Server
5
+ class ProtocolHandler
6
+ attr_reader :initialized
7
+
8
+ def initialize(server)
9
+ @server = server
10
+ @initialized = false
11
+ @supported_protocol_versions = [PROTOCOL_VERSION]
12
+ end
13
+
14
+ def process_message(message)
15
+ message = message.to_s.force_encoding("UTF-8")
16
+ result = begin
17
+ request = JSON.parse(message, symbolize_names: true)
18
+ if !request[:jsonrpc] || !request[:method]
19
+ error_response(nil, ErrorCode::INVALID_REQUEST, "Invalid JSON-RPC format")
20
+ else
21
+ handle_request(request)
22
+ end
23
+ rescue JSON::ParserError => e
24
+ Server.log_error("JSON parse error", e)
25
+ error_response(nil, ErrorCode::PARSE_ERROR, "Invalid JSON format")
26
+ rescue => e
27
+ Server.log_error("Internal error during message processing", e)
28
+ error_response(nil, ErrorCode::INTERNAL_ERROR, "An internal error occurred")
29
+ end
30
+
31
+ json_result = JSON.generate(result).force_encoding("UTF-8") if result
32
+ json_result
33
+ end
34
+
35
+ private
36
+
37
+ def handle_request(request)
38
+ case request[:method]
39
+ when Method::INITIALIZE
40
+ handle_initialize(request)
41
+ when Method::INITIALIZED
42
+ handle_initialized(request)
43
+ when Method::PING
44
+ handle_ping(request)
45
+ when Method::RESOURCES_LIST
46
+ handle_list_resources(request)
47
+ when Method::RESOURCES_TEMPLATES_LIST
48
+ handle_list_resource_templates(request)
49
+ when Method::TOOLS_LIST
50
+ handle_list_tools(request)
51
+ when Method::TOOLS_CALL
52
+ handle_call_tool(request)
53
+ when Method::RESOURCES_READ
54
+ handle_read_resource(request)
55
+ when Method::COMPLETION_COMPLETE
56
+ handle_complete(request)
57
+ when Method::PROMPTS_LIST
58
+ handle_list_prompts(request)
59
+ when Method::PROMPTS_GET
60
+ handle_get_prompt(request)
61
+ else
62
+ error_response(request[:id], ErrorCode::METHOD_NOT_FOUND, "Unknown method: #{request[:method]}")
63
+ end
64
+ rescue => e
65
+ Server.log_error("Error #{request[:method]}", e)
66
+ error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred")
67
+ end
68
+
69
+ def handle_initialize(request)
70
+ return error_response(request[:id], ErrorCode::ALREADY_INITIALIZED, "Server already initialized") if @initialized
71
+
72
+ client_version = request.dig(:params, :protocolVersion)
73
+
74
+ unless @supported_protocol_versions.include?(client_version)
75
+ return error_response(
76
+ request[:id],
77
+ ErrorCode::INVALID_PARAMS,
78
+ "Unsupported protocol version",
79
+ {
80
+ supported: @supported_protocol_versions,
81
+ requested: client_version
82
+ }
83
+ )
84
+ end
85
+
86
+ response = {
87
+ jsonrpc: JSON_RPC_VERSION,
88
+ id: request[:id],
89
+ result: {
90
+ protocolVersion: PROTOCOL_VERSION,
91
+ capabilities: {
92
+ resources: {},
93
+ tools: {},
94
+ prompts: {}
95
+ },
96
+ serverInfo: {
97
+ name: @server.name,
98
+ version: @server.version
99
+ }
100
+ }
101
+ }
102
+
103
+ @initialized = true
104
+ response
105
+ end
106
+
107
+ def handle_initialized(request)
108
+ @initialized = true
109
+ nil
110
+ end
111
+
112
+ def handle_ping(request)
113
+ success_response(request[:id], {})
114
+ end
115
+
116
+ def handle_list_resources(request)
117
+ success_response(
118
+ request[:id],
119
+ @server.fetch(
120
+ params: {
121
+ method: Method::RESOURCES_LIST,
122
+ params: request[:params]
123
+ }
124
+ )[:result]
125
+ )
126
+ end
127
+
128
+ def handle_list_resource_templates(request)
129
+ success_response(
130
+ request[:id],
131
+ @server.fetch(
132
+ params: {
133
+ method: Method::RESOURCES_TEMPLATES_LIST,
134
+ params: request[:params]
135
+ }
136
+ )[:result]
137
+ )
138
+ end
139
+
140
+ def handle_list_tools(request)
141
+ success_response(
142
+ request[:id],
143
+ @server.fetch(params: {method: Method::TOOLS_LIST, arguments: {}})[:result]
144
+ )
145
+ end
146
+
147
+ def handle_call_tool(request)
148
+ name = request.dig(:params, :name)
149
+ arguments = request.dig(:params, :arguments) || {}
150
+ result = @server.fetch(params: {method: Method::TOOLS_CALL, params: {name:, arguments:}})
151
+
152
+ success_response(request[:id], result[:result])
153
+ end
154
+
155
+ def handle_read_resource(request)
156
+ uri = request.dig(:params, :uri)
157
+ result = @server.fetch(params: {method: Method::RESOURCES_READ, params: {uri:, arguments: {}}})
158
+
159
+ success_response(request[:id], result[:result])
160
+ end
161
+
162
+ def handle_complete(request)
163
+ result = @server.fetch(params: {method: Method::COMPLETION_COMPLETE, params: request[:params]})
164
+ success_response(request[:id], result[:result])
165
+ end
166
+
167
+ def handle_list_prompts(request)
168
+ result = @server.fetch(params: {method: Method::PROMPTS_LIST})
169
+ success_response(request[:id], result[:result])
170
+ end
171
+
172
+ def handle_get_prompt(request)
173
+ result = @server.fetch(params: {method: Method::PROMPTS_GET, params: request[:params]})
174
+
175
+ success_response(request[:id], result[:result])
176
+ end
177
+
178
+ def success_response(id, result)
179
+ {
180
+ jsonrpc: JSON_RPC_VERSION,
181
+ id: id,
182
+ result:
183
+ }
184
+ end
185
+
186
+ def error_response(id, code, message, data = nil)
187
+ response = {
188
+ jsonrpc: JSON_RPC_VERSION,
189
+ id: id || 0,
190
+ error: {
191
+ code:,
192
+ message:
193
+ }
194
+ }
195
+ response[:error][:data] = data if data
196
+ response
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module McpLite
4
+ class StdioConnection
5
+ def initialize
6
+ $stdout.sync = true
7
+ end
8
+
9
+ def read_next_message
10
+ message = $stdin.gets&.chomp
11
+ message.to_s.dup.force_encoding("UTF-8")
12
+ end
13
+
14
+ def send_message(message)
15
+ message = message.to_s.dup.force_encoding("UTF-8")
16
+ $stdout.binmode
17
+ $stdout.write(message + "\n")
18
+ $stdout.flush
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,81 @@
1
+ require "json"
2
+ require "English"
3
+ require_relative "server/method"
4
+ require_relative "server/error_code"
5
+ require_relative "server/stdio_connection"
6
+ require_relative "server/fetcher"
7
+ require_relative "server/protocol_handler"
8
+
9
+ module McpLite
10
+ class Server
11
+ class Logger
12
+ attr_reader :messages
13
+
14
+ def initialize
15
+ @messages = []
16
+ end
17
+
18
+ def log(message, error = nil)
19
+ @messages << {message: message, error: error}
20
+ warn("#{message}: #{error&.message}")
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def logger
26
+ @logger ||= Logger.new
27
+ end
28
+
29
+ def log_error(message, error)
30
+ logger.log(message, error)
31
+ end
32
+ end
33
+
34
+ attr_reader :name, :version, :uri, :protocol_handler, :fetcher
35
+
36
+ def initialize(
37
+ version: "1.0.0",
38
+ name: "McpLite",
39
+ uri: nil,
40
+ headers: {}
41
+ )
42
+ @name = name
43
+ @version = version
44
+ @uri = uri
45
+ @fetcher = Fetcher.new(base_uri: uri, headers:)
46
+ @protocol_handler = ProtocolHandler.new(self)
47
+ end
48
+
49
+ def fetch(params:)
50
+ @fetcher.call(params:)
51
+ end
52
+
53
+ def start
54
+ serve_stdio
55
+ end
56
+
57
+ def serve_stdio
58
+ serve(StdioConnection.new)
59
+ end
60
+
61
+ def serve(connection)
62
+ loop do
63
+ message = connection.read_next_message
64
+ break if message.nil?
65
+
66
+ response = @protocol_handler.process_message(message)
67
+ next if response.nil?
68
+
69
+ connection.send_message(response)
70
+ end
71
+ end
72
+
73
+ def initialized
74
+ @protocol_handler.initialized
75
+ end
76
+
77
+ def tools
78
+ @tool_manager.tools
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,68 @@
1
+ require "json-schema"
2
+
3
+ module McpLite
4
+ module Tool
5
+ class Base
6
+ class << self
7
+ attr_reader :tool_name_value, :description_value
8
+
9
+ def tool_name(value)
10
+ @tool_name_value = value
11
+ end
12
+
13
+ def description(value)
14
+ @description_value = value
15
+ end
16
+
17
+ def argument(name, type, required: false, description: "", visible: ->(_context) { true })
18
+ @schema ||= default_schema
19
+
20
+ @schema["properties"][name.to_s] = {"type" => type.to_s, "visible" => visible}
21
+ @schema["properties"][name.to_s]["description"] = description if description
22
+ @schema["required"] << name.to_s if required
23
+ end
24
+
25
+ def default_schema
26
+ {
27
+ "type" => "object",
28
+ "properties" => {},
29
+ "required" => []
30
+ }
31
+ end
32
+
33
+ def visible?(context: {})
34
+ true
35
+ end
36
+
37
+ def render_schema(context)
38
+ return default_schema unless @schema
39
+
40
+ {
41
+ "type" => "object",
42
+ "properties" => @schema["properties"].filter do |_k, v|
43
+ v["visible"].call(context)
44
+ end.map { |k, v| [k, v.except("visible")] }.to_h,
45
+ "required" => @schema["required"].filter do |item|
46
+ @schema["properties"][item]["visible"].call(context)
47
+ end
48
+ }
49
+ end
50
+ end
51
+
52
+ def initialize
53
+ end
54
+
55
+ def validate(args, context)
56
+ return true unless self.class.render_schema(context)
57
+
58
+ JSON::Validator.validate!(self.class.render_schema(context), args)
59
+ rescue JSON::Schema::ValidationError => e
60
+ {error: e.message}
61
+ end
62
+
63
+ def call(context: {}, **args)
64
+ raise NotImplementedError, "#{self.class.name}#call must be implemented"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ module McpLite
2
+ VERSION = "2.0.0"
3
+ end
data/lib/mcp_lite.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mcp_lite/version"
4
+ require_relative "mcp_lite/configuration"
5
+ require_relative "mcp_lite/schema/base"
6
+ require_relative "mcp_lite/tool/base"
7
+ require_relative "mcp_lite/resource/base"
8
+ require_relative "mcp_lite/prompt/base"
9
+ require_relative "mcp_lite/executor/resource_reader"
10
+ require_relative "mcp_lite/executor/tool_executor"
11
+ require_relative "mcp_lite/message/text"
12
+ require_relative "mcp_lite/message/image"
13
+ require_relative "mcp_lite/message/audio"
14
+ require_relative "mcp_lite/message/resource"
15
+ require_relative "mcp_lite/server"
16
+ require_relative "mcp_lite/completion"
17
+
18
+ module McpLite
19
+ JSON_RPC_VERSION = "2.0"
20
+ PROTOCOL_VERSION = "2024-11-05"
21
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mcp_lite
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Moeki Kawakami
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-04-12 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json-schema
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: A Ruby library that provides Model Context Protocol capabilities
27
+ email:
28
+ - hi@moeki.org
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - MIT-LICENSE
34
+ - README.md
35
+ - Rakefile
36
+ - lib/mcp_lite.rb
37
+ - lib/mcp_lite/completion.rb
38
+ - lib/mcp_lite/configuration.rb
39
+ - lib/mcp_lite/executor/resource_reader.rb
40
+ - lib/mcp_lite/executor/tool_executor.rb
41
+ - lib/mcp_lite/message/audio.rb
42
+ - lib/mcp_lite/message/image.rb
43
+ - lib/mcp_lite/message/resource.rb
44
+ - lib/mcp_lite/message/text.rb
45
+ - lib/mcp_lite/prompt/base.rb
46
+ - lib/mcp_lite/resource/base.rb
47
+ - lib/mcp_lite/schema/base.rb
48
+ - lib/mcp_lite/schema/method.rb
49
+ - lib/mcp_lite/server.rb
50
+ - lib/mcp_lite/server/error_code.rb
51
+ - lib/mcp_lite/server/fetcher.rb
52
+ - lib/mcp_lite/server/method.rb
53
+ - lib/mcp_lite/server/protocol_handler.rb
54
+ - lib/mcp_lite/server/stdio_connection.rb
55
+ - lib/mcp_lite/tool/base.rb
56
+ - lib/mcp_lite/version.rb
57
+ homepage: https://github.com/moekiorg/mcp_lite
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.7.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.6
76
+ specification_version: 4
77
+ summary: Model Context Protocol (MCP) implementation in Ruby
78
+ test_files: []