Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Before you begin, ensure you have the following installed:
- **.NET SDK 10.0 or later** - Required to build and test the project
- Download from [dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)
- Verify installation: `dotnet --version`
- **Node.js 22 or later and npm** - Required for integration and conformance tests
- Run `npm ci` from the repository root before running tests to install the pinned test dependencies.
- Integration tests launch the installed servers directly with `node`, avoiding npm installation and shared-cache access during parallel tests.

The dev container configuration in this repository includes all the necessary tools and SDKs to get started quickly.

Expand Down
14 changes: 14 additions & 0 deletions tests/Common/Utils/NodeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ private static string FindRepoRoot()
throw new InvalidOperationException("Could not find repository root (no package-lock.json found in ancestor directories).");
}

/// <summary>
/// Gets the entry point of a test server installed by 'npm ci', without invoking npm during tests.
/// </summary>
public static string GetTestServerScriptPath(string serverName)
{
var scriptPath = Path.Combine(FindRepoRoot(), "node_modules", "@modelcontextprotocol", $"server-{serverName}", "dist", "index.js");
if (!File.Exists(scriptPath))
{
throw new FileNotFoundException("Test server is not installed. Run 'npm ci' from the repository root before running tests.", scriptPath);
}

return scriptPath;
}

/// <summary>
/// Ensures npm dependencies are installed by running 'npm ci' from the repo root.
/// This is safe to call multiple times; it only runs once per test process.
Expand Down
10 changes: 5 additions & 5 deletions tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ public class ClientIntegrationTestFixture

public ClientIntegrationTestFixture()
{
const string ServerEverythingVersion = "2026.1.26";
string testServerExecutable = Path.Combine(AppContext.BaseDirectory, "TestServer.exe");
string testServerDll = Path.Combine(AppContext.BaseDirectory, "TestServer.dll");

EverythingServerTransportOptions = new()
{
Command = "npx",
// Change to Arguments = ["mcp-server-everything"] if you want to run the server locally after creating a symlink
Arguments = ["-y", "--verbose", $"@modelcontextprotocol/server-everything@{ServerEverythingVersion}"],
Command = "node",
// To use a local Everything server, build it, then run `npm link <path-to-server-everything>`
// from this repository root after `npm ci`. The linked package must contain dist/index.js;
// no argument changes are needed because this path resolves through the package link.
Arguments = [NodeHelpers.GetTestServerScriptPath("everything")],
Name = "Everything",
};

Expand All @@ -36,7 +37,6 @@ public ClientIntegrationTestFixture()

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Change to Arguments to "mcp-server-everything" if you want to run the server locally after creating a symlink
TestServerTransportOptions.Arguments = [PlatformDetection.IsMonoRuntime ? testServerExecutable : testServerDll];
}
}
Expand Down
16 changes: 14 additions & 2 deletions tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ public async Task Connect_ShouldProvideServerFields(string clientId)
Assert.Null(client.SessionId);
}

[Theory]
[MemberData(nameof(GetClients))]
public async Task ConnectConcurrently_Stdio(string clientId)
{
await Task.WhenAll(Enumerable.Range(0, 4).Select(async _ =>
{
await using var client = await _fixture.CreateClientAsync(clientId);
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
Assert.NotEmpty(tools);
}));
}

[Theory]
[MemberData(nameof(GetClients))]
public async Task ListTools_Stdio(string clientId)
Expand Down Expand Up @@ -464,8 +476,8 @@ public async Task CallTool_Stdio_MemoryServer()
// arrange
StdioClientTransportOptions stdioOptions = new()
{
Command = "npx",
Arguments = ["-y", "@modelcontextprotocol/server-memory"],
Command = "node",
Arguments = [NodeHelpers.GetTestServerScriptPath("memory")],
Name = "memory",
};

Expand Down
Loading