Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

mcp: a property with an array "type" (any pointer field from jsonschema.For) disables x-mcp-header handling for the whole tool

Open
#1,406 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@jay23606 is already working on this.

Since Oct 16, 2026.

  • #1408 by @jay23606 — open
  • #1410 by @RafalOfficial — open

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
50/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
go
Domain
api

Research direction

Start in mcp/streamable_headers.go: headerSchemaProperty decodes type as a string, so an array or boolean subschema makes unmarshalSchemaProperties return nil for the whole tool. Read validateParamHeaderAnnotations and the two tests named in the issue, TestExtractToolParamHeaders and TestValidateToolParamHeaders. Done when a property with an array or boolean schema no longer disables annotations on other properties, and an annotated nullable property is rejected with the existing primitive-types error, as the issue proposes. The maintainers still need to decide the nullable case.

Written by the indexing model from the issue text.

Description

Describe the bug

headerSchemaProperty (mcp/streamable_headers.go) decodes type into a string. JSON Schema also allows an array there, and jsonschema.For emits one for every pointer field ("type": ["null", "string"]). When any property in a tool's inputSchema has an array type, the remarshal in unmarshalSchemaProperties fails and returns nil. The client and server then treat the tool as having no x-mcp-header annotations at all, even on other properties:

  • The client sends no Mcp-Param-* headers for the tool. A server that validates them, as the 2026-07-28 spec requires, answers with HeaderMismatch.
  • The server skips validateParamHeaders, so a request whose Mcp-Param-* header contradicts the body is accepted.
  • validateParamHeaderAnnotations returns nil without checking anything, so AddTool and the client's tools/list filter also accept an invalid annotation, such as one on a number property.

A boolean subschema ("properties": {"extra": true}) fails the same way.

To Reproduce

A typed tool with an annotated region and an optional *string field, served over Streamable HTTP and called by the go-sdk client on 2026-07-28 (go run . with a replace to the checkout):

type withOptionalIn struct {
	Region string  `json:"region"`
	Query  string  `json:"query"`
	Note   *string `json:"note,omitempty"` // inferred as "type": ["null", "string"]
}

s, _ := jsonschema.For[withOptionalIn](nil)
s.Properties["region"].Extra = map[string]any{"x-mcp-header": "Region"}
mcp.AddTool(server, &mcp.Tool{Name: "with_optional", InputSchema: s}, handler)

The full program registers this tool next to an identical plain tool without Note. The program serves both tools with NewStreamableHTTPHandler (stateless, TTLMs set so the client caches tools/list). It calls each tool with {"region": "eu", "query": "q"} and records the outgoing headers in a RoundTripper. Then it calls each tool again with Mcp-Param-Region overwritten to forged:

plain          client sent Mcp-Param-Region="eu", call err=<nil>
with_optional  client sent Mcp-Param-Region="", call err=<nil>
plain          header forged to "forged" (body region=eu): call err=... header mismatch: Mcp-Param-Region header value 'forged' does not match body value: rejected by transport: Bad Request
with_optional  header forged to "forged" (body region=eu): call err=<nil>

The only difference between the two tools is the extra optional field.

Expected behavior

with_optional behaves like plain: the client sends Mcp-Param-Region: eu, and the server rejects the forged header.

Proposed fix

In headerSchemaProperty, keep type as a json.RawMessage. Give the struct an UnmarshalJSON that turns true/false into an empty property, so each property decodes independently. An annotated property must still have a single primitive type. A nullable annotated property (["null", "string"]) is rejected with the existing "primitive types" error, like the TypeScript SDK's check (typeof schema.type === 'string'). The change is about 20 lines, plus table cases in TestExtractToolParamHeaders and TestValidateToolParamHeaders. If you'd rather accept ["null", <primitive>] on annotated properties (both header paths already skip null arguments), that is a small change.

Patch: https://github.com/modelcontextprotocol/go-sdk/compare/main...RafalOfficial:go-sdk:fix/param-header-type-arrays

The new cases fail on main and pass with the patch. With the patch, the repro output matches for both tools. go test ./..., go vet ./..., gofmt -l . and staticcheck 2026.2.2 are clean. I couldn't run v0.6.1, the CI version, because it can't read Go 1.27 export data. This is separate from #1250 / #1341, which covers annotations in positions that are not reachable through properties.

If you're happy with this direction, I can open the PR.

Additional context

  • Go MCP SDK: main at 9012a21
  • go version go1.27.0 windows/amd64

This report and the patch were written with Claude Code (AI-assisted). I ran the repro and the tests above on my machine.

Dominant language
Go
Stars
5.2k
Forks
583
Avg merge
2d 1h
Merged PRs (30d)
55

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from modelcontextprotocol/go-sdk

All issues in modelcontextprotocol/go-sdk

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.