Add Conformance tests for the server (#983)
This commit is contained in:
parent
5a65af8ed7
commit
f45996d4b5
1 changed files with 981 additions and 2 deletions
118
tests/ModelContextProtocol.ConformanceServer/Program.cs
Normal file
118
tests/ModelContextProtocol.ConformanceServer/Program.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using ConformanceServer.Prompts;
|
||||
using ConformanceServer.Resources;
|
||||
using ConformanceServer.Tools;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.ConformanceServer;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvider = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
if (loggerProvider != null)
|
||||
{
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddProvider(loggerProvider);
|
||||
}
|
||||
|
||||
// Dictionary of session IDs to a set of resource URIs they are subscribed to
|
||||
// The value is a ConcurrentDictionary used as a thread-safe HashSet
|
||||
// because .NET does not have a built-in concurrent HashSet
|
||||
ConcurrentDictionary<string, ConcurrentDictionary<string, byte>> subscriptions = new();
|
||||
|
||||
builder.Services
|
||||
.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
.WithTools<ConformanceTools>()
|
||||
.WithPrompts<ConformancePrompts>()
|
||||
.WithResources<ConformanceResources>()
|
||||
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
||||
{
|
||||
if (ctx.Server.SessionId == null)
|
||||
{
|
||||
throw new McpException("Cannot add subscription for server with null SessionId");
|
||||
}
|
||||
if (ctx.Params?.Uri is { } uri)
|
||||
{
|
||||
subscriptions[ctx.Server.SessionId].TryAdd(uri, 0);
|
||||
|
||||
await ctx.Server.SampleAsync([
|
||||
new ChatMessage(ChatRole.System, "You are a helpful test server"),
|
||||
new ChatMessage(ChatRole.User, $"Resource {uri}, context: A new subscription was started"),
|
||||
],
|
||||
options: new ChatOptions
|
||||
{
|
||||
MaxOutputTokens = 100,
|
||||
Temperature = 0.7f,
|
||||
},
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
return new EmptyResult();
|
||||
})
|
||||
.WithUnsubscribeFromResourcesHandler(async (ctx, ct) =>
|
||||
{
|
||||
if (ctx.Server.SessionId == null)
|
||||
{
|
||||
throw new McpException("Cannot remove subscription for server with null SessionId");
|
||||
}
|
||||
if (ctx.Params?.Uri is { } uri)
|
||||
{
|
||||
subscriptions[ctx.Server.SessionId].TryRemove(uri, out _);
|
||||
}
|
||||
return new EmptyResult();
|
||||
})
|
||||
.WithCompleteHandler(async (ctx, ct) =>
|
||||
{
|
||||
// Basic completion support - returns empty array for conformance
|
||||
// Real implementations would provide contextual suggestions
|
||||
return new CompleteResult
|
||||
{
|
||||
Completion = new Completion
|
||||
{
|
||||
Values = [],
|
||||
HasMore = false,
|
||||
Total = 0
|
||||
}
|
||||
};
|
||||
})
|
||||
.WithSetLoggingLevelHandler(async (ctx, ct) =>
|
||||
{
|
||||
if (ctx.Params?.Level is null)
|
||||
{
|
||||
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
// The SDK updates the LoggingLevel field of the McpServer
|
||||
// Send a log notification to confirm the level was set
|
||||
await ctx.Server.SendNotificationAsync("notifications/message", new LoggingMessageNotificationParams
|
||||
{
|
||||
Level = LoggingLevel.Info,
|
||||
Logger = "conformance-test-server",
|
||||
Data = JsonElement.Parse($"\"Log level set to: {ctx.Params.Level}\""),
|
||||
}, cancellationToken: ct);
|
||||
|
||||
return new EmptyResult();
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapMcp();
|
||||
|
||||
app.MapGet("/health", () => TypedResults.Ok("Healthy"));
|
||||
|
||||
await app.RunAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
await MainAsync(args);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue