Add ResourceNotFound error code (-32002) (#1062)
This commit is contained in:
parent
4808ca0324
commit
d76bc16bd1
8 changed files with 36 additions and 26 deletions
|
|
@ -6,12 +6,29 @@ namespace ModelContextProtocol;
|
||||||
public enum McpErrorCode
|
public enum McpErrorCode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the JSON received could not be parsed.
|
/// Indicates that the requested resource could not be found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This error occurs when the input contains malformed JSON or incorrect syntax.
|
/// This error should be used when a resource URI does not match any available resource on the server.
|
||||||
|
/// It allows clients to distinguish between missing resources and other types of errors.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
ParseError = -32700,
|
ResourceNotFound = -32002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that URL-mode elicitation is required to complete the requested operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This error is returned when a server operation requires additional user input through URL-mode elicitation
|
||||||
|
/// before it can proceed. The error data must include the `data.elicitations` payload describing the pending
|
||||||
|
/// elicitation(s) for the client to present to the user.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Common scenarios include OAuth authorization and other out-of-band flows that cannot be completed inside
|
||||||
|
/// the MCP client.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
UrlElicitationRequired = -32042,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the JSON payload does not conform to the expected Request object structure.
|
/// Indicates that the JSON payload does not conform to the expected Request object structure.
|
||||||
|
|
@ -58,18 +75,10 @@ public enum McpErrorCode
|
||||||
InternalError = -32603,
|
InternalError = -32603,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that URL-mode elicitation is required to complete the requested operation.
|
/// Indicates that the JSON received could not be parsed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// This error occurs when the input contains malformed JSON or incorrect syntax.
|
||||||
/// This error is returned when a server operation requires additional user input through URL-mode elicitation
|
|
||||||
/// before it can proceed. The error data must include the `data.elicitations` payload describing the pending
|
|
||||||
/// elicitation(s) for the client to present to the user.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Common scenarios include OAuth authorization and other out-of-band flows that cannot be completed inside
|
|
||||||
/// the MCP client.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
UrlElicitationRequired = -32042,
|
ParseError = -32700,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
|
|
||||||
listResourcesHandler ??= (static async (_, __) => new ListResourcesResult());
|
listResourcesHandler ??= (static async (_, __) => new ListResourcesResult());
|
||||||
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
|
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
|
||||||
readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
|
readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.ResourceNotFound));
|
||||||
subscribeHandler ??= (static async (_, __) => new EmptyResult());
|
subscribeHandler ??= (static async (_, __) => new EmptyResult());
|
||||||
unsubscribeHandler ??= (static async (_, __) => new EmptyResult());
|
unsubscribeHandler ??= (static async (_, __) => new EmptyResult());
|
||||||
var listChanged = resourcesCapability?.ListChanged;
|
var listChanged = resourcesCapability?.ListChanged;
|
||||||
|
|
|
||||||
|
|
@ -434,7 +434,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
||||||
?? throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
?? throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound);
|
||||||
|
|
||||||
return new ReadResourceResult
|
return new ReadResourceResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ public class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||||
throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound);
|
||||||
|
|
||||||
return new ReadResourceResult
|
return new ReadResourceResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new McpProtocolException($"Resource not found: {request.Params?.Uri}");
|
throw new McpProtocolException($"Resource not found: {request.Params?.Uri}", McpErrorCode.ResourceNotFound);
|
||||||
})
|
})
|
||||||
.WithResources<SimpleResources>();
|
.WithResources<SimpleResources>();
|
||||||
}
|
}
|
||||||
|
|
@ -249,6 +249,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
cancellationToken: TestContext.Current.CancellationToken));
|
cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
Assert.Contains("Resource not found", e.Message);
|
Assert.Contains("Resource not found", e.Message);
|
||||||
|
Assert.Equal(McpErrorCode.ResourceNotFound, e.ErrorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public sealed class McpServerResourceRoutingTests(ITestOutputHelper testOutputHe
|
||||||
Assert.Equal("params: a, b, c", ((TextResourceContents)paramsResult.Contents[0]).Text);
|
Assert.Equal("params: a, b, c", ((TextResourceContents)paramsResult.Contents[0]).Text);
|
||||||
|
|
||||||
var mcpEx = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.ReadResourceAsync("test://params{?a1,a2,a3}", null, TestContext.Current.CancellationToken));
|
var mcpEx = await Assert.ThrowsAsync<McpProtocolException>(async () => await client.ReadResourceAsync("test://params{?a1,a2,a3}", null, TestContext.Current.CancellationToken));
|
||||||
Assert.Equal(McpErrorCode.InvalidParams, mcpEx.ErrorCode);
|
Assert.Equal(McpErrorCode.ResourceNotFound, mcpEx.ErrorCode);
|
||||||
Assert.Equal("Request failed (remote): Unknown resource URI: 'test://params{?a1,a2,a3}'", mcpEx.Message);
|
Assert.Equal("Request failed (remote): Unknown resource URI: 'test://params{?a1,a2,a3}'", mcpEx.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
switch (toolName)
|
switch (toolName)
|
||||||
{
|
{
|
||||||
case "throw_with_serializable_data":
|
case "throw_with_serializable_data":
|
||||||
throw new McpProtocolException("Resource not found", (McpErrorCode)(-32002))
|
throw new McpProtocolException("Resource not found", McpErrorCode.ResourceNotFound)
|
||||||
{
|
{
|
||||||
Data =
|
Data =
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +43,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
};
|
};
|
||||||
|
|
||||||
case "throw_with_nonserializable_data":
|
case "throw_with_nonserializable_data":
|
||||||
throw new McpProtocolException("Resource not found", (McpErrorCode)(-32002))
|
throw new McpProtocolException("Resource not found", McpErrorCode.ResourceNotFound)
|
||||||
{
|
{
|
||||||
Data =
|
Data =
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +55,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
};
|
};
|
||||||
|
|
||||||
case "throw_with_only_nonserializable_data":
|
case "throw_with_only_nonserializable_data":
|
||||||
throw new McpProtocolException("Resource not found", (McpErrorCode)(-32002))
|
throw new McpProtocolException("Resource not found", McpErrorCode.ResourceNotFound)
|
||||||
{
|
{
|
||||||
Data =
|
Data =
|
||||||
{
|
{
|
||||||
|
|
@ -79,7 +79,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
await client.CallToolAsync("throw_with_serializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
await client.CallToolAsync("throw_with_serializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
||||||
Assert.Equal((McpErrorCode)(-32002), exception.ErrorCode);
|
Assert.Equal(McpErrorCode.ResourceNotFound, exception.ErrorCode);
|
||||||
|
|
||||||
// Verify the data was propagated to the exception
|
// Verify the data was propagated to the exception
|
||||||
// The Data collection should contain the expected keys
|
// The Data collection should contain the expected keys
|
||||||
|
|
@ -113,7 +113,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
await client.CallToolAsync("throw_with_nonserializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
await client.CallToolAsync("throw_with_nonserializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
||||||
Assert.Equal((McpErrorCode)(-32002), exception.ErrorCode);
|
Assert.Equal(McpErrorCode.ResourceNotFound, exception.ErrorCode);
|
||||||
|
|
||||||
// Verify that only the serializable data was propagated (non-serializable was filtered out)
|
// Verify that only the serializable data was propagated (non-serializable was filtered out)
|
||||||
var hasUri = false;
|
var hasUri = false;
|
||||||
|
|
@ -142,7 +142,7 @@ public class McpProtocolExceptionDataTests : ClientServerTestBase
|
||||||
await client.CallToolAsync("throw_with_only_nonserializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
await client.CallToolAsync("throw_with_only_nonserializable_data", cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
|
||||||
Assert.Equal((McpErrorCode)(-32002), exception.ErrorCode);
|
Assert.Equal(McpErrorCode.ResourceNotFound, exception.ErrorCode);
|
||||||
|
|
||||||
// When all data is non-serializable, the Data collection should be empty
|
// When all data is non-serializable, the Data collection should be empty
|
||||||
// (the server's ConvertExceptionData returns null when no serializable data exists)
|
// (the server's ConvertExceptionData returns null when no serializable data exists)
|
||||||
|
|
|
||||||
|
|
@ -681,7 +681,7 @@ public class McpServerTests : LoggedTest
|
||||||
public async Task Can_Handle_Call_Tool_Requests_With_McpProtocolException_And_Data()
|
public async Task Can_Handle_Call_Tool_Requests_With_McpProtocolException_And_Data()
|
||||||
{
|
{
|
||||||
const string ErrorMessage = "Resource not found";
|
const string ErrorMessage = "Resource not found";
|
||||||
const McpErrorCode ErrorCode = (McpErrorCode)(-32002);
|
const McpErrorCode ErrorCode = McpErrorCode.ResourceNotFound;
|
||||||
const string ResourceUri = "file:///path/to/resource";
|
const string ResourceUri = "file:///path/to/resource";
|
||||||
|
|
||||||
await using var transport = new TestServerTransport();
|
await using var transport = new TestServerTransport();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue