---
title: Building MCP Server Using .NET
description: "Step-by-step guide to building a Model Context Protocol (MCP) server in .NET with stdio transport—JSON-RPC over stdin/stdout, tools, and Claude Desktop testing."
image: "https://wavepillars.com/learn/mcp-og.webp"
url: "https://wavepillars.com/learn/ai/build-mcp-server-stdio-dotnet/"
---

This tutorial walks through building a **Model Context Protocol (MCP) server** in .NET using **stdio transport** — the simplest path for local integrations where the client spawns your server as a child process and exchanges JSON-RPC over **stdin** and **stdout**.

For remote HTTP-based MCP, see [Build MCP Server with Streamable HTTP](/learn/ai/build-mcp-server-streamable-http-dotnet/) (recommended) or [Build MCP Server with SSE Transport](/learn/ai/build-mcp-server-sse-dotnet/) (legacy). To containerize the same stdio server, continue with [Run MCP Server in Docker](/learn/ai/run-mcp-server-in-docker/).

## Key Takeaways

- Use the MCP .NET SDK with stdio transport for local desktop clients.
- Register tools with typed schemas so clients discover capabilities at runtime.
- Pass secrets via environment variables—never embed tokens in source or logs.
- Stdio avoids TLS complexity; HTTP fits shared remote deployment.
- Containerize when you need reproducible deps—not because stdio requires it.

## Prerequisites

- .NET 8 SDK or later
- Basic familiarity with C# and the .NET CLI
- Optional: [Claude Desktop](https://claude.ai/download) for end-to-end testing
- Recommended: [What is the MCP Protocol?](/learn/ai/what-is-mcp-server/) — stdio transport and how clients spawn servers

## How stdio transport works

With stdio, there is no HTTP port and no URL. The MCP client (Claude Desktop, Cursor, VS Code, or a custom client) launches your executable and speaks JSON-RPC over the process pipes:

```
MCP client  ←JSON-RPC on stdout→  MCP Server (console app)
            ←JSON-RPC on stdin←
```

**stdout** carries the protocol stream exclusively. Any other output on stdout — including `Console.WriteLine` or default console logging — corrupts MCP. Write diagnostics to **stderr** only.

## Complete example: Echo MCP server

### Create the project

Stdio MCP servers run as a **console app**, not a web API. Create the project and add the required packages:

```bash
dotnet new console -n MyMcpServer
cd MyMcpServer
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
```

Restore locally to confirm the project compiles:

```bash
dotnet build
```

### Configure Program.cs

Replace the contents of `Program.cs`:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateEmptyApplicationBuilder(settings: null);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

await app.RunAsync();
```

`WithStdioServerTransport()` wires the server to stdin/stdout.

If you prefer attribute-based discovery instead of registering a specific type, replace `.WithTools<MyTools>()` with `.WithToolsFromAssembly()` — the SDK scans the assembly for classes marked with `[McpServerToolType]`.

### Define tools

Create a `Tools` directory and add `Tools/MyTools.cs`:

```csharp
using System.ComponentModel;
using ModelContextProtocol.Server;

namespace MyMcpServer.Tools;

[McpServerToolType]
public sealed class MyTools
{
    [McpServerTool, Description("Echoes the input back to the client.")]
    public static string Echo(string message) => message;
}
```

#### How it works

- **`[McpServerToolType]`** — marks the class as containing MCP tools
- **`[McpServerTool]`** — exposes the method as a callable tool
- **`Description`** — documents the tool and its parameters for the model; clear descriptions improve when and how the LLM invokes the tool

Tool methods can be `static` or instance-based. Parameters are mapped from JSON arguments; return values are sent back as tool result content.

## Run and smoke test

From the project root:

```bash
dotnet run
```

The process should start and **wait** — there is no URL to browse. The MCP client will launch this process and attach to stdio when configured.

**Manual JSON-RPC test:** with the server running, paste this line into the terminal and press Enter:

```text
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
```

You should see a JSON response on stdout listing available tools (for example `Echo`). If nothing appears, the server may require an `initialize` request first — see the [MCP specification](https://modelcontextprotocol.io/).

Stop the server with Ctrl+C once verified.

## Test with Claude Desktop

Claude Desktop spawns the MCP server as a subprocess. Configure it to run your .NET project with `dotnet run`.

### Configure Claude Desktop

Edit `claude_desktop_config.json`:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "dotnet",
      "args": ["run", "--project", "/absolute/path/to/MyMcpServer"]
    }
  }
}
```

Replace `/absolute/path/to/MyMcpServer` with the full path to your `.csproj` directory. On Windows, use forward slashes or escaped backslashes in the path.

For production, you can point `command` at the published DLL instead:

```json
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "dotnet",
      "args": ["/absolute/path/to/MyMcpServer/bin/Release/net8.0/MyMcpServer.dll"]
    }
  }
}
```

Run `dotnet publish -c Release` first to produce the DLL.

### Step-by-step

1. Build the project: `dotnet build`
2. Add the config above to `claude_desktop_config.json` with your project path
3. **Fully quit** Claude Desktop and relaunch (config is read at startup)
4. Open Claude and check the MCP/tools indicator (hammer icon) — `my-mcp-server` should appear as connected
5. Send a test prompt, for example: *"Use the echo tool to repeat the phrase hello mcp"*
6. Confirm Claude calls `Echo` and returns the echoed text

### Verify and troubleshoot

- **Claude logs:** Help → View Logs (or Developer settings) — look for MCP connection errors
- **Wrong path:** ensure the `--project` path points to the folder containing `MyMcpServer.csproj`
- **Stale config:** quit Claude completely (not just close the window), then relaunch
- **Logs on stdout:** use stderr for logging; polluted stdout corrupts the protocol
- **Tools not discovered:** confirm `[McpServerToolType]` and `[McpServerTool]` attributes are present and the project builds without errors

## Best practices

- Keep tool descriptions clear and specific — the model uses them to decide when to call each tool
- Validate all inputs before side effects (database writes, API calls, file changes)
- Log tool invocations to **stderr** for audit trails; never write diagnostics to stdout
- Version your server alongside client configs so tool schemas stay in sync
- Prefer stdio for local desktop integrations; use HTTP transport when the server must run remotely or serve multiple clients

## FAQ

### How do I build an MCP server with stdio in .NET?

Add the official MCP .NET SDK, implement tool handlers that expose your domain operations, and host the server using stdio transport so the client launches your process and communicates over stdin and stdout. Configure Claude Desktop or Cursor with a JSON block pointing at your published executable and required environment variables. Stdio avoids HTTP certificates and suits local development and single-user setups.

### Why choose stdio over HTTP for an MCP server?

Stdio is the simplest path for desktop AI clients that spawn subprocesses—no ports, reverse proxies, or TLS to manage. HTTP transports fit shared team servers and remote deployment. Start with stdio to prove tools and auth; graduate to Streamable HTTP when multiple users or hosts need the same backend.

### How do I pass secrets to a stdio MCP server safely?

Use environment variables referenced in the client config—not hard-coded tokens in source. Claude Desktop supports env blocks per server entry. Never log secrets; redact tool arguments in diagnostics. Rotate credentials if configs leak into screenshots or shared dotfiles.

### Can I run the same .NET MCP server in Docker with stdio?

Yes. Containerize the published binary and point the client at docker run with interactive stdio flags. Docker adds isolation and reproducible dependencies; see the Docker guide for Claude Desktop wiring patterns. Remote multi-user production usually moves to Streamable HTTP instead.

## Internal Links

- [What is the MCP Protocol?](/learn/ai/what-is-mcp-server/)
- [Build MCP Server with Streamable HTTP in .NET](/learn/ai/build-mcp-server-streamable-http-dotnet/)
- [Run MCP Server in Docker](/learn/ai/run-mcp-server-in-docker/)
- [MCP for business integrations](/learn/ai/mcp-accelerate-business-growth/)
- [Build MCP Server with SSE Transport in .NET](/learn/ai/build-mcp-server-sse-dotnet/)

```json
{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","headline":"Building MCP Server Using .NET","description":"Step-by-step guide to building a Model Context Protocol (MCP) server in .NET with stdio transport—JSON-RPC over stdin/stdout, tools, and Claude Desktop testing.","datePublished":"2025-07-25","dateModified":"2026-07-11","url":"https://wavepillars.com/learn/ai/build-mcp-server-stdio-dotnet/","author":{"@type":"Person","name":"Kiryl Bahdanovich"},"publisher":{"@type":"Organization","name":"WAVEPILLARS","url":"https://wavepillars.com/"},"image":"https://wavepillars.com/learn/mcp-og.webp","timeRequired":"PT15M"},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"How do I build an MCP server with stdio in .NET?","acceptedAnswer":{"@type":"Answer","text":"Add the official MCP .NET SDK, implement tool handlers that expose your domain operations, and host the server using stdio transport so the client launches your process and communicates over stdin and stdout. Configure Claude Desktop or Cursor with a JSON block pointing at your published executable and required environment variables. Stdio avoids HTTP certificates and suits local development and single-user setups."}},{"@type":"Question","name":"Why choose stdio over HTTP for an MCP server?","acceptedAnswer":{"@type":"Answer","text":"Stdio is the simplest path for desktop AI clients that spawn subprocesses—no ports, reverse proxies, or TLS to manage. HTTP transports fit shared team servers and remote deployment. Start with stdio to prove tools and auth; graduate to Streamable HTTP when multiple users or hosts need the same backend."}},{"@type":"Question","name":"How do I pass secrets to a stdio MCP server safely?","acceptedAnswer":{"@type":"Answer","text":"Use environment variables referenced in the client config—not hard-coded tokens in source. Claude Desktop supports env blocks per server entry. Never log secrets; redact tool arguments in diagnostics. Rotate credentials if configs leak into screenshots or shared dotfiles."}},{"@type":"Question","name":"Can I run the same .NET MCP server in Docker with stdio?","acceptedAnswer":{"@type":"Answer","text":"Yes. Containerize the published binary and point the client at docker run with interactive stdio flags. Docker adds isolation and reproducible dependencies; see the Docker guide for Claude Desktop wiring patterns. Remote multi-user production usually moves to Streamable HTTP instead."}}]}]}
```