Skip to content

Commit 012460f

Browse files
committed
Add test project for conformance tests
1 parent 4e61590 commit 012460f

File tree

12 files changed

+238
-0
lines changed

12 files changed

+238
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System.Diagnostics;
2+
using System.Text;
3+
using Xunit;
4+
5+
namespace ModelContextProtocol.ConformanceTests;
6+
7+
/// <summary>
8+
/// Runs the official MCP conformance tests against the ConformanceServer.
9+
/// This test starts the ConformanceServer, runs the Node.js-based conformance test suite,
10+
/// and reports the results.
11+
/// </summary>
12+
public class ConformanceTests : IAsyncLifetime
13+
{
14+
private const int ServerPort = 3001;
15+
private static readonly string ServerUrl = $"http://localhost:{ServerPort}";
16+
private Process? _serverProcess;
17+
18+
public async ValueTask InitializeAsync()
19+
{
20+
// Start the ConformanceServer
21+
_serverProcess = StartConformanceServer();
22+
23+
// Wait for server to be ready (retry for up to 30 seconds)
24+
var timeout = TimeSpan.FromSeconds(30);
25+
var stopwatch = Stopwatch.StartNew();
26+
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
27+
28+
while (stopwatch.Elapsed < timeout)
29+
{
30+
try
31+
{
32+
// Try to connect to the MCP endpoint
33+
var response = await httpClient.GetAsync($"{ServerUrl}/mcp");
34+
// Any response (even an error) means the server is up
35+
return;
36+
}
37+
catch (HttpRequestException)
38+
{
39+
// Connection refused means server not ready yet
40+
}
41+
catch (TaskCanceledException)
42+
{
43+
// Timeout means server might be processing, give it more time
44+
}
45+
46+
await Task.Delay(500);
47+
}
48+
49+
throw new InvalidOperationException("ConformanceServer failed to start within the timeout period");
50+
}
51+
52+
public ValueTask DisposeAsync()
53+
{
54+
// Stop the server
55+
if (_serverProcess != null && !_serverProcess.HasExited)
56+
{
57+
_serverProcess.Kill(entireProcessTree: true);
58+
_serverProcess.WaitForExit(5000);
59+
_serverProcess.Dispose();
60+
}
61+
return ValueTask.CompletedTask;
62+
}
63+
64+
[Fact]
65+
[Trait("Execution", "Manual")] // Requires Node.js/npm to be installed
66+
public async Task RunConformanceTests()
67+
{
68+
// Run the conformance test suite
69+
var result = await RunNpxConformanceTests();
70+
71+
// Report the results
72+
Assert.True(result.Success,
73+
$"Conformance tests failed.\n\nStdout:\n{result.Output}\n\nStderr:\n{result.Error}");
74+
} private static Process StartConformanceServer()
75+
{
76+
// Find the ConformanceServer project directory
77+
var testProjectDir = AppContext.BaseDirectory;
78+
var conformanceServerDir = Path.GetFullPath(
79+
Path.Combine(testProjectDir, "..", "..", "..", "..", "ConformanceServer"));
80+
81+
if (!Directory.Exists(conformanceServerDir))
82+
{
83+
throw new DirectoryNotFoundException(
84+
$"ConformanceServer directory not found at: {conformanceServerDir}");
85+
}
86+
87+
var startInfo = new ProcessStartInfo
88+
{
89+
FileName = "dotnet",
90+
Arguments = $"run --no-build --project ConformanceServer.csproj --urls {ServerUrl}",
91+
WorkingDirectory = conformanceServerDir,
92+
RedirectStandardOutput = true,
93+
RedirectStandardError = true,
94+
UseShellExecute = false,
95+
CreateNoWindow = true,
96+
Environment =
97+
{
98+
["ASPNETCORE_ENVIRONMENT"] = "Development"
99+
}
100+
};
101+
102+
var process = Process.Start(startInfo);
103+
if (process == null)
104+
{
105+
throw new InvalidOperationException("Failed to start ConformanceServer process");
106+
}
107+
108+
return process;
109+
}
110+
111+
private static async Task<(bool Success, string Output, string Error)> RunNpxConformanceTests()
112+
{
113+
var startInfo = new ProcessStartInfo
114+
{
115+
FileName = "npx",
116+
Arguments = $"@modelcontextprotocol/conformance server --url {ServerUrl}",
117+
RedirectStandardOutput = true,
118+
RedirectStandardError = true,
119+
UseShellExecute = false,
120+
CreateNoWindow = true
121+
};
122+
123+
var outputBuilder = new StringBuilder();
124+
var errorBuilder = new StringBuilder();
125+
126+
var process = new Process { StartInfo = startInfo };
127+
128+
process.OutputDataReceived += (sender, e) =>
129+
{
130+
if (e.Data != null)
131+
{
132+
Console.WriteLine(e.Data); // Echo to test output
133+
outputBuilder.AppendLine(e.Data);
134+
}
135+
};
136+
137+
process.ErrorDataReceived += (sender, e) =>
138+
{
139+
if (e.Data != null)
140+
{
141+
Console.Error.WriteLine(e.Data); // Echo to test output
142+
errorBuilder.AppendLine(e.Data);
143+
}
144+
};
145+
146+
process.Start();
147+
process.BeginOutputReadLine();
148+
process.BeginErrorReadLine();
149+
150+
await process.WaitForExitAsync();
151+
152+
return (
153+
Success: process.ExitCode == 0,
154+
Output: outputBuilder.ToString(),
155+
Error: errorBuilder.ToString()
156+
);
157+
}
158+
}

0 commit comments

Comments
 (0)