22
33require "json_rpc_handler"
44require_relative "instrumentation"
5+ require_relative "methods"
56
67module ModelContextProtocol
78 class Server
89 class RequestHandlerError < StandardError
9- def initialize ( message , request )
10+ attr_reader :error_type
11+ attr_reader :original_error
12+
13+ def initialize ( message , request , error_type : :internal_error , original_error : nil )
1014 super ( message )
1115 @request = request
16+ @error_type = error_type
17+ @original_error = original_error
1218 end
1319 end
1420
15- PROTOCOL_VERSION = "2025-03-26"
16-
1721 include Instrumentation
1822
1923 attr_accessor :name , :tools , :prompts , :resources , :context , :configuration
@@ -28,14 +32,14 @@ def initialize(name: "model_context_protocol", tools: [], prompts: [], resources
2832 @context = context
2933 @configuration = ModelContextProtocol . configuration . merge ( configuration )
3034 @handlers = {
31- "resources/list" => method ( :list_resources ) ,
32- "resources/read" => method ( :read_resource ) ,
33- "tools/list" => method ( :list_tools ) ,
34- "tools/call" => method ( :call_tool ) ,
35- "prompts/list" => method ( :list_prompts ) ,
36- "prompts/get" => method ( :get_prompt ) ,
37- "initialize" => method ( :init ) ,
38- "ping" => -> ( _ ) { { } } ,
35+ Methods :: RESOURCES_LIST => method ( :list_resources ) ,
36+ Methods :: RESOURCES_READ => method ( :read_resource ) ,
37+ Methods :: TOOLS_LIST => method ( :list_tools ) ,
38+ Methods :: TOOLS_CALL => method ( :call_tool ) ,
39+ Methods :: PROMPTS_LIST => method ( :list_prompts ) ,
40+ Methods :: PROMPTS_GET => method ( :get_prompt ) ,
41+ Methods :: INITIALIZE => method ( :init ) ,
42+ Methods :: PING => -> ( _ ) { { } } ,
3943 }
4044 end
4145
@@ -52,47 +56,61 @@ def handle_json(request)
5256 end
5357
5458 def resources_list_handler ( &block )
55- @handlers [ "resources/list" ] = block
59+ @handlers [ Methods :: RESOURCES_LIST ] = block
5660 end
5761
5862 def resources_read_handler ( &block )
59- @handlers [ "resources/read" ] = block
63+ @handlers [ Methods :: RESOURCES_READ ] = block
6064 end
6165
6266 def tools_list_handler ( &block )
63- @handlers [ "tools/list" ] = block
67+ @handlers [ Methods :: TOOLS_LIST ] = block
6468 end
6569
6670 def tools_call_handler ( &block )
67- @handlers [ "tools/call" ] = block
71+ @handlers [ Methods :: TOOLS_CALL ] = block
6872 end
6973
7074 def prompts_list_handler ( &block )
71- @handlers [ "prompts/list" ] = block
75+ @handlers [ Methods :: PROMPTS_LIST ] = block
7276 end
7377
7478 def prompts_get_handler ( &block )
75- @handlers [ "prompts/get" ] = block
79+ @handlers [ Methods :: PROMPTS_GET ] = block
7680 end
7781
7882 private
7983
8084 def handle_request ( request , method )
81- instrument_call ( method ) do
82- case method
83- when "tools/list"
84- -> ( params ) { { tools : @handlers [ "tools/list" ] . call ( params ) } }
85- when "prompts/list"
86- -> ( params ) { { prompts : @handlers [ "prompts/list" ] . call ( params ) } }
87- when "resources/list"
88- -> ( params ) { { resources : @handlers [ "resources/list" ] . call ( params ) } }
89- else
90- @handlers [ method ]
91- end
92- rescue => e
93- report_exception ( e , { request : request } )
94- raise RequestHandlerError . new ( "Internal error handling #{ request [ :method ] } request" , request )
85+ handler = @handlers [ method ]
86+ unless handler
87+ instrument_call ( "unsupported_method" ) { }
88+ return
9589 end
90+
91+ -> ( params ) {
92+ instrument_call ( method ) do
93+ case method
94+ when Methods ::TOOLS_LIST
95+ { tools : @handlers [ Methods ::TOOLS_LIST ] . call ( params ) }
96+ when Methods ::PROMPTS_LIST
97+ { prompts : @handlers [ Methods ::PROMPTS_LIST ] . call ( params ) }
98+ when Methods ::RESOURCES_LIST
99+ { resources : @handlers [ Methods ::RESOURCES_LIST ] . call ( params ) }
100+ else
101+ @handlers [ method ] . call ( params )
102+ end
103+ rescue => e
104+ report_exception ( e , { request : request } )
105+ if e . is_a? ( RequestHandlerError )
106+ add_instrumentation_data ( error : e . error_type )
107+ raise e
108+ end
109+
110+ add_instrumentation_data ( error : :internal_error )
111+ raise RequestHandlerError . new ( "Internal error handling #{ method } request" , request , original_error : e )
112+ end
113+ }
96114 end
97115
98116 def capabilities
@@ -111,52 +129,49 @@ def server_info
111129 end
112130
113131 def init ( request )
114- add_instrumentation_data ( method : "initialize" )
132+ add_instrumentation_data ( method : Methods :: INITIALIZE )
115133 {
116- protocolVersion : PROTOCOL_VERSION ,
134+ protocolVersion : configuration . protocol_version ,
117135 capabilities : capabilities ,
118136 serverInfo : server_info ,
119137 }
120138 end
121139
122140 def list_tools ( request )
123- add_instrumentation_data ( method : "tools/list" )
141+ add_instrumentation_data ( method : Methods :: TOOLS_LIST )
124142 @tools . map { |_ , tool | tool . to_h }
125143 end
126144
127145 def call_tool ( request )
128- add_instrumentation_data ( method : "tools/call" )
146+ add_instrumentation_data ( method : Methods :: TOOLS_CALL )
129147 tool_name = request [ :name ]
130148 tool = tools [ tool_name ]
131149 unless tool
132150 add_instrumentation_data ( error : :tool_not_found )
133- raise "Tool not found #{ tool_name } "
151+ raise RequestHandlerError . new ( "Tool not found #{ tool_name } " , request , error_type : :tool_not_found )
134152 end
135153
136154 add_instrumentation_data ( tool_name :)
137155
138156 begin
139- result = tool . call ( **request [ :arguments ] , context :)
140- result . to_h
157+ tool . call ( **request [ :arguments ] , context :) . to_h
141158 rescue => e
142- report_exception ( e , { tool_name : tool_name , arguments : request [ :arguments ] } )
143- add_instrumentation_data ( error : :internal_error )
144- raise RequestHandlerError . new ( "Internal error calling tool #{ tool_name } " , request )
159+ raise RequestHandlerError . new ( "Internal error calling tool #{ tool_name } " , request , original_error : e )
145160 end
146161 end
147162
148163 def list_prompts ( request )
149- add_instrumentation_data ( method : "prompts/list" )
164+ add_instrumentation_data ( method : Methods :: PROMPTS_LIST )
150165 @prompts . map { |_ , prompt | prompt . to_h }
151166 end
152167
153168 def get_prompt ( request )
154- add_instrumentation_data ( method : "prompts/get" )
169+ add_instrumentation_data ( method : Methods :: PROMPTS_GET )
155170 prompt_name = request [ :name ]
156171 prompt = @prompts [ prompt_name ]
157172 unless prompt
158173 add_instrumentation_data ( error : :prompt_not_found )
159- raise "Prompt not found #{ prompt_name } "
174+ raise RequestHandlerError . new ( "Prompt not found #{ prompt_name } " , request , error_type : :prompt_not_found )
160175 end
161176
162177 add_instrumentation_data ( prompt_name :)
@@ -168,19 +183,19 @@ def get_prompt(request)
168183 end
169184
170185 def list_resources ( request )
171- add_instrumentation_data ( method : "resources/list" )
186+ add_instrumentation_data ( method : Methods :: RESOURCES_LIST )
172187
173188 @resources . map ( &:to_h )
174189 end
175190
176191 def read_resource ( request )
177- add_instrumentation_data ( method : "resources/read" )
192+ add_instrumentation_data ( method : Methods :: RESOURCES_READ )
178193 resource_uri = request [ :uri ]
179194
180195 resource = @resource_index [ resource_uri ]
181196 unless resource
182197 add_instrumentation_data ( error : :resource_not_found )
183- raise "Resource not found #{ resource_uri } "
198+ raise RequestHandlerError . new ( "Resource not found #{ resource_uri } " , request , error_type : :resource_not_found )
184199 end
185200
186201 add_instrumentation_data ( resource_uri :)
0 commit comments