Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ModelContextProtocol.Core/Client/McpClientImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions src/ModelContextProtocol.Core/Client/McpClientOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ModelContextProtocol.Protocol;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;

namespace ModelContextProtocol.Client;

Expand Down Expand Up @@ -31,6 +32,21 @@ public sealed class McpClientOptions
/// </summary>
public ClientCapabilities? Capabilities { get; set; }

/// <summary>
/// Gets or sets the metadata to include in the <c>_meta</c> field of the <see cref="RequestMethods.Initialize"/> request.
/// </summary>
/// <remarks>
/// <para>
/// When set, this value is sent as <see cref="RequestParams.Meta"/> on the <see cref="InitializeRequestParams"/> during the initialization handshake.
/// This allows passing implementation-specific data to the server alongside the standard <c>initialize</c> parameters,
/// such as authentication context a server validates before completing the handshake.
/// </para>
/// <para>
/// When <see langword="null"/>, no <c>_meta</c> field is sent.
/// </para>
/// </remarks>
public JsonObject? InitializeMeta { get; set; }

/// <summary>
/// Gets or sets the protocol version to request from the server, using a date-based versioning scheme.
/// </summary>
Expand Down
44 changes: 44 additions & 0 deletions tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace ModelContextProtocol.Tests.Client;

public class McpClientMetaTests : ClientServerTestBase
{
private readonly TaskCompletionSource<JsonNode?> _initializeMeta = new();

public McpClientMetaTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
Expand All @@ -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]
Expand Down