Propagate tool call exceptions through filters (#844)
* Add McpProtocolException specifically for JSON-RPC errors
This commit is contained in:
parent
2c9558cf21
commit
3255adb4d6
2 changed files with 334 additions and 163 deletions
|
|
@ -291,7 +291,7 @@ public abstract partial class McpServer : McpSession, IMcpServer
|
|||
/// <param name="type">The type of the schema being built.</param>
|
||||
/// <param name="serializerOptions">The serializer options to use.</param>
|
||||
/// <returns>The built request schema.</returns>
|
||||
/// <exception cref="McpException"></exception>
|
||||
/// <exception cref="McpProtocolException"></exception>
|
||||
private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, JsonSerializerOptions serializerOptions)
|
||||
{
|
||||
var schema = new ElicitRequestParams.RequestSchema();
|
||||
|
|
@ -301,7 +301,7 @@ public abstract partial class McpServer : McpSession, IMcpServer
|
|||
|
||||
if (typeInfo.Kind != JsonTypeInfoKind.Object)
|
||||
{
|
||||
throw new McpException($"Type '{type.FullName}' is not supported for elicitation requests.");
|
||||
throw new McpProtocolException($"Type '{type.FullName}' is not supported for elicitation requests.");
|
||||
}
|
||||
|
||||
foreach (JsonPropertyInfo pi in typeInfo.Properties)
|
||||
|
|
@ -319,33 +319,33 @@ public abstract partial class McpServer : McpSession, IMcpServer
|
|||
/// <param name="type">The type to create the schema for.</param>
|
||||
/// <param name="serializerOptions">The serializer options to use.</param>
|
||||
/// <returns>The created primitive schema definition.</returns>
|
||||
/// <exception cref="McpException">Thrown when the type is not supported.</exception>
|
||||
/// <exception cref="McpProtocolException">Thrown when the type is not supported.</exception>
|
||||
private static ElicitRequestParams.PrimitiveSchemaDefinition CreatePrimitiveSchema(Type type, JsonSerializerOptions serializerOptions)
|
||||
{
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests. Nullable types are not supported.");
|
||||
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests. Nullable types are not supported.");
|
||||
}
|
||||
|
||||
var typeInfo = serializerOptions.GetTypeInfo(type);
|
||||
|
||||
if (typeInfo.Kind != JsonTypeInfoKind.None)
|
||||
{
|
||||
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
|
||||
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
|
||||
}
|
||||
|
||||
var jsonElement = AIJsonUtilities.CreateJsonSchema(type, serializerOptions: serializerOptions);
|
||||
|
||||
if (!TryValidateElicitationPrimitiveSchema(jsonElement, type, out var error))
|
||||
{
|
||||
throw new McpException(error);
|
||||
throw new McpProtocolException(error);
|
||||
}
|
||||
|
||||
var primitiveSchemaDefinition =
|
||||
jsonElement.Deserialize(McpJsonUtilities.JsonContext.Default.PrimitiveSchemaDefinition);
|
||||
|
||||
if (primitiveSchemaDefinition is null)
|
||||
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
|
||||
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
|
||||
|
||||
return primitiveSchemaDefinition;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
|
||||
listResourcesHandler ??= (static async (_, __) => new ListResourcesResult());
|
||||
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
|
||||
readResourceHandler ??= (static async (request, _) => throw new McpException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
|
||||
readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
|
||||
subscribeHandler ??= (static async (_, __) => new EmptyResult());
|
||||
unsubscribeHandler ??= (static async (_, __) => new EmptyResult());
|
||||
var listChanged = resourcesCapability?.ListChanged;
|
||||
|
|
@ -452,7 +452,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
ServerCapabilities.Prompts = new();
|
||||
|
||||
listPromptsHandler ??= (static async (_, __) => new ListPromptsResult());
|
||||
getPromptHandler ??= (static async (request, _) => throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
|
||||
getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
|
||||
var listChanged = promptsCapability?.ListChanged;
|
||||
|
||||
// Handle tools provided via DI by augmenting the handlers to incorporate them.
|
||||
|
|
@ -540,7 +540,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
ServerCapabilities.Tools = new();
|
||||
|
||||
listToolsHandler ??= (static async (_, __) => new ListToolsResult());
|
||||
callToolHandler ??= (static async (request, _) => throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
|
||||
callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
|
||||
var listChanged = toolsCapability?.ListChanged;
|
||||
|
||||
// Handle tools provided via DI by augmenting the handlers to incorporate them.
|
||||
|
|
@ -580,7 +580,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
|
||||
listToolsHandler = BuildFilterPipeline(listToolsHandler, options.Filters.ListToolsFilters);
|
||||
callToolHandler = BuildFilterPipeline(callToolHandler, options.Filters.CallToolFilters, handler =>
|
||||
(request, cancellationToken) =>
|
||||
async (request, cancellationToken) =>
|
||||
{
|
||||
// Initial handler that sets MatchedPrimitive
|
||||
if (request.Params?.Name is { } toolName && tools is not null &&
|
||||
|
|
@ -589,37 +589,23 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
request.MatchedPrimitive = tool;
|
||||
}
|
||||
|
||||
return handler(request, cancellationToken);
|
||||
}, handler =>
|
||||
async (request, cancellationToken) =>
|
||||
{
|
||||
// Final handler that provides exception handling only for tool execution
|
||||
// Only wrap tool execution in try-catch, not tool resolution
|
||||
if (request.MatchedPrimitive is McpServerTool)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
return await handler(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
ToolCallError(request.Params?.Name ?? string.Empty, e);
|
||||
|
||||
string errorMessage = e is McpException ?
|
||||
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
|
||||
$"An error occurred invoking '{request.Params?.Name}'.";
|
||||
|
||||
return new()
|
||||
{
|
||||
IsError = true,
|
||||
Content = [new TextContentBlock { Text = errorMessage }],
|
||||
};
|
||||
}
|
||||
return await handler(request, cancellationToken);
|
||||
}
|
||||
else
|
||||
catch (Exception e) when (e is not OperationCanceledException and not McpProtocolException)
|
||||
{
|
||||
// For unmatched tools, let exceptions bubble up as protocol errors
|
||||
return await handler(request, cancellationToken).ConfigureAwait(false);
|
||||
ToolCallError(request.Params?.Name ?? string.Empty, e);
|
||||
|
||||
string errorMessage = e is McpException ?
|
||||
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
|
||||
$"An error occurred invoking '{request.Params?.Name}'.";
|
||||
|
||||
return new()
|
||||
{
|
||||
IsError = true,
|
||||
Content = [new TextContentBlock { Text = errorMessage }],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -735,16 +721,10 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
private static McpRequestHandler<TParams, TResult> BuildFilterPipeline<TParams, TResult>(
|
||||
McpRequestHandler<TParams, TResult> baseHandler,
|
||||
List<McpRequestFilter<TParams, TResult>> filters,
|
||||
McpRequestFilter<TParams, TResult>? initialHandler = null,
|
||||
McpRequestFilter<TParams, TResult>? finalHandler = null)
|
||||
McpRequestFilter<TParams, TResult>? initialHandler = null)
|
||||
{
|
||||
var current = baseHandler;
|
||||
|
||||
if (finalHandler is not null)
|
||||
{
|
||||
current = finalHandler(current);
|
||||
}
|
||||
|
||||
for (int i = filters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
current = filters[i](current);
|
||||
|
|
|
|||
Loading…
Reference in a new issue