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

Open

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

  1. jjj-n commented on Sep 10, 2026

    @jjj-n

    Hi, I'd like to work on this issue if the author is not already implementing a fix.

    I checked the current validation path in mcp/streamable_headers.go. My proposed scope is to detect x-mcp-header annotations in disallowed schema positions, reject invalid tool definitions at registration, and preserve the behavior of valid top-level and nested properties annotations. I would add focused regression tests for invalid placements and valid annotations, including checks that the new regressions fail before the fix.

    Since this issue is not labeled help wanted, I'll wait for maintainer confirmation before implementing or opening a PR, as recommended by the contribution guide. Would you be happy for me to take this on? If the author already has work underway, I'm happy to coordinate to avoid duplication.

  2. swayyaam commented on Oct 3, 2026

    @swayyaam

    I can confirm this on current main. headerSchemaProperty in mcp/streamable_headers.go only decodes type, x-mcp-header and properties, so validateParamHeadersIn never sees an annotation under items, oneOf/anyOf/allOf, not, if/then/else or $ref. Calling validateParamHeaderAnnotations directly on schemas with the annotation under items, oneOf and $ref returns nil for all three.

    One small correction to the report: Server.AddTool doesn't return an error on invalid annotations, it panics (AddTool "t": invalid parameter header annotations: ...). So once these placements are rejected, AddTool will panic for them too. On the client side, ClientSession.ListTools runs the same check through filterValidTools, which logs the tool and drops it from the result, so a fix would also make clients drop tools like this.

    The fix could be covered by new cases in TestValidateToolParamHeaders (mcp/streamable_headers_test.go), each expecting an error:

    • annotation under items of an array property
    • annotation inside a oneOf / anyOf / allOf branch
    • annotation under not and under if / then / else
    • annotation in a $defs entry reached through $ref

    plus a case confirming that an annotation on a nested properties chain is still accepted.

    @jjj-n are you still planning to take this? If not, and a maintainer is OK with it, I have a fix with tests ready to send.

  3. jjj-n commented on Oct 5, 2026

    @jjj-n

    Please go ahead — I never started an implementation and was still waiting on maintainer confirmation since the issue is not labeled help wanted. Your analysis of headerSchemaProperty and the proposed test cases are solid, so it makes sense for this to move forward with your ready fix rather than sit unclaimed. Good luck with the PR!

  4. swayyaam commented on Oct 5, 2026

    @swayyaam

    Thanks @jjj-n. PR is up in #1341. Maintainers, happy to adjust or hold if you'd rather approach this differently.

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