-
Notifications
You must be signed in to change notification settings - Fork 122
Update MCP parser to support latest specification #1993
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JAORMX
wants to merge
3
commits into
main
Choose a base branch
from
feat/update-mcp-parser-latest-spec
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
"encoding/json" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
"strings" | ||
|
||
"golang.org/x/exp/jsonrpc2" | ||
|
@@ -135,18 +136,25 @@ func parseMCPRequest(bodyBytes []byte) *ParsedMCPRequest { | |
return nil | ||
} | ||
|
||
// Handle only request messages | ||
// Handle only request messages (both calls with ID and notifications without ID) | ||
req, ok := msg.(*jsonrpc2.Request) | ||
if !ok { | ||
// Response or error messages are not parsed here | ||
return nil | ||
} | ||
|
||
// Extract resource ID and arguments based on the method | ||
resourceID, arguments := extractResourceAndArguments(req.Method, req.Params) | ||
|
||
// Determine the ID - will be nil for notifications | ||
var id interface{} | ||
if req.ID.IsValid() { | ||
id = req.ID.Raw() | ||
} | ||
|
||
return &ParsedMCPRequest{ | ||
Method: req.Method, | ||
ID: req.ID.Raw(), | ||
ID: id, | ||
Params: req.Params, | ||
ResourceID: resourceID, | ||
Arguments: arguments, | ||
|
@@ -162,24 +170,36 @@ type methodHandler func(map[string]interface{}) (string, map[string]interface{}) | |
|
||
// methodHandlers maps MCP methods to their respective handlers | ||
var methodHandlers = map[string]methodHandler{ | ||
"initialize": handleInitializeMethod, | ||
"tools/call": handleNamedResourceMethod, | ||
"prompts/get": handleNamedResourceMethod, | ||
"resources/read": handleResourceReadMethod, | ||
"resources/list": handleListMethod, | ||
"tools/list": handleListMethod, | ||
"prompts/list": handleListMethod, | ||
"progress/update": handleProgressMethod, | ||
"notifications/message": handleNotificationMethod, | ||
"logging/setLevel": handleLoggingMethod, | ||
"completion/complete": handleCompletionMethod, | ||
"initialize": handleInitializeMethod, | ||
"tools/call": handleNamedResourceMethod, | ||
"prompts/get": handleNamedResourceMethod, | ||
"resources/read": handleResourceReadMethod, | ||
"resources/list": handleListMethod, | ||
"tools/list": handleListMethod, | ||
"prompts/list": handleListMethod, | ||
"progress/update": handleProgressMethod, | ||
"notifications/message": handleNotificationMethod, | ||
"logging/setLevel": handleLoggingMethod, | ||
"completion/complete": handleCompletionMethod, | ||
"elicitation/create": handleElicitationMethod, | ||
"sampling/createMessage": handleSamplingMethod, | ||
"resources/subscribe": handleResourceSubscribeMethod, | ||
"resources/unsubscribe": handleResourceUnsubscribeMethod, | ||
"resources/templates/list": handleListMethod, | ||
"roots/list": handleListMethod, | ||
"notifications/progress": handleProgressNotificationMethod, | ||
"notifications/cancelled": handleCancelledNotificationMethod, | ||
} | ||
|
||
// staticResourceIDs maps methods to their static resource IDs | ||
var staticResourceIDs = map[string]string{ | ||
"ping": "ping", | ||
"notifications/roots/list_changed": "roots", | ||
"notifications/initialized": "initialized", | ||
"ping": "ping", | ||
"notifications/roots/list_changed": "roots", | ||
"notifications/initialized": "initialized", | ||
"notifications/prompts/list_changed": "prompts", | ||
"notifications/resources/list_changed": "resources", | ||
"notifications/resources/updated": "resources", | ||
"notifications/tools/list_changed": "tools", | ||
} | ||
|
||
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 | |
return "", nil | ||
} | ||
|
||
// handleCompletionMethod extracts resource ID for completion requests | ||
// handleCompletionMethod extracts resource ID for completion requests. | ||
// For PromptReference: extracts the prompt name | ||
// For ResourceTemplateReference: extracts the template URI | ||
// For legacy string ref: returns the string value | ||
// Always returns paramsMap as arguments since completion requests need the full context | ||
// including the argument being completed and any context from previous completions. | ||
func handleCompletionMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
// Check if ref is a map (PromptReference or ResourceTemplateReference) | ||
if ref, ok := paramsMap["ref"].(map[string]interface{}); ok { | ||
// Try to extract name for PromptReference | ||
if name, ok := ref["name"].(string); ok { | ||
return name, paramsMap | ||
} | ||
// Try to extract uri for ResourceTemplateReference | ||
if uri, ok := ref["uri"].(string); ok { | ||
return uri, paramsMap | ||
} | ||
} | ||
// Fallback to string ref (legacy support) | ||
if ref, ok := paramsMap["ref"].(string); ok { | ||
return ref, nil | ||
return ref, paramsMap | ||
} | ||
return "", paramsMap | ||
} | ||
|
||
// handleElicitationMethod extracts resource ID for elicitation requests | ||
func handleElicitationMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
// The message field could be used as a resource identifier | ||
if message, ok := paramsMap["message"].(string); ok { | ||
return message, paramsMap | ||
} | ||
return "", paramsMap | ||
} | ||
|
||
// handleSamplingMethod extracts resource ID for sampling/createMessage requests. | ||
// Returns the model name from modelPreferences if available, otherwise returns a | ||
// truncated version of the systemPrompt. The 50-character truncation provides a | ||
// reasonable balance between uniqueness and readability for authorization and audit logs. | ||
func handleSamplingMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
// Use model preferences or system prompt as identifier if available | ||
if modelPrefs, ok := paramsMap["modelPreferences"].(map[string]interface{}); ok && modelPrefs != nil { | ||
// Try direct name field first (simplified structure) | ||
if name, ok := modelPrefs["name"].(string); ok && name != "" { | ||
return name, paramsMap | ||
} | ||
// Try to get model name from hints array (full spec structure) | ||
if hints, ok := modelPrefs["hints"].([]interface{}); ok && len(hints) > 0 { | ||
if hint, ok := hints[0].(map[string]interface{}); ok { | ||
if name, ok := hint["name"].(string); ok && name != "" { | ||
return name, paramsMap | ||
} | ||
} | ||
} | ||
} | ||
if systemPrompt, ok := paramsMap["systemPrompt"].(string); ok && systemPrompt != "" { | ||
// Use first 50 chars of system prompt as identifier | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why that number, is that defined somewhere? also... it's a prompt, is it ok to have it as a resource identifier? can contain special chars, etc... shouldn't be better to use a hash for it? |
||
// This provides a reasonable balance between uniqueness and readability | ||
if len(systemPrompt) > 50 { | ||
return systemPrompt[:50], paramsMap | ||
} | ||
return systemPrompt, paramsMap | ||
} | ||
return "", paramsMap | ||
} | ||
|
||
// handleResourceSubscribeMethod extracts resource ID for resource subscribe operations | ||
func handleResourceSubscribeMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
if uri, ok := paramsMap["uri"].(string); ok { | ||
return uri, nil | ||
} | ||
return "", nil | ||
} | ||
|
||
// handleResourceUnsubscribeMethod extracts resource ID for resource unsubscribe operations | ||
func handleResourceUnsubscribeMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
if uri, ok := paramsMap["uri"].(string); ok { | ||
return uri, nil | ||
} | ||
return "", nil | ||
} | ||
|
||
// handleProgressNotificationMethod extracts resource ID for progress notifications. | ||
// Extracts the progressToken which can be either a string or numeric value. | ||
func handleProgressNotificationMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
if token, ok := paramsMap["progressToken"].(string); ok { | ||
return token, paramsMap | ||
} | ||
// Also handle numeric progress tokens | ||
if token, ok := paramsMap["progressToken"].(float64); ok { | ||
return strconv.FormatFloat(token, 'f', 0, 64), paramsMap | ||
} | ||
return "", paramsMap | ||
} | ||
|
||
// handleCancelledNotificationMethod extracts resource ID for cancelled notifications. | ||
// Extracts the requestId which can be either a string or numeric value. | ||
func handleCancelledNotificationMethod(paramsMap map[string]interface{}) (string, map[string]interface{}) { | ||
// Extract request ID as the resource identifier | ||
if requestId, ok := paramsMap["requestId"].(string); ok { | ||
return requestId, paramsMap | ||
} | ||
// Handle numeric request IDs | ||
if requestId, ok := paramsMap["requestId"].(float64); ok { | ||
return strconv.FormatFloat(requestId, 'f', 0, 64), paramsMap | ||
} | ||
return "", paramsMap | ||
} | ||
|
||
// GetMCPMethod is a convenience function to get the MCP method from the context. | ||
func GetMCPMethod(ctx context.Context) string { | ||
if parsed := GetParsedMCPRequest(ctx); parsed != nil { | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The legacy string ref case should return
nil
for arguments to maintain consistency with the original implementation. The change from returningnil
toparamsMap
could break existing code that expectsnil
when only extracting the resource ID.Copilot uses AI. Check for mistakes.