2
2
3
3
require "json_rpc_handler"
4
4
require_relative "instrumentation"
5
+ require_relative "methods"
5
6
6
7
module ModelContextProtocol
7
8
class Server
8
9
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 )
10
14
super ( message )
11
15
@request = request
16
+ @error_type = error_type
17
+ @original_error = original_error
12
18
end
13
19
end
14
20
15
- PROTOCOL_VERSION = "2025-03-26"
16
-
17
21
include Instrumentation
18
22
19
23
attr_accessor :name , :tools , :prompts , :resources , :context , :configuration
@@ -28,14 +32,14 @@ def initialize(name: "model_context_protocol", tools: [], prompts: [], resources
28
32
@context = context
29
33
@configuration = ModelContextProtocol . configuration . merge ( configuration )
30
34
@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 => -> ( _ ) { { } } ,
39
43
}
40
44
end
41
45
@@ -52,47 +56,61 @@ def handle_json(request)
52
56
end
53
57
54
58
def resources_list_handler ( &block )
55
- @handlers [ "resources/list" ] = block
59
+ @handlers [ Methods :: RESOURCES_LIST ] = block
56
60
end
57
61
58
62
def resources_read_handler ( &block )
59
- @handlers [ "resources/read" ] = block
63
+ @handlers [ Methods :: RESOURCES_READ ] = block
60
64
end
61
65
62
66
def tools_list_handler ( &block )
63
- @handlers [ "tools/list" ] = block
67
+ @handlers [ Methods :: TOOLS_LIST ] = block
64
68
end
65
69
66
70
def tools_call_handler ( &block )
67
- @handlers [ "tools/call" ] = block
71
+ @handlers [ Methods :: TOOLS_CALL ] = block
68
72
end
69
73
70
74
def prompts_list_handler ( &block )
71
- @handlers [ "prompts/list" ] = block
75
+ @handlers [ Methods :: PROMPTS_LIST ] = block
72
76
end
73
77
74
78
def prompts_get_handler ( &block )
75
- @handlers [ "prompts/get" ] = block
79
+ @handlers [ Methods :: PROMPTS_GET ] = block
76
80
end
77
81
78
82
private
79
83
80
84
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
95
89
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
+ }
96
114
end
97
115
98
116
def capabilities
@@ -111,52 +129,49 @@ def server_info
111
129
end
112
130
113
131
def init ( request )
114
- add_instrumentation_data ( method : "initialize" )
132
+ add_instrumentation_data ( method : Methods :: INITIALIZE )
115
133
{
116
- protocolVersion : PROTOCOL_VERSION ,
134
+ protocolVersion : configuration . protocol_version ,
117
135
capabilities : capabilities ,
118
136
serverInfo : server_info ,
119
137
}
120
138
end
121
139
122
140
def list_tools ( request )
123
- add_instrumentation_data ( method : "tools/list" )
141
+ add_instrumentation_data ( method : Methods :: TOOLS_LIST )
124
142
@tools . map { |_ , tool | tool . to_h }
125
143
end
126
144
127
145
def call_tool ( request )
128
- add_instrumentation_data ( method : "tools/call" )
146
+ add_instrumentation_data ( method : Methods :: TOOLS_CALL )
129
147
tool_name = request [ :name ]
130
148
tool = tools [ tool_name ]
131
149
unless tool
132
150
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 )
134
152
end
135
153
136
154
add_instrumentation_data ( tool_name :)
137
155
138
156
begin
139
- result = tool . call ( **request [ :arguments ] , context :)
140
- result . to_h
157
+ tool . call ( **request [ :arguments ] , context :) . to_h
141
158
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 )
145
160
end
146
161
end
147
162
148
163
def list_prompts ( request )
149
- add_instrumentation_data ( method : "prompts/list" )
164
+ add_instrumentation_data ( method : Methods :: PROMPTS_LIST )
150
165
@prompts . map { |_ , prompt | prompt . to_h }
151
166
end
152
167
153
168
def get_prompt ( request )
154
- add_instrumentation_data ( method : "prompts/get" )
169
+ add_instrumentation_data ( method : Methods :: PROMPTS_GET )
155
170
prompt_name = request [ :name ]
156
171
prompt = @prompts [ prompt_name ]
157
172
unless prompt
158
173
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 )
160
175
end
161
176
162
177
add_instrumentation_data ( prompt_name :)
@@ -168,19 +183,19 @@ def get_prompt(request)
168
183
end
169
184
170
185
def list_resources ( request )
171
- add_instrumentation_data ( method : "resources/list" )
186
+ add_instrumentation_data ( method : Methods :: RESOURCES_LIST )
172
187
173
188
@resources . map ( &:to_h )
174
189
end
175
190
176
191
def read_resource ( request )
177
- add_instrumentation_data ( method : "resources/read" )
192
+ add_instrumentation_data ( method : Methods :: RESOURCES_READ )
178
193
resource_uri = request [ :uri ]
179
194
180
195
resource = @resource_index [ resource_uri ]
181
196
unless resource
182
197
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 )
184
199
end
185
200
186
201
add_instrumentation_data ( resource_uri :)
0 commit comments