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 of WithStdioServerTransport().
  • app.MapMcp("/mcp") creates the endpoint. It returns an IEndpointConventionBuilder, and to that you attach RequireAuthorization, 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:

Bash
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.