Skip to main content
Glama
by V0v1kkk
EndToEndToolTests.cs10.6 kB
using DotNetMetadataMcpServer; using DotNetMetadataMcpServer.Configuration; using DotNetMetadataMcpServer.Models; using DotNetMetadataMcpServer.Services; using DotNetMetadataMcpServer.Tools; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Text.Json; namespace MetadataExplorerTest.Integration; /// <summary> /// End-to-end integration tests that verify tools work correctly through the full MCP protocol stack. /// These tests use a real MCP server and client connected through in-memory pipes. /// </summary> [TestFixture] [NonParallelizable] // MCP client with pipe streams does not support concurrent reads/writes public class EndToEndToolTests : McpServerIntegrationTestBase { protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) { // Configure all MCP tools mcpServerBuilder .WithTools<AssemblyTools>() .WithTools<NamespaceTools>() .WithTools<TypeTools>() .WithTools<NuGetTools>(); // Configure settings var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string?> { ["Tools:DefaultPageSize"] = "20", ["Tools:IntendResponse"] = "true" // Use indented JSON for easier debugging }) .Build(); services.Configure<ToolsConfiguration>(configuration.GetSection("Tools")); // Register all services as scoped services.AddScoped<MsBuildHelper>(); services.AddScoped<ReflectionTypesCollector>(); services.AddScoped<IDependenciesScanner, DependenciesScanner>(); services.AddScoped<AssemblyToolService>(); services.AddScoped<NamespaceToolService>(); services.AddScoped<TypeToolService>(); services.AddScoped<NuGetToolService>(); } [Test] public async Task ListTools_Should_Return_All_Registered_Tools() { // Arrange await using var client = await CreateMcpClientAsync(); // Act var tools = await client.ListToolsAsync(); // Assert Assert.That(tools, Is.Not.Empty); var toolNames = tools.Select(t => t.Name).ToList(); Assert.That(toolNames, Does.Contain("ReferencedAssembliesExplorer")); Assert.That(toolNames, Does.Contain("NamespacesExplorer")); Assert.That(toolNames, Does.Contain("NamespaceTypes")); Assert.That(toolNames, Does.Contain("NuGetPackageSearch")); Assert.That(toolNames, Does.Contain("NuGetPackageVersions")); } [Test] public async Task NuGetPackageSearch_Should_Return_Results() { // Arrange await using var client = await CreateMcpClientAsync(); var tools = await client.ListToolsAsync(); var searchTool = tools.First(t => t.Name == "NuGetPackageSearch"); // Act var arguments = new AIFunctionArguments { ["searchQuery"] = "Newtonsoft.Json", ["includePrerelease"] = false, ["pageNumber"] = 1 }; var result = await searchTool.InvokeAsync(arguments); // Assert Assert.That(result, Is.Not.Null); // The result is a JsonElement containing CallToolResult with content array var jsonResult = (JsonElement)result; var contentArray = jsonResult.GetProperty("content"); var textContent = contentArray[0].GetProperty("text").GetString(); Assert.That(textContent, Is.Not.Null.And.Not.Empty); var response = JsonSerializer.Deserialize<NuGetPackageSearchResponse>(textContent!); Assert.That(response, Is.Not.Null); Assert.That(response!.Packages, Is.Not.Empty); var hasNewtonsoftJson = response.Packages.Any(p => p.Id.Contains("Newtonsoft.Json", StringComparison.OrdinalIgnoreCase)); Assert.That(hasNewtonsoftJson, Is.True, "Expected to find Newtonsoft.Json in search results"); } [Test] public async Task NuGetPackageVersions_Should_Return_Version_History() { // Arrange await using var client = await CreateMcpClientAsync(); var tools = await client.ListToolsAsync(); var versionsTool = tools.First(t => t.Name == "NuGetPackageVersions"); // Act var arguments = new AIFunctionArguments { ["packageId"] = "Newtonsoft.Json", ["includePrerelease"] = false, ["pageNumber"] = 1 }; var result = await versionsTool.InvokeAsync(arguments); // Assert Assert.That(result, Is.Not.Null); // The result is a JsonElement containing CallToolResult with content array var jsonResult = (JsonElement)result; var contentArray = jsonResult.GetProperty("content"); var textContent = contentArray[0].GetProperty("text").GetString(); Assert.That(textContent, Is.Not.Null.And.Not.Empty); var response = JsonSerializer.Deserialize<NuGetPackageVersionsResponse>(textContent!); Assert.That(response, Is.Not.Null); Assert.That(response!.PackageId, Is.EqualTo("Newtonsoft.Json")); Assert.That(response.Versions, Is.Not.Empty); } [Test] public async Task Tools_Should_Have_Proper_Descriptions_And_Parameters() { // Arrange await using var client = await CreateMcpClientAsync(); // Act var tools = await client.ListToolsAsync(); // Assert foreach (var tool in tools) { Assert.That(tool.Name, Is.Not.Null.And.Not.Empty, "Tool should have a name"); Assert.That(tool.Description, Is.Not.Null.And.Not.Empty, $"Tool {tool.Name} should have a description"); // Verify the tool has JSON schema Assert.That(tool.JsonSchema.ValueKind, Is.Not.EqualTo(JsonValueKind.Undefined), $"Tool {tool.Name} should have a JSON schema defined"); } } [Test] public async Task Multiple_Tool_Invocations_Should_Work_Independently() { // Arrange await using var client = await CreateMcpClientAsync(); var tools = await client.ListToolsAsync(); var searchTool = tools.First(t => t.Name == "NuGetPackageSearch"); // Act - Invoke the same tool multiple times var results = new List<object?>(); for (int i = 0; i < 3; i++) { var arguments = new AIFunctionArguments { ["searchQuery"] = "System.Text.Json", ["includePrerelease"] = false, ["pageNumber"] = 1 }; var result = await searchTool.InvokeAsync(arguments); results.Add(result); } // Assert - All invocations should succeed Assert.That(results, Has.Count.EqualTo(3)); foreach (var result in results) { Assert.That(result, Is.Not.Null); // Verify we can extract content from the result var jsonResult = (JsonElement)result!; var contentArray = jsonResult.GetProperty("content"); Assert.That(contentArray.GetArrayLength(), Is.GreaterThan(0)); } } [Test] public async Task Tool_With_Invalid_Parameters_Should_Return_Error() { // Arrange await using var client = await CreateMcpClientAsync(); var tools = await client.ListToolsAsync(); var assemblyTool = tools.First(t => t.Name == "ReferencedAssembliesExplorer"); // Act - Invoke with invalid parameters var arguments = new AIFunctionArguments { ["projectFileAbsolutePath"] = "/nonexistent/path/project.csproj", ["pageNumber"] = 1 }; // The tool should either throw or return a result with isError=true // Let's check what actually happens try { var result = await assemblyTool.InvokeAsync(arguments); // If we get a result, check if it has isError flag var jsonResult = (JsonElement)(result ?? throw new InvalidOperationException("Result is null")); if (jsonResult.TryGetProperty("isError", out var isErrorProp)) { Assert.That(isErrorProp.GetBoolean(), Is.True, "Expected isError to be true for invalid project path"); } // If there's no isError property, the result should contain error information in content else if (jsonResult.TryGetProperty("content", out var contentArray)) { var textContent = contentArray[0].GetProperty("text").GetString(); Assert.That(textContent, Does.Contain("error").IgnoreCase.Or.Contains("exception").IgnoreCase, "Expected error message in content for invalid project path"); } } catch (Exception ex) { // If it throws, that's also acceptable behavior Assert.Pass($"Tool threw exception as expected: {ex.GetType().Name}"); } } [Test] public async Task Server_Should_Handle_Concurrent_Tool_Invocations() { // Arrange await using var client = await CreateMcpClientAsync(); var tools = await client.ListToolsAsync(); var searchTool = tools.First(t => t.Name == "NuGetPackageSearch"); // Act - Invoke tools concurrently var tasks = Enumerable.Range(0, 5).Select(async i => { var arguments = new AIFunctionArguments { ["searchQuery"] = i % 2 == 0 ? "Newtonsoft.Json" : "System.Text.Json", ["includePrerelease"] = false, ["pageNumber"] = 1 }; return await searchTool.InvokeAsync(arguments); }); var results = await Task.WhenAll(tasks); // Assert - All concurrent invocations should succeed Assert.That(results, Has.Length.EqualTo(5)); foreach (var result in results) { Assert.That(result, Is.Not.Null); // Verify we can extract content from the result var jsonResult = (JsonElement)result!; var contentArray = jsonResult.GetProperty("content"); Assert.That(contentArray.GetArrayLength(), Is.GreaterThan(0)); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/V0v1kkk/DotNetMetadataMcpServer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server