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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +584 -0
- data/Rakefile +9 -0
- data/lib/mcp_lite/completion.rb +21 -0
- data/lib/mcp_lite/configuration.rb +22 -0
- data/lib/mcp_lite/executor/resource_reader.rb +70 -0
- data/lib/mcp_lite/executor/tool_executor.rb +102 -0
- data/lib/mcp_lite/message/audio.rb +22 -0
- data/lib/mcp_lite/message/image.rb +24 -0
- data/lib/mcp_lite/message/resource.rb +24 -0
- data/lib/mcp_lite/message/text.rb +20 -0
- data/lib/mcp_lite/prompt/base.rb +52 -0
- data/lib/mcp_lite/resource/base.rb +79 -0
- data/lib/mcp_lite/schema/base.rb +141 -0
- data/lib/mcp_lite/schema/method.rb +20 -0
- data/lib/mcp_lite/server/error_code.rb +14 -0
- data/lib/mcp_lite/server/fetcher.rb +55 -0
- data/lib/mcp_lite/server/method.rb +18 -0
- data/lib/mcp_lite/server/protocol_handler.rb +200 -0
- data/lib/mcp_lite/server/stdio_connection.rb +21 -0
- data/lib/mcp_lite/server.rb +81 -0
- data/lib/mcp_lite/tool/base.rb +68 -0
- data/lib/mcp_lite/version.rb +3 -0
- data/lib/mcp_lite.rb +21 -0
- metadata +78 -0
@@ -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
|
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: []
|