Adds McpClientOptions.Filters.Request.CallToolFilters, mirroring the
server-side filter pipeline, so hosts can inspect, rewrite, or block
tools/call requests (for example, based on tool annotations) before they
reach the server. Every CallToolAsync overload, McpClientTool.CallAsync,
and McpClientTool invocations through an IChatClient route through a
single private protected CallToolCoreAsync seam, so no path bypasses the
filters. Filters receive the tool definition from the existing tool cache
(populated by ListToolsAsync/AddKnownTools), or null when unknown.
The new APIs are marked experimental (MCPEXP002).
Fixes modelcontextprotocol#1453
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p29dMDsLnn6KDFmsz2PGr
Closes #1453
Motivation
Hosts that let a model choose tools need a single place to apply policy before a tools/call leaves the client, for example requiring confirmation for tools annotated destructiveHint: true. Today there is no client-side equivalent of the server's CallToolFilters, so hosts have to wrap every McpClientTool by hand, and any path they miss (such as a direct client.CallToolAsync(...)) bypasses the policy.
Change
This follows the design @elzouhery laid out in the issue thread. It adds client request filters that mirror the server-side filter shape (McpServerOptions.Filters.Request.CallToolFilters):
Design notes
Tests
McpClientCallToolFilterTests has 8 tests against a real in-memory McpServer/McpClient pair:
To check that the tests can fail, I built once with the pipeline never installed: 7 of the 8 tests failed (the setter test doesn't depend on the pipeline).
Full local run on Windows (.NET SDK 10.0.302): dotnet build of the solution is clean (warnings are errors), and the new tests pass on all four target frameworks (net10.0, net9.0, net8.0, net472). The results of the full suites:
🤖 Generated with Claude Code
https://claude.ai/code/session_011p29dMDsLnn6KDFmsz2PGr