using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using ModelContextProtocol.Tests.Utils;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Tests.Server;
///
/// Verifies that custom request handlers registered through
/// cannot silently replace a built-in method
/// or another custom handler.
///
public class CustomRequestHandlerCollisionTests(ITestOutputHelper testOutputHelper) : LoggedTest(testOutputHelper)
{
#pragma warning disable MCPEXP002
private static McpServerRequestHandler CreateHandler(string method) => new()
{
Method = method,
Handler = (request, cancellationToken) => new ValueTask((JsonNode?)null),
};
[Fact]
public async Task CustomHandler_CollidingWithBuiltInMethod_Throws()
{
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
var options = new McpServerOptions
{
Capabilities = new() { Tools = new() },
RequestHandlers = [CreateHandler("tools/call")],
};
var ex = Assert.Throws(
() => McpServer.Create(transport, options, LoggerFactory));
Assert.Contains("tools/call", ex.Message);
}
[Fact]
public async Task CustomHandler_CollidingWithInitialize_Throws()
{
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
var options = new McpServerOptions
{
RequestHandlers = [CreateHandler("initialize")],
};
Assert.Throws(
() => McpServer.Create(transport, options, LoggerFactory));
}
[Fact]
public async Task CustomHandler_DuplicateCustomMethod_Throws()
{
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
var options = new McpServerOptions
{
RequestHandlers = [CreateHandler("custom/method"), CreateHandler("custom/method")],
};
var ex = Assert.Throws(
() => McpServer.Create(transport, options, LoggerFactory));
Assert.Contains("custom/method", ex.Message);
}
[Fact]
public async Task CustomHandler_UniqueMethod_Succeeds()
{
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
var options = new McpServerOptions
{
RequestHandlers = [CreateHandler("custom/method")],
};
await using var server = McpServer.Create(transport, options, LoggerFactory);
Assert.NotNull(server);
}
[Fact]
public async Task CustomHandler_EmptyRoutingNameParameter_Throws()
{
await using var transport = new StreamServerTransport(Stream.Null, Stream.Null);
var options = new McpServerOptions
{
RequestHandlers =
[
new McpServerRequestHandler
{
Method = "custom/method",
RoutingNameParameter = " ",
Handler = (request, cancellationToken) => new ValueTask((JsonNode?)null),
},
],
};
var ex = Assert.Throws(
() => McpServer.Create(transport, options, LoggerFactory));
Assert.Contains(nameof(McpServerRequestHandler.RoutingNameParameter), ex.Message);
}
#pragma warning restore MCPEXP002
}