Build Your First MCP Server in C#: A Step-by-Step Quickstart
If you want to build an MCP server in C#, the fastest useful path is a local stdio server: a console app, the official C# SDK, a generic host, and one tool that a compatible AI host can discover and call. This quickstart shows how to build an MCP server in C# without jumping into remote transport, auth, or deployment. Just the first working MCP server C# developers can run locally and wire into VS Code or Claude Desktop.
Model Context Protocol gives AI hosts a standard way to call tools exposed by external processes. For .NET developers, that means you can take familiar pieces like Host.CreateApplicationBuilder, dependency injection, logging, and NuGet packages, then expose a narrow set of capabilities through MCP. If you're already exploring agent tooling, the ideas pair naturally with Microsoft Agent Framework in C#, but this article stays focused on the server side.
On July 5, 2026, the latest stable NuGet version I verified for ModelContextProtocol, ModelContextProtocol.Core, and ModelContextProtocol.AspNetCore is 1.4.0. The NuGet feed also lists 2.0.0-preview.1, but this walkthrough uses stable APIs from the official MCP C# SDK, the Microsoft Learn MCP server quickstart, and the broader Model Context Protocol documentation. Check NuGet again before publishing because the SDK is moving quickly.
Before You Build an MCP Server in C#, Know the Stdio Shape
A local stdio MCP server is launched as a child process by a host. The host writes JSON-RPC messages to your process through standard input. Your server writes JSON-RPC responses back through standard output. That design is simple and portable, but it has one gotcha that trips up first-time implementations: stdout is not for logs.
For this quickstart, the tradeoff is deliberate. Stdio is the easiest way to create MCP server .NET code that works with local developer hosts. You do not need ASP.NET Core, Kestrel, CORS, OAuth, or a public endpoint. You do need to keep your process output clean because any random Console.WriteLine, banner text, debug output, or default logger writing to stdout can corrupt the protocol stream.
That makes the first decision straightforward. Use ModelContextProtocol for a local stdio server. Use Microsoft.Extensions.Hosting so setup feels like the rest of modern .NET. Keep logs on stderr. Keep tools small, descriptive, and safe to call.
If you want a deeper mental model for exposing callable functions to agents, Function Tools with AIFunctionFactory in Microsoft Agent Framework is a useful adjacent concept. MCP is the wire protocol and server contract here. The host decides when to invoke your tool.
Create the Console Project for an MCP Server C# Quickstart
Start with a normal console application. The official SDK docs show this as the minimal path for a stdio server, and it is still the simplest way to build an MCP server in C# without pulling in unrelated hosting concerns.
dotnet new console -n FirstMcpServer
cd FirstMcpServer
dotnet add package ModelContextProtocol --version 1.4.0
dotnet add package Microsoft.Extensions.Hosting
You can omit the explicit version if you want NuGet to resolve the latest stable package at install time. I prefer pinning while learning because it makes the quickstart reproducible. Once you understand the moving parts, you can update intentionally and verify breaking changes against the SDK release notes.
The package choice matters. ModelContextProtocol.Core is the leaner package for client and low-level APIs. ModelContextProtocol.AspNetCore is for HTTP-based MCP servers. For this local MCP server example, ModelContextProtocol is the right package because it includes hosting and dependency injection extensions, stdio transport support, and attribute-based discovery.
That package layout lines up with normal NuGet decision-making. If you need a refresher on how package versions and prereleases affect .NET projects, NuGet versioning and Semantic Versioning in .NET is directly relevant.
Add Host.CreateApplicationBuilder and the Stdio Transport
Replace Program.cs with the smallest useful server. This code creates the host, configures logging, registers the MCP server, selects stdio transport, and discovers tools from the current assembly.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(options =>
{
// Stdio MCP reserves stdout for JSON-RPC messages.
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
[McpServerToolType]
public static class EchoTools
{
[McpServerTool]
[Description("Echoes the provided message back to the caller.")]
public static string Echo(
[Description("The message to echo.")] string message) =>
$"Hello from C#: {message}";
}
This is the heart of the quickstart. Host.CreateApplicationBuilder(args) gives you standard .NET hosting behavior. AddMcpServer() registers the server services. WithStdioServerTransport() tells the SDK to communicate over stdin and stdout. WithToolsFromAssembly() scans the assembly for tool types and tool methods.
The attributes are the discovery contract. [McpServerToolType] marks a class that contains tools. [McpServerTool] marks a method that can be called. [Description] gives the host and model useful language about what the tool does and what each argument means. The official tool docs describe the attribute-based approach as the common path, and it is a good fit for a first MCP server C# quickstart.
Notice what this example does not do. It does not define resources. It does not define prompts. It does not explain every supported parameter or return type. Those are important, but they belong in deeper articles. For the first server, the goal is one callable tool with a clear description.
Write a Slightly More Useful First Tool
An echo tool proves that the server works, but a realistic first tool should do something useful without touching production systems. Here is a small developer-oriented tool that formats a work item summary. It accepts a title, priority, and optional note, then returns plain text that an AI host can use in a response.
using ModelContextProtocol.Server;
using System.ComponentModel;
[McpServerToolType]
public static class DeveloperTools
{
[McpServerTool]
[Description("Formats a concise engineering work item summary.")]
public static string FormatWorkItem(
[Description("The short title of the work item.")] string title,
[Description("The priority from 1 to 5, where 1 is highest.")] int priority,
[Description("Optional implementation note or risk to include.")] string? note = null)
{
var normalizedPriority = Math.Clamp(priority, 1, 5);
var noteLine = string.IsNullOrWhiteSpace(note)
? "No additional implementation notes."
: note.Trim();
return $"Title: {title.Trim()}
Priority: P{normalizedPriority}
Note: {noteLine}";
}
}
This is still intentionally modest. It uses simple parameter types, returns a string, and avoids side effects. That makes it safe for a first MCP server example while still showing the shape of a real tool. The parameter descriptions are part of the contract, not decoration. A vague parameter like text gives the model less guidance than The short title of the work item.
When you build an MCP server in C#, keep early tools boring on purpose. Read-only or formatting tools are easier to test, easier to trust, and easier to debug. Once you have confidence in discovery, invocation, and logging, you can evaluate tools that call APIs or touch local files. If that tool will mutate state, use human approval patterns. The safety mindset overlaps with tool approval and human-in-the-loop in Microsoft Agent Framework, even though the mechanics differ.
Run the MCP Server Locally Without Breaking JSON-RPC
You can run the project from the command line:
dotnet run --project .FirstMcpServer.csproj
Do not expect a normal web server message or a friendly prompt. A stdio MCP server is waiting for protocol messages on stdin. If you run it directly, it may appear to sit there doing nothing. That can be correct. The meaningful test is whether a host can start it, initialize the MCP session, list tools, and call one.
The most important first-run rule is this: do not write anything to stdout except the JSON-RPC stream managed by the SDK. Avoid Console.WriteLine in startup code. Avoid printing ASCII banners. Avoid using a logger that writes informational output to stdout. The quickstart code sets LogToStandardErrorThreshold = LogLevel.Trace so every log level goes to stderr instead.
This is also where general .NET logging knowledge helps. If you later add structured logging or a different provider, preserve the same boundary. Stdio protocol messages stay on stdout. Logs and diagnostics go to stderr. For broader logging patterns, Logging in .NET: The Complete Developer's Guide is a good companion read.
Wire the MCP Server into VS Code mcp.json
To actually use the server, configure a host to launch it. In VS Code, you can define an MCP server in an mcp.json file. The exact location can vary by workspace or user configuration, but the important part is the stdio command and arguments.
{
"servers": {
"first-mcp-server": {
"type": "stdio",
"command": "dotnet",
"args": [
"run",
"--project",
"C:\dev\FirstMcpServer\FirstMcpServer.csproj"
]
}
}
}
Use the absolute path to your project file. If the path contains spaces, keep it as a single JSON string. After the host reloads the configuration, it should be able to start the process and discover tools such as Echo and FormatWorkItem.
There is a practical tradeoff here. dotnet run --project is convenient while developing because you can edit code and restart the host. For a more stable local setup, you can publish the server and point the host at the compiled executable instead. That avoids restore and build work during host startup. For the first quickstart, dotnet run is fine because it keeps the edit-run loop simple.
Wire the Same MCP Server into Claude Desktop
Claude Desktop uses a JSON configuration shape that is similar in spirit: name the server, provide a command, and provide the arguments needed to launch it. For a .NET console project, the command can also be dotnet.
{
"mcpServers": {
"first-mcp-server": {
"command": "dotnet",
"args": [
"run",
"--project",
"C:\dev\FirstMcpServer\FirstMcpServer.csproj"
]
}
}
}
Restart the host after changing the config. If the server starts correctly, the host can initialize the MCP connection and discover the tools. If the server fails at startup, the host may show a connection error rather than a .NET exception, so checking stderr logs becomes important.
For this article, VS Code and Claude Desktop are just host examples. The server contract is MCP. The same local stdio pattern can be consumed by other compatible clients as long as they can launch your process and speak the protocol.
Common First-Run Errors When You Create MCP Server .NET Projects
The first class of failure is protocol corruption. If the host reports an invalid JSON message, unexpected token, failed initialization, or similar parse error, check for stdout pollution first. Search your code for Console.WriteLine. Look for logging providers that write to stdout. Remove startup banners. Keep the AddConsole configuration that sends logs to stderr.
The second class is tool discovery. If the host connects but does not show your tools, confirm three things. The class must have [McpServerToolType]. The method must have [McpServerTool]. The server registration must include .WithToolsFromAssembly(). If you moved tools into a different assembly, assembly scanning may not find them the way you expect.
The third class is launch configuration. If the host cannot start the server, run the same command manually from a terminal. Verify the project path. Verify the SDK is installed. Verify dotnet restore succeeds. If your path is wrong in JSON, the host cannot fix that for you.
The fourth class is package drift. If copied code does not compile, check the installed ModelContextProtocol package version. The official SDK is evolving. For this quickstart, I verified AddMcpServer(), WithStdioServerTransport(), WithToolsFromAssembly(), [McpServerToolType], and [McpServerTool] against the official C# SDK docs and stable 1.4.0 package feed. If you intentionally install a preview, validate against that preview's docs.
When This MCP Server Example Is Enough, and When It Is Not
This local stdio server is enough when you want a personal or team developer tool that runs beside an IDE or desktop AI host. It is a good starting point for repository helpers, local diagnostics, formatting utilities, read-only lookup tools, and wrappers around safe internal workflows.
It is not enough when you need a shared remote service, browser access, centralized auth, multi-tenant controls, or cloud deployment. Those requirements push you toward ASP.NET Core, Streamable HTTP, host filtering, CORS decisions, authentication, and operational telemetry. Those are real topics, but adding them here would make the first-server path harder to follow.
If your future MCP tool calls HTTP APIs, the fundamentals from HttpClient in C#: The Complete Guide for .NET Developers still matter. If you package the server for others, the mechanics from The Complete Guide to Creating NuGet Packages in .NET become relevant. But neither is required to build an MCP server in C# for your first local run.
Quick Checklist to Build an MCP Server in C# That Actually Starts
Before you debug the host, walk through this checklist in the project itself. A local MCP server C# project has a small number of moving parts, so most first-run issues are visible quickly.
- The project is a console app that builds successfully.
ModelContextProtocolandMicrosoft.Extensions.Hostingare installed.Program.csusesHost.CreateApplicationBuilder(args).- Logging sends console output to stderr with
LogToStandardErrorThreshold. - Services call
AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly(). - Tool classes use
[McpServerToolType]. - Tool methods use
[McpServerTool]and useful[Description]attributes. - The host config points to the correct project or executable path.
That checklist is intentionally not fancy. The goal is a repeatable baseline. Once this works, you can start making design decisions instead of fighting startup mechanics.
FAQ: Build an MCP Server in C#
What package do I need to build an MCP server in C#?
For the local stdio walkthrough in this article, use ModelContextProtocol plus Microsoft.Extensions.Hosting. The ModelContextProtocol package includes the hosting and dependency injection extensions used by AddMcpServer(), WithStdioServerTransport(), and WithToolsFromAssembly(). You do not need ModelContextProtocol.AspNetCore unless you are building an HTTP-based server.
Why does my MCP server break when I use Console.WriteLine?
A stdio MCP server uses stdout for JSON-RPC protocol messages. Console.WriteLine writes to stdout by default, so it can mix ordinary text into the protocol stream. The host then tries to parse that text as JSON and fails. Send logs to stderr instead, and avoid direct console output in server startup and tools.
Do I need ASP.NET Core for this MCP server example?
No. This MCP server example is a local stdio server, so a console app is enough. ASP.NET Core becomes relevant when you move to remote HTTP transport, which has different decisions around hosting, security, CORS, and deployment. Those topics are intentionally outside this first-server quickstart.
How does WithToolsFromAssembly find my tools?
WithToolsFromAssembly() scans the current assembly for classes marked with [McpServerToolType] and methods marked with [McpServerTool]. If tools do not appear in the host, check both attributes first. Also check whether the tool class lives in a different assembly than the one being scanned.
Should my first MCP tool call a database or production API?
Probably not for the first run. Start with a read-only or formatting tool so you can verify startup, discovery, invocation, logging, and host configuration. After that baseline works, evaluate any API or database access with normal engineering care: least privilege, safe defaults, clear descriptions, and human approval for risky actions.
Can I use dependency injection in an MCP server C# project?
Yes. The SDK integrates with Microsoft.Extensions.DependencyInjection through the host setup. Keep the first version simple, but registering services behind your tools is a natural next step when the tool needs existing application logic. Just avoid turning the first quickstart into a full application architecture exercise.
Conclusion: Keep the First MCP Server Boring
The easiest way to build an MCP server in C# is to keep the first version boring: console app, ModelContextProtocol, Microsoft.Extensions.Hosting, stdio transport, assembly-discovered tools, clear descriptions, and logs routed to stderr. That gives you a working baseline without dragging in remote hosting, auth, deployment, or a full schema-design discussion.
Once that baseline works in VS Code or Claude Desktop, you can make informed tradeoffs. Add more useful tools. Decide whether the server should stay local. Decide whether it needs packaging. Decide whether it should ever become remote. But get the stdio server working first because it proves the contract end to end.

