Research guide · MCP interoperability · 13 September 2026
MCP _meta HTTP 400: fix empty request metadata without breaking 2026-07-28
If a hosted MCP server works from one client but another fails immediately with HTTP 400, inspect the raw request before changing authentication or tool code. A current MCP Python SDK issue reports a particularly sharp interoperability failure: the client serializes _meta: {} on every request, while Meta's hosted Ads MCP server rejects that empty field during initialize. The fix depends on which MCP protocol era is actually on the wire.
Published 13 September 2026 · sources checked 13 September 2026
#3473
Open Python SDK interoperability report
HTTP 400
Reported failure against Meta Ads MCP
≤ 2025-11-25
Handshake-era MCP lifecycle
2026-07-28
Stateless era with per-request metadata
The exact symptom
An empty object is not always equivalent to an absent field
Python SDK issue 3473, opened 8 September 2026, says JSONRPCDispatcher.send_raw_request currently builds an _meta object and attaches it to params even when no progress callback, tracing context or other metadata populated it. The result is a wire payload containing an empty object.
The reporter tested Meta's hosted Ads MCP endpoint and observed HTTP 400 with JSON-RPC error -32602 on initialize when the request contained either an empty _meta object or null. In the reported reproduction, leaving the field out allowed the legacy initialization request to proceed. That is implementation evidence from an open issue, not a universal rule that all MCP metadata must be removed.
That qualification matters because the current MCP protocol has two lifecycle eras. A workaround that is correct for a strict handshake-era server can break a native 2026-07-28 request.
Protocol-era test
First decide whether you should omit metadata or populate it
| Wire shape | Interpretation | Safe first move |
|---|---|---|
initialize present; _meta is empty | Handshake-era request carrying meaningless metadata | Omit _meta when it has no progress, trace or extension content; preserve any real metadata. |
initialize present; _meta omitted | Normal legacy shape when no request metadata is needed | Keep it absent unless the negotiated feature actually needs metadata. |
2026-07-28 request; _meta is empty | Modern request missing required per-request protocol context | Do not merely strip it. Populate the reserved protocol-version and client-capability keys through the SDK. |
2026-07-28 request; complete _meta | Modern stateless lifecycle | Validate the reserved values on every request and keep client identity/version handling current. |
Why 2026-07-28 changes the answer
Modern MCP intentionally moves connection context onto every request
MCP 2026-07-28 retired the initialize/initialized handshake and protocol-level sessions. In the modern lifecycle, each request carries its own protocol context. Official MCP documentation says protocol version and client capabilities now travel in _meta on every request; current SDK documentation treats client identity as recommended rather than mandatory.
That means a modern request with _meta: {} has a different problem from the Meta Ads report: it is not merely carrying a redundant field, it is missing the metadata that replaced the handshake. The correct repair is to make the session layer serialize the required reserved entries, not to delete the envelope globally.
Conversely, versions through 2025-11-25 still use initialize. On that path, do not inject an empty metadata object just because the SDK has a common request model. Preserve metadata when a real feature needs it; otherwise omission can be the interoperable representation for strict peers.
Wire-level diagnosis
Six checks before you patch a vendor or fork an SDK
- 1. Capture the failing JSON-RPC request. Look at the actual serialized
params, not the in-memory model. Confirm whether_metais absent, empty, null or populated. - 2. Identify the lifecycle from the wire. If the client is sending
initialize, you are on the handshake path. A native 2026-07-28 request should not be using that initialization exchange. - 3. Compare against a working client. Keep endpoint, credentials and requested operation constant. A different serialization shape with the same server is stronger evidence than changing several variables at once.
- 4. Preserve meaningful metadata. Progress tokens, trace context, extensions and modern reserved keys must not be lost because an empty-object bug exists elsewhere.
- 5. Test the smallest era-correct repair. On a handshake request with no metadata semantics, omit an empty field. On 2026-07-28, make the client populate the required protocol metadata.
- 6. Remove compatibility patches when upstream behavior changes. Vendor-specific serialization workarounds should be version-gated and temporary. Re-test after SDK releases rather than turning a transient interop defect into your permanent protocol contract.
Minimal mental model
Empty, absent and populated are three different states
Absent
No request metadata is being asserted. This can be correct on a handshake-era request when no metadata-bearing feature is in use.
Empty
The field exists but conveys nothing. A strict implementation may reject this shape, and it is insufficient for a native 2026-07-28 request.
Populated
The field carries real progress, trace, extension or protocol context. Validate its keys for the negotiated protocol era instead of deleting it.
Related zFinia paths
Keep metadata failures separate from routing, OAuth and server trust
Sources
Primary implementation and protocol references
- MCP Python SDK issue #3473 — open interoperability report for unconditional empty
_metaserialization and Meta Ads MCP HTTP 400 behavior, opened 8 September 2026. - MCP 2026-07-28 specification release — official explanation of the stateless lifecycle and per-request metadata model.
- MCP Go SDK lifecycle documentation — current two-era lifecycle map and reserved per-request metadata keys.
Bottom line
If a strict MCP server rejects _meta: {}, do not jump from “empty metadata is bad here” to “metadata should never be sent.” First identify the protocol era. On handshake-era traffic, omit metadata that has no semantic content. On MCP 2026-07-28, send the meaningful per-request protocol context the stateless lifecycle requires. That distinction fixes the interoperability bug without creating the next one.