Propagate tool call exceptions through filters (#844)
* Add McpProtocolException specifically for JSON-RPC errors
This commit is contained in:
parent
2c9558cf21
commit
3255adb4d6
3 changed files with 334 additions and 163 deletions
|
|
@ -36,7 +36,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.CallToolAsync(
|
||||
"authorized_tool",
|
||||
new Dictionary<string, object?> { ["message"] = "test" },
|
||||
|
|
@ -101,7 +101,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.CallToolAsync(
|
||||
"admin_tool",
|
||||
new Dictionary<string, object?> { ["message"] = "test" },
|
||||
|
|
@ -188,7 +188,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.GetPromptAsync(
|
||||
"authorized_prompt",
|
||||
new Dictionary<string, object?> { ["message"] = "test" },
|
||||
|
|
@ -235,7 +235,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ReadResourceAsync(
|
||||
"resource://authorized",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
|
@ -277,7 +277,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithTools<AuthorizationTestTools>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Equal("Request failed (remote): An error occurred.", exception.Message);
|
||||
|
|
@ -289,21 +289,23 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallTool_WithoutAuthFilters_ThrowsInvalidOperationException()
|
||||
public async Task CallTool_WithoutAuthFilters_ReturnsError()
|
||||
{
|
||||
_mockLoggerProvider.LogMessages.Clear();
|
||||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithTools<AuthorizationTestTools>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
await client.CallToolAsync(
|
||||
var toolResult = await client.CallToolAsync(
|
||||
"authorized_tool",
|
||||
new Dictionary<string, object?> { ["message"] = "test" },
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("Request failed (remote): An error occurred.", exception.Message);
|
||||
Assert.True(toolResult.IsError);
|
||||
|
||||
var errorContent = Assert.IsType<TextContentBlock>(Assert.Single(toolResult.Content));
|
||||
Assert.Equal("An error occurred invoking 'authorized_tool'.", errorContent.Text);
|
||||
Assert.Contains(_mockLoggerProvider.LogMessages, log =>
|
||||
log.LogLevel == LogLevel.Warning &&
|
||||
log.LogLevel == LogLevel.Error &&
|
||||
log.Exception is InvalidOperationException &&
|
||||
log.Exception.Message.Contains("Authorization filter was not invoked for tools/call operation") &&
|
||||
log.Exception.Message.Contains("Ensure that AddAuthorizationFilters() is called"));
|
||||
|
|
@ -316,7 +318,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithPrompts<AuthorizationTestPrompts>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Equal("Request failed (remote): An error occurred.", exception.Message);
|
||||
|
|
@ -334,7 +336,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithPrompts<AuthorizationTestPrompts>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.GetPromptAsync(
|
||||
"authorized_prompt",
|
||||
new Dictionary<string, object?> { ["message"] = "test" },
|
||||
|
|
@ -355,7 +357,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithResources<AuthorizationTestResources>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Equal("Request failed (remote): An error occurred.", exception.Message);
|
||||
|
|
@ -373,7 +375,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithResources<AuthorizationTestResources>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ReadResourceAsync(
|
||||
"resource://authorized",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
|
@ -393,7 +395,7 @@ public class AuthorizeAttributeTests(ITestOutputHelper testOutputHelper) : Kestr
|
|||
await using var app = await StartServerWithoutAuthFilters(builder => builder.WithResources<AuthorizationTestResources>());
|
||||
var client = await ConnectAsync();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Equal("Request failed (remote): An error occurred.", exception.Message);
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
|
||||
// act
|
||||
await using var client = await GetClientAsync();
|
||||
await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<McpProtocolException>(async () => await client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using ModelContextProtocol.Client;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ModelContextProtocol.AspNetCore.Tests;
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ public class MapMcpStreamableHttpTests(ITestOutputHelper outputHelper) : MapMcpT
|
|||
[Fact]
|
||||
public async Task StreamableHttpClient_SendsMcpProtocolVersionHeader_AfterInitialization()
|
||||
{
|
||||
var protocolVersionHeaderValues = new List<string?>();
|
||||
var protocolVersionHeaderValues = new ConcurrentQueue<string?>();
|
||||
|
||||
Builder.Services.AddMcpServer().WithHttpTransport(ConfigureStateless).WithTools<EchoHttpContextUserTools>();
|
||||
|
||||
|
|
@ -160,7 +161,7 @@ public class MapMcpStreamableHttpTests(ITestOutputHelper outputHelper) : MapMcpT
|
|||
{
|
||||
if (!StringValues.IsNullOrEmpty(context.Request.Headers["mcp-protocol-version"]))
|
||||
{
|
||||
protocolVersionHeaderValues.Add(context.Request.Headers["mcp-protocol-version"]);
|
||||
protocolVersionHeaderValues.Enqueue(context.Request.Headers["mcp-protocol-version"]);
|
||||
}
|
||||
|
||||
await next(context);
|
||||
|
|
|
|||
Loading…
Reference in a new issue