Building MCP Servers and Clients in C#: The Complete Guide
If you're building AI-enabled .NET applications, an MCP server C# implementation is quickly becoming one of the most practical ways to connect agents, copilots, and LLM workflows to real tools and data. Model Context Protocol gives us a standard contract for exposing capabilities, and the official C# SDK gives .NET developers a familiar path through hosting, dependency injection, ASP.NET Core, HttpClient, and Microsoft.Extensions.AI.
This guide maps the full surface area: what MCP is, which packages matter, how to build a local stdio server, how tools get described, where resources and prompts fit, when to choose Streamable HTTP, how to build an MCP client in C#, and what to consider before hosting a remote MCP server C# teams will depend on.
As of July 2026, the latest stable version on NuGet for ModelContextProtocol, ModelContextProtocol.Core, and ModelContextProtocol.AspNetCore is 1.4.0. Those same NuGet feeds list a 2.0.0 preview, but this article sticks to stable APIs verified against the current SDK docs and NuGet feeds. Check nuget.org before you publish or pin a version.
What MCP Is and Why a .NET Dev Should Care
Model Context Protocol is an open protocol for connecting AI applications to external systems. The official MCP docs describe it as a standard way for AI applications to access data sources, tools, and workflows. That's the part that matters for .NET developers: instead of hardwiring every agent to every internal API, you can expose capabilities through a common protocol and let compatible hosts discover and invoke them.
The architecture has three roles. The host is the AI-enabled application, such as an IDE, agent runtime, or assistant. The client lives inside that host and manages a connection to a server. The server exposes capabilities: tools the model can call, resources it can read, and prompts it can reuse.
For a C# developer, the practical value is reuse. You probably already have ASP.NET Core APIs, background services, domain services, authentication policies, logging, and packaging workflows. The C# SDK lets you bring those patterns forward. If you've been working through agent architectures like Microsoft Agent Framework in C#, MCP gives you a boundary that can outlive one particular agent framework or chat client.
MCP doesn't remove design decisions. You still decide what is callable, what needs authorization, how much data a tool returns, and whether the server runs locally or remotely.
The C# SDK: Three Packages for an MCP Server C# Project
The official C# SDK ships as three NuGet packages, and picking the right one prevents dependency confusion later.
Use ModelContextProtocol.Core when you only need the client or low-level server APIs with the smallest dependency set. That is useful for libraries or constrained applications where you don't want hosting abstractions pulled in unnecessarily.
Use ModelContextProtocol for most local server and client work. It references ModelContextProtocol.Core and adds hosting, dependency injection, stdio transport support, and attribute-based discovery for tools, prompts, and resources. If you're building a local MCP server C# application that runs under VS Code, Claude Desktop, or another host as a child process, start here.
Use ModelContextProtocol.AspNetCore when you're hosting a remote MCP server over HTTP in ASP.NET Core. It references ModelContextProtocol, adds HTTP server support, and exposes MapMcp() for the Streamable HTTP endpoint.
This maps well to how .NET teams separate concerns. A client library can stay lean. A worker-style server can use generic host and DI. A remote endpoint can use ASP.NET Core, middleware, authentication, CORS, and observability. If you need a refresher on why package versions matter, NuGet versioning and Semantic Versioning in .NET is worth keeping in mind.
The key is to avoid treating the packages as interchangeable. For a stdio MCP server in .NET, ModelContextProtocol is enough. For a remote HTTP server, add ModelContextProtocol.AspNetCore.
Build a Minimal Stdio MCP Server C# Developers Can Run Locally
Stdio is the most common starting point for a local MCP server C# project. The client starts your process, sends JSON-RPC over standard input, and reads JSON-RPC from standard output. That creates one critical gotcha: don't write application logs to stdout. Stdout carries protocol messages. Logs belong on stderr.
The Microsoft Learn quickstart uses Microsoft.Extensions.Hosting, AddMcpServer(), WithStdioServerTransport(), and WithToolsFromAssembly(). Here's a minimal server that exposes one echo tool:
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 uses stdout for JSON-RPC, so logs must go to stderr.
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
[McpServerToolType]
public static class EchoTools
{
[McpServerTool]
[Description("Echoes the message back to the client.")]
public static string Echo(
[Description("The message to echo.")] string message) => $"hello {message}";
}
That is enough for a host to discover and call the tool. The class attribute marks a tool container. The method attribute marks the operation as callable. The description helps generate the schema that clients and models inspect.
For local developer tools, stdio is usually the lowest-friction path. But still think about environment variables. A stdio client can inherit sensitive values into the child process unless you intentionally restrict that behavior.
Defining Tools with [McpServerTool] in an MCP Server C# Implementation
Tools are the primary way an MCP server lets an LLM take action. In the C# SDK, the most common approach is attribute-based: mark a class with [McpServerToolType], mark methods with [McpServerTool], and use [Description] to document the tool and its parameters.
Those descriptions are not just comments for humans. The SDK uses method signatures and descriptions to generate JSON Schema for tool inputs. Vague descriptions lead to vague tool usage. Specific descriptions improve the odds that the model passes the right values.
Here's a slightly more realistic tool that uses dependency injection. The SDK can resolve registered services as method parameters, while normal tool arguments remain visible in the generated schema. Assume SupportNoteSearchService is registered in DI before the server starts.
using ModelContextProtocol.Server;
using System.ComponentModel;
[McpServerToolType]
public sealed class CustomerTools
{
[McpServerTool]
[Description("Finds customer support notes that match a search term.")]
public static async Task<string> SearchSupportNotesAsync(
SupportNoteSearchService searchService,
[Description("The customer name, account ID, or support topic to search for.")] string query,
[Description("The maximum number of notes to return.")] int maxResults = 5)
{
var notes = await searchService.SearchAsync(query, maxResults);
return string.Join("
", notes.Select(note => $"- {note.Title}: {note.Summary}"));
}
}
Notice the boundary here. MCP tools should be shaped for agent use, not blindly mirror every domain method. If a tool mutates state, deletes data, sends emails, or spends money, design for authorization and human approval. The same principle shows up in tool approval and human-in-the-loop workflows.
Resources and Prompts in Model Context Protocol in C#
Tools get the most attention because they perform actions, but MCP also supports resources and prompts. An MCP server C# implementation can expose all three when the scenario calls for it.
Resources represent data the client can read. They can be direct resources with fixed URIs, or template resources with parameters. This works well for documents, configuration, records, or generated content that should be read as context rather than invoked as an action.
Prompts are reusable prompt templates exposed by the server. They help clients discover guided workflows, such as a code review prompt, a troubleshooting prompt, or a domain-specific analysis prompt.
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
[McpServerResourceType]
public sealed class DocumentationResources
{
[McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")]
[Description("Returns article text by ID.")]
public static TextResourceContents GetArticle(string id) => new()
{
Uri = $"docs://articles/{id}",
MimeType = "text/markdown",
Text = $"# Article {id}
Load this from your real content store."
};
}
[McpServerPromptType]
public sealed class ReviewPrompts
{
[McpServerPrompt]
[Description("Creates a short code review prompt for a C# snippet.")]
public static ChatMessage CodeReview(
[Description("The C# code to review.")] string code) =>
new(ChatRole.User, $"Review this C# code for correctness and maintainability:
{code}");
}
In practice, I treat tools, resources, and prompts as different contract shapes. Tools are for doing. Resources are for reading. Prompts are for guiding. Mixing those responsibilities makes the server harder for clients to reason about.
Transports for MCP Server C#: Stdio vs Streamable HTTP
Transport choice is one of the first architectural decisions. Stdio and Streamable HTTP both matter, but they solve different problems.
Stdio is a local process transport. The client launches your executable and communicates over stdin and stdout. It is a great fit for local developer tooling, workspace-aware utilities, and servers that should run beside an IDE or desktop application. It is simple to configure, but it is not the shape you want for a shared cloud service.
The C# SDK transport docs describe Streamable HTTP as the recommended transport for remote MCP servers. The SDK exposes it through WithHttpTransport(...) on the server and HttpClientTransport on the client. It lets your server sit behind normal HTTP infrastructure, use ASP.NET Core authentication, participate in logs and traces, and scale like other web services. If you're already thinking about typed clients, retries, and observability, the patterns in IHttpClientFactory in .NET apply conceptually when you're building clients around remote endpoints.
SSE deserves a careful note. The C# SDK docs describe Server-Sent Events as legacy. New HTTP implementations should prefer Streamable HTTP. The SDK diagnostics list marks EnableLegacySse as obsolete with diagnostic MCP9004, and the transport docs say legacy SSE endpoints require explicit opt-in. Mention SSE for migration, not as the default recommendation.
The tradeoff is straightforward. Use stdio for local, process-scoped integration. Use Streamable HTTP for remote servers. Reach for stateful HTTP only when you need capabilities like server-to-client requests or unsolicited notifications. For most HTTP servers, the SDK docs recommend setting stateless mode explicitly.
Hosting a Remote MCP Server in ASP.NET Core
When you need a remote MCP server C# developers and agents can share, ASP.NET Core is the natural hosting model. You add ModelContextProtocol.AspNetCore, register the MCP server, enable the HTTP transport, and map the endpoint.
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
// Recommended for servers that do not need server-to-client requests.
options.Stateless = true;
})
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp("/mcp");
app.Run();
[McpServerToolType]
public static class TimeTools
{
[McpServerTool]
[Description("Returns the current UTC time in ISO 8601 format.")]
public static string GetUtcTime() => DateTimeOffset.UtcNow.ToString("O");
}
A remote server is not just the stdio example with HTTP bolted on. You need normal web application discipline: allowed hosts, restrictive CORS, authentication, rate limits, structured logs, and telemetry. If your tools call other HTTP APIs, the fundamentals from HttpClient in C# are still relevant.
Stateless mode is usually the starting point for production because it avoids session affinity and makes horizontal scaling easier. Stateful mode can be valid, but it is a deliberate choice. Use it when the feature needs it, not because you forgot to set the option.
Building an MCP Client in C#
An MCP client connects to a server, lists available capabilities, and calls them. For stdio, the client can launch a process. For HTTP, it can connect to a remote MCP endpoint. The SDK's getting-started docs show StdioClientTransport, McpClient.CreateAsync, ListToolsAsync, and CallToolAsync.
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);
foreach (var tool in await client.ListToolsAsync())
{
Console.WriteLine($"{tool.Name}: {tool.Description}");
}
var result = await client.CallToolAsync(
"echo",
new Dictionary<string, object?> { ["message"] = "Hello MCP from C#!" },
cancellationToken: CancellationToken.None);
Console.WriteLine(result.Content.OfType<TextContentBlock>().First().Text);
For remote servers, use HttpClientTransport with a Streamable HTTP endpoint. The transport docs show that HttpTransportMode.AutoDetect tries Streamable HTTP first and falls back to SSE for legacy servers, but I would still design new servers around Streamable HTTP.
Client-side security matters too. When launching stdio servers, don't casually pass every environment variable to an untrusted child process. For HTTP clients, treat the MCP endpoint like any privileged API.
Consuming MCP Tools from Microsoft.Extensions.AI, Semantic Kernel, and Agent Framework
One of the best parts of the C# SDK is the bridge into Microsoft.Extensions.AI. The SDK docs state that McpClientTool inherits from AIFunction, which means tools discovered from an MCP server can be passed directly into an IChatClient call.
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
IList<McpClientTool> tools = await client.ListToolsAsync();
IChatClient chatClient = /* your configured chat client */;
ChatResponse response = await chatClient.GetResponseAsync(
"Use the available tools to summarize the support notes for Contoso.",
new ChatOptions { Tools = [.. tools] });
Console.WriteLine(response.Text);
That handoff is important. It means an MCP server C# project can expose capabilities once, and different agent frameworks can consume them through common function abstractions. If you're using Microsoft.Extensions.AI directly, this reduces glue code.
This is also where tool design becomes more visible. A chat client can only call what it understands. Tool names, descriptions, parameter descriptions, and bounded behavior all affect quality. If you've used function tools before, the ideas in Function Tools with AIFunctionFactory in Microsoft Agent Framework transfer nicely.
For Semantic Kernel and Agent Framework, avoid thinking in terms of competition. A practical architecture might use Semantic Kernel agents, Agent Framework workflows, and MCP servers together. The MCP server owns integration with external tools and data. The agent runtime owns reasoning, conversation, approvals, and orchestration.
Securing a Remote MCP Server C# Teams Can Trust
A remote MCP server is an API surface for agents. That means security cannot be an afterthought.
The C# SDK's protected server sample uses ASP.NET Core authentication with JWT bearer tokens, MCP authentication metadata, authorization middleware, and RequireAuthorization() on MapMcp(). The identity docs also show that ClaimsPrincipal can be injected into handlers without appearing in the tool schema.
For declarative authorization, the identity docs show ASP.NET Core [Authorize] attributes on tools, prompts, and resources when AddAuthorizationFilters() is added during server configuration. Unauthorized items can be filtered from list operations, and forbidden individual operations become JSON-RPC errors.
Remote servers should also publish the right OAuth protected resource metadata when OAuth-based clients need it. The protected server sample configures MCP authentication metadata with authorization servers and supported scopes. That matters because clients need to understand how to acquire and present access tokens.
Security decisions are full of tradeoffs. Stdio avoids public network exposure but runs local code. Streamable HTTP supports normal web auth and centralized deployment but creates an internet-facing or network-facing surface. Broad CORS may make browser testing easy, but it can widen your attack surface. The responsible answer is not to avoid MCP. It's to treat a production MCP server in .NET like a production API that happens to speak JSON-RPC through MCP.
Deploying an MCP Server in .NET to Azure
For Azure, there are two practical paths to think about: Azure Functions and containerized ASP.NET Core, such as Azure Container Apps.
Azure Functions has MCP bindings for remote MCP servers. The Microsoft Learn docs say the extension can expose Functions as MCP tools, resources, and prompts, and for C# it uses the isolated worker model. This is a good fit when your capabilities are naturally eventless, function-shaped operations and you want serverless scale characteristics. The Functions docs also note Streamable HTTP and legacy SSE endpoints, with Streamable HTTP being the direction to prefer unless a client specifically requires SSE.
ASP.NET Core in a container gives you more control over middleware, hosting behavior, observability, background services, and dependency injection. If your MCP server C# application has several tools, richer auth, shared services, or custom routing, Container Apps can be a better mental model than a set of individual Functions.
There isn't a universal winner. Functions can be simpler for discrete operations. Containers can be more flexible for service-style servers. The deciding criteria are operational: authentication model, cold start tolerance, session requirements, observability needs, scaling behavior, and how much ASP.NET Core surface area your server needs. For packaging and distribution, creating NuGet packages in .NET can also matter if you distribute local stdio servers through developer tooling.
Testing and Debugging with MCP Inspector
The MCP Inspector is the official developer tool for testing and debugging MCP servers. The GitHub repository describes it as a React-based web UI plus a Node.js proxy that connects to MCP servers using stdio, SSE, or Streamable HTTP. You can start it with npx @modelcontextprotocol/inspector, then connect to your server and inspect tools, resources, prompts, and calls interactively.
For a local MCP server C# executable, Inspector is especially helpful because it separates protocol debugging from whatever host you eventually use. If the Inspector cannot list your tools, the problem is probably in your server registration, process launch, stdout/stderr behavior, or transport configuration. If Inspector works but your target host doesn't, the problem is likely in that host's configuration.
Pay attention to the Inspector security warnings. Its proxy can spawn local processes and connect to configured servers. It should bind to localhost for normal development and should not be exposed to untrusted networks. The current Inspector uses proxy authentication by default, and that is a good thing.
I also like pairing Inspector with application logs and traces. For remote servers, use normal ASP.NET Core logging and OpenTelemetry patterns. If your tools call downstream HTTP services, make those calls observable. The moment an agent starts chaining tool calls, diagnostics become a feature, not a nice-to-have. For broader logging fundamentals, see Logging in .NET.
Where to Go Next with Model Context Protocol in C#
A practical way to learn MCP is to build one small stdio server, inspect it, then promote the right ideas into a remote server. Start with one safe read-only tool. Give it a precise description. Add a resource if the client needs context more than action. Add a prompt if the workflow benefits from a reusable template. Then decide whether local stdio or Streamable HTTP matches the deployment model.
For intermediate-to-senior .NET developers, the hard part isn't the first AddMcpServer() call. The hard part is designing a stable integration boundary that agents can use safely. Keep your tools narrow. Keep auth explicit. Keep logs off stdout for stdio. Prefer Streamable HTTP for remote servers. Treat SSE as legacy. And keep checking the live SDK docs and NuGet versions because this space is moving quickly.
FAQ
What package should I use to build an MCP server C# project?
Use ModelContextProtocol for most stdio servers and clients. Use ModelContextProtocol.AspNetCore when the server is hosted over HTTP in ASP.NET Core. Use ModelContextProtocol.Core when you only need low-level or client APIs with fewer dependencies.
Is stdio or Streamable HTTP better for an MCP server in .NET?
Stdio is better for local process-based integrations, especially IDE and desktop-hosted tooling. Streamable HTTP is better for remote servers, shared services, and production deployments where you need HTTP authentication, hosting infrastructure, and scaling.
Should I use SSE for a new MCP server C# implementation?
Generally, no. The SDK transport docs describe SSE as legacy and recommend Streamable HTTP for HTTP-based communication. SSE may matter for migration or compatibility with older clients, but new remote servers should generally use Streamable HTTP.
How do MCP tools become available to an LLM in C#?
The server exposes tools through MCP. A client calls ListToolsAsync() and receives McpClientTool instances. Because McpClientTool inherits from AIFunction, those tools can be passed into IChatClient through ChatOptions.Tools.
Can an MCP server C# tool use dependency injection?
Yes. Tool methods can receive services registered in dependency injection. Normal parameters remain part of the tool schema, while registered services are resolved by the server. This lets tools call application services without exposing those service parameters to the client.
How should I secure a remote MCP server?
Use the normal ASP.NET Core security stack: authentication, authorization, restrictive CORS, host validation, and least-privilege tool behavior. The SDK identity docs and protected server sample cover JWT/OAuth-oriented protected resource metadata, ClaimsPrincipal injection, endpoint authorization, and authorization filters for tools, prompts, and resources.
How do I debug an MCP server in C#?
Use MCP Inspector first. It can connect to stdio and Streamable HTTP servers, list capabilities, and call tools interactively. For stdio servers, also verify that logs go to stderr, because stdout is reserved for JSON-RPC protocol messages.

