using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Server; using Nexus.Api.Services; using Xunit; namespace Nexus.Api.Tests; /// /// Verifies that the MCP server configuration is correct: /// all tools are registered, the streamable-http transport is configured /// via the service extensions, and MapMcp is called in Program.cs. /// public sealed class McpServerConfigurationTests { [Fact] public void McpServer_CanBeRegistered_WithoutError() { var services = new ServiceCollection(); services.AddLogging(); services.AddOptions(); services.AddHttpContextAccessor(); // Simulate what AddNexusApplicationServices does services.AddMcpServer() .WithHttpTransport(options => options.Stateless = true) .WithTools(); // Build the container — this should not throw var provider = services.BuildServiceProvider(); // Verify the ToolType is properly decorated var attr = typeof(NexusMcpTools).GetCustomAttributes( typeof(McpServerToolTypeAttribute), inherit: false); Assert.Single(attr); } [Fact] public void McpServer_ToolTypeAttribute_IsPresent() { var attr = typeof(NexusMcpTools).GetCustomAttributes( typeof(McpServerToolTypeAttribute), inherit: false); Assert.Single(attr); } [Fact] public void McpEndpoint_MapMcp_IsCalledInProgram() { // Source path relative to the test output directory var programPath = ResolveSourcePath("backend", "Program.cs"); Assert.True(File.Exists(programPath), $"Program.cs not found at {programPath}"); var source = File.ReadAllText(programPath); Assert.Contains("MapMcp", source, StringComparison.Ordinal); } [Fact] public void McpServerRegistration_IsCalledInServiceExtensions() { var extPath = ResolveSourcePath("backend", "Extensions", "ServiceCollectionExtensions.cs"); Assert.True(File.Exists(extPath), $"ServiceCollectionExtensions.cs not found at {extPath}"); var source = File.ReadAllText(extPath); Assert.Contains("AddMcpServer", source, StringComparison.Ordinal); Assert.Contains("WithHttpTransport", source, StringComparison.Ordinal); Assert.Contains("Stateless", source, StringComparison.Ordinal); Assert.Contains("WithTools", source, StringComparison.Ordinal); } [Fact] public void NuGetPackage_ModelContextProtocol_AspNetCore_IsReferenced() { var csprojPath = ResolveSourcePath("backend", "Nexus.Api.csproj"); Assert.True(File.Exists(csprojPath), $"Nexus.Api.csproj not found at {csprojPath}"); var source = File.ReadAllText(csprojPath); Assert.Contains("ModelContextProtocol.AspNetCore", source, StringComparison.Ordinal); } private static string ResolveSourcePath(params string[] segments) { // Navigate from test output directory to the repo root // Test DLL is at: backend-tests/bin/Debug/net10.0/Nexus.Api.Tests.dll // We go up 4 levels (net10.0 → Debug → bin → backend-tests) to reach repo root var baseDir = AppContext.BaseDirectory; var repoRoot = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..")); return Path.Combine(new[] { repoRoot }.Concat(segments).ToArray()); } }