Skip to content

Commit 610e6cb

Browse files
Extract MessageHandler
1 parent aabd10b commit 610e6cb

File tree

3 files changed

+208
-222
lines changed

3 files changed

+208
-222
lines changed

lib/mcp/message_handler.rb

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "English"
5+
require "uri"
6+
require_relative "constants"
7+
require_relative "message_validator"
8+
9+
module MCP
10+
class MessageHandler
11+
def initialize(server_info:)
12+
@server_info = server_info
13+
@app = App.new
14+
@initialized = false
15+
@message_validator = MessageValidator.new protocol_version: Constants::PROTOCOL_VERSION
16+
end
17+
18+
def initialized?
19+
@initialized
20+
end
21+
22+
def handle_message(message)
23+
result = begin
24+
request = JSON.parse(message)
25+
@message_validator.validate_client_message!(request)
26+
handle_request deep_symbolize_keys(request)
27+
rescue JSON::ParserError => e
28+
error_response(nil, Constants::ErrorCodes::PARSE_ERROR, "Invalid JSON: #{e.message}")
29+
rescue MessageValidator::UnknownMethod
30+
error_response(
31+
request["id"],
32+
Constants::ErrorCodes::METHOD_NOT_FOUND,
33+
"Unknown method: #{request["method"]}"
34+
)
35+
rescue MessageValidator::InvalidParams => e
36+
error_response(
37+
request["id"],
38+
Constants::ErrorCodes::INVALID_PARAMS,
39+
"Invalid params",
40+
{errors: e.errors}
41+
)
42+
rescue MessageValidator::InvalidMessage => e
43+
error_response(
44+
nil,
45+
Constants::ErrorCodes::INVALID_REQUEST,
46+
"Invalid request",
47+
{errors: e.errors}
48+
)
49+
rescue => e
50+
error_response(nil, Constants::ErrorCodes::INTERNAL_ERROR, e.message)
51+
end
52+
53+
result = JSON.generate(result) if result
54+
result
55+
end
56+
57+
private
58+
59+
def deep_symbolize_keys(obj)
60+
case obj
61+
when Hash
62+
obj.to_h { |key, value| [key.to_sym, deep_symbolize_keys(value)] }
63+
when Array
64+
obj.map { |value| deep_symbolize_keys(value) }
65+
else
66+
obj
67+
end
68+
end
69+
70+
def handle_request(request)
71+
allowed_methods = [
72+
Constants::RequestMethods::INITIALIZE,
73+
Constants::RequestMethods::INITIALIZED,
74+
Constants::RequestMethods::PING
75+
]
76+
if !@initialized && !allowed_methods.include?(request[:method])
77+
return error_response(request[:id], Constants::ErrorCodes::NOT_INITIALIZED, "Server not initialized")
78+
end
79+
80+
case request[:method]
81+
when Constants::RequestMethods::INITIALIZE then handle_initialize(request)
82+
when Constants::RequestMethods::INITIALIZED then handle_initialized(request)
83+
when Constants::RequestMethods::PING then handle_ping(request)
84+
when Constants::RequestMethods::TOOLS_LIST then handle_list_tools(request)
85+
when Constants::RequestMethods::TOOLS_CALL then handle_call_tool(request)
86+
when Constants::RequestMethods::RESOURCES_LIST then handle_list_resources(request)
87+
when Constants::RequestMethods::RESOURCES_READ then handle_read_resource(request)
88+
when Constants::RequestMethods::RESOURCES_TEMPLATES_LIST then handle_list_resources_templates(request)
89+
end
90+
end
91+
92+
def handle_initialize(request)
93+
return error_response(request[:id], Constants::ErrorCodes::ALREADY_INITIALIZED, "Server already initialized") if @initialized
94+
95+
client_version = request.dig(:params, :protocolVersion)
96+
unless Constants::SUPPORTED_PROTOCOL_VERSIONS.include?(client_version)
97+
return error_response(
98+
request[:id],
99+
Constants::ErrorCodes::INVALID_PARAMS,
100+
"Unsupported protocol version",
101+
{
102+
supported: Constants::SUPPORTED_PROTOCOL_VERSIONS,
103+
requested: client_version
104+
}
105+
)
106+
end
107+
108+
{
109+
jsonrpc: MCP::Constants::JSON_RPC_VERSION,
110+
id: request[:id],
111+
result: {
112+
protocolVersion: Constants::PROTOCOL_VERSION,
113+
capabilities: {
114+
resources: {
115+
subscribe: false,
116+
listChanged: false
117+
},
118+
tools: {
119+
listChanged: false
120+
}
121+
},
122+
serverInfo: @server_info
123+
}
124+
}
125+
end
126+
127+
def handle_initialized(request)
128+
return error_response(request[:id], Constants::ErrorCodes::ALREADY_INITIALIZED, "Server already initialized") if @initialized
129+
130+
@initialized = true
131+
nil # 通知に対しては応答を返さない
132+
end
133+
134+
def handle_list_tools(request)
135+
cursor = request.dig(:params, :cursor)
136+
result = @app.list_tools(cursor: cursor)
137+
success_response(request[:id], result)
138+
end
139+
140+
def handle_call_tool(request)
141+
name = request.dig(:params, :name)
142+
arguments = request.dig(:params, :arguments)
143+
begin
144+
result = @app.call_tool(name, **arguments.transform_keys(&:to_sym))
145+
if result[:isError]
146+
error_response(request[:id], Constants::ErrorCodes::INVALID_REQUEST, result[:content].first[:text])
147+
else
148+
success_response(request[:id], result)
149+
end
150+
rescue ArgumentError => e
151+
error_response(request[:id], Constants::ErrorCodes::INVALID_REQUEST, e.message)
152+
end
153+
end
154+
155+
def handle_list_resources(request)
156+
cursor = request.dig(:params, :cursor)
157+
result = @app.list_resources(cursor:)
158+
success_response(request[:id], result)
159+
end
160+
161+
def handle_list_resources_templates(request)
162+
cursor = request.dig(:params, :cursor)
163+
result = @app.list_resource_templates(cursor:)
164+
success_response(request[:id], result)
165+
end
166+
167+
def handle_read_resource(request)
168+
uri = request.dig(:params, :uri)
169+
result = @app.read_resource(uri)
170+
171+
if result
172+
success_response(request[:id], result)
173+
else
174+
error_response(request[:id], Constants::ErrorCodes::INVALID_REQUEST, "Resource not found", {uri: uri})
175+
end
176+
end
177+
178+
def handle_ping(request)
179+
success_response(request[:id], {})
180+
end
181+
182+
def success_response(id, result)
183+
{
184+
jsonrpc: MCP::Constants::JSON_RPC_VERSION,
185+
id: id,
186+
result: result
187+
}
188+
end
189+
190+
def error_response(id, code, message, data = nil)
191+
response = {
192+
jsonrpc: MCP::Constants::JSON_RPC_VERSION,
193+
id: id,
194+
error: {
195+
code: code,
196+
message: message
197+
}
198+
}
199+
response[:error][:data] = data if data
200+
response
201+
end
202+
end
203+
end

0 commit comments

Comments
 (0)