77 "encoding/json"
88 "io"
99 "net/http"
10+ "strconv"
1011 "strings"
1112
1213 "golang.org/x/exp/jsonrpc2"
@@ -135,18 +136,25 @@ func parseMCPRequest(bodyBytes []byte) *ParsedMCPRequest {
135136 return nil
136137 }
137138
138- // Handle only request messages
139+ // Handle only request messages (both calls with ID and notifications without ID)
139140 req , ok := msg .(* jsonrpc2.Request )
140141 if ! ok {
142+ // Response or error messages are not parsed here
141143 return nil
142144 }
143145
144146 // Extract resource ID and arguments based on the method
145147 resourceID , arguments := extractResourceAndArguments (req .Method , req .Params )
146148
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+
147155 return & ParsedMCPRequest {
148156 Method : req .Method ,
149- ID : req . ID . Raw () ,
157+ ID : id ,
150158 Params : req .Params ,
151159 ResourceID : resourceID ,
152160 Arguments : arguments ,
@@ -162,24 +170,36 @@ type methodHandler func(map[string]interface{}) (string, map[string]interface{})
162170
163171// methodHandlers maps MCP methods to their respective handlers
164172var 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 ,
176192}
177193
178194// staticResourceIDs maps methods to their static resource IDs
179195var 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" ,
183203}
184204
185205func extractResourceAndArguments (method string , params json.RawMessage ) (string , map [string ]interface {}) {
@@ -277,14 +297,114 @@ func handleLoggingMethod(paramsMap map[string]interface{}) (string, map[string]i
277297 return "" , nil
278298}
279299
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.
281306func 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)
282319 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
284369 }
285370 return "" , nil
286371}
287372
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+
288408// GetMCPMethod is a convenience function to get the MCP method from the context.
289409func GetMCPMethod (ctx context.Context ) string {
290410 if parsed := GetParsedMCPRequest (ctx ); parsed != nil {
0 commit comments