How to install the packages, run the standalone container, embed the server in ASP.NET Core or Aspire, and build from source. Back to the README.
| Setup | Install / start | Best fit |
|---|---|---|
| .NET + Aspire | ManagedCode.LlmTestServer.Aspire + ManagedCode.LlmTestServer.Client |
Start an isolated .NET server from your AppHost or integration test; Docker is optional. |
| Standalone Docker | managedcode/llm-test-server:local, built with the .NET SDK |
Run the dashboard, management API and provider routes together for applications in any language. |
| ASP.NET Core library | ManagedCode.LlmTestServer.Hosting |
Embed the server in your own host with AddLlmTestServer(...) and MapLlmTestServer(). |
The v1.0.0 release contains all 19 packages accepted by NuGet. Install the packages in your consumer project:
dotnet add package ManagedCode.LlmTestServer.Aspire --version 1.0.0
dotnet add package ManagedCode.LlmTestServer.Client --version 1.0.0For an existing ASP.NET Core application, install ManagedCode.LlmTestServer.Hosting from NuGet
and follow ASP.NET Core startup. The core package owns scenarios and model configuration;
provider packages own their wire contracts, and the client package owns management and typed client APIs.
Build a local image from the checkout with the .NET SDK's PublishContainer target, then run it with Docker:
dotnet publish samples/ManagedCode.LlmTestServer.Service/ManagedCode.LlmTestServer.Service.csproj \
--configuration Release --runtime linux-arm64 --self-contained false \
/t:PublishContainer -p:ContainerRuntimeIdentifiers=linux-arm64 \
-p:ContainerImageTag=local -p:UseAppHost=false
docker run --rm --name llm-test-server -p 8080:8080 \
-e LlmTestServer__RequiredBearerToken=local-test-key \
managedcode/llm-test-server:localUse linux-x64 in both RID arguments on an amd64 machine. Open http://localhost:8080/ for the dashboard.
The host port is explicitly mapped to container port 8080. Configure a model and response through the running
server, keeping the same test API key in the replacement configuration:
curl --fail-with-body http://localhost:8080/admin-api/configure \
-H 'Authorization: Bearer local-test-key' -H 'Content-Type: application/json' \
--data '{"requiredBearerToken":"local-test-key","models":[{"id":"gpt-4.1-mini","kind":"chat"}],"chatScenarios":[{"id":"hello","modelId":"gpt-4.1-mini","responses":[{"content":"Hello from your fixture"}]}]}'
curl --fail-with-body http://localhost:8080/openai/v1/chat/completions \
-H 'Authorization: Bearer local-test-key' -H 'Content-Type: application/json' \
--data '{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"Hello"}]}'The API key is a local fixture credential; production provider credentials are unnecessary. Provider routes
and /admin-api/* use it. The intended public release image is
ghcr.io/managedcode/llm-test-server:1.0.0; the multi-platform image has been pushed, but public pulling remains pending GHCR visibility
configuration and anonymous-pull verification.
See Docker distribution for both architectures, authentication,
health checks and release requirements.
The simplest host config is one chat scenario:
using ManagedCode.LlmTestServer.Hosting;
using ManagedCode.LlmTestServer.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLlmTestServer(options =>
{
options.AddChatScenario(
"blue-whale",
scenario => scenario
.ForModel(LlmTestServerKnownModelIds.Gpt41Mini)
.WhenUserContains("largest animal")
.Responds("blue whale", "blue ", "whale"));
});
var app = builder.Build();
app.MapLlmTestServer();
app.Run();A fuller config can advertise your application's real model names while still serving deterministic fixtures:
using ManagedCode.LlmTestServer.Hosting;
using ManagedCode.LlmTestServer.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLlmTestServer(options =>
{
options.RequireBearerToken("test-key");
options.AddDefaultOpenAiModels();
options.WithDefaultEmbeddingVector(0.125f, 0.25f, 0.5f, 1.0f);
options.WithDefaultAudio(File.ReadAllBytes("fixtures/ok.wav"), "audio/wav");
options.AddChatScenario(
"invoice-total",
scenario => scenario
.ForModel(LlmTestServerKnownModelIds.Gpt41Mini)
.WhenUserContains("invoice total")
.Responds(
"{\"total\":42.50}",
"{\"total\":",
"42.50",
"}"));
options.AddChatScenario(
"provider-rate-limit",
scenario => scenario
.ForModel(LlmTestServerKnownModelIds.Gpt41Mini)
.WhenUserContains("rate limit")
.Fails(429, "rate_limit_exceeded", "Scripted rate limit from LLM Test Server."));
options.AddChatScenario(
"slow-response",
scenario => scenario
.ForModel(LlmTestServerKnownModelIds.Gpt41Mini)
.WhenUserContains("slow")
.Responds("done")
.DelaysBy(250));
});
var app = builder.Build();
app.MapLlmTestServer();
app.Run();Install ManagedCode.LlmTestServer.Aspire in the AppHost using the versioned
NuGet installation commands above.
Then add the package-owned Test Server resource directly:
using ManagedCode.LlmTestServer.Aspire;
var builder = DistributedApplication.CreateBuilder(args);
var llmTestServer = builder
.AddLlmTestServer()
.WithApiKey("test-key");
builder
.AddProject<Projects.Consumer>("consumer")
.WithReference(llmTestServer)
.WithEnvironment("LLM_PROVIDER_ENDPOINT", llmTestServer.GetHttpEndpoint())
.WithEnvironment("OPENAI_ENDPOINT", llmTestServer.GetOpenAiEndpoint())
.WithEnvironment("OPENAI_API_KEY", "test-key")
.WaitFor(llmTestServer);
builder.Build().Run();AddLlmTestServer() creates a LlmTestServerResource backed by the packaged .NET LLM Test Server service executable and exposes its http endpoint. It does not require a consumer service project reference, generated Projects.* metadata type, project path, Docker, or a container runtime. Consumer resources should reference the Test Server resource, wait for it, and use llmTestServer.GetHttpEndpoint() when they need the provider-compatible base URL. .WithApiKey("test-key") sets LlmTestServer:RequiredBearerToken so both provider endpoints and /admin-api/* control endpoints require the same bearer token.
Use AddLlmTestServerContainer() only when you explicitly want a container-backed resource, for example for a deployment or container-runtime smoke test. The container mode uses a ghcr.io/managedcode/llm-test-server image tagged with the same release version as the installed ManagedCode.LlmTestServer.Aspire package; it is not the default local Aspire path.
dotnet restore ManagedCode.LlmTestServer.slnx
dotnet build ManagedCode.LlmTestServer.slnx --configuration Release --no-restore
dotnet test --project tests/ManagedCode.LlmTestServer.Tests/ManagedCode.LlmTestServer.Tests.csproj --configuration Release --no-build --verbosity normal
for project in src/*/*.csproj; do dotnet pack "$project" --configuration Release --no-build --output artifacts/packages; doneglobal.json opts dotnet test into Microsoft.Testing.Platform for .NET 10.
After restore/build, run bash scripts/coverage.sh for the production 90% coverage gate. After packing, run python3 scripts/verify-package.py for an isolated NuGet consumer, test-owned Aspire host and Chromium dashboard test; this requires Python 3 and PowerShell (pwsh). CI installs Chromium dependencies and saves coverage/browser evidence. Release delivery is marked complete only after NuGet push and GitHub artifact attachment succeed.