MCP C# SDK for .NET
Trigger On
- building or consuming MCP servers from a .NET application or library
- choosing between stdio and HTTP transport for MCP
- exposing tools, resources, prompts, completions, or logging to an MCP host
- connecting a .NET app to an existing MCP server and passing discovered tools into
IChatClient
- bootstrapping a minimal MCP client/server from the
.NET AI quickstarts or publishing a server to the MCP Registry
- implementing capability-aware flows such as roots, sampling, elicitation, subscriptions, or session resumption
Use This Skill Instead Of
- Use
dotnet-mcp when protocol interoperability is the requirement.
- Use
dotnet-microsoft-extensions-ai when you only need model/provider abstraction or local tool orchestration without the MCP wire protocol.
- Use
dotnet-microsoft-agent-framework when the main problem is agent orchestration; combine it with dotnet-mcp only when those agents must consume or expose MCP endpoints.
- Use the
.NET AI quickstarts for the very first vertical slice, then come back here to harden transport, capability negotiation, publishing, and host interoperability.
Documentation
References
Load only what the task needs:
references/patterns.md - current server/client patterns, transports, capabilities, filters, and chat-client integration
references/security.md - safe error handling, auth boundaries, stdio logging hygiene, and defensive tool/resource patterns
Package Selection
| Package |
Choose when |
ModelContextProtocol.Core |
You only need a client or low-level server APIs and want the smallest dependency set. |
ModelContextProtocol |
You want the main SDK package with hosting, DI, attribute discovery, and stdio server support. Start here for most projects. |
ModelContextProtocol.AspNetCore |
You are hosting a remote MCP server in ASP.NET Core over HTTP. This includes the main package. |
Transport Selection
| Transport |
Use when |
Notes |
StdioClientTransport / WithStdioServerTransport() |
The MCP server should run as a local child process. |
Best for local tooling and editor/agent integrations. |
HttpClientTransport + HttpTransportMode.StreamableHttp |
The server is remote or should be reachable over HTTP. |
Recommended HTTP transport; supports streaming and session resumption. |
HttpTransportMode.Sse |
You must connect to an older SSE-only server. |
Legacy compatibility only; do not choose this for new servers. |
flowchart LR
A["Need MCP interoperability in .NET"] --> B{"Role?"}
B -->|"Expose MCP surface"| C{"Where will it run?"}
B -->|"Consume an MCP server"| D{"Transport?"}
C -->|"Local child process"| E["ModelContextProtocol\nAddMcpServer()\nWithStdioServerTransport()"]
C -->|"Remote HTTP endpoint"| F["ModelContextProtocol.AspNetCore\nAddMcpServer()\nWithHttpTransport()\nMapMcp()"]
D -->|"stdio"| G["StdioClientTransport\nMcpClient.CreateAsync()"]
D -->|"HTTP"| H["HttpClientTransport\nAutoDetect or StreamableHttp"]
E --> I["Register tools/resources/prompts"]
F --> I
G --> J["Check ServerCapabilities\nbefore optional features"]
H --> J
Workflow
Pick the package and transport first.
- Local child-process server:
ModelContextProtocol + WithStdioServerTransport().
- Remote server:
ModelContextProtocol.AspNetCore + WithHttpTransport() + MapMcp().
- Client-only app: start with
ModelContextProtocol or ModelContextProtocol.Core.
- Registry distribution: pair a minimal server with the MCP Registry publishing flow only after the server contract is stable.
Model the MCP surface explicitly.
- Tools:
[McpServerToolType] + [McpServerTool]
- Resources:
[McpServerResourceType] + [McpServerResource]
- Prompts:
[McpServerPromptType] + [McpServerPrompt]
- Use custom handlers or filters only for cross-cutting behavior, protocol extensions, or advanced routing.
Prefer attribute discovery for straightforward servers.
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 =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
[McpServerToolType]
public static class EchoTool
{
[McpServerTool, Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"hello {message}";
}
- For HTTP servers, use the ASP.NET Core transport and map the endpoint directly.
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp("/mcp");
app.Run();
[McpServerToolType]
public static class EchoTool
{
[McpServerTool, Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"hello {message}";
}
- When consuming a server, use
McpClient.CreateAsync(...) and stay capability-aware.
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "Everything",
Command = "npx",
Arguments = ["-y", "@modelcontextprotocol/server-everything"],
});
await using var client = await McpClient.CreateAsync(transport);
IList<McpClientTool> tools = await client.ListToolsAsync();
if (client.ServerCapabilities.Prompts is not null)
{
var prompts = await client.ListPromptsAsync();
}
Treat optional features as negotiated capabilities, not assumptions.
- Client capabilities: configure
McpClientOptions.Capabilities for roots, sampling, and elicitation.
- Server capabilities are inferred from registered features.
- Check
client.ServerCapabilities before using completions, logging, prompt list-change notifications, or resource subscriptions.
- Use
client.NegotiatedProtocolVersion or server.NegotiatedProtocolVersion only when version-specific behavior matters.
Keep HTTP guidance current.
- Streamable HTTP is the recommended transport for remote servers.
MapMcp() also serves SSE compatibility endpoints for older clients.
- HTTP clients can use
AutoDetect by default, or force StreamableHttp / Sse.
- Session resumption is available for Streamable HTTP through
McpClient.ResumeSessionAsync(...).
Treat the .NET AI MCP quickstarts as bootstrap examples.
build-mcp-client and build-mcp-server are good starting points when the surrounding app is still MEAI-centric.
publish-mcp-registry is the distribution step, not the design step. Stabilize the protocol surface before publishing.
Respect current error and serialization rules.
- Tool exceptions normally come back as
CallToolResult.IsError == true.
- Throw
McpProtocolException only for protocol-level JSON-RPC failures.
McpClientTool inherits from AIFunction, so discovered tools can be passed directly into IChatClient.
- Experimental APIs use
MCPEXP... diagnostics; suppress them intentionally, not globally by accident.
- If you use a custom
JsonSerializerContext, prepend McpJsonUtilities.DefaultOptions.TypeInfoResolver so MCP protocol types keep the SDK's contract.
Anti-Patterns To Avoid
| Anti-pattern |
Why it causes trouble |
Better approach |
| Picking HTTP transport for a purely local child-process scenario |
Adds unnecessary hosting, auth, and deployment surface |
Use stdio for local/editor-hosted integrations |
| Treating SSE as the default remote transport |
Locks new work to legacy behavior |
Prefer Streamable HTTP and keep SSE only for backward compatibility |
Writing tools without [Description] metadata |
Hosts and models lose schema clarity |
Describe tool purpose and parameters explicitly |
| Returning huge binary/text payloads from every tool call |
Bloats context and slows hosts |
Return focused content and move large data to resources |
| Logging to stdout on stdio servers |
Corrupts the protocol stream |
Send logs to stderr |
| Assuming prompts/resources/logging/completions exist |
Breaks against partial implementations |
Check negotiated capabilities first |
| Using filters for normal business logic |
Makes handlers opaque and hard to reason about |
Keep filters for cross-cutting policy, audit, or protocol plumbing |
Deliver
- a correctly packaged MCP server or client that matches the deployment topology
- explicit tool/resource/prompt definitions with descriptions and bounded payloads
- capability-aware handling for optional MCP features
- validation notes for transport, auth boundary, and host/client interoperability
Validate
- chosen package matches the topology:
Core, ModelContextProtocol, or AspNetCore
- stdio servers do not write logs or diagnostics to stdout
- HTTP servers use
MapMcp() and are tested at the final route, for example /mcp
- tools, resources, and prompts use current
[McpServer*] attributes or documented handler/filter alternatives
- client code checks
ServerCapabilities before using subscriptions, completions, logging, or prompt/resource list-change flows
- Streamable HTTP is the default for new remote servers; SSE is used only for legacy compatibility
- experimental APIs and custom serialization settings are reviewed intentionally rather than copied blindly
1---2name: dotnet-mcp3description: Build or consume Model Context Protocol (MCP) servers and clients in .NET using the official MCP C# SDK, including stdio, Streamable HTTP, tools, prompts, resources, and capability negotiation.4---56# MCP C# SDK for .NET78## Trigger On910- building or consuming MCP servers from a .NET application or library11- choosing between stdio and HTTP transport for MCP12- exposing tools, resources, prompts, completions, or logging to an MCP host13- connecting a .NET app to an existing MCP server and passing discovered tools into `IChatClient`14- bootstrapping a minimal MCP client/server from the `.NET AI` quickstarts or publishing a server to the MCP Registry15- implementing capability-aware flows such as roots, sampling, elicitation, subscriptions, or session resumption1617## Use This Skill Instead Of1819- Use `dotnet-mcp` when **protocol interoperability** is the requirement.20- Use `dotnet-microsoft-extensions-ai` when you only need model/provider abstraction or local tool orchestration without the MCP wire protocol.21- Use `dotnet-microsoft-agent-framework` when the main problem is agent orchestration; combine it with `dotnet-mcp` only when those agents must consume or expose MCP endpoints.22- Use the `.NET AI` quickstarts for the very first vertical slice, then come back here to harden transport, capability negotiation, publishing, and host interoperability.2324## Documentation2526- [MCP C# SDK overview](https://csharp.sdk.modelcontextprotocol.io/)27- [Getting Started](https://csharp.sdk.modelcontextprotocol.io/concepts/getting-started.html)28- [API reference](https://csharp.sdk.modelcontextprotocol.io/api/ModelContextProtocol.html)29- [Conceptual docs](https://csharp.sdk.modelcontextprotocol.io/concepts/index.html)30- [Versioning policy](https://csharp.sdk.modelcontextprotocol.io/versioning.html)31- [Experimental APIs](https://csharp.sdk.modelcontextprotocol.io/experimental.html)32- [MCP C# SDK repository](https://github.com/modelcontextprotocol/csharp-sdk)33- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/)3435## References3637Load only what the task needs:3839- [`references/patterns.md`](references/patterns.md) - current server/client patterns, transports, capabilities, filters, and chat-client integration40- [`references/security.md`](references/security.md) - safe error handling, auth boundaries, stdio logging hygiene, and defensive tool/resource patterns4142## Package Selection4344| Package | Choose when |45|---------|-------------|46| `ModelContextProtocol.Core` | You only need a client or low-level server APIs and want the smallest dependency set. |47| `ModelContextProtocol` | You want the main SDK package with hosting, DI, attribute discovery, and stdio server support. Start here for most projects. |48| `ModelContextProtocol.AspNetCore` | You are hosting a remote MCP server in ASP.NET Core over HTTP. This includes the main package. |4950## Transport Selection5152| Transport | Use when | Notes |53|-----------|----------|-------|54| `StdioClientTransport` / `WithStdioServerTransport()` | The MCP server should run as a local child process. | Best for local tooling and editor/agent integrations. |55| `HttpClientTransport` + `HttpTransportMode.StreamableHttp` | The server is remote or should be reachable over HTTP. | Recommended HTTP transport; supports streaming and session resumption. |56| `HttpTransportMode.Sse` | You must connect to an older SSE-only server. | Legacy compatibility only; do not choose this for new servers. |5758```mermaid59flowchart LR60 A["Need MCP interoperability in .NET"] --> B{"Role?"}61 B -->|"Expose MCP surface"| C{"Where will it run?"}62 B -->|"Consume an MCP server"| D{"Transport?"}63 C -->|"Local child process"| E["ModelContextProtocol\nAddMcpServer()\nWithStdioServerTransport()"]64 C -->|"Remote HTTP endpoint"| F["ModelContextProtocol.AspNetCore\nAddMcpServer()\nWithHttpTransport()\nMapMcp()"]65 D -->|"stdio"| G["StdioClientTransport\nMcpClient.CreateAsync()"]66 D -->|"HTTP"| H["HttpClientTransport\nAutoDetect or StreamableHttp"]67 E --> I["Register tools/resources/prompts"]68 F --> I69 G --> J["Check ServerCapabilities\nbefore optional features"]70 H --> J71```7273## Workflow74751. Pick the package and transport first.76 - Local child-process server: `ModelContextProtocol` + `WithStdioServerTransport()`.77 - Remote server: `ModelContextProtocol.AspNetCore` + `WithHttpTransport()` + `MapMcp()`.78 - Client-only app: start with `ModelContextProtocol` or `ModelContextProtocol.Core`.79 - Registry distribution: pair a minimal server with the MCP Registry publishing flow only after the server contract is stable.80812. Model the MCP surface explicitly.82 - Tools: `[McpServerToolType]` + `[McpServerTool]`83 - Resources: `[McpServerResourceType]` + `[McpServerResource]`84 - Prompts: `[McpServerPromptType]` + `[McpServerPrompt]`85 - Use custom handlers or filters only for cross-cutting behavior, protocol extensions, or advanced routing.86873. Prefer attribute discovery for straightforward servers.8889```csharp90using Microsoft.Extensions.DependencyInjection;91using Microsoft.Extensions.Hosting;92using Microsoft.Extensions.Logging;93using ModelContextProtocol.Server;94using System.ComponentModel;9596var builder = Host.CreateApplicationBuilder(args);97builder.Logging.AddConsole(options =>98{99 options.LogToStandardErrorThreshold = LogLevel.Trace;100});101102builder.Services103 .AddMcpServer()104 .WithStdioServerTransport()105 .WithToolsFromAssembly();106107await builder.Build().RunAsync();108109[McpServerToolType]110public static class EchoTool111{112 [McpServerTool, Description("Echoes the message back to the client.")]113 public static string Echo(string message) => $"hello {message}";114}115```1161174. For HTTP servers, use the ASP.NET Core transport and map the endpoint directly.118119```csharp120using ModelContextProtocol.Server;121using System.ComponentModel;122123var builder = WebApplication.CreateBuilder(args);124125builder.Services126 .AddMcpServer()127 .WithHttpTransport()128 .WithToolsFromAssembly();129130var app = builder.Build();131app.MapMcp("/mcp");132app.Run();133134[McpServerToolType]135public static class EchoTool136{137 [McpServerTool, Description("Echoes the message back to the client.")]138 public static string Echo(string message) => $"hello {message}";139}140```1411425. When consuming a server, use `McpClient.CreateAsync(...)` and stay capability-aware.143144```csharp145using ModelContextProtocol.Client;146using ModelContextProtocol.Protocol;147148var transport = new StdioClientTransport(new StdioClientTransportOptions149{150 Name = "Everything",151 Command = "npx",152 Arguments = ["-y", "@modelcontextprotocol/server-everything"],153});154155await using var client = await McpClient.CreateAsync(transport);156157IList<McpClientTool> tools = await client.ListToolsAsync();158159if (client.ServerCapabilities.Prompts is not null)160{161 var prompts = await client.ListPromptsAsync();162}163```1641656. Treat optional features as negotiated capabilities, not assumptions.166 - Client capabilities: configure `McpClientOptions.Capabilities` for roots, sampling, and elicitation.167 - Server capabilities are inferred from registered features.168 - Check `client.ServerCapabilities` before using completions, logging, prompt list-change notifications, or resource subscriptions.169 - Use `client.NegotiatedProtocolVersion` or `server.NegotiatedProtocolVersion` only when version-specific behavior matters.1701717. Keep HTTP guidance current.172 - Streamable HTTP is the recommended transport for remote servers.173 - `MapMcp()` also serves SSE compatibility endpoints for older clients.174 - HTTP clients can use `AutoDetect` by default, or force `StreamableHttp` / `Sse`.175 - Session resumption is available for Streamable HTTP through `McpClient.ResumeSessionAsync(...)`.1761778. Treat the `.NET AI` MCP quickstarts as bootstrap examples.178 - `build-mcp-client` and `build-mcp-server` are good starting points when the surrounding app is still MEAI-centric.179 - `publish-mcp-registry` is the distribution step, not the design step. Stabilize the protocol surface before publishing.1801819. Respect current error and serialization rules.182 - Tool exceptions normally come back as `CallToolResult.IsError == true`.183 - Throw `McpProtocolException` only for protocol-level JSON-RPC failures.184 - `McpClientTool` inherits from `AIFunction`, so discovered tools can be passed directly into `IChatClient`.185 - Experimental APIs use `MCPEXP...` diagnostics; suppress them intentionally, not globally by accident.186 - If you use a custom `JsonSerializerContext`, prepend `McpJsonUtilities.DefaultOptions.TypeInfoResolver` so MCP protocol types keep the SDK's contract.187188## Anti-Patterns To Avoid189190| Anti-pattern | Why it causes trouble | Better approach |191|--------------|-----------------------|-----------------|192| Picking HTTP transport for a purely local child-process scenario | Adds unnecessary hosting, auth, and deployment surface | Use stdio for local/editor-hosted integrations |193| Treating SSE as the default remote transport | Locks new work to legacy behavior | Prefer Streamable HTTP and keep SSE only for backward compatibility |194| Writing tools without `[Description]` metadata | Hosts and models lose schema clarity | Describe tool purpose and parameters explicitly |195| Returning huge binary/text payloads from every tool call | Bloats context and slows hosts | Return focused content and move large data to resources |196| Logging to stdout on stdio servers | Corrupts the protocol stream | Send logs to stderr |197| Assuming prompts/resources/logging/completions exist | Breaks against partial implementations | Check negotiated capabilities first |198| Using filters for normal business logic | Makes handlers opaque and hard to reason about | Keep filters for cross-cutting policy, audit, or protocol plumbing |199200## Deliver201202- a correctly packaged MCP server or client that matches the deployment topology203- explicit tool/resource/prompt definitions with descriptions and bounded payloads204- capability-aware handling for optional MCP features205- validation notes for transport, auth boundary, and host/client interoperability206207## Validate208209- chosen package matches the topology: `Core`, `ModelContextProtocol`, or `AspNetCore`210- stdio servers do not write logs or diagnostics to stdout211- HTTP servers use `MapMcp()` and are tested at the final route, for example `/mcp`212- tools, resources, and prompts use current `[McpServer*]` attributes or documented handler/filter alternatives213- client code checks `ServerCapabilities` before using subscriptions, completions, logging, or prompt/resource list-change flows214- Streamable HTTP is the default for new remote servers; SSE is used only for legacy compatibility215- experimental APIs and custom serialization settings are reviewed intentionally rather than copied blindly