diff --git a/Directory.Packages.props b/Directory.Packages.props
index 69ed858b3..ce902d18b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -69,6 +69,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
diff --git a/ModelContextProtocol.slnx b/ModelContextProtocol.slnx
index 9020d2fbe..7976fe8f9 100644
--- a/ModelContextProtocol.slnx
+++ b/ModelContextProtocol.slnx
@@ -39,6 +39,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/concepts/transports/samples/integration-testing/server/IntegrationTestingMcpServer.csproj b/docs/concepts/transports/samples/integration-testing/server/IntegrationTestingMcpServer.csproj
new file mode 100644
index 000000000..e8e8a5b6b
--- /dev/null
+++ b/docs/concepts/transports/samples/integration-testing/server/IntegrationTestingMcpServer.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/docs/concepts/transports/samples/integration-testing/server/Program.cs b/docs/concepts/transports/samples/integration-testing/server/Program.cs
new file mode 100644
index 000000000..febad8180
--- /dev/null
+++ b/docs/concepts/transports/samples/integration-testing/server/Program.cs
@@ -0,0 +1,24 @@
+//
+using System.ComponentModel;
+using ModelContextProtocol.Server;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddMcpServer()
+ .WithHttpTransport()
+ .WithTools();
+
+var app = builder.Build();
+app.MapMcp("/mcp");
+app.Run();
+
+// WebApplicationFactory needs a public entry point.
+public partial class Program;
+
+[McpServerToolType]
+public sealed class EchoTools
+{
+ [McpServerTool(Name = "echo"), Description("Returns the supplied message.")]
+ public static string Echo(string message) => $"Echo: {message}";
+}
+//
diff --git a/docs/concepts/transports/samples/integration-testing/tests/IntegrationTestingMcpServer.Tests.csproj b/docs/concepts/transports/samples/integration-testing/tests/IntegrationTestingMcpServer.Tests.csproj
new file mode 100644
index 000000000..892ff5abd
--- /dev/null
+++ b/docs/concepts/transports/samples/integration-testing/tests/IntegrationTestingMcpServer.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ true
+ enable
+ enable
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/docs/concepts/transports/samples/integration-testing/tests/McpServerTests.cs b/docs/concepts/transports/samples/integration-testing/tests/McpServerTests.cs
new file mode 100644
index 000000000..d7a695cd5
--- /dev/null
+++ b/docs/concepts/transports/samples/integration-testing/tests/McpServerTests.cs
@@ -0,0 +1,37 @@
+//
+using Microsoft.AspNetCore.Mvc.Testing;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol;
+using Xunit;
+
+namespace IntegrationTestingMcpServer.Tests;
+
+public class McpServerTests(WebApplicationFactory factory)
+ : IClassFixture>
+{
+ [Fact]
+ public async Task EchoTool_RoundTripsThroughStreamableHttp()
+ {
+ using HttpClient httpClient = factory.CreateClient();
+ await using var transport = new HttpClientTransport(
+ new HttpClientTransportOptions
+ {
+ Endpoint = new Uri(httpClient.BaseAddress!, "/mcp"),
+ TransportMode = HttpTransportMode.StreamableHttp,
+ },
+ httpClient);
+
+ await using McpClient client = await McpClient.CreateAsync(
+ transport,
+ cancellationToken: TestContext.Current.CancellationToken);
+
+ var result = await client.CallToolAsync(
+ "echo",
+ new Dictionary { ["message"] = "Hello MCP" },
+ cancellationToken: TestContext.Current.CancellationToken);
+
+ var text = Assert.Single(result.Content.OfType());
+ Assert.Equal("Echo: Hello MCP", text.Text);
+ }
+}
+//
diff --git a/docs/concepts/transports/transports.md b/docs/concepts/transports/transports.md
index bb4e155f2..149e6d902 100644
--- a/docs/concepts/transports/transports.md
+++ b/docs/concepts/transports/transports.md
@@ -188,6 +188,18 @@ app.Run();
By default, the HTTP transport runs **statelessly** — the server does not assign an `Mcp-Session-Id` or track transport session state in memory. This simplifies deployment, enables horizontal scaling without session affinity, and matches the `2026-07-28` Streamable HTTP wire format. Set `SessionMode = HttpServerSessionMode.Stateful` explicitly when your server needs stateful sessions for unsolicited notifications, resource subscriptions, or per-client isolation. For a detailed guide on when to use stateless vs. stateful mode, configure session options, and understand [cancellation and disposal](xref:stateless#cancellation-and-disposal) behavior during shutdown, see [Stateless and Stateful](xref:stateless).
+#### Integration testing an ASP.NET Core MCP server
+
+Use `Microsoft.AspNetCore.Mvc.Testing` to host the complete ASP.NET Core application in process and exercise its Streamable HTTP endpoint without opening a network port. When the server uses top-level statements, expose its generated entry point to the test project by adding `public partial class Program`:
+
+[!code-csharp[](samples/integration-testing/server/Program.cs?name=snippet_IntegrationTestServer)]
+
+Create an `HttpClient` from `WebApplicationFactory` and pass that same client to . The factory client is backed by the in-memory test server; constructing `HttpClientTransport` without it would create a separate `HttpClient` that cannot reach the in-process application.
+
+[!code-csharp[](samples/integration-testing/tests/McpServerTests.cs?name=snippet_IntegrationTest)]
+
+The test project needs references to the server project and `ModelContextProtocol.Core`, plus the `Microsoft.AspNetCore.Mvc.Testing` package. Keep ownership of the factory-created `HttpClient` in the test, dispose the MCP client and transport asynchronously, and pass the test cancellation token to MCP operations so a failed connection cannot hang the test run.
+
#### Host name validation
For local HTTP servers, keep the set of accepted host names limited to loopback values. This helps protect against DNS rebinding, where a browser reaches a local server through an attacker-controlled DNS name while sending that DNS name in the HTTP `Host` header. ASP.NET Core's Kestrel server doesn't validate `Host` headers by default, so configure `AllowedHosts` with known host names rather than `"*"`. This also avoids reflecting untrusted host names through ASP.NET Core features such as absolute URL generation. See [Host filtering with ASP.NET Core Kestrel web server | Microsoft Learn](https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel/host-filtering) and [URL generation concepts | Microsoft Learn](https://learn.microsoft.com/aspnet/core/fundamentals/routing#url-generation-concepts).