Skip to content

Commit d99f882

Browse files
PederHPstephentoub
andauthored
Add McpClientTool.WithMeta (#1027)
Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 4a84412 commit d99f882

File tree

3 files changed

+436
-37
lines changed

3 files changed

+436
-37
lines changed

src/ModelContextProtocol.Core/Client/McpClientTool.cs

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using ModelContextProtocol.Protocol;
33
using System.Collections.ObjectModel;
44
using System.Text.Json;
5+
using System.Text.Json.Nodes;
56

67
namespace ModelContextProtocol.Client;
78

@@ -36,6 +37,7 @@ public sealed class McpClientTool : AIFunction
3637
private readonly string _name;
3738
private readonly string _description;
3839
private readonly IProgress<ProgressNotificationValue>? _progress;
40+
private readonly JsonObject? _meta;
3941

4042
/// <summary>
4143
/// Initializes a new instance of the <see cref="McpClientTool"/> class.
@@ -73,6 +75,7 @@ public McpClientTool(
7375
_name = tool.Name;
7476
_description = tool.Description ?? string.Empty;
7577
_progress = null;
78+
_meta = null;
7679
}
7780

7881
internal McpClientTool(
@@ -81,14 +84,16 @@ internal McpClientTool(
8184
JsonSerializerOptions serializerOptions,
8285
string? name = null,
8386
string? description = null,
84-
IProgress<ProgressNotificationValue>? progress = null)
87+
IProgress<ProgressNotificationValue>? progress = null,
88+
JsonObject? meta = null)
8589
{
8690
_client = client;
8791
ProtocolTool = tool;
8892
JsonSerializerOptions = serializerOptions;
8993
_name = name ?? tool.Name;
9094
_description = description ?? tool.Description ?? string.Empty;
9195
_progress = progress;
96+
_meta = meta;
9297
}
9398

9499
/// <summary>
@@ -196,15 +201,38 @@ public ValueTask<CallToolResult> CallAsync(
196201
IReadOnlyDictionary<string, object?>? arguments = null,
197202
IProgress<ProgressNotificationValue>? progress = null,
198203
RequestOptions? options = null,
199-
CancellationToken cancellationToken = default) =>
200-
_client.CallToolAsync(
204+
CancellationToken cancellationToken = default)
205+
{
206+
// If there's any metadata provided with WithMeta, we can't just pass along the options as-is,
207+
// and instead need to create new options that merges in _meta.
208+
if (_meta is { } meta)
209+
{
210+
// Create a new RequestOptions, as we're going to need to store a new JsonObject for Meta (either
211+
// _meta or _meta+options.Meta), and we don't want to mutate the user's options object.
212+
RequestOptions newOptions = options?.Clone() ?? new();
213+
214+
// If we also have newOptions.Meta, merge that with _meta into a new JsonObject, preferring
215+
// the objects from newOptions.Meta in case of conflicts.
216+
if (newOptions.Meta is { } newOptionsMeta)
217+
{
218+
meta = (JsonObject)meta.DeepClone();
219+
foreach (var p in newOptionsMeta)
220+
{
221+
meta[p.Key] = p.Value?.DeepClone();
222+
}
223+
}
224+
225+
newOptions.Meta = meta;
226+
options = newOptions;
227+
}
228+
229+
return _client.CallToolAsync(
201230
ProtocolTool.Name,
202231
arguments,
203232
progress,
204-
options ?? new RequestOptions() {
205-
JsonSerializerOptions = JsonSerializerOptions
206-
},
233+
options,
207234
cancellationToken);
235+
}
208236

209237
/// <summary>
210238
/// Creates a new instance of the tool but modified to return the specified name from its <see cref="Name"/> property.
@@ -232,7 +260,7 @@ public ValueTask<CallToolResult> CallAsync(
232260
/// </remarks>
233261
/// <returns>A new instance of <see cref="McpClientTool"/> with the provided name.</returns>
234262
public McpClientTool WithName(string name) =>
235-
new(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress);
263+
new(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress, _meta);
236264

237265
/// <summary>
238266
/// Creates a new instance of the tool but modified to return the specified description from its <see cref="Description"/> property.
@@ -256,7 +284,7 @@ public McpClientTool WithName(string name) =>
256284
/// </remarks>
257285
/// <returns>A new instance of <see cref="McpClientTool"/> with the provided description.</returns>
258286
public McpClientTool WithDescription(string description) =>
259-
new(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress);
287+
new(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress, _meta);
260288

261289
/// <summary>
262290
/// Creates a new instance of the tool but modified to report progress via the specified <see cref="IProgress{T}"/>.
@@ -280,6 +308,35 @@ public McpClientTool WithProgress(IProgress<ProgressNotificationValue> progress)
280308
{
281309
Throw.IfNull(progress);
282310

283-
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress);
311+
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress, _meta);
284312
}
313+
314+
/// <summary>
315+
/// Creates a new instance of the tool but modified to include the specified metadata in tool call requests.
316+
/// </summary>
317+
/// <param name="meta">
318+
/// The metadata to include in tool call requests. This will be serialized as the <c>_meta</c> field
319+
/// in the JSON-RPC request parameters.
320+
/// </param>
321+
/// <remarks>
322+
/// <para>
323+
/// Adding metadata to the tool allows you to pass additional protocol-level information with each tool call.
324+
/// This can be useful for tracing, logging, or passing context information to the server.
325+
/// </para>
326+
/// <para>
327+
/// Only one metadata object can be specified at a time. Calling <see cref="WithMeta"/> again
328+
/// will overwrite any previously specified metadata object. If passed <see langword="null"/>,
329+
/// any previously supplied metadata will be removed.
330+
/// </para>
331+
/// <para>
332+
/// The metadata is passed through to the server as-is, merged with any protocol-level metadata
333+
/// such as progress tokens when <see cref="WithProgress"/> is also used. If a <see cref="RequestOptions"/>
334+
/// is passed to <see cref="CallAsync"/>, the metadata from both <paramref name="meta"/> and its
335+
/// <see cref="RequestOptions"/> will be merged, preferring values from the <see cref="RequestOptions"/> in
336+
/// case of conflicts.
337+
/// </para>
338+
/// </remarks>
339+
/// <returns>A new instance of <see cref="McpClientTool"/>, configured with the provided metadata.</returns>
340+
public McpClientTool WithMeta(JsonObject? meta) =>
341+
new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, _progress, meta);
285342
}

src/ModelContextProtocol.Core/RequestOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ public RequestOptions()
1616
{
1717
}
1818

19+
/// <summary>Creates a shallow clone of this options instance.</summary>
20+
/// <returns>A shallow clone of this options instance.</returns>
21+
internal RequestOptions Clone() =>
22+
new()
23+
{
24+
JsonSerializerOptions = JsonSerializerOptions,
25+
Meta = Meta,
26+
ProgressToken = ProgressToken,
27+
};
28+
1929
/// <summary>
2030
/// Gets or sets optional metadata to include as the "_meta" property in a request.
2131
/// </summary>

0 commit comments

Comments
 (0)