7
7
"encoding/json"
8
8
"io"
9
9
"net/http"
10
+ "strconv"
10
11
"strings"
11
12
12
13
"golang.org/x/exp/jsonrpc2"
@@ -135,18 +136,25 @@ func parseMCPRequest(bodyBytes []byte) *ParsedMCPRequest {
135
136
return nil
136
137
}
137
138
138
- // Handle only request messages
139
+ // Handle only request messages (both calls with ID and notifications without ID)
139
140
req , ok := msg .(* jsonrpc2.Request )
140
141
if ! ok {
142
+ // Response or error messages are not parsed here
141
143
return nil
142
144
}
143
145
144
146
// Extract resource ID and arguments based on the method
145
147
resourceID , arguments := extractResourceAndArguments (req .Method , req .Params )
146
148
149
+ // Determine the ID - will be nil for notifications
150
+ var id interface {}
151
+ if req .ID .IsValid () {
152
+ id = req .ID .Raw ()
153
+ }
154
+
147
155
return & ParsedMCPRequest {
148
156
Method : req .Method ,
149
- ID : req . ID . Raw () ,
157
+ ID : id ,
150
158
Params : req .Params ,
151
159
ResourceID : resourceID ,
152
160
Arguments : arguments ,
@@ -162,24 +170,36 @@ type methodHandler func(map[string]interface{}) (string, map[string]interface{})
162
170
163
171
// methodHandlers maps MCP methods to their respective handlers
164
172
var methodHandlers = map [string ]methodHandler {
165
- "initialize" : handleInitializeMethod ,
166
- "tools/call" : handleNamedResourceMethod ,
167
- "prompts/get" : handleNamedResourceMethod ,
168
- "resources/read" : handleResourceReadMethod ,
169
- "resources/list" : handleListMethod ,
170
- "tools/list" : handleListMethod ,
171
- "prompts/list" : handleListMethod ,
172
- "progress/update" : handleProgressMethod ,
173
- "notifications/message" : handleNotificationMethod ,
174
- "logging/setLevel" : handleLoggingMethod ,
175
- "completion/complete" : handleCompletionMethod ,
173
+ "initialize" : handleInitializeMethod ,
174
+ "tools/call" : handleNamedResourceMethod ,
175
+ "prompts/get" : handleNamedResourceMethod ,
176
+ "resources/read" : handleResourceReadMethod ,
177
+ "resources/list" : handleListMethod ,
178
+ "tools/list" : handleListMethod ,
179
+ "prompts/list" : handleListMethod ,
180
+ "progress/update" : handleProgressMethod ,
181
+ "notifications/message" : handleNotificationMethod ,
182
+ "logging/setLevel" : handleLoggingMethod ,
183
+ "completion/complete" : handleCompletionMethod ,
184
+ "elicitation/create" : handleElicitationMethod ,
185
+ "sampling/createMessage" : handleSamplingMethod ,
186
+ "resources/subscribe" : handleResourceSubscribeMethod ,
187
+ "resources/unsubscribe" : handleResourceUnsubscribeMethod ,
188
+ "resources/templates/list" : handleListMethod ,
189
+ "roots/list" : handleListMethod ,
190
+ "notifications/progress" : handleProgressNotificationMethod ,
191
+ "notifications/cancelled" : handleCancelledNotificationMethod ,
176
192
}
177
193
178
194
// staticResourceIDs maps methods to their static resource IDs
179
195
var staticResourceIDs = map [string ]string {
180
- "ping" : "ping" ,
181
- "notifications/roots/list_changed" : "roots" ,
182
- "notifications/initialized" : "initialized" ,
196
+ "ping" : "ping" ,
197
+ "notifications/roots/list_changed" : "roots" ,
198
+ "notifications/initialized" : "initialized" ,
199
+ "notifications/prompts/list_changed" : "prompts" ,
200
+ "notifications/resources/list_changed" : "resources" ,
201
+ "notifications/resources/updated" : "resources" ,
202
+ "notifications/tools/list_changed" : "tools" ,
183
203
}
184
204
185
205
func extractResourceAndArguments (method string , params json.RawMessage ) (string , map [string ]interface {}) {
@@ -277,14 +297,114 @@ func handleLoggingMethod(paramsMap map[string]interface{}) (string, map[string]i
277
297
return "" , nil
278
298
}
279
299
280
- // handleCompletionMethod extracts resource ID for completion requests
300
+ // handleCompletionMethod extracts resource ID for completion requests.
301
+ // For PromptReference: extracts the prompt name
302
+ // For ResourceTemplateReference: extracts the template URI
303
+ // For legacy string ref: returns the string value
304
+ // Always returns paramsMap as arguments since completion requests need the full context
305
+ // including the argument being completed and any context from previous completions.
281
306
func handleCompletionMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
307
+ // Check if ref is a map (PromptReference or ResourceTemplateReference)
308
+ if ref , ok := paramsMap ["ref" ].(map [string ]interface {}); ok {
309
+ // Try to extract name for PromptReference
310
+ if name , ok := ref ["name" ].(string ); ok {
311
+ return name , paramsMap
312
+ }
313
+ // Try to extract uri for ResourceTemplateReference
314
+ if uri , ok := ref ["uri" ].(string ); ok {
315
+ return uri , paramsMap
316
+ }
317
+ }
318
+ // Fallback to string ref (legacy support)
282
319
if ref , ok := paramsMap ["ref" ].(string ); ok {
283
- return ref , nil
320
+ return ref , paramsMap
321
+ }
322
+ return "" , paramsMap
323
+ }
324
+
325
+ // handleElicitationMethod extracts resource ID for elicitation requests
326
+ func handleElicitationMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
327
+ // The message field could be used as a resource identifier
328
+ if message , ok := paramsMap ["message" ].(string ); ok {
329
+ return message , paramsMap
330
+ }
331
+ return "" , paramsMap
332
+ }
333
+
334
+ // handleSamplingMethod extracts resource ID for sampling/createMessage requests.
335
+ // Returns the model name from modelPreferences if available, otherwise returns a
336
+ // truncated version of the systemPrompt. The 50-character truncation provides a
337
+ // reasonable balance between uniqueness and readability for authorization and audit logs.
338
+ func handleSamplingMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
339
+ // Use model preferences or system prompt as identifier if available
340
+ if modelPrefs , ok := paramsMap ["modelPreferences" ].(map [string ]interface {}); ok && modelPrefs != nil {
341
+ // Try direct name field first (simplified structure)
342
+ if name , ok := modelPrefs ["name" ].(string ); ok && name != "" {
343
+ return name , paramsMap
344
+ }
345
+ // Try to get model name from hints array (full spec structure)
346
+ if hints , ok := modelPrefs ["hints" ].([]interface {}); ok && len (hints ) > 0 {
347
+ if hint , ok := hints [0 ].(map [string ]interface {}); ok {
348
+ if name , ok := hint ["name" ].(string ); ok && name != "" {
349
+ return name , paramsMap
350
+ }
351
+ }
352
+ }
353
+ }
354
+ if systemPrompt , ok := paramsMap ["systemPrompt" ].(string ); ok && systemPrompt != "" {
355
+ // Use first 50 chars of system prompt as identifier
356
+ // This provides a reasonable balance between uniqueness and readability
357
+ if len (systemPrompt ) > 50 {
358
+ return systemPrompt [:50 ], paramsMap
359
+ }
360
+ return systemPrompt , paramsMap
361
+ }
362
+ return "" , paramsMap
363
+ }
364
+
365
+ // handleResourceSubscribeMethod extracts resource ID for resource subscribe operations
366
+ func handleResourceSubscribeMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
367
+ if uri , ok := paramsMap ["uri" ].(string ); ok {
368
+ return uri , nil
284
369
}
285
370
return "" , nil
286
371
}
287
372
373
+ // handleResourceUnsubscribeMethod extracts resource ID for resource unsubscribe operations
374
+ func handleResourceUnsubscribeMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
375
+ if uri , ok := paramsMap ["uri" ].(string ); ok {
376
+ return uri , nil
377
+ }
378
+ return "" , nil
379
+ }
380
+
381
+ // handleProgressNotificationMethod extracts resource ID for progress notifications.
382
+ // Extracts the progressToken which can be either a string or numeric value.
383
+ func handleProgressNotificationMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
384
+ if token , ok := paramsMap ["progressToken" ].(string ); ok {
385
+ return token , paramsMap
386
+ }
387
+ // Also handle numeric progress tokens
388
+ if token , ok := paramsMap ["progressToken" ].(float64 ); ok {
389
+ return strconv .FormatFloat (token , 'f' , 0 , 64 ), paramsMap
390
+ }
391
+ return "" , paramsMap
392
+ }
393
+
394
+ // handleCancelledNotificationMethod extracts resource ID for cancelled notifications.
395
+ // Extracts the requestId which can be either a string or numeric value.
396
+ func handleCancelledNotificationMethod (paramsMap map [string ]interface {}) (string , map [string ]interface {}) {
397
+ // Extract request ID as the resource identifier
398
+ if requestId , ok := paramsMap ["requestId" ].(string ); ok {
399
+ return requestId , paramsMap
400
+ }
401
+ // Handle numeric request IDs
402
+ if requestId , ok := paramsMap ["requestId" ].(float64 ); ok {
403
+ return strconv .FormatFloat (requestId , 'f' , 0 , 64 ), paramsMap
404
+ }
405
+ return "" , paramsMap
406
+ }
407
+
288
408
// GetMCPMethod is a convenience function to get the MCP method from the context.
289
409
func GetMCPMethod (ctx context.Context ) string {
290
410
if parsed := GetParsedMCPRequest (ctx ); parsed != nil {
0 commit comments