Repository navigation
McpClient should allow passing _meta in InitializeRequest #940
Description
The MCP specification defines _meta as a valid field on every request, including initialize, via the RequestParams base type:
// schema.ts (2025-11-25)
interface RequestParams {
_meta?: {
progressToken?: ProgressToken;
[key: string]: unknown;
};
}
interface InitializeRequestParams extends RequestParams {
protocolVersion: string;
capabilities: ClientCapabilities;
clientInfo: Implementation;
}McpSchema.InitializeRequest in the Java SDK correctly models this (the _meta field was added in #344). However, McpSyncClient#initialize() / LifecycleInitializer#doInitialize always constructs the request with the 3-arg convenience constructor, hardwiring _meta to null. There is no API to populate it.
Impact
Servers that rely on _meta of the initialize request will always receive null metadata when called from a Java client, even though the spec explicitly allows it. This is inconsistent with tools/call, where CallToolRequest._meta is fully caller-controlled.
Workaround (current)
The only workaround is to wrap the McpClientTransport and intercept the outgoing initialize JSONRPCRequest in sendMessage, rebuilding it with _meta populated. This works but couples application code to internal wire-format details (JSONRPCRequest, InitializeRequest record structure) that should be opaque to callers.
Proposed solution
Expose _meta on the McpClient builder, consistent with the pattern used in PR #906 for paginated list methods:
// Option A — builder-level (static meta per client lifetime)
McpClient.sync(transport)
.initializeMeta(Map.of("server_id", "…", "invocation_id", "…"))
.build();
// Option B — overloaded initialize() (dynamic meta per call)
client.initialize(Map<String, Object> meta);Related
- feat: Add support for _meta across fields defined in MCPSchema #344 — added
_metafield toMcpSchemaclasses (schema layer, no client API) - feat: Add support for meta parameter to list tools call( paginated request already support meta field). #907 / PR feat: add support for meta parameter in client paginated list queries #906 — added
_metato paginated list client methods (listTools, etc.) - MCP spec — General fields:
_meta - MCP spec schema (2025-11-25)
Activity
Kehrlann commentedon Jul 1, 2026
Hey @nquinquenel ! Thanks for the report.
It is indeed specified in the protocol, but AFAIK, neither TypeScript nor Python SDKS, the reference implementations, allow you to pass
_metain init requests (please correct me if I am wrong).Before we decide to update our public APIs, what are you trying to achieve specifically by passing
_metain init? What's your use-case?nquinquenel commentedon Jul 1, 2026
Thanks for the reply, @Kehrlann!
Our use case is that we run multiple MCP servers for different purposes, and expose them through a single primary MCP server. This primary server is responsible for starting the secondary MCP servers and connecting to them via the MCP protocol (client <> server communication).
To correlate telemetry across the servers, we'd like to propagate a correlation identifier from the primary server to each secondary server. Since this information is needed before any other messages are exchanged, the
initializerequest (via the_metaobject) seems like the natural place to pass it.Kehrlann commentedon Jul 1, 2026
Why
initializerather than at the actualtools/call,prompts/list, etc? On the java side, I don't think there is a way too hook into the initialization request on the server side to make any use of this information[1], so the client would send it but the server can't do anything useful with it. Maybe in other SDKs but I don't know much about the specific implementation. Also,initializeis a less frequent event than regular operations liketools/call.On that note, the
*/listmethods don't support custom_metaso you're going to miss those calls too.The initialize handshake is going away in the new version of the spec 2026-07-28 (SEP-2575), so this is probably not a future-proof solution.
And any particular reason to add tracing at the MCP level, rather than using HTTP headers? I know OTel context propagation has reserved keys in meta (https://modelcontextprotocol.io/seps/414-request-meta), but there seems to be work in progress to mirror meta keys in HTTP headers going forward.
In any case, you'll have a similar problem with headers, which is that the the initialize handler cannot be customized, but you can intercept it outside of MCP.
[1] Maybe in the
McpStreamableServerSession.Factorybut if you plug that manually into the transport you'll end up with seriousresources/subscribecleanup issue.nquinquenel commentedon Jul 2, 2026
Thanks for the detailed follow-up, all fair points.
Why
initializeat all?Our main correlation path is indeed
tools/call: we already attach information in_metaon every tool invocation (and forward it when proxying). That covers the telemetry we ship today.The
initialize._metaask was for session-level identity of the connecting aggregator, i.e. letting a proxied server know which primary server established the MCP session, before any tool is called. That's a nice-to-have for potential connection/session analytics, not something we depend on for current tool-invocation events.Server-side readability
You're right that the Java SDK doesn't expose a clean server-side hook for initialize today, that would be a blocker for Java-to-Java.
In our case the proxied server is a Rust MCP server (stdio), not Java. It can read
initialize.params._metaat the protocol level, so the client-side gap in the Java SDK is the practical blocker for us, not server-side consumption. But I do agree that it should be provided on both side in the Java SDK to make it truly useful.Transport / headers
Our proxied connections are stdio child processes, not streamable HTTP, so HTTP header propagation doesn't apply there. For our HTTP-facing mode, headers (or the OTel
_metakeys from SEP-414) may be the better long-term path, we're watching that work.SEP-2575
Agreed that betting on
initializelong-term isn't ideal. We're not blocked on it as we already ship a transport-wrapper workaround, but we'd still prefer a first-class client API for spec consistency and to avoid coupling to JSON-RPC wire details. We'll follow whatever correlation mechanism the spec settles on post-2575.Given all of the above: we're fine treating this as lower priority / API-consistency rather than urgent. If the SDK team prefers to wait for SEP-2575 and header-based propagation to land before adding init-meta APIs, that makes sense to us.
Kehrlann commentedon Jul 2, 2026
Thanks a lot, that's very useful! I can see the use-case, esp. if you have STDIO.
Let's schedule this for 2.1.0.
I'm thinking we should add
initialize(InitializeRequest initializeRequest)method rather than focusing on meta ; it's a bit clunky because you need to specify protocol version, client caps and client info manually, but it's consistent with other methods and more flexible. What do you think?Would you be open to submitting a PR?
nquinquenel commentedon Jul 2, 2026
Sounds great!
+1 on
initialize(InitializeRequest)over a meta-only API. It's consistent withcallTool(CallToolRequest)and covers our case cleanly (we only need to set_metatoday, but the full request object is the right abstraction I think).Yes, happy to submit a PR if we're good on the solution!
Kehrlann commentedon Jul 2, 2026
I've assigned you to the issue.
Take your time, we have to release a 2.0.1 and I'll probably merge it in a few days.
nquinquenel commentedon Jul 6, 2026
Hey @Kehrlann, quick scope question while working on the changes.
Alongside the
initialize(InitializeRequest)overload we agreed on, I was wondering whether it'd be worth having the client remember the custom request and resend it if the transport session is re-established (e.g. after aMcpTransportSessionNotFoundException), so a silently recovered session doesn't drop the_meta/correlation info. We didn't discuss this, so I wanted to check: do you think that's a good idea to include, or would you rather keep the PR to just the overload and leave that for later?Kehrlann commentedon Sep 4, 2026
That's an important consideration indeed. There are a lot more corner cases to this than I had originally account for:
McpTransportSessionNotFoundException.initialize()vs implicitly initializing with any other method (e.g..listTools()).initialize()multiple times, a single call is taken into account (seeLifecycleInitializerTests#shouldHandleConcurrentInitializationRequests)Problem 3 complicates exposing
.initialize(InitializeRequest): what if you call.initialize(requestOne)followed by.initialize(requestTwo)? It would be a weird thing to do, but our code allows it and it would have surprising results. And there is no way to programmatically de-init a client.I'm leaning towards adding an initialize meta for the client. It adds a method on the client builder, for a single feature, which isn't great. Let me think about it a bit more.
Kehrlann commentedon Sep 4, 2026
@nquinquenel we're working on the 2026-07-28 spec, is this something you'll need once the new spec is supported?
nquinquenel commentedon Sep 4, 2026
Thanks for circling back! Once we’re on 2026-07-28 sessions, AFAIU, we won’t need an
initialize(InitializeRequest)/initialize-_metaAPI. With the handshake gone, we can put that information on_metaof server/discover and/or every request (we already do this ontools/call).However, as long as we still speak 2025-11-25, the client still sends
initializeas first contact. Without a first-class way to set_metaon that request, we have to keep the transport wrapper.Kehrlann commentedon Sep 4, 2026
Yes, let's drop this so we can focus our efforts on
2026-07-28. The design would be too wonky for a quick fix.