Skip to content

Commit 2e941c5

Browse files
author
Tarek Mahmoud Sayed
committed
Clarify error when mixing call-tool filter styles
Improve the InvalidOperationException thrown when CallToolFilters and CallToolWithAlternateFilters are both configured, naming the common indirect cause (combining AddAuthorizationFilters() with WithTasks()) and the current limitation. Add filter property doc notes and tests.
1 parent d3b7fa1 commit 2e941c5

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/ModelContextProtocol.Core/Server/McpRequestFilters.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public IList<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListTool
4343
/// </para>
4444
/// <para>
4545
/// Cannot be used together with <see cref="CallToolWithAlternateFilters"/>. If both are non-empty at configuration time,
46-
/// an <see cref="InvalidOperationException"/> will be thrown.
46+
/// an <see cref="InvalidOperationException"/> will be thrown. This can happen indirectly when combining features that
47+
/// register different tool-call filter styles, such as authorization filters (which use this collection) and the
48+
/// tasks extension (which uses <see cref="CallToolWithAlternateFilters"/>).
4749
/// </para>
4850
/// </remarks>
4951
public IList<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters
@@ -68,7 +70,9 @@ public IList<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFi
6870
/// </para>
6971
/// <para>
7072
/// Cannot be used together with <see cref="CallToolFilters"/>. If both are non-empty at configuration time,
71-
/// an <see cref="InvalidOperationException"/> will be thrown.
73+
/// an <see cref="InvalidOperationException"/> will be thrown. This can happen indirectly when combining features that
74+
/// register different tool-call filter styles, such as the tasks extension (which uses this collection) and
75+
/// authorization filters (which use <see cref="CallToolFilters"/>).
7276
/// </para>
7377
/// </remarks>
7478
public IList<McpRequestFilter<CallToolRequestParams, ResultOrAlternate<CallToolResult>>> CallToolWithAlternateFilters

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,12 @@ private void ConfigureTools(McpServerOptions options)
10831083
{
10841084
throw new InvalidOperationException(
10851085
$"Cannot mix non-alternate ({nameof(McpServerHandlers.CallToolHandler)}/{nameof(McpRequestFilters.CallToolFilters)}) " +
1086-
$"with alternate-based ({nameof(McpServerHandlers.CallToolWithAlternateHandler)}/{nameof(McpRequestFilters.CallToolWithAlternateFilters)}). Use one style or the other.");
1086+
$"with alternate-based ({nameof(McpServerHandlers.CallToolWithAlternateHandler)}/{nameof(McpRequestFilters.CallToolWithAlternateFilters)}) tool-call filters or handlers. " +
1087+
$"These two styles cannot currently be composed on the same server. " +
1088+
$"This most commonly happens when combining features that register different tool-call filter styles, " +
1089+
$"for example AddAuthorizationFilters() (which registers a {nameof(McpRequestFilters.CallToolFilters)} filter) together with " +
1090+
$"WithTasks() (which registers a {nameof(McpRequestFilters.CallToolWithAlternateFilters)} filter). " +
1091+
$"Configure only one style, or avoid combining features that require different styles.");
10871092
}
10881093

10891094
// Handle tools provided via DI by augmenting the list handler.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using ModelContextProtocol.Protocol;
2+
using ModelContextProtocol.Server;
3+
using ModelContextProtocol.Tests.Utils;
4+
5+
namespace ModelContextProtocol.Tests.Server;
6+
7+
/// <summary>
8+
/// Verifies that combining the non-alternate <see cref="McpRequestFilters.CallToolFilters"/> with the
9+
/// alternate <see cref="McpRequestFilters.CallToolWithAlternateFilters"/> fails at configuration time
10+
/// with an actionable message.
11+
/// </summary>
12+
public class CallToolFilterMixingTests(ITestOutputHelper testOutputHelper) : LoggedTest(testOutputHelper)
13+
{
14+
private static McpRequestFilter<CallToolRequestParams, CallToolResult> PassThroughCallToolFilter =>
15+
next => next;
16+
17+
private static McpRequestFilter<CallToolRequestParams, ResultOrAlternate<CallToolResult>> PassThroughAlternateFilter =>
18+
next => next;
19+
20+
[Fact]
21+
public async Task MixingCallToolFilters_WithAlternateFilters_ThrowsActionableError()
22+
{
23+
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
24+
var options = new McpServerOptions { Capabilities = new() { Tools = new() } };
25+
options.Filters.Request.CallToolFilters.Add(PassThroughCallToolFilter);
26+
options.Filters.Request.CallToolWithAlternateFilters.Add(PassThroughAlternateFilter);
27+
28+
var ex = Assert.Throws<InvalidOperationException>(
29+
() => McpServer.Create(transport, options, LoggerFactory));
30+
31+
Assert.Contains(nameof(McpRequestFilters.CallToolFilters), ex.Message);
32+
Assert.Contains(nameof(McpRequestFilters.CallToolWithAlternateFilters), ex.Message);
33+
Assert.Contains("AddAuthorizationFilters", ex.Message);
34+
Assert.Contains("WithTasks", ex.Message);
35+
}
36+
37+
[Fact]
38+
public async Task CallToolFiltersAlone_Succeeds()
39+
{
40+
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
41+
var options = new McpServerOptions { Capabilities = new() { Tools = new() } };
42+
options.Filters.Request.CallToolFilters.Add(PassThroughCallToolFilter);
43+
44+
await using var server = McpServer.Create(transport, options, LoggerFactory);
45+
Assert.NotNull(server);
46+
}
47+
48+
[Fact]
49+
public async Task AlternateFiltersAlone_Succeeds()
50+
{
51+
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
52+
var options = new McpServerOptions { Capabilities = new() { Tools = new() } };
53+
options.Filters.Request.CallToolWithAlternateFilters.Add(PassThroughAlternateFilter);
54+
55+
await using var server = McpServer.Create(transport, options, LoggerFactory);
56+
Assert.NotNull(server);
57+
}
58+
}

0 commit comments

Comments
 (0)