Skip to content

McpClient should allow passing _meta in InitializeRequest #940

Closed as not planned

Description

@nquinquenel

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

Activity

  1. added
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    and removed on Jul 1, 2026
  2. Kehrlann commented on Jul 1, 2026

    @Kehrlann
    Contributor

    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 _meta in 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 _meta in init? What's your use-case?

  3. nquinquenel commented on Jul 1, 2026

    @nquinquenel
    ContributorAuthor

    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 initialize request (via the _meta object) seems like the natural place to pass it.

  4. Kehrlann commented on Jul 1, 2026

    @Kehrlann
    Contributor

    Why initialize rather than at the actual tools/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, initialize is a less frequent event than regular operations like tools/call.
    On that note, the */list methods don't support custom _meta so 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.Factory but if you plug that manually into the transport you'll end up with serious resources/subscribe cleanup issue.

  5. nquinquenel commented on Jul 2, 2026

    @nquinquenel
    ContributorAuthor

    Thanks for the detailed follow-up, all fair points.

    Why initialize at all?

    Our main correlation path is indeed tools/call: we already attach information in _meta on every tool invocation (and forward it when proxying). That covers the telemetry we ship today.

    The initialize._meta ask 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._meta at 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 _meta keys from SEP-414) may be the better long-term path, we're watching that work.

    SEP-2575

    Agreed that betting on initialize long-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.

  6. added this to the 2.1.0 Planning milestone on Jul 2, 2026
  7. Kehrlann commented on Jul 2, 2026

    @Kehrlann
    Contributor

    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?

  8. added
    ready for workThe goal is clear and work towards it can be commenced
    on Jul 2, 2026
  9. nquinquenel commented on Jul 2, 2026

    @nquinquenel
    ContributorAuthor

    Sounds great!

    +1 on initialize(InitializeRequest) over a meta-only API. It's consistent with callTool(CallToolRequest) and covers our case cleanly (we only need to set _meta today, but the full request object is the right abstraction I think).

    Yes, happy to submit a PR if we're good on the solution!

  10. Kehrlann commented on Jul 2, 2026

    @Kehrlann
    Contributor

    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.

  11. nquinquenel commented on Jul 6, 2026

    @nquinquenel
    ContributorAuthor

    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 a McpTransportSessionNotFoundException), 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?

  12. Kehrlann commented on Sep 4, 2026

    @Kehrlann
    Contributor

    That's an important consideration indeed. There are a lot more corner cases to this than I had originally account for:

    1. As you mentioned, re-initialization on McpTransportSessionNotFoundException
    2. Calling .initialize() vs implicitly initializing with any other method (e.g. .listTools())
    3. The caching of initialize results - if you call .initialize() multiple times, a single call is taken into account (see LifecycleInitializerTests#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.

  13. Kehrlann commented on Sep 4, 2026

    @Kehrlann
    Contributor

    @nquinquenel we're working on the 2026-07-28 spec, is this something you'll need once the new spec is supported?

  14. nquinquenel commented on Sep 4, 2026

    @nquinquenel
    ContributorAuthor

    Thanks for circling back! Once we’re on 2026-07-28 sessions, AFAIU, we won’t need an initialize(InitializeRequest) / initialize-_meta API. With the handshake gone, we can put that information on _meta of server/discover and/or every request (we already do this on tools/call).

    However, as long as we still speak 2025-11-25, the client still sends initialize as first contact. Without a first-class way to set _meta on that request, we have to keep the transport wrapper.

  15. Kehrlann commented on Sep 4, 2026

    @Kehrlann
    Contributor

    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.

  16. added
    wontfixThis will not be worked on
    and removed
    ready for workThe goal is clear and work towards it can be commenced
    on Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Moderate issues affecting some users, edge cases, potentially valuable featurearea/clientwontfixThis will not be worked on

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions