From 7b0909cdd2cc0b7b9de7b71480a533ef3a1a3328 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Sun, 24 May 2026 05:32:11 -0700 Subject: [PATCH] Add McpClientOptions.InitializeMeta to set _meta on initialize request InitializeRequestParams already inherits Meta (_meta) from RequestParams, but McpClientImpl.ConnectAsync built the params without reading from McpClientOptions, so callers had no way to populate it. Add an InitializeMeta property on McpClientOptions and thread it through the handshake. Fixes #1593 --- .../Client/McpClientImpl.cs | 1 + .../Client/McpClientOptions.cs | 16 +++++++ .../Client/McpClientMetaTests.cs | 44 +++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index 0d5803559..44458807b 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -556,6 +556,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default) ProtocolVersion = requestProtocol, Capabilities = _options.Capabilities ?? new ClientCapabilities(), ClientInfo = _options.ClientInfo ?? DefaultImplementation, + Meta = _options.InitializeMeta, }, McpJsonUtilities.JsonContext.Default.InitializeRequestParams, McpJsonUtilities.JsonContext.Default.InitializeResult, diff --git a/src/ModelContextProtocol.Core/Client/McpClientOptions.cs b/src/ModelContextProtocol.Core/Client/McpClientOptions.cs index 6d91f5b03..f84b84826 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientOptions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientOptions.cs @@ -1,5 +1,6 @@ using ModelContextProtocol.Protocol; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; namespace ModelContextProtocol.Client; @@ -31,6 +32,21 @@ public sealed class McpClientOptions /// public ClientCapabilities? Capabilities { get; set; } + /// + /// Gets or sets the metadata to include in the _meta field of the request. + /// + /// + /// + /// When set, this value is sent as on the during the initialization handshake. + /// This allows passing implementation-specific data to the server alongside the standard initialize parameters, + /// such as authentication context a server validates before completing the handshake. + /// + /// + /// When , no _meta field is sent. + /// + /// + public JsonObject? InitializeMeta { get; set; } + /// /// Gets or sets the protocol version to request from the server, using a date-based versioning scheme. /// diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs index 863d8e671..7e67eb44c 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs @@ -8,6 +8,8 @@ namespace ModelContextProtocol.Tests.Client; public class McpClientMetaTests : ClientServerTestBase { + private readonly TaskCompletionSource _initializeMeta = new(); + public McpClientMetaTests(ITestOutputHelper outputHelper) : base(outputHelper) { @@ -28,6 +30,48 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer o.ResourceCollection = new (); o.PromptCollection = new (); }); + + // Capture the _meta the server receives on the initialize request so tests can + // assert that McpClientOptions.InitializeMeta is threaded through the handshake. + mcpServerBuilder.WithMessageFilters(filters => + filters.AddIncomingFilter(next => async (context, cancellationToken) => + { + if (context.JsonRpcMessage is JsonRpcRequest { Method: RequestMethods.Initialize } request) + { + _initializeMeta.TrySetResult(request.Params?["_meta"]); + } + + await next(context, cancellationToken); + })); + } + + [Fact] + public async Task InitializeMeta_IsSentToServer_WhenSet() + { + var clientOptions = new McpClientOptions + { + InitializeMeta = new JsonObject + { + { "foo", "bar baz" } + } + }; + + await using McpClient client = await CreateMcpClientForServer(clientOptions); + + var meta = await _initializeMeta.Task.WaitAsync(TestContext.Current.CancellationToken); + + Assert.NotNull(meta); + Assert.Equal("bar baz", meta["foo"]?.ToString()); + } + + [Fact] + public async Task InitializeMeta_IsOmitted_WhenNotSet() + { + await using McpClient client = await CreateMcpClientForServer(); + + var meta = await _initializeMeta.Task.WaitAsync(TestContext.Current.CancellationToken); + + Assert.Null(meta); } [Fact]