-
Notifications
You must be signed in to change notification settings - Fork 598
Add client conformance tests #1102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+343
−40
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
4707267
New dotnet console app for client compliance tests
mikekistler 842fcf0
First client tests passing
mikekistler 564840d
Client Conformance test tools_call working
mikekistler 7eb8943
Add ClientConformanceTests test runner
mikekistler 3f9b8fa
Restructure client conformance tests.
mikekistler 8349cd0
Refactor client to single codepath for all tests
mikekistler edf74f5
Make tool call conditional on scenario
mikekistler ff16ca6
Fixes/improvements from debug session
mikekistler 9817b62
Remove unnecessary usings
mikekistler 19b753c
Copilot fix for hanging tests
mikekistler 1c18628
Fix auth/scope-step-up conformance test
mikekistler 014be50
Use conformance test's expected client metadata document URI
mikekistler 0bb4c5a
Apply suggestions from code review
mikekistler f047c47
Refactor conformance tests to use shared NodeHelpers and improve clie…
mikekistler ca348f8
Merge branch 'main' into mdk/conformance-client
mikekistler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace ModelContextProtocol.Tests.Utils; | ||
|
|
||
| /// <summary> | ||
| /// Helper utilities for Node.js and npm operations. | ||
| /// </summary> | ||
| public static class NodeHelpers | ||
| { | ||
| /// <summary> | ||
| /// Creates a ProcessStartInfo configured to run npx with the specified arguments. | ||
| /// </summary> | ||
| /// <param name="arguments">The arguments to pass to npx.</param> | ||
| /// <returns>A configured ProcessStartInfo for running npx.</returns> | ||
| public static ProcessStartInfo NpxStartInfo(string arguments) | ||
| { | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
| { | ||
| // On Windows, npx is a PowerShell script, so we need to use cmd.exe to invoke it | ||
| return new ProcessStartInfo | ||
| { | ||
| FileName = "cmd.exe", | ||
| Arguments = $"/c npx {arguments}", | ||
| RedirectStandardOutput = true, | ||
| RedirectStandardError = true, | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| }; | ||
| } | ||
| else | ||
| { | ||
| // On Unix-like systems, npx is typically a shell script that can be executed directly | ||
| return new ProcessStartInfo | ||
| { | ||
| FileName = "npx", | ||
| Arguments = arguments, | ||
| RedirectStandardOutput = true, | ||
| RedirectStandardError = true, | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if Node.js and npx are installed and available on the system. | ||
| /// </summary> | ||
| /// <returns>True if npx is available, false otherwise.</returns> | ||
| public static bool IsNpxInstalled() | ||
| { | ||
| try | ||
| { | ||
| var startInfo = NpxStartInfo("--version"); | ||
|
|
||
| using var process = Process.Start(startInfo); | ||
| if (process == null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| process.WaitForExit(5000); | ||
| return process.ExitCode == 0; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| } |
99 changes: 99 additions & 0 deletions
99
tests/ModelContextProtocol.AspNetCore.Tests/ClientConformanceTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| using System.Diagnostics; | ||
| using System.Text; | ||
| using ModelContextProtocol.Tests.Utils; | ||
|
|
||
| namespace ModelContextProtocol.ConformanceTests; | ||
|
|
||
| /// <summary> | ||
| /// Runs the official MCP conformance tests against the ConformanceClient. | ||
| /// This test runs the Node.js-based conformance test suite for the client | ||
| /// and reports the results. | ||
| /// </summary> | ||
| public class ClientConformanceTests //: IAsyncLifetime | ||
| { | ||
| private readonly ITestOutputHelper _output; | ||
|
|
||
| // Public static property required for SkipUnless attribute | ||
| public static bool IsNpxInstalled => NodeHelpers.IsNpxInstalled(); | ||
|
|
||
| public ClientConformanceTests(ITestOutputHelper output) | ||
| { | ||
| _output = output; | ||
| } | ||
|
|
||
| [Theory(Skip = "npx is not installed. Skipping client conformance tests.", SkipUnless = nameof(IsNpxInstalled))] | ||
| [InlineData("initialize")] | ||
| [InlineData("tools_call")] | ||
| [InlineData("auth/metadata-default")] | ||
| [InlineData("auth/metadata-var1")] | ||
| [InlineData("auth/metadata-var2")] | ||
| [InlineData("auth/metadata-var3")] | ||
| [InlineData("auth/basic-cimd")] | ||
| // [InlineData("auth/2025-03-26-oauth-metadata-backcompat")] | ||
| // [InlineData("auth/2025-03-26-oauth-endpoint-fallback")] | ||
| [InlineData("auth/scope-from-www-authenticate")] | ||
| [InlineData("auth/scope-from-scopes-supported")] | ||
| [InlineData("auth/scope-omitted-when-undefined")] | ||
| [InlineData("auth/scope-step-up")] | ||
| public async Task RunConformanceTest(string scenario) | ||
| { | ||
| // Run the conformance test suite | ||
| var result = await RunClientConformanceScenario(scenario); | ||
|
|
||
| // Report the results | ||
| Assert.True(result.Success, | ||
| $"Conformance test failed.\n\nStdout:\n{result.Output}\n\nStderr:\n{result.Error}"); | ||
| } | ||
|
|
||
| private async Task<(bool Success, string Output, string Error)> RunClientConformanceScenario(string scenario) | ||
| { | ||
| // Construct an absolute path to the conformance client executable | ||
| var exeSuffix = OperatingSystem.IsWindows() ? ".exe" : ""; | ||
| var conformanceClientPath = Path.GetFullPath($"./ModelContextProtocol.ConformanceClient{exeSuffix}"); | ||
| // Replace AspNetCore.Tests with ConformanceClient in the path | ||
| conformanceClientPath = conformanceClientPath.Replace("AspNetCore.Tests", "ConformanceClient"); | ||
|
|
||
| if (!File.Exists(conformanceClientPath)) | ||
| { | ||
| throw new FileNotFoundException( | ||
| $"ConformanceClient executable not found at: {conformanceClientPath}"); | ||
| } | ||
|
|
||
| var startInfo = NodeHelpers.NpxStartInfo($"-y @modelcontextprotocol/conformance client --scenario {scenario} --command \"{conformanceClientPath} {scenario}\""); | ||
|
|
||
| var outputBuilder = new StringBuilder(); | ||
| var errorBuilder = new StringBuilder(); | ||
|
|
||
| var process = new Process { StartInfo = startInfo }; | ||
|
|
||
| process.OutputDataReceived += (sender, e) => | ||
| { | ||
| if (e.Data != null) | ||
| { | ||
| _output.WriteLine(e.Data); | ||
| outputBuilder.AppendLine(e.Data); | ||
| } | ||
| }; | ||
|
|
||
| process.ErrorDataReceived += (sender, e) => | ||
| { | ||
| if (e.Data != null) | ||
| { | ||
| _output.WriteLine(e.Data); | ||
| errorBuilder.AppendLine(e.Data); | ||
| } | ||
| }; | ||
|
|
||
| process.Start(); | ||
| process.BeginOutputReadLine(); | ||
| process.BeginErrorReadLine(); | ||
|
|
||
| await process.WaitForExitAsync(); | ||
|
|
||
| return ( | ||
| Success: process.ExitCode == 0, | ||
| Output: outputBuilder.ToString(), | ||
| Error: errorBuilder.ToString() | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
tests/ModelContextProtocol.ConformanceClient/ModelContextProtocol.ConformanceClient.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <OutputType>Exe</OutputType> | ||
| </PropertyGroup> | ||
|
|
||
| <PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'"> | ||
| <!-- For better test coverage, only disable reflection in one of the targets --> | ||
| <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="../../src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Logging.Console" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.