Build an MCP server in .NET 11 and connect it to Claude
Published · Last updated

Short answer
The .NET 11 SDK (11.0.100, released November 10, 2026) ships an MCP server template. Run dotnet new mcpserver -o MyMcpServer, build it, then add it to Claude Code with claude mcp add --transport stdio <name> -- dotnet run --project <path>.
We ran every step below on a fresh Ubuntu 24.04 Boat machine on 2026-11-11, then deleted the machine. Each output block is pasted as the terminal printed it. Facts come from Microsoft's .NET blog, Microsoft Learn, NuGet and Anthropic's docs, quoted and linked where they appear.
What .NET 11 is and how long it is supported
Microsoft announced .NET 11 on November 10, 2026 (.NET blog). The post says: ".NET 11 is a Standard Term Support (STS) release with two years of support under the .NET support policy." It adds that STS and LTS releases "have the same quality and servicing policies; the difference is the length of support."
The .NET support policy lists .NET 11 with an original release date of November 10, 2026, release type STS, support phase Active, and end of support on November 14, 2028. .NET 10, the LTS release, has the same end of support date. The policy also says "The maintenance support period is the final 6 months of support for any release (STS or LTS)." The same blog post notes that ".NET 8 and .NET 9 reach end of support on November 10, 2026."
On MCP, the post says: "The .NET 11 SDK includes an MCP server template. MCP gives AI applications a common way to discover and call tools and services. The template uses the official MCP C# SDK and includes a working sample tool, so you can start exposing your own functionality without first installing a separate template package". Its command is dotnet new mcpserver -o MyMcpServer, which is what we ran.
Versions we used
| Piece | Exact version | Published |
|---|---|---|
| .NET SDK (with runtime 11.0.0) | 11.0.100 | 2026-11-10 (releases.json) |
| mcpserver template inside the SDK | Microsoft.McpServer.ProjectTemplates.11.0 11.0.0 | 2026-11-10 (NuGet) |
| mcpserver template from NuGet (optional) | Microsoft.McpServer.ProjectTemplates 1.2.1 | 2026-05-08 (NuGet) |
| MCP C# SDK the template references | ModelContextProtocol 2.1.0 | 2026-08-05 (NuGet) |
| Claude Code | 2.1.316 (npm, @anthropic-ai/claude-code) | 2026-10-31 (npm) |
Step 1: install the .NET 11 SDK at an exact version
Microsoft's manual install steps for Linux download a binary release, extract it into DOTNET_ROOT and put it on PATH. We pinned SDK 11.0.100, the build listed in Microsoft's 11.0 releases.json with release date 2026-11-10, and checked the tarball against the SHA-512 in that file.
One change from Microsoft's example: it appends the SDK to the end of PATH. Our machine image already had a .NET 8 SDK at /usr/bin/dotnet, so on the first try dotnet --version printed 8.0.129 and dotnet new mcpserver failed with "No templates found matching: 'mcpserver'." Putting $DOTNET_ROOT first fixed it. Telemetry is off with DOTNET_CLI_TELEMETRY_OPTOUT=1.
DOTNET_FILE=dotnet-sdk-11.0.100-linux-x64.tar.gz
curl -fsSLO https://builds.dotnet.microsoft.com/dotnet/Sdk/11.0.100/$DOTNET_FILE
# SHA-512 copied from the 11.0.100 entry in releases.json
echo "1bec9cca2ec689b1e80245b84f2b53c9441d1aa6daccf2ceb27490768bffa95954fa585bd275def31069d874d543d9ba9e6591fe9ade2dbd9f34ec5c0fd0050c $DOTNET_FILE" | sha512sum -c
export DOTNET_ROOT=$HOME/.dotnet
mkdir -p "$DOTNET_ROOT" && tar zxf "$DOTNET_FILE" --no-same-owner -C "$DOTNET_ROOT"
export PATH=$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH
export DOTNET_CLI_TELEMETRY_OPTOUT=1
dotnet --version
dotnet --list-sdks
dotnet --list-runtimesdotnet-sdk-11.0.100-linux-x64.tar.gz: OK
11.0.100
11.0.100 [/home/user/.dotnet/sdk]
Microsoft.AspNetCore.App 11.0.0 [/home/user/.dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 11.0.0 [/home/user/.dotnet/shared/Microsoft.NETCore.App]Step 2: generate the project
The template is already in the SDK. Its templates folder holds this package, which is Microsoft.McpServer.ProjectTemplates.11.0 11.0.0 on NuGet (published 2026-11-10, owners Microsoft and dotnetframework):
ls $DOTNET_ROOT/templates/*/ | grep -i mcpmicrosoft.mcpserver.projecttemplates.11.0.11.0.0.nupkgThe first dotnet command also printed the SDK's first-run message.
dotnet new list mcpserverWelcome to .NET 11.0!
---------------------
SDK Version: 11.0.100
----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate, run 'dotnet dev-certs https --trust'
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
These templates matched your input: 'mcpserver'
Template Name Short Name Language Tags
-------------- ---------- -------- -------------
MCP Server App mcpserver [C#] Common/AI/MCPThe template options, as the SDK prints them:
dotnet new mcpserver --helpMCP Server App (C#)
Author: Microsoft
Description: A project template for creating a Model Context Protocol (MCP) server using C# and the ModelContextProtocol package.
Usage:
dotnet new mcpserver [options] [template options]
Options:
-n, --name <name> The name for the output being created. If no name is specified, the name of the output directory is used.
-o, --output <output> Location to place the generated output.
--dry-run Displays a summary of what would happen if the given command line were run if it would result in a template creation. [default: False]
--force Forces content to be generated even if it would change existing files. [default: False]
--no-update-check Disables checking for the template package updates when instantiating a template. [default: False]
--project <project> The project that should be used for context evaluation.
-lang, --language <C#> Specifies the template language to instantiate.
--type <project> Specifies the template type to instantiate.
Template options:
-t, --transport <local|remote> Whether to create a 'local' (stdio transport) or 'remote' (http transport) MCP server
Type: choice
local A console application will be created to use the stdio transport as a local MCP server
remote An ASP.NET Core web application will be created to use the http transport as a remote MCP server
Default: local
--aot Whether to enable the MCP server for publishing as a native AOT application.
Type: bool
Default: false
--self-contained Whether to enable the MCP server for publishing as a self-contained application.
Type: bool
Default: true
-f, --framework <net11.0> The target framework for the project.
Type: choice
net11.0
Default: net11.0
--no-restore If specified, skips the automatic restore of the project on create.
Type: bool
Default: falseWe kept the defaults: local transport (stdio), self-contained on, AOT off, framework net11.0.
dotnet new mcpserver -o MyMcpServerThe template "MCP Server App" was created successfully.
Processing post-creation actions...
Restoring /home/user/work/MyMcpServer/MyMcpServer.csproj:
Determining projects to restore...
Restored /home/user/work/MyMcpServer/MyMcpServer.csproj (in 22.53 sec).
Restore succeeded.The generated file tree
Listed right after creation, before the build. The obj/ files come from the restore the template runs.
find MyMcpServer -type f | sortMyMcpServer/.mcp/server.json
MyMcpServer/MyMcpServer.csproj
MyMcpServer/obj/MyMcpServer.csproj.nuget.dgspec.json
MyMcpServer/obj/MyMcpServer.csproj.nuget.g.props
MyMcpServer/obj/MyMcpServer.csproj.nuget.g.targets
MyMcpServer/obj/project.assets.json
MyMcpServer/obj/project.nuget.cache
MyMcpServer/Program.cs
MyMcpServer/README.md
MyMcpServer/Tools/RandomNumberTools.csMicrosoft's MCP server quickstart describes the main files. Program.cs is "A file defining the application as an MCP server and registering MCP services such as transport type and MCP tools." RandomNumberTools.cs is "A class defining an example MCP server tool that returns a random number between user-specified min/max values." server.json is "A file defining how and where your MCP server is published."
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// Configure all logs to go to stderr (stdout is used for the MCP protocol messages).
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
// Add the MCP services: the transport to use (stdio) and the tools to register.
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<RandomNumberTools>();
await builder.Build().RunAsync();Tools/RandomNumberTools.cs
using System.ComponentModel;
using ModelContextProtocol.Server;
/// <summary>
/// Sample MCP tools for demonstration purposes.
/// These tools can be invoked by MCP clients to perform various operations.
/// </summary>
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);
}
}MyMcpServer.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<RuntimeIdentifiers>win-x64;win-arm64;osx-arm64;linux-x64;linux-arm64;linux-musl-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Set up the NuGet package to be an MCP server -->
<PackAsTool>true</PackAsTool>
<PackageType>McpServer</PackageType>
<!-- Set up the MCP server to be a self-contained application that does not rely on a shared framework -->
<SelfContained>true</SelfContained>
<PublishSelfContained>true</PublishSelfContained>
<!-- Set up the MCP server to be a single file executable -->
<PublishSingleFile>true</PublishSingleFile>
<!-- Set recommended package metadata -->
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageId>SampleMcpServer</PackageId>
<PackageVersion>0.1.0-beta</PackageVersion>
<PackageTags>AI; MCP; server; stdio</PackageTags>
<Description>An MCP server using the MCP C# SDK.</Description>
</PropertyGroup>
<!-- Include additional files for browsing the MCP server. -->
<ItemGroup>
<None Include=".mcp\server.json" Pack="true" PackagePath="/.mcp/" />
<None Include="README.md" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="11.0.0" />
<PackageReference Include="ModelContextProtocol" Version="2.1.0" />
</ItemGroup>
</Project>The project targets six runtime identifiers and sets SelfContained and PublishSingleFile. The quickstart says that by default the command "creates a self-contained tool package targeting all of the most common platforms that .NET is supported on." The .mcp/server.json file holds placeholders such as <your package ID here> until you publish to NuGet.
Step 3: build it
dotnet build MyMcpServer Determining projects to restore...
All projects are up-to-date for restore.
MyMcpServer -> /home/user/work/MyMcpServer/bin/Debug/net11.0/linux-x64/MyMcpServer.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.51
build exit: 0Step 4: run it and call the tool
A stdio MCP server reads JSON-RPC messages on stdin and writes replies on stdout. It prints nothing until a client talks to it. To run it and see a real tool call, we used a 28-line Python client that starts dotnet run --project MyMcpServer, sends initialize, lists the tools and calls the first one with min 1 and max 100.
# Minimal MCP stdio client: initialize, tools/list, tools/call. Prints every line sent and received.
import json, subprocess, sys, signal
signal.alarm(180)
cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=open("server-stderr.log", "w"), text=True)
def send(m):
s = json.dumps(m)
print(">>> " + s, flush=True)
p.stdin.write(s + "\n"); p.stdin.flush()
def recv():
line = p.stdout.readline()
print("<<< " + line.rstrip(), flush=True)
return json.loads(line)
send({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "ayclaude-test-client", "version": "1.0.0"}}})
recv()
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
tools = recv()["result"]["tools"]
name = tools[0]["name"]
send({"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {"name": name, "arguments": {"min": 1, "max": 100}}})
recv()
p.stdin.close()
p.wait(timeout=30)
print("server exit code:", p.returncode, flush=True)python3 client.py dotnet run --project MyMcpServer>>> {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "ayclaude-test-client", "version": "1.0.0"}}}
<<< {"result":{"protocolVersion":"2025-06-18","capabilities":{"logging":{},"tools":{"listChanged":true}},"serverInfo":{"name":"MyMcpServer","version":"1.0.0.0"}},"id":1,"jsonrpc":"2.0"}
>>> {"jsonrpc": "2.0", "method": "notifications/initialized"}
>>> {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
<<< {"result":{"tools":[{"name":"get_random_number","description":"Generates a random number between the specified minimum and maximum values.","inputSchema":{"type":"object","properties":{"min":{"description":"Minimum value (inclusive)","type":"integer","default":0},"max":{"description":"Maximum value (exclusive)","type":"integer","default":100}}}}]},"id":2,"jsonrpc":"2.0"}
>>> {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "get_random_number", "arguments": {"min": 1, "max": 100}}}
<<< {"result":{"content":[{"type":"text","text":"64"}]},"id":3,"jsonrpc":"2.0"}
server exit code: 0
client exit: 0The tool is named get_random_number and returned 64. The template's Program.cs sends all logs to stderr, because stdout carries the protocol. Here is what the server wrote there during the same run:
info: ModelContextProtocol.Server.StdioServerTransport[857250842]
Server (stream) (MyMcpServer) transport reading messages.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /home/user/work
info: ModelContextProtocol.Server.McpServer[570385771]
Server (MyMcpServer 1.0.0.0) method 'initialize' request handler called.
info: ModelContextProtocol.Server.McpServer[570385771]
Server (MyMcpServer 1.0.0.0), Client (ayclaude-test-client 1.0.0) method 'tools/list' request handler called.
info: ModelContextProtocol.Server.McpServer[1867955179]
Server (MyMcpServer 1.0.0.0), Client (ayclaude-test-client 1.0.0) method 'initialize' request handler completed in 16.4853ms.
info: ModelContextProtocol.Server.McpServer[1867955179]
Server (MyMcpServer 1.0.0.0), Client (ayclaude-test-client 1.0.0) method 'tools/list' request handler completed in 10.4492ms.
info: ModelContextProtocol.Server.McpServer[570385771]
Server (MyMcpServer 1.0.0.0), Client (ayclaude-test-client 1.0.0) method 'tools/call' request handler called.
info: ModelContextProtocol.Server.McpServer[2065726448]
"get_random_number" completed. IsError = False.
info: ModelContextProtocol.Server.McpServer[1867955179]
Server (MyMcpServer 1.0.0.0), Client (ayclaude-test-client 1.0.0) method 'tools/call' request handler completed in 38.7849ms.
info: ModelContextProtocol.Server.StdioServerTransport[1221292038]
Server (stream) (MyMcpServer) transport completed reading messages.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: ModelContextProtocol.Server.StdioServerTransport[1375634372]
Server (stream) (MyMcpServer) shutting down.
info: ModelContextProtocol.Server.StdioServerTransport[1256455528]
Server (stream) (MyMcpServer) shut down.Optional: pin the template package from NuGet
Microsoft's quickstart lists the .NET 10.0 SDK as a prerequisite and installs the template with dotnet new install Microsoft.McpServer.ProjectTemplates. It notes: "The Microsoft.McpServer.ProjectTemplates template package is currently in preview." On .NET 11 you can skip this step. If you want the NuGet package anyway, pin the version. We installed 1.2.1 (published 2026-05-08, owners Microsoft and dotnetframework on NuGet) on a second clean machine and compared its output with the SDK's template.
dotnet new mcpserver -o A --no-restore # SDK template
dotnet new install Microsoft.McpServer.ProjectTemplates@1.2.1
dotnet new mcpserver -o B --no-restore # after installing 1.2.1
diff A/A.csproj B/B.csproj; echo "diff exit: $?"
diff -r A B -x '*.csproj' -x README.md; echo "diff exit: $?"The following template packages will be installed:
Microsoft.McpServer.ProjectTemplates@1.2.1
Success: Microsoft.McpServer.ProjectTemplates@1.2.1 installed the following templates:
Template Name Short Name Language Tags
-------------- ---------- -------- -------------
MCP Server App mcpserver [C#] Common/AI/MCP
diff exit: 0
diff exit: 0Both runs produced the same files. The only change we saw: after installing 1.2.1, the --framework option also offers older targets.
-f, --framework <net10.0|net11.0|net8.0|net9.0> The target framework for the project.
net11.0
net10.0 .NET 10
net9.0 .NET 9
net8.0 .NET 8
Default: net11.0Step 5: connect it to Claude Code
Anthropic's Claude Code MCP docs give the syntax for a local stdio server: claude mcp add [options] <name> -- <command> [args...]. They explain: "For stdio servers, the -- (double dash) separates Claude's own options, such as --transport, --env, and --scope, from the command and arguments that run the server." Without a scope flag, "Each command writes to local scope unless you add --scope project or --scope user."
The command after -- is the one Microsoft's quickstart puts in its mcp.json examples: dotnet run --project <path>. We installed Claude Code 2.1.316 from npm at that exact version and pointed CLAUDE_CONFIG_DIR at an empty folder, so the box's own settings would not show up in the list.
npm install --prefix ~/cc @anthropic-ai/claude-code@2.1.316
export PATH=$HOME/cc/node_modules/.bin:$PATH
export CLAUDE_CONFIG_DIR=$HOME/claude-clean
claude --version
cd ~/work
claude mcp add --transport stdio dotnet-mcp -- dotnet run --project $HOME/work/MyMcpServer
claude mcp list
claude mcp get dotnet-mcp2.1.316 (Claude Code)
Added stdio MCP server dotnet-mcp with command: dotnet run --project /home/user/work/MyMcpServer to local config
File modified: /home/user/claude-clean/.claude.json [project: /home/user/work]
exit: 0
Checking MCP server health…
dotnet-mcp: dotnet run --project /home/user/work/MyMcpServer - ✔ Connected
exit: 0
dotnet-mcp:
Scope: Local config (private to you in this project)
Status: ✔ Connected
Type: stdio
Command: dotnet
Args: run --project /home/user/work/MyMcpServer
Environment:
To remove this server, run: claude mcp remove dotnet-mcp -s local
exit: 0The docs say claude mcp list "shows a health status next to each server it lists, such as ✔ Connected, ! Needs authentication, or ✘ Failed to connect." Our server shows ✔ Connected, which means Claude Code started it and connected. For a running session, the docs list /mcp to check server status.
What we could not test
- A tool call made by Claude. That needs a logged-in Claude Code. We kept the throwaway machine free of any credentials, so the prompt stopped at login:
claude -p "Use the dotnet-mcp tool to get a random number between 1 and 100" --allowedTools mcp__dotnet-mcpNot logged in · Please run /login
exit: 1- The tool call in Step 4 went straight to the server over MCP, without Claude. On your own logged-in machine you can ask Claude Code for a random number between 1 and 100. We have not seen that run, so this page shows no output for it.
- Claude Desktop. It is a desktop app and our machine had no desktop session. Anthropic's local MCP article says Claude Desktop installs local servers as desktop extensions (
.mcpbfiles) and that "Desktop extensions support Node.js, Python, and binary MCP servers." We did not package or test this server as an extension. - The Claude API MCP connector. The connector docs say: "The server must be publicly exposed through HTTP (supports both Streamable HTTP and SSE transports). Local STDIO servers cannot be connected directly." Our server is stdio. The template's
--transport remoteoption creates an HTTP server instead; we did not build or test that variant. - Windows and macOS. We only ran linux-x64. We did not run
dotnet pack, publish to NuGet, or try the--aotoption.
Where Microsoft.Extensions.AI fits
The .NET blog splits the AI pieces this way: "Build AI-powered, agentic applications with Microsoft.Extensions.AI (MEAI) for model access, MCP for tool integration, and Microsoft Agent Framework (MAF) for agents and workflows." MEAI is for apps that call models: it "provides abstractions such as IChatClient and IEmbeddingGenerator" (.NET blog). Microsoft Learn adds that "Most consuming applications and services should reference the Microsoft.Extensions.AI package along with one or more libraries that provide concrete implementations of the abstractions" (Microsoft.Extensions.AI docs).
A server like this one does not call a model, so the template does not reference MEAI. The MCP C# SDK still brings in the abstractions: our restore pulled Microsoft.Extensions.AI.Abstractions 10.8.3 as a dependency of ModelContextProtocol 2.1.0 (from obj/project.assets.json). The ModelContextProtocol package describes itself as "The official C# SDK for the Model Context Protocol, enabling .NET applications, services, and libraries to implement and interact with MCP clients and servers."
Next steps
To write your own tool, add a method with the [McpServerTool] and [Description] attributes, the same shape as GetRandomNumber above, and register its class in Program.cs. For the model to run on the Claude side, see Claude Code with Claude Fable 5.2: setup and cost.
Sources
All fetched and checked 2026-11-11.
- Microsoft, "Announcing .NET 11", November 10, 2026: https://devblogs.microsoft.com/dotnet/announcing-dotnet-11/
- Microsoft, .NET support policy: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
- Microsoft, .NET 11.0 release metadata: https://builds.dotnet.microsoft.com/dotnet/release-metadata/11.0/releases.json
- Microsoft Learn, install .NET on Linux by using a script or by extracting binaries: https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual
- Microsoft Learn, create a minimal MCP server using C# and publish to NuGet: https://learn.microsoft.com/en-us/dotnet/ai/quickstarts/build-mcp-server
- Microsoft Learn, Microsoft.Extensions.AI libraries: https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai
- NuGet: Microsoft.McpServer.ProjectTemplates.11.0 11.0.0, Microsoft.McpServer.ProjectTemplates 1.2.1, ModelContextProtocol
- Anthropic, Claude Code MCP docs: https://code.claude.com/docs/en/mcp
- Anthropic, MCP connector (Claude API): https://platform.claude.com/docs/en/agents-and-tools/mcp-connector
- Anthropic, getting started with local MCP servers on Claude Desktop: https://support.claude.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop