Skip to content

Streamable HTTP: an x-mcp-header in a non-reachable schema position (items, oneOf, $ref, ...) is silently accepted instead of rejected #1250

Description

@arpankernel

Describe the bug

validateParamHeaderAnnotations (run from Server.AddTool, mcp/server.go:354) only inspects properties. Its schema struct headerSchemaProperty (mcp/streamable_headers.go:57) parses only type, x-mcp-header, and properties, so an x-mcp-header annotation placed under items, oneOf, anyOf, allOf, not, if/then/else, or $ref is never seen. AddTool accepts the tool with no error and the annotation is silently dropped: no Mcp-Param-* header is ever generated for it.

The SDK already rejects the other invalid x-mcp-header cases at registration (non-primitive type, invalid token syntax, duplicate token), so the non-reachable placement is the remaining gap.

Per the 2026-07-28 Streamable HTTP spec (Standard Request Headers, Schema Extension), an x-mcp-header MUST only be applied to a property that is statically reachable from the schema root via a chain consisting solely of properties keys; the chain MUST NOT pass through items, composition keywords (oneOf, anyOf, allOf, not), conditional keywords (if/then/else), or $ref. An annotation anywhere else makes the annotation, and thus the tool definition, invalid. So such a tool should be rejected, not served with the annotation silently ignored.

To Reproduce

package main

import (
	"context"
	"fmt"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	s := mcp.NewServer(&mcp.Implementation{Name: "s", Version: "0"}, nil)
	h := func(context.Context, *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		return &mcp.CallToolResult{}, nil
	}
	// x-mcp-header under array `items`: not statically reachable via `properties`.
	s.AddTool(&mcp.Tool{
		Name: "tag",
		InputSchema: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"tags": map[string]any{
					"type":  "array",
					"items": map[string]any{"type": "string", "x-mcp-header": "Tag"},
				},
			},
		},
	}, h)
	fmt.Println("AddTool returned without rejecting the invalid annotation")
}

AddTool returns normally. The same holds for oneOf/anyOf/allOf/$ref placements. A valid top-level or nested-properties annotation is still handled correctly, so only the non-reachable placements are silently swallowed.

Expected behavior

AddTool rejects the tool, as it already does for the other invalid x-mcp-header cases, so the author gets a signal instead of shipping a tool whose annotation every conforming client drops.

Additional context

headerSchemaProperty would need to also parse items/oneOf/anyOf/allOf/not/if/then/else/$ref so the validator can detect an annotation in a non-reachable position and reject it.

Related (separate, happy to file if useful): the client has no tools/list filter that excludes a tool carrying an invalid x-mcp-header, which the spec lists as a client MUST.

If the direction is welcome I can put up a PR with the validation extension and a regression test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Nice to haves, rare edge cases

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions