1+ require 'json'
2+
13class JsonRpcError < StandardError
24 attr_reader :code
35
@@ -15,6 +17,11 @@ def rpc_error!(code, message)
1517
1618class McpProcessor
1719 PROTOCOL_VERSION = '2025-06-18'
20+ DEFAULT_SERVER_INFO = {
21+ name : 'mcp-server' ,
22+ title : 'MCP Server' ,
23+ version : '1.0.0'
24+ } . freeze
1825
1926 include RpcErrorHelpers
2027 ParseError = Class . new ( StandardError ) do
@@ -27,7 +34,9 @@ def initialize(body:, status:)
2734 end
2835 end
2936
30- def initialize ( logger : LOGGER )
37+ def initialize ( registry : nil , server_info : DEFAULT_SERVER_INFO , logger : ( defined? ( LOGGER ) ? LOGGER : nil ) )
38+ @registry = registry
39+ @server_info = server_info
3140 @logger = logger
3241 end
3342
@@ -37,14 +46,22 @@ def root_endpoint
3746
3847 def rpc_endpoint ( raw_body )
3948 req = JSON . parse ( raw_body . to_s )
40- rpc_response ( id : req [ "id" ] , method : req [ "method" ] , params : req [ "params" ] )
41- rescue JSON ::ParserError
49+ method = req [ "method" ]
50+ params = req [ "params" ]
51+
52+ if req . key? ( "id" )
53+ rpc_response ( id : req [ "id" ] , method : method , params : params )
54+ else
55+ notification_response ( method : method , params : params )
56+ end
57+ rescue JSON ::ParserError => e
58+ @logger &.warn ( "MCP JSON parse failed: #{ e . message } " )
4259 body = error_response ( id : nil , code : -32700 , message : "Parse error" )
4360 raise ParseError . new ( body : body , status : 400 )
4461 end
4562
4663 def list_tools
47- { tools : ToolRegistry . list , nextCursor : 'no-more' }
64+ { tools : registry . list , nextCursor : 'no-more' }
4865 end
4966
5067 def root_response
@@ -56,32 +73,47 @@ def error_response(id:, code:, message:)
5673 end
5774
5875 def rpc_response ( id :, method :, params :)
59- json_rpc_response ( id : id ) { |body | handle ( method : method , params : params , body : body ) }
76+ json_rpc_response ( id : id ) { handle ( method : method , params : params ) }
77+ end
78+
79+ def notification_response ( method :, params :)
80+ handle_notification ( method : method , params : params )
81+ nil
82+ rescue => e
83+ @logger &.error ( "Unhandled MCP notification error: #{ e . class } : #{ e . message } " )
84+ nil
6085 end
6186
62- def handle ( method :, params :, body : )
87+ def handle ( method :, params :)
6388 case method
6489 when "tools/list" then list_tools
6590 # when "resources/list" then {}
6691 # when "prompts/list" then {}
6792 when "tools/call" then call_tool ( params || { } )
68- when "initialize" then initialize_ ( body )
69- when "notifications/initialized" then LOGGER . debug params ; { }
70- when "logging/setLevel" then LOGGER . debug params ; { }
93+ when "initialize" then initialize_response
94+ when "notifications/initialized" then @logger & .debug ( params ) ; { }
95+ when "logging/setLevel" then @logger & .debug ( params ) ; { }
7196 else
7297 rpc_error! ( -32601 , "Unknown method #{ method } " )
7398 end
7499 end
75100
76- # https://gist.github.com/ruvnet/7b6843c457822cbcf42fc4aa635eadbb
101+ def handle_notification ( method :, params :)
102+ case method
103+ when "notifications/initialized" , "notifications/cancelled"
104+ @logger &.debug ( "MCP notification accepted: #{ method } " )
105+ else
106+ @logger &.debug ( "MCP notification ignored: #{ method } " )
107+ end
108+ end
77109
78- def initialize_ ( body )
110+ def initialize_ ( _body = nil )
111+ initialize_response
112+ end
113+
114+ def initialize_response
79115 {
80- serverInfo : {
81- name : 'mcp-server' ,
82- title : 'MCP Server' ,
83- version : '1.0.0'
84- } ,
116+ serverInfo : @server_info ,
85117 protocolVersion : PROTOCOL_VERSION ,
86118 capabilities : {
87119 logging : { } ,
@@ -98,7 +130,7 @@ def json_rpc_response(id:)
98130 body = { jsonrpc : "2.0" , id : id }
99131
100132 begin
101- result = yield ( body )
133+ result = yield
102134 body [ :result ] = result unless body [ :error ] || result . nil?
103135 rescue JsonRpcError => e
104136 body [ :error ] = { code : e . code , message : e . message }
@@ -114,11 +146,29 @@ def json_rpc_response(id:)
114146 def call_tool ( params )
115147 name = params [ "name" ]
116148 arguments = params [ "arguments" ] || { }
117- tool = ToolRegistry . fetch ( name ) || rpc_error! ( -32601 , "Unknown tool #{ name } " )
149+ tool = registry . fetch ( name ) || rpc_error! ( -32601 , "Unknown tool #{ name } " )
118150 response = tool . call_tool ( arguments )
151+ return response if mcp_tool_response? ( response )
152+
153+ wrap_tool_response ( response )
154+ end
155+
156+ def registry
157+ @registry || ToolRegistry . default
158+ end
159+
160+ def mcp_tool_response? ( response )
161+ return false unless response . is_a? ( Hash )
162+
163+ [ :content , :structuredContent , :isError , "content" , "structuredContent" , "isError" ] . any? do |key |
164+ response . key? ( key )
165+ end
166+ end
167+
168+ def wrap_tool_response ( response )
119169 {
120170 content : [
121- { "type" : "text" , "text" : response . is_a? ( String ) ? response : response . to_json }
171+ { "type" : "text" , "text" : response . is_a? ( String ) ? response : JSON . dump ( response ) }
122172 ] ,
123173 isError : false
124174 }
0 commit comments