diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
index 60a9c3a6..02716ca6 100644
--- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
@@ -789,6 +789,101 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri,
return UnsubscribeFromResourceAsync(client, uri.ToString(), cancellationToken);
}
+ ///
+ /// Invokes a tool on the server.
+ ///
+ /// The client instance used to communicate with the MCP server.
+ /// The name of the tool to call on the server.
+ /// A containing arguments to pass to the tool. Each property represents a tool parameter name,
+ /// and its associated value represents the argument value as a .
+ ///
+ ///
+ /// An optional to have progress notifications reported to it. Setting this to a non-
+ /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation
+ /// routed to this instance.
+ ///
+ /// The to monitor for cancellation requests. The default is .
+ ///
+ /// A task containing the from the tool execution. The response includes
+ /// the tool's output content, which may be structured data, text, or an error message.
+ ///
+ /// is .
+ /// is .
+ /// does not represent a JSON object.
+ /// The server could not find the requested tool, or the server encountered an error while processing the request.
+ ///
+ ///
+ /// // Call a tool with JsonElement arguments
+ /// var arguments = JsonDocument.Parse("""{"message": "Hello MCP!"}""").RootElement;
+ /// var result = await client.CallToolAsync("echo", arguments);
+ ///
+ ///
+ public static ValueTask CallToolAsync(
+ this IMcpClient client,
+ string toolName,
+ JsonElement arguments,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(client);
+ Throw.IfNull(toolName);
+
+ if (arguments.ValueKind != JsonValueKind.Object)
+ {
+ throw new ArgumentException($"The arguments parameter must represent a JSON object, but was {arguments.ValueKind}.", nameof(arguments));
+ }
+
+ if (progress is not null)
+ {
+ return SendRequestWithProgressAsync(client, toolName, arguments, progress, cancellationToken);
+ }
+
+ return client.SendRequestAsync(
+ RequestMethods.ToolsCall,
+ new()
+ {
+ Name = toolName,
+ Arguments = arguments.EnumerateObject().ToDictionary(prop => prop.Name, prop => prop.Value) ?? []
+ },
+ McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
+ McpJsonUtilities.JsonContext.Default.CallToolResult,
+ cancellationToken: cancellationToken);
+
+ static async ValueTask SendRequestWithProgressAsync(
+ IMcpClient client,
+ string toolName,
+ JsonElement arguments,
+ IProgress progress,
+ CancellationToken cancellationToken)
+ {
+ ProgressToken progressToken = new(Guid.NewGuid().ToString("N"));
+
+ await using var _ = client.RegisterNotificationHandler(NotificationMethods.ProgressNotification,
+ (notification, cancellationToken) =>
+ {
+ if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams) is { } pn &&
+ pn.ProgressToken == progressToken)
+ {
+ progress.Report(pn.Progress);
+ }
+
+ return default;
+ }).ConfigureAwait(false);
+
+ return await client.SendRequestAsync(
+ RequestMethods.ToolsCall,
+ new()
+ {
+ Name = toolName,
+ Arguments = arguments.EnumerateObject().ToDictionary(prop => prop.Name, prop => prop.Value) ?? [],
+ ProgressToken = progressToken,
+ },
+ McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
+ McpJsonUtilities.JsonContext.Default.CallToolResult,
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+ }
+
///
/// Invokes a tool on the server.
///
diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
index 3e4361a5..14a305a7 100644
--- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
+++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
@@ -96,6 +96,56 @@ public async Task CallTool_Stdio_EchoServer(string clientId)
Assert.Equal("Echo: Hello MCP!", textContent.Text);
}
+ [Theory]
+ [MemberData(nameof(GetClients))]
+ public async Task CallTool_Stdio_EchoServer_WithJsonElementArguments(string clientId)
+ {
+ // arrange
+ JsonElement arguments = JsonDocument.Parse("""
+ {
+ "message": "Hello MCP with JsonElement!"
+ }
+ """).RootElement;
+
+ // act
+ await using var client = await _fixture.CreateClientAsync(clientId);
+ var result = await client.CallToolAsync(
+ "echo",
+ arguments,
+ cancellationToken: TestContext.Current.CancellationToken
+ );
+
+ // assert
+ Assert.NotNull(result);
+ Assert.Null(result.IsError);
+ var textContent = Assert.Single(result.Content.OfType());
+ Assert.Equal("Echo: Hello MCP with JsonElement!", textContent.Text);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetClients))]
+ public async Task CallTool_Stdio_EchoServer_WithJsonElementArguments_ThrowsForNonObject(string clientId)
+ {
+ // arrange - JsonElement representing a string, not an object
+ JsonElement stringArguments = JsonDocument.Parse("""
+ "Hello MCP!"
+ """).RootElement;
+
+ // act & assert
+ await using var client = await _fixture.CreateClientAsync(clientId);
+ var exception = await Assert.ThrowsAsync(async () =>
+ await client.CallToolAsync(
+ "echo",
+ stringArguments,
+ cancellationToken: TestContext.Current.CancellationToken
+ )
+ );
+
+ Assert.Contains("arguments parameter must represent a JSON object", exception.Message);
+ Assert.Contains("String", exception.Message);
+ Assert.Equal("arguments", exception.ParamName);
+ }
+
[Fact]
public async Task CallTool_Stdio_EchoSessionId_ReturnsEmpty()
{