mcp: decode a Base64-wrapped Mcp-Name before comparing it to the body (#1242)
The 2026-07-28 transport spec lets a client Base64-wrap a name or resource URI that is not header-safe (`=?base64?...?=`) and requires the server to decode it before comparing. `validateMcpHeaders` decoded `Mcp-Param-*` values that way but compared `Mcp-Name` raw, so a spec-legal encoded name was refused as a header mismatch. This decodes `Mcp-Name` before comparing, mirroring the existing `Mcp-Param-*` path, and rejects an invalid Base64 wrapper with a clear error. Added table cases covering a valid encoded name and an invalid one (red before, green after). Fixes #1234 Co-authored-by: Arpan Mondal <arpanmondal@Arpans-MacBook-Pro.local> Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
This commit is contained in:
parent
3f3b699b2b
commit
a5026ea7b7
2 changed files with 26 additions and 3 deletions
|
|
@ -378,13 +378,18 @@ func validateMcpHeaders(header http.Header, msg jsonrpc.Message, toolLookup func
|
||||||
if nameInHeader == "" {
|
if nameInHeader == "" {
|
||||||
return fmt.Errorf("missing required Mcp-Name header for method %q", msg.Method)
|
return fmt.Errorf("missing required Mcp-Name header for method %q", msg.Method)
|
||||||
}
|
}
|
||||||
var ok bool
|
// A name that is not header-safe may be Base64-wrapped by the client
|
||||||
|
// (=?base64?...?=), so decode before comparing, as with Mcp-Param-*.
|
||||||
|
decodedName, ok := decodeHeaderValue(nameInHeader)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("header mismatch: Mcp-Name header contains invalid Base64 encoding")
|
||||||
|
}
|
||||||
nameInBody, ok = extractName(msg.Method, msg.Params)
|
nameInBody, ok = extractName(msg.Method, msg.Params)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("failed to extract name from parameters for method %q", msg.Method)
|
return fmt.Errorf("failed to extract name from parameters for method %q", msg.Method)
|
||||||
}
|
}
|
||||||
if nameInHeader != nameInBody {
|
if decodedName != nameInBody {
|
||||||
return fmt.Errorf("header mismatch: Mcp-Name header value '%s' does not match body value '%s'", nameInHeader, nameInBody)
|
return fmt.Errorf("header mismatch: Mcp-Name header value '%s' does not match body value '%s'", decodedName, nameInBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package mcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -335,6 +336,23 @@ func TestValidateMcpHeaders(t *testing.T) {
|
||||||
msg: &jsonrpc.Request{Method: "prompts/get", Params: mustMarshal(&GetPromptParams{Name: "code_review"})},
|
msg: &jsonrpc.Request{Method: "prompts/get", Params: mustMarshal(&GetPromptParams{Name: "code_review"})},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Base64-encoded Mcp-Name is decoded before comparison",
|
||||||
|
version: minVersionForStandardHeaders,
|
||||||
|
methodHeader: "tools/call",
|
||||||
|
nameHeader: base64Prefix + base64.StdEncoding.EncodeToString([]byte("café-tool")) + base64Suffix,
|
||||||
|
msg: &jsonrpc.Request{Method: "tools/call", Params: mustMarshal(&CallToolParams{Name: "café-tool"})},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mcp-Name with invalid Base64 encoding",
|
||||||
|
version: minVersionForStandardHeaders,
|
||||||
|
methodHeader: "tools/call",
|
||||||
|
nameHeader: base64Prefix + "not valid base64!" + base64Suffix,
|
||||||
|
msg: &jsonrpc.Request{Method: "tools/call", Params: mustMarshal(&CallToolParams{Name: "my-tool"})},
|
||||||
|
wantErr: true,
|
||||||
|
wantErrContain: "invalid Base64 encoding",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "valid initialize (no name needed)",
|
name: "valid initialize (no name needed)",
|
||||||
version: minVersionForStandardHeaders,
|
version: minVersionForStandardHeaders,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue