Bug Description
The MCP endpoint (POST /api/mcp) rejects Content-Type values that are legal spellings of application/json, returning HTTP 415 Content-Type must be 'application/json'.
Two forms are refused that should be accepted per RFC 9110 §8.3:
application/json; charset=utf-8— parameters are explicitly permittedApplication/JSON— media types are case-insensitive
This is not hoop's own code: the check comes from modelcontextprotocol/go-sdk, which gateway/go.mod pins at v1.5.0. Upstream fixed it in v1.6.0 (PR #878, "mcp: allow Content-Type parameters in streamable transport").
Why it matters in practice: Apache HttpClient (Java) appends charset=utf-8 to JSON bodies by default, so any Java-based MCP client is refused. We hit it with AWS Bedrock AgentCore, but AWS is incidental — it just happens to use that library. AgentCore sends a capability probe first (bare application/json, accepted), then falls back to a standard initialize carrying the charset parameter — and that fallback is refused, so MCP server registration never completes.
Steps to Reproduce
Any valid hoop API key against a hoop gateway (initialize is a read-only handshake):
export HOOP_API_KEY='<key>'
URL=https://<your-hoop-host>/api/mcp
BODY='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
for CT in 'application/json' 'application/json; charset=utf-8' 'Application/JSON'; do
printf '%-34s -> ' "$CT"
curl -s -o /tmp/p.txt -w '%{http_code} ' -X POST \
-H "Authorization: Bearer $HOOP_API_KEY" \
-H "Content-Type: $CT" \
-H 'Accept: application/json, text/event-stream' \
--data "$BODY" "$URL"
head -c 80 /tmp/p.txt; echo
doneExpected Behavior
All three succeed. The media type is application/json in every case; a charset parameter and letter case are not semantic differences.
Actual Behavior
application/json -> 200 event: message
data: {"jsonrpc":"2.0","id":1,"result":{...
application/json; charset=utf-8 -> 415 Content-Type must be 'application/json'
Application/JSON -> 415 Content-Type must be 'application/json'Version
1.150.2 (latest release; also present on main)
Browser(s)
Other — not browser-related; server-side HTTP API
Operating System
Server-side (hoop gateway, Linux container); reproduced from a Linux client
Additional Context
Root cause. gateway/go.mod pins github.com/modelcontextprotocol/go-sdk v1.5.0, whose mcp/streamable.go does a literal string comparison:
contentType := req.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
return
}v1.6.0 parses the media type instead, which fixes both the parameter and the case problem:
if req.Method == http.MethodPost && baseMediaType(req.Header.Get("Content-Type")) != "application/json" {where baseMediaType is mime.ParseMediaType(value) returning the type without parameters (and normalized case).
Note on dates: PR #878 merged 2026-04-03 and v1.5.0 was released 2026-04-07, so it looks as though v1.5.0 should already contain the fix. It does not — v1.5.0 was cut from an earlier commit (the merge commit is 7 commits ahead of the v1.5.0 tag, 0 behind). Comparing the tagged sources directly is the reliable check:
for T in v1.5.0 v1.6.0; do
echo "== $T"
gh api "repos/modelcontextprotocol/go-sdk/contents/mcp/streamable.go?ref=$T" \
--jq '.content' | base64 -d | grep -n -B2 "Content-Type must be"
doneSuggested fix. Bump modelcontextprotocol/go-sdk in gateway/go.mod to v1.6.1 or later (v1.7.0 is current). We suggest v1.6.1 rather than v1.6.0 because v1.6.1 adds the MCPGODEBUG=disablecontenttypecheck=1 escape hatch, which does not exist in earlier builds.
⚠️ One thing to be careful about when bumping — a security default changed. In v1.5.0, leaving CrossOriginProtection unset meant protection was on:
if h.opts.CrossOriginProtection == nil {
h.opts.CrossOriginProtection = &http.CrossOriginProtection{}
}In v1.6.0 the same line became conditional on an opt-in:
if h.opts.CrossOriginProtection == nil && enableoriginverification == "1" {gateway/api/mcpserver/mcpserver.go constructs the handler as &mcp.StreamableHTTPOptions{SessionTimeout: mcpSessionTimeout} and never sets CrossOriginProtection — so a straight version bump would silently disable cross-origin protection on the gateway. Setting MCPGODEBUG=enableoriginverification=1, or configuring CrossOriginProtection explicitly (or wrapping the handler with http.NewCrossOriginProtection().Handler(...), as the v1.6.0 deprecation note suggests), preserves the current behavior.
Two smaller v1.6.0 behavior changes worth knowing while upgrading: CallToolResult.SetError now preserves existing content instead of overwriting it (if len(r.Content) == 0 || seterroroverwrite == "1"), and the jsonescaping option was removed.
Observability note. The api-audit log line records content-length, email, method, org, path and user-agent, but not content-type — so a rejected request looks unremarkable in the logs. Filtering path == "/api/mcp" shows the successful traffic; the 415s are distinguishable mainly by a user-agent such as Apache-HttpAsyncClient/5.6.1 (Java/21.0.12.1).
We searched existing issues for content-type, 415 and charset and found nothing, and no open PR bumping the SDK — apologies if we missed a duplicate.
Relevant Log Output
$ curl -s -o /dev/stderr -w '%{http_code}\n' -X POST \
-H "Authorization: Bearer $HOOP_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
https://<hoop-host>/api/mcp
Content-Type must be 'application/json'
415
🔗 Synced to Linear: SUP-226