Propagate tool call exceptions through filters (#844)
* Add McpProtocolException specifically for JSON-RPC errors
This commit is contained in:
parent
2c9558cf21
commit
3255adb4d6
4 changed files with 334 additions and 163 deletions
|
|
@ -58,7 +58,18 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
|
|||
var logger = GetLogger(request.Services, "CallToolFilter");
|
||||
var primitiveId = request.MatchedPrimitive?.Id ?? "unknown";
|
||||
logger.LogInformation($"CallToolFilter executed for tool: {primitiveId}");
|
||||
return await next(request, cancellationToken);
|
||||
try
|
||||
{
|
||||
return await next(request, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Type = "text", Text = $"Error from filter: {ex.Message}" }],
|
||||
IsError = true
|
||||
};
|
||||
}
|
||||
})
|
||||
.AddListPromptsFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
|
|
@ -162,6 +173,20 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
|
|||
Assert.Equal("CallToolFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddCallToolFilter_Catches_Exception_From_Tool()
|
||||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var result = await client.CallToolAsync("throwing_tool_method", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(result.IsError);
|
||||
Assert.NotNull(result.Content);
|
||||
var textContent = Assert.Single(result.Content);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(textContent);
|
||||
Assert.Equal("Error from filter: This tool always throws an exception", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListPromptsFilter_Logs_When_ListPrompts_Called()
|
||||
{
|
||||
|
|
@ -286,6 +311,12 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
|
|||
{
|
||||
return "test result";
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
public static string ThrowingToolMethod()
|
||||
{
|
||||
throw new InvalidOperationException("This tool always throws an exception");
|
||||
}
|
||||
}
|
||||
|
||||
[McpServerPromptType]
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
};
|
||||
|
||||
default:
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithGetPromptHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -78,7 +78,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
};
|
||||
|
||||
default:
|
||||
throw new McpException($"Unknown prompt '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown prompt '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithPrompts<SimplePrompts>();
|
||||
|
|
@ -190,7 +190,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync(
|
||||
await Assert.ThrowsAsync<McpProtocolException>(async () => await client.GetPromptAsync(
|
||||
nameof(SimplePrompts.ThrowsException),
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
|
@ -200,7 +200,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var e = await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync(
|
||||
var e = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.GetPromptAsync(
|
||||
"NotRegisteredPrompt",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var e = await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync(
|
||||
var e = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.GetPromptAsync(
|
||||
"returns_chat_messages",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
};
|
||||
|
||||
default:
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithListResourceTemplatesHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -91,7 +91,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
}],
|
||||
};
|
||||
default:
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithReadResourceHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -109,7 +109,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
};
|
||||
}
|
||||
|
||||
throw new McpException($"Resource not found: {request.Params?.Uri}");
|
||||
throw new McpProtocolException($"Resource not found: {request.Params?.Uri}");
|
||||
})
|
||||
.WithResources<SimpleResources>();
|
||||
}
|
||||
|
|
@ -235,7 +235,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync(
|
||||
await Assert.ThrowsAsync<McpProtocolException>(async () => await client.ReadResourceAsync(
|
||||
$"resource://mcp/{nameof(SimpleResources.ThrowsException)}",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
|
@ -245,7 +245,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var e = await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync(
|
||||
var e = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.ReadResourceAsync(
|
||||
"test:///NotRegisteredResource",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
};
|
||||
|
||||
default:
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithCallToolHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -106,7 +106,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
};
|
||||
|
||||
default:
|
||||
throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options);
|
||||
|
|
@ -388,7 +388,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var e = await Assert.ThrowsAsync<McpException>(async () => await client.CallToolAsync(
|
||||
var e = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.CallToolAsync(
|
||||
"NotRegisteredTool",
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue