The stdio server from T8.1 runs on your machine, started by your client, with your permissions. It needs no sign-in, because only one person uses it. If the Kladde is to offer its tools over the network, say for the agent in the cloud or for the VS Code windows of several colleagues, that changes: many callers use the same server, and with every request the server has to know who is asking and whether that person may use this tool.
In this lesson, the server gets a second way in for that purpose, over HTTP. It validates a token from Microsoft Entra ID, grants permissions per tool, runs a tool with an outward effect only after an explicit confirmation, and writes every writing call into a log. All of it can be tested without Azure and without a sign-in; Entra ID is the optional cloud part. The examples ran with these packages:
| Package | Version |
|---|---|
ModelContextProtocol.AspNetCore |
2.4.0 |
Microsoft.AspNetCore.Authentication.JwtBearer |
10.0.14 |
Microsoft.AspNetCore.Mvc.Testing |
10.0.14 |
xunit.v3.mtp-v2 |
4.0.2 |
MCP over HTTP
Streamable HTTP is the second transport from T8.1. The server is an ASP.NET Core application with a single endpoint, say /mcp. Every
message from the client is a POST to it with a JSON-RPC message in the body. The response comes as JSON or, if the server wants to
report something in between, as a stream of Server-Sent Events. The package ModelContextProtocol.AspNetCore brings two parts for this:
WithHttpTransport()on the server's builder, in place ofWithStdioServerTransport().app.MapMcp("/mcp")creates the endpoint. It returns anIEndpointConventionBuilder, and to that you attachRequireAuthorization, as with any other endpoint from K2.
One setting deserves a decision: SessionMode. With HttpServerSessionMode.Stateless, every request stands on its own. There is no
session that the server has to remember, and every instance can answer every request. That suits a Container App that grows to
several instances under load (K3). A server needs a session only if it wants to send messages to the client on its own, for example to ask it for
input. The Kladde doesn't need that.
The HTTP server becomes a project of its own next to the stdio server. It uses the same reading tools from Kladde.Mcp, and that is exactly what
the library has stood on its own for since T8.1:
dotnet new web -o src/Kladde.Mcp.Http --framework net10.0
dotnet sln add src/Kladde.Mcp.Http/Kladde.Mcp.Http.csproj
dotnet reference add src/Kladde.Mcp/Kladde.Mcp.csproj --project src/Kladde.Mcp.Http/Kladde.Mcp.Http.csproj
dotnet package add ModelContextProtocol.AspNetCore --version 2.4.0 --project src/Kladde.Mcp.Http/Kladde.Mcp.Http.csproj
dotnet package add Microsoft.AspNetCore.Authentication.JwtBearer --version 10.0.14 --project src/Kladde.Mcp.Http/Kladde.Mcp.Http.csproj
Why not simply put /mcp into Kladde.Web? The web application signs people in with a cookie and OpenID Connect. A call without a sign-in
gets a redirect to the sign-in page there. A machine client that follows redirects then takes the sign-in page with its 200 for
success; that is the field report from K2. An MCP endpoint must answer with 401, never with 302. In a host of its own with only one
kind of sign-in, that happens by itself. If you do put the endpoint into the web application, you need an authentication scheme of its own for it that applies to /mcp.