Propagate tool call exceptions through filters (#844)
* Add McpProtocolException specifically for JSON-RPC errors
This commit is contained in:
parent
2c9558cf21
commit
3255adb4d6
21 changed files with 334 additions and 163 deletions
|
|
@ -207,7 +207,7 @@ McpServerOptions options = new()
|
|||
{
|
||||
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
|
||||
{
|
||||
throw new McpException("Missing required argument 'message'");
|
||||
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(new CallToolResult
|
||||
|
|
@ -216,7 +216,7 @@ McpServerOptions options = new()
|
|||
});
|
||||
}
|
||||
|
||||
throw new McpException($"Unknown tool: '{request.Params?.Name}'");
|
||||
throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidRequest);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ builder.Services
|
|||
{
|
||||
if (ctx.Params?.Level is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_minimumLoggingLevel = ctx.Params.Level;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
|||
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
throw new McpException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
|
||||
throw new McpProtocolException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
|
||||
}
|
||||
|
||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||
|
|
@ -170,7 +170,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
|||
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
throw new McpException("Access forbidden: This resource requires authorization.", McpErrorCode.InvalidRequest);
|
||||
throw new McpProtocolException("Access forbidden: This resource requires authorization.", McpErrorCode.InvalidRequest);
|
||||
}
|
||||
|
||||
return await next(context, cancellationToken);
|
||||
|
|
@ -230,7 +230,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
|||
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
throw new McpException("Access forbidden: This prompt requires authorization.", McpErrorCode.InvalidRequest);
|
||||
throw new McpProtocolException("Access forbidden: This prompt requires authorization.", McpErrorCode.InvalidRequest);
|
||||
}
|
||||
|
||||
return await next(context, cancellationToken);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
|
||||
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
|
||||
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpException"/> may be
|
||||
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
|
||||
/// to be included, a different exception type should be used.
|
||||
/// The <see cref="Exception.Message"/> from a <see cref="McpException"/> may be propagated to the remote
|
||||
/// endpoint; sensitive information should not be included. If sensitive details need to be included,
|
||||
/// a different exception type should be used.
|
||||
///
|
||||
/// This exception type can be thrown by MCP tools or tool call filters to propagate detailed error messages
|
||||
/// from <see cref="Exception.Message"/> when a tool execution fails via a <see cref="CallToolResult"/>.
|
||||
/// For non-tool calls, this exception controls the message propogated via a <see cref="JsonRpcError"/>.
|
||||
///
|
||||
/// <see cref="McpProtocolException"/> is a derived type that can be used to also specify the
|
||||
/// <see cref="McpErrorCode"/> that should be used for the resulting <see cref="JsonRpcError"/>.
|
||||
/// </remarks>
|
||||
public class McpException : Exception
|
||||
{
|
||||
|
|
@ -28,46 +35,13 @@ public class McpException : Exception
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
|
||||
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and
|
||||
/// a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null
|
||||
/// reference if no inner exception is specified.</param>
|
||||
public McpException(string message, Exception? innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message, inner exception, and JSON-RPC error code.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error code associated with this exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
|
||||
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
|
||||
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
|
||||
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
|
||||
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
src/ModelContextProtocol.Core/McpProtocolException.cs
Normal file
73
src/ModelContextProtocol.Core/McpProtocolException.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
|
||||
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
|
||||
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpProtocolException"/> may be
|
||||
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
|
||||
/// to be included, a different exception type should be used.
|
||||
/// </remarks>
|
||||
public sealed class McpProtocolException : McpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpProtocolException"/> class.
|
||||
/// </summary>
|
||||
public McpProtocolException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public McpProtocolException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||
public McpProtocolException(string message, Exception? innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message and JSON-RPC error code.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||
public McpProtocolException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message, inner exception, and JSON-RPC error code.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||
public McpProtocolException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error code associated with this exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
|
||||
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
|
||||
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
|
||||
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
|
||||
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
|
||||
}
|
||||
|
|
@ -181,11 +181,17 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
|
|||
{
|
||||
LogRequestHandlerException(EndpointName, request.Method, ex);
|
||||
|
||||
JsonRpcErrorDetail detail = ex is McpException mcpe ?
|
||||
JsonRpcErrorDetail detail = ex is McpProtocolException mcpProtocolException ?
|
||||
new()
|
||||
{
|
||||
Code = (int)mcpe.ErrorCode,
|
||||
Message = mcpe.Message,
|
||||
Code = (int)mcpProtocolException.ErrorCode,
|
||||
Message = mcpProtocolException.Message,
|
||||
} : ex is McpException mcpException ?
|
||||
new()
|
||||
{
|
||||
|
||||
Code = (int)McpErrorCode.InternalError,
|
||||
Message = mcpException.Message,
|
||||
} :
|
||||
new()
|
||||
{
|
||||
|
|
@ -336,7 +342,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
|
|||
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
|
||||
{
|
||||
LogNoHandlerFoundForRequest(EndpointName, request.Method);
|
||||
throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
|
||||
throw new McpProtocolException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
|
||||
}
|
||||
|
||||
LogRequestHandlerCalled(EndpointName, request.Method);
|
||||
|
|
@ -446,7 +452,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
|
|||
if (response is JsonRpcError error)
|
||||
{
|
||||
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
|
||||
throw new McpException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
|
||||
throw new McpProtocolException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
|
||||
}
|
||||
|
||||
if (response is JsonRpcResponse success)
|
||||
|
|
@ -640,7 +646,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
|
|||
}
|
||||
|
||||
int? intErrorCode =
|
||||
(int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
|
||||
(int?)((e as McpProtocolException)?.ErrorCode) is int errorCode ? errorCode :
|
||||
e is JsonException ? (int)McpErrorCode.ParseError :
|
||||
null;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ internal static class Program
|
|||
{
|
||||
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
|
|
@ -190,7 +190,7 @@ internal static class Program
|
|||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
||||
cancellationToken);
|
||||
|
|
@ -209,7 +209,7 @@ internal static class Program
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ internal static class Program
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
|
|
@ -305,7 +305,7 @@ internal static class Program
|
|||
{
|
||||
if (request.Params?.Level is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_minimumLoggingLevel = request.Params.Level;
|
||||
|
|
@ -387,7 +387,7 @@ internal static class Program
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ internal static class Program
|
|||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
|
|
@ -417,7 +417,7 @@ internal static class Program
|
|||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new ReadResourceResult
|
||||
|
|
@ -434,7 +434,7 @@ internal static class Program
|
|||
}
|
||||
|
||||
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
||||
?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
?? throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
|
|
@ -446,12 +446,12 @@ internal static class Program
|
|||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_subscribedResources.TryAdd(request.Params.Uri, true);
|
||||
|
|
@ -463,12 +463,12 @@ internal static class Program
|
|||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_subscribedResources.TryRemove(request.Params.Uri, out _);
|
||||
|
|
@ -509,7 +509,7 @@ internal static class Program
|
|||
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||
|
||||
default:
|
||||
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,13 +158,13 @@ public class Program
|
|||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (request.Params.Name == "echo")
|
||||
{
|
||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
|
|
@ -184,7 +184,7 @@ public class Program
|
|||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||
cancellationToken);
|
||||
|
|
@ -196,7 +196,7 @@ public class Program
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
},
|
||||
ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||
|
|
@ -226,7 +226,7 @@ public class Program
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ public class Program
|
|||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
|
|
@ -256,7 +256,7 @@ public class Program
|
|||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new ReadResourceResult
|
||||
|
|
@ -273,7 +273,7 @@ public class Program
|
|||
}
|
||||
|
||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||
throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
|
|
@ -317,7 +317,7 @@ public class Program
|
|||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
List<PromptMessage> messages = new();
|
||||
if (request.Params.Name == "simple_prompt")
|
||||
|
|
@ -354,7 +354,7 @@ public class Program
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||
throw new McpProtocolException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
await Assert.ThrowsAsync<McpException>(async () =>
|
||||
await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class DiagnosticTests
|
|||
await RunConnected(async (client, server) =>
|
||||
{
|
||||
await client.CallToolAsync("Throw", cancellationToken: TestContext.Current.CancellationToken);
|
||||
await Assert.ThrowsAsync<McpException>(async () => await client.CallToolAsync("does-not-exist", cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<McpProtocolException>(async () => await client.CallToolAsync("does-not-exist", cancellationToken: TestContext.Current.CancellationToken));
|
||||
}, new List<string>());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
},
|
||||
});
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(async() =>
|
||||
var ex = await Assert.ThrowsAsync<McpProtocolException>(async() =>
|
||||
await client.CallToolAsync("TestElicitationUnsupportedType", cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains(typeof(UnsupportedForm.Nested).FullName!, ex.Message);
|
||||
|
|
@ -270,7 +270,7 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
}
|
||||
});
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var ex = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.CallToolAsync("TestElicitationNullablePropertyForm", cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
}
|
||||
});
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
var ex = await Assert.ThrowsAsync<McpProtocolException>(async () =>
|
||||
await client.CallToolAsync("TestElicitationNonObjectGenericType", cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains(typeof(string).FullName!, ex.Message);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue