Clean up (use of) McpException and McpTransportException (#321)
* Clean up (use of) McpException * Replace McpTransportException with InvalidOperationException * Add cancellation test
This commit is contained in:
parent
d65ddce268
commit
7d77be68cf
7 changed files with 231 additions and 219 deletions
|
|
@ -5,6 +5,7 @@ using ModelContextProtocol.Protocol.Transport;
|
|||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using Moq;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
|
||||
|
|
@ -29,6 +30,31 @@ public class McpClientFactoryTests
|
|||
Assert.NotNull(client);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task Cancellation_ThrowsCancellationException(bool preCanceled)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
if (preCanceled)
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
Task t = McpClientFactory.CreateAsync(
|
||||
new StreamClientTransport(new Pipe().Writer.AsStream(), new Pipe().Reader.AsStream()),
|
||||
cancellationToken: cts.Token);
|
||||
Assert.False(t.IsCompleted);
|
||||
|
||||
if (!preCanceled)
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => t);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(NopTransport))]
|
||||
[InlineData(typeof(FailureTransport))]
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
NextCursor = "abc",
|
||||
Prompts = [new()
|
||||
{
|
||||
Name = "FirstCustomPrompt",
|
||||
Description = "First prompt returned by custom handler",
|
||||
}],
|
||||
{
|
||||
Name = "FirstCustomPrompt",
|
||||
Description = "First prompt returned by custom handler",
|
||||
}],
|
||||
};
|
||||
|
||||
case "abc":
|
||||
|
|
@ -42,10 +42,10 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
NextCursor = "def",
|
||||
Prompts = [new()
|
||||
{
|
||||
Name = "SecondCustomPrompt",
|
||||
Description = "Second prompt returned by custom handler",
|
||||
}],
|
||||
{
|
||||
Name = "SecondCustomPrompt",
|
||||
Description = "Second prompt returned by custom handler",
|
||||
}],
|
||||
};
|
||||
|
||||
case "def":
|
||||
|
|
@ -53,14 +53,14 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
NextCursor = null,
|
||||
Prompts = [new()
|
||||
{
|
||||
Name = "FinalCustomPrompt",
|
||||
Description = "Final prompt returned by custom handler",
|
||||
}],
|
||||
{
|
||||
Name = "FinalCustomPrompt",
|
||||
Description = "Final prompt returned by custom handler",
|
||||
}],
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Exception("Unexpected cursor");
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithGetPromptHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -76,7 +76,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
};
|
||||
|
||||
default:
|
||||
throw new Exception($"Unknown prompt '{request.Params?.Name}'");
|
||||
throw new McpException($"Unknown prompt '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithPrompts<SimplePrompts>();
|
||||
|
|
@ -194,7 +194,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
nameof(SimplePrompts.ReturnsChatMessages),
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains("Missing required parameter", e.Message);
|
||||
Assert.Equal(McpErrorCode.InternalError, e.ErrorCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
};
|
||||
|
||||
default:
|
||||
throw new Exception("Unexpected cursor");
|
||||
throw new McpException($"Unexpected cursor: '{cursor}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithCallToolHandler(async (request, cancellationToken) =>
|
||||
|
|
@ -104,7 +104,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
};
|
||||
|
||||
default:
|
||||
throw new Exception($"Unknown tool '{request.Params?.Name}'");
|
||||
throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
})
|
||||
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class DiagnosticTests
|
|||
a.Kind == ActivityKind.Client);
|
||||
|
||||
Assert.Equal(ActivityStatusCode.Error, doesNotExistToolClient.Status);
|
||||
Assert.Equal("-32603", doesNotExistToolClient.Tags.Single(t => t.Key == "rpc.jsonrpc.error_code").Value);
|
||||
Assert.Equal("-32602", doesNotExistToolClient.Tags.Single(t => t.Key == "rpc.jsonrpc.error_code").Value);
|
||||
|
||||
var doesNotExistToolServer = Assert.Single(activities, a =>
|
||||
a.Tags.Any(t => t.Key == "mcp.tool.name" && t.Value == "does-not-exist") &&
|
||||
|
|
@ -117,7 +117,7 @@ public class DiagnosticTests
|
|||
a.Kind == ActivityKind.Server);
|
||||
|
||||
Assert.Equal(ActivityStatusCode.Error, doesNotExistToolServer.Status);
|
||||
Assert.Equal("-32603", doesNotExistToolClient.Tags.Single(t => t.Key == "rpc.jsonrpc.error_code").Value);
|
||||
Assert.Equal("-32602", doesNotExistToolClient.Tags.Single(t => t.Key == "rpc.jsonrpc.error_code").Value);
|
||||
}
|
||||
|
||||
private static async Task RunConnected(Func<IMcpClient, IMcpServer, Task> action)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class McpServerPromptTests
|
|||
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await prompt.GetAsync(
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,6 @@ public class McpServerResourceTests
|
|||
};
|
||||
});
|
||||
var sp = services.BuildServiceProvider();
|
||||
Assert.Throws<McpException>(() => sp.GetRequiredService<IMcpServer>());
|
||||
Assert.Throws<InvalidOperationException>(sp.GetRequiredService<IMcpServer>);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public class McpServerTests : LoggedTest
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestSamplingAsync_Should_Throw_McpException_If_Client_Does_Not_Support_Sampling()
|
||||
public async Task RequestSamplingAsync_Should_Throw_Exception_If_Client_Does_Not_Support_Sampling()
|
||||
{
|
||||
// Arrange
|
||||
await using var transport = new TestServerTransport();
|
||||
|
|
@ -104,7 +104,7 @@ public class McpServerTests : LoggedTest
|
|||
var action = () => server.RequestSamplingAsync(new CreateMessageRequestParams { Messages = [] }, CancellationToken.None);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>("server", action);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -130,7 +130,7 @@ public class McpServerTests : LoggedTest
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestRootsAsync_Should_Throw_McpException_If_Client_Does_Not_Support_Roots()
|
||||
public async Task RequestRootsAsync_Should_Throw_Exception_If_Client_Does_Not_Support_Roots()
|
||||
{
|
||||
// Arrange
|
||||
await using var transport = new TestServerTransport();
|
||||
|
|
@ -138,7 +138,7 @@ public class McpServerTests : LoggedTest
|
|||
SetClientCapabilities(server, new ClientCapabilities());
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>("server", () => server.RequestRootsAsync(new ListRootsRequestParams(), CancellationToken.None));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => server.RequestRootsAsync(new ListRootsRequestParams(), CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -507,7 +507,7 @@ public class McpServerTests : LoggedTest
|
|||
await using var transport = new TestServerTransport();
|
||||
var options = CreateOptions(serverCapabilities);
|
||||
|
||||
Assert.Throws<McpException>(() => McpServerFactory.Create(transport, options, LoggerFactory));
|
||||
Assert.Throws<InvalidOperationException>(() => McpServerFactory.Create(transport, options, LoggerFactory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -515,7 +515,7 @@ public class McpServerTests : LoggedTest
|
|||
{
|
||||
await using var server = new TestServerForIChatClient(supportsSampling: false);
|
||||
|
||||
Assert.Throws<ArgumentException>("server", () => server.AsSamplingChatClient());
|
||||
Assert.Throws<InvalidOperationException>(server.AsSamplingChatClient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue