dotnet new mcpserver, no extra install
.NET 11 is out since 10 November 2026, a Standard Term Support release with two years of support. Besides union types in C# 15 and a faster CLI it brings one small change with a large effect: the mcpserver template is now part of the SDK. Up to .NET 10 you had to install Microsoft.McpServer.ProjectTemplates first.
The template builds on the official MCP C# SDK and creates a runnable server with a sample tool. By default you get a console app that talks stdio, the transport that local clients such as VS Code, Visual Studio or Claude Code start as a child process.
dotnet new mcpserver -o OrdersMcp
dotnet new mcpserver -o OrdersMcp --transport remote| Option | Effect |
|---|---|
| -t local | Default. Console app using the stdio transport, packed as a .NET tool with PackageType McpServer for NuGet. |
| -t remote | ASP.NET Core app using the HTTP transport via MapMcp(), stateless by default. |
| --aot | Prepares the server for publishing with Native AOT. |
| --self-contained | On by default. Ships the runtime with the server, so the client machine needs no installed .NET. |
| -f net10.0 | Target framework, net11.0 by default, net10.0 for the LTS release. |
A tool is a plain method. [McpServerTool] exposes it as get_random_number, and the [Description] texts are what the model reads to decide when and how to call it. Treat them like API documentation for a very literal reader. One detail: the template references ModelContextProtocol 2.1.0, newer SDK versions are already on NuGet.
internal class RandomNumberTools
{
[McpServerTool]
[Description("Generates a random number between the specified minimum and maximum values.")]
public int GetRandomNumber(
[Description("Minimum value (inclusive)")] int min = 0,
[Description("Maximum value (exclusive)")] int max = 100)
{
return Random.Shared.Next(min, max);
}
}Where MCP fits in the .NET AI stack
MCP is one of four building blocks Microsoft names for AI in .NET. Each solves its own problem, and they combine.
Microsoft.Extensions.AI
IChatClient and IEmbeddingGenerator as one abstraction across model providers, with DI, caching, logging and OpenTelemetry as middleware.
Microsoft.Extensions.VectorData
Storing embeddings and semantic search behind one abstraction, for RAG over your own data.
Microsoft Agent Framework
Agents with tools, conversation state and multi-step workflows. Agents can use MCP tools directly.
Model Context Protocol
The open protocol between an AI client and your software. Your server exposes tools, resources and prompts, any MCP-capable agent can use them.
An MCP client is a client like any other
An MCP server is an API. The only difference to a REST API is who is calling: not your own frontend, but a language model that reads text from emails, tickets, web pages or documents and decides which tool to call with which arguments. One prepared sentence in such a text (prompt injection) is enough to make it call something nobody intended.
So the same rule applies as for every browser: the server decides what is allowed, never the client. And the remote template starts open, without any authentication. Anyone who can reach the endpoint can call every tool.
The model chooses the arguments
A parameter customerId is an invitation. Derive identity and tenant from the validated token, not from what the model passes in.
Hidden is not forbidden
Filtering tools/list is convenient, but a client can call any tool name directly. The check has to happen on the server at call time.
The server acts as the user
A tool must never do more than the signed-in user could do in your normal application. A service account with full access turns every prompt injection into a data leak.
Securing the remote template with OAuth
The MCP specification uses OAuth 2.1 for HTTP servers. Your server is the protected resource, an existing identity provider such as Entra ID, Keycloak or Auth0 issues the tokens. ModelContextProtocol.AspNetCore brings the missing pieces: AddMcp() publishes the Protected Resource Metadata (RFC 9728) so clients find the login, AddAuthorizationFilters() makes [Authorize] work on individual tools.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using ModelContextProtocol.AspNetCore.Authentication;
using ModelContextProtocol.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://login.example.com";
options.Audience = "https://mcp.example.com";
})
.AddMcp(options =>
{
options.ResourceMetadata = new ProtectedResourceMetadata
{
AuthorizationServers = ["https://login.example.com"],
ScopesSupported = ["orders.read", "orders.write"]
};
});
builder.Services.AddAuthorization(options =>
options.AddPolicy("orders.write", policy => policy.RequireClaim("scope", "orders.write")));
builder.Services
.AddMcpServer()
.WithHttpTransport(options => options.Stateless = true)
.AddAuthorizationFilters()
.WithTools<OrderTools>();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapMcp().RequireAuthorization();
app.Run();Plain ASP.NET Core does the rest. JWT Bearer validates signature, issuer and audience, RequireAuthorization() locks the whole endpoint, and one policy per scope protects individual tools. The tool itself takes a ClaimsPrincipal parameter and knows the user from the token, not from the model.
internal class OrderTools
{
[McpServerTool, Description("Lists the open orders of the signed-in customer.")]
public string[] GetOpenOrders(ClaimsPrincipal user) =>
[$"orders for {user.FindFirstValue(ClaimTypes.NameIdentifier)}"];
[McpServerTool, Description("Cancels one order of the signed-in customer.")]
[Authorize(Policy = "orders.write")]
public string CancelOrder(ClaimsPrincipal user, [Description("Order number")] string orderId) =>
$"cancel {orderId} for {user.FindFirstValue(ClaimTypes.NameIdentifier)}";
}What happens on the wire
- Without a token the server answers
401with aWWW-Authenticateheader that points to the metadata. That is how an MCP client knows where to sign in. /.well-known/oauth-protected-resourcenames the authorization server and the supported scopes.- With scope
orders.read,tools/listonly returnsget_open_orders. Addorders.writeandcancel_orderappears as well. - If the client calls
cancel_orderanyway, the server refuses. The filtered list is convenience, the check at call time is the protection. - A token issued for another application of the same identity provider gets
401, because the server only accepts its own audience. In our first test run the audience was misconfigured, and the foreign token went straight through. - The counter-example: a tool with a
customerIdparameter. Signed in as alice, the call withbobreturns bob's orders. Authentication was correct, the tool was not.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="http://localhost:6182/.well-known/oauth-protected-resource/"
GET /.well-known/oauth-protected-resource
{"resource":"http://localhost:6182","authorization_servers":["https://login.example.com"],
"bearer_methods_supported":["header"],"scopes_supported":["orders.read","orders.write"]}
tools/call cancel_order (scope orders.read)
{"error":{"code":-32600,"message":"Access forbidden: This tool requires authorization."}}
tools/list (token for https://other-app.example.com)
HTTP/1.1 401 Unauthorized
tools/call get_orders_for {"customerId":"bob"} (signed in as alice)
{"result":{"content":[{"type":"text","text":"[\"orders for bob\"]"}]}}Tested on 12 November 2026 with .NET SDK 11.0.100, the template's ModelContextProtocol.AspNetCore 2.1.0 and tokens from dotnet user-jwts.
Before your MCP server goes into production
Validate the audience
Only accept tokens issued for this server. Without audience validation, a token for another application of the same identity provider works on your MCP server too.
No token passthrough
The MCP specification forbids passing the client's token on to downstream APIs. Get a token of your own for those calls, for example via On-Behalf-Of, so every system checks its own permissions.
Small scopes per tool
Reading and writing belong in separate scopes. Clients then request only what the task needs, and a stolen token does less damage.
stdio is not a sandbox
A local server runs with the rights of the user who starts it, and the specification defines no OAuth flow for stdio. Pass secrets via environment variables, never as tool arguments, and give the server only the access it needs.
Confirmation for irreversible actions
Cancelling, deleting, paying: let a human confirm before the tool acts, in the client or via elicitation from the server, which needs the stateful HTTP mode. The model may suggest, it should not decide alone.
Log every tool call
Who called which tool with which arguments, and what came back? Logging and OpenTelemetry in ASP.NET Core make this traceable without extra infrastructure.
Frequently asked questions about MCP servers in .NET 11
Is the MCP server template included in the .NET 11 SDK?
Yes. Since .NET 11, released on 10 November 2026, dotnet new mcpserver works without installing anything. Up to .NET 10 the template came from the separate Microsoft.McpServer.ProjectTemplates package.
Does the .NET MCP server template include authentication?
No. The remote template with HTTP transport starts without authentication, so anyone who can reach the endpoint can call every tool. You add it with ASP.NET Core authentication, JWT Bearer and AddMcp() from ModelContextProtocol.AspNetCore.
How do I restrict a single MCP tool to certain users in .NET?
Call AddAuthorizationFilters() on the MCP server builder and put [Authorize] with a policy or role on the tool method or class. Users without permission do not see the tool in tools/list, and a direct call is rejected.
Why can't an MCP server trust its client?
The client is driven by a language model that reads untrusted text and chooses tools and arguments itself. A prompt injection can make it call any tool with any value, so the server has to check identity and permissions on every call, exactly as for a browser.
Do stdio MCP servers need OAuth?
No. The authorization part of the MCP specification applies to HTTP transports. A stdio server runs locally with the rights of the user who starts it and reads its credentials from the environment.
May an MCP server forward the client's access token to other APIs?
No. The MCP specification forbids token passthrough. The server only accepts tokens issued for itself and gets its own token for downstream calls, for example with the On-Behalf-Of flow.
Making your .NET software agent-ready, safely
We build MCP servers for existing .NET applications, connected to your identity provider and with exactly the permissions your users already have. No service account with full access, no tool call nobody can trace.
More on AI agents for your softwareGet In Touch
Tell us what you're planning.
You talk directly to the people who will write your code.
- You get in touch: We do our best to reply within 24 hours.
- We get to know each other: 30 minutes. You talk, we ask. No pitch.
- We decide together: How to move forward, or honestly, that we're not the right fit.