Skip to content
Merged
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
75 changes: 66 additions & 9 deletions src/ModelContextProtocol.Core/Client/McpClientTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using ModelContextProtocol.Protocol;
using System.Collections.ObjectModel;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace ModelContextProtocol.Client;

Expand Down Expand Up @@ -36,6 +37,7 @@ public sealed class McpClientTool : AIFunction
private readonly string _name;
private readonly string _description;
private readonly IProgress<ProgressNotificationValue>? _progress;
private readonly JsonObject? _meta;

/// <summary>
/// Initializes a new instance of the <see cref="McpClientTool"/> class.
Expand Down Expand Up @@ -73,6 +75,7 @@ public McpClientTool(
_name = tool.Name;
_description = tool.Description ?? string.Empty;
_progress = null;
_meta = null;
}

internal McpClientTool(
Expand All @@ -81,14 +84,16 @@ internal McpClientTool(
JsonSerializerOptions serializerOptions,
string? name = null,
string? description = null,
IProgress<ProgressNotificationValue>? progress = null)
IProgress<ProgressNotificationValue>? progress = null,
JsonObject? meta = null)
{
_client = client;
ProtocolTool = tool;
JsonSerializerOptions = serializerOptions;
_name = name ?? tool.Name;
_description = description ?? tool.Description ?? string.Empty;
_progress = progress;
_meta = meta;
}

/// <summary>
Expand Down Expand Up @@ -196,15 +201,38 @@ public ValueTask<CallToolResult> CallAsync(
IReadOnlyDictionary<string, object?>? arguments = null,
IProgress<ProgressNotificationValue>? progress = null,
RequestOptions? options = null,
CancellationToken cancellationToken = default) =>
_client.CallToolAsync(
CancellationToken cancellationToken = default)
{
// If there's any metadata provided with WithMeta, we can't just pass along the options as-is,
// and instead need to create new options that merges in _meta.
if (_meta is { } meta)
{
// Create a new RequestOptions, as we're going to need to store a new JsonObject for Meta (either
// _meta or _meta+options.Meta), and we don't want to mutate the user's options object.
RequestOptions newOptions = options?.Clone() ?? new();

// If we also have newOptions.Meta, merge that with _meta into a new JsonObject, preferring
// the objects from newOptions.Meta in case of conflicts.
if (newOptions.Meta is { } newOptionsMeta)
{
meta = (JsonObject)meta.DeepClone();
foreach (var p in newOptionsMeta)
{
meta[p.Key] = p.Value?.DeepClone();
}
}

newOptions.Meta = meta;
options = newOptions;
}

return _client.CallToolAsync(
ProtocolTool.Name,
arguments,
progress,
options ?? new RequestOptions() {
JsonSerializerOptions = JsonSerializerOptions
},
options,
cancellationToken);
}

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

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

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

return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress);
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress, _meta);
}

/// <summary>
/// Creates a new instance of the tool but modified to include the specified metadata in tool call requests.
/// </summary>
/// <param name="meta">
/// The metadata to include in tool call requests. This will be serialized as the <c>_meta</c> field
/// in the JSON-RPC request parameters.
/// </param>
/// <remarks>
/// <para>
/// Adding metadata to the tool allows you to pass additional protocol-level information with each tool call.
/// This can be useful for tracing, logging, or passing context information to the server.
/// </para>
/// <para>
/// Only one metadata object can be specified at a time. Calling <see cref="WithMeta"/> again
/// will overwrite any previously specified metadata object. If passed <see langword="null"/>,
/// any previously supplied metadata will be removed.
/// </para>
/// <para>
/// The metadata is passed through to the server as-is, merged with any protocol-level metadata
/// such as progress tokens when <see cref="WithProgress"/> is also used. If a <see cref="RequestOptions"/>
/// is passed to <see cref="CallAsync"/>, the metadata from both <paramref name="meta"/> and its
/// <see cref="RequestOptions"/> will be merged, preferring values from the <see cref="RequestOptions"/> in
/// case of conflicts.
/// </para>
/// </remarks>
/// <returns>A new instance of <see cref="McpClientTool"/>, configured with the provided metadata.</returns>
public McpClientTool WithMeta(JsonObject? meta) =>
new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, _progress, meta);
}
10 changes: 10 additions & 0 deletions src/ModelContextProtocol.Core/RequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ public RequestOptions()
{
}

/// <summary>Creates a shallow clone of this options instance.</summary>
/// <returns>A shallow clone of this options instance.</returns>
internal RequestOptions Clone() =>
new()
{
JsonSerializerOptions = JsonSerializerOptions,
Meta = Meta,
ProgressToken = ProgressToken,
};

/// <summary>
/// Gets or sets optional metadata to include as the "_meta" property in a request.
/// </summary>
Expand Down
Loading