|
| 1 | +using System.Diagnostics; |
| 2 | +using System.Text; |
| 3 | +using ModelContextProtocol.Tests.Utils; |
| 4 | + |
| 5 | +namespace ModelContextProtocol.ConformanceTests; |
| 6 | + |
| 7 | +/// <summary> |
| 8 | +/// Runs the official MCP conformance tests against the ConformanceClient. |
| 9 | +/// This test runs the Node.js-based conformance test suite for the client |
| 10 | +/// and reports the results. |
| 11 | +/// </summary> |
| 12 | +public class ClientConformanceTests //: IAsyncLifetime |
| 13 | +{ |
| 14 | + private readonly ITestOutputHelper _output; |
| 15 | + |
| 16 | + // Public static property required for SkipUnless attribute |
| 17 | + public static bool IsNpxInstalled => NodeHelpers.IsNpxInstalled(); |
| 18 | + |
| 19 | + public ClientConformanceTests(ITestOutputHelper output) |
| 20 | + { |
| 21 | + _output = output; |
| 22 | + } |
| 23 | + |
| 24 | + [Theory(Skip = "npx is not installed. Skipping client conformance tests.", SkipUnless = nameof(IsNpxInstalled))] |
| 25 | + [InlineData("initialize")] |
| 26 | + [InlineData("tools_call")] |
| 27 | + [InlineData("auth/metadata-default")] |
| 28 | + [InlineData("auth/metadata-var1")] |
| 29 | + [InlineData("auth/metadata-var2")] |
| 30 | + [InlineData("auth/metadata-var3")] |
| 31 | + [InlineData("auth/basic-cimd")] |
| 32 | + // [InlineData("auth/2025-03-26-oauth-metadata-backcompat")] |
| 33 | + // [InlineData("auth/2025-03-26-oauth-endpoint-fallback")] |
| 34 | + [InlineData("auth/scope-from-www-authenticate")] |
| 35 | + [InlineData("auth/scope-from-scopes-supported")] |
| 36 | + [InlineData("auth/scope-omitted-when-undefined")] |
| 37 | + [InlineData("auth/scope-step-up")] |
| 38 | + public async Task RunConformanceTest(string scenario) |
| 39 | + { |
| 40 | + // Run the conformance test suite |
| 41 | + var result = await RunClientConformanceScenario(scenario); |
| 42 | + |
| 43 | + // Report the results |
| 44 | + Assert.True(result.Success, |
| 45 | + $"Conformance test failed.\n\nStdout:\n{result.Output}\n\nStderr:\n{result.Error}"); |
| 46 | + } |
| 47 | + |
| 48 | + private async Task<(bool Success, string Output, string Error)> RunClientConformanceScenario(string scenario) |
| 49 | + { |
| 50 | + // Construct an absolute path to the conformance client executable |
| 51 | + var exeSuffix = OperatingSystem.IsWindows() ? ".exe" : ""; |
| 52 | + var conformanceClientPath = Path.GetFullPath($"./ModelContextProtocol.ConformanceClient{exeSuffix}"); |
| 53 | + // Replace AspNetCore.Tests with ConformanceClient in the path |
| 54 | + conformanceClientPath = conformanceClientPath.Replace("AspNetCore.Tests", "ConformanceClient"); |
| 55 | + |
| 56 | + if (!File.Exists(conformanceClientPath)) |
| 57 | + { |
| 58 | + throw new FileNotFoundException( |
| 59 | + $"ConformanceClient executable not found at: {conformanceClientPath}"); |
| 60 | + } |
| 61 | + |
| 62 | + var startInfo = NodeHelpers.NpxStartInfo($"-y @modelcontextprotocol/conformance client --scenario {scenario} --command \"{conformanceClientPath} {scenario}\""); |
| 63 | + |
| 64 | + var outputBuilder = new StringBuilder(); |
| 65 | + var errorBuilder = new StringBuilder(); |
| 66 | + |
| 67 | + var process = new Process { StartInfo = startInfo }; |
| 68 | + |
| 69 | + process.OutputDataReceived += (sender, e) => |
| 70 | + { |
| 71 | + if (e.Data != null) |
| 72 | + { |
| 73 | + _output.WriteLine(e.Data); |
| 74 | + outputBuilder.AppendLine(e.Data); |
| 75 | + } |
| 76 | + }; |
| 77 | + |
| 78 | + process.ErrorDataReceived += (sender, e) => |
| 79 | + { |
| 80 | + if (e.Data != null) |
| 81 | + { |
| 82 | + _output.WriteLine(e.Data); |
| 83 | + errorBuilder.AppendLine(e.Data); |
| 84 | + } |
| 85 | + }; |
| 86 | + |
| 87 | + process.Start(); |
| 88 | + process.BeginOutputReadLine(); |
| 89 | + process.BeginErrorReadLine(); |
| 90 | + |
| 91 | + await process.WaitForExitAsync(); |
| 92 | + |
| 93 | + return ( |
| 94 | + Success: process.ExitCode == 0, |
| 95 | + Output: outputBuilder.ToString(), |
| 96 | + Error: errorBuilder.ToString() |
| 97 | + ); |
| 98 | + } |
| 99 | +} |
0 commit comments