Skip to main content

MCP with .NET

Summaryโ€‹

๐Ÿงญ What this page covers

MCP Tools, Resources, and Prompts explained the primitives. This page shows how to implement an MCP server in .NET and how to connect it to a .NET AI application.

.NET is a strong fit for MCP servers because many enterprise capabilities already live in C#, ASP.NET Core, worker services, and internal APIs. MCP lets you expose those existing capabilities to AI hosts without rebuilding business logic in another stack.

Concern.NET approachExample
Tool implementationWrap existing domain/application services in typed methods instead of duplicating business rules in the MCP layer.CreateIncidentTool calls existing IncidentService.CreateAsync(...) with policy checks already enforced in the service layer.
ValidationUse request/response DTOs, data annotations, and explicit guard clauses before executing downstream operations.Reject a request when priority is missing or outside allowed values (low, medium, high) before calling any repository/API.
Transport hostingStart with a console-hosted stdio server for local integration; move to ASP.NET Core hosted remote transport when multiple hosts need shared access.Local desktop copilot uses stdio; later, a centralized internal assistant calls the same server over secured HTTP streaming.
ObservabilityEmit structured logs and traces for every tool invocation, including correlation IDs, latency, and outcome status.Log: tool=GetDeviceStatus correlationId=... durationMs=84 status=success and export spans via OpenTelemetry to Application Insights.
Reuse existing business services

Your MCP layer should usually be thin. It should adapt existing application services, policy checks, and repositories rather than duplicating domain logic inside a new server project.

Typed contracts reduce tool ambiguity

C# request and response types make it easier to validate inputs, produce predictable outputs, and keep tool schemas aligned with the actual runtime behaviour.

Instrument every tool invocation

Once a server is shared across AI hosts, debugging without traces becomes expensive. Emit tool name, latency, correlation ID, and outcome for every invocation from day one.

Why This Mattersโ€‹

๐Ÿข
Enterprise capabilities already live in .NET
Most useful AI-adjacent business logic does not need to be rewritten

Order lookup, policy evaluation, CRM search, document generation, and back-office workflows often already exist in .NET services. MCP gives those systems an AI-facing contract without changing their core responsibility.

๐Ÿ›ก๏ธ
Strong typing helps safety and governance
Explicit contracts are easier to review than free-form tool handlers

Typed request models, enums, validation attributes, and dependency injection all help build safer tool surfaces than loosely typed dictionaries passed through dynamic handlers.

โš™๏ธ
The host/server boundary maps cleanly to .NET apps
Console apps, worker services, and ASP.NET Core all fit naturally

A local MCP server can be a small console app over stdio. A shared enterprise server can run as ASP.NET Core behind your normal auth and monitoring stack. The platform options are already familiar to most .NET teams.

Core Conceptsโ€‹

Minimal server shapeโ€‹

public sealed class TicketSearchRequest
{
public required string Query { get; init; }
public string Status { get; init; } = "open";
}

public sealed class TicketSearchResult
{
public required string TicketId { get; init; }
public required string Title { get; init; }
public required string Status { get; init; }
}

public interface ITicketService
{
Task<IReadOnlyList<TicketSearchResult>> SearchAsync(TicketSearchRequest request, CancellationToken cancellationToken);
}

Thin MCP adapter patternโ€‹

public sealed class TicketTools(ITicketService ticketService, ILogger<TicketTools> logger)
{
public async Task<IReadOnlyList<TicketSearchResult>> SearchTicketsAsync(
TicketSearchRequest request,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.Query))
{
throw new ArgumentException("Query is required.", nameof(request));
}

using var scope = logger.BeginScope("mcp_tool:{ToolName}", "search_tickets");
logger.LogInformation("Searching tickets with status {Status}", request.Status);

return await ticketService.SearchAsync(request, cancellationToken);
}
}

Host-side usage with a .NET AI appโ€‹

using Microsoft.Extensions.AI;

IChatClient chatClient = new OpenAIChatClient(
modelId: "gpt-4.1",
apiKey: builder.Configuration["OpenAI:ApiKey"]!);

// Pseudocode: connect an MCP client and surface approved tools to the agent loop.
var approvedTools = await mcpClient.ListToolsAsync(cancellationToken);
var safeTools = approvedTools.Where(tool => tool.Name is "search_tickets" or "get_ticket_details");

var response = await chatClient.GetResponseAsync(
messages,
new ChatOptions
{
Tools = safeTools.Select(ConvertToAIFunction).ToList()
},
cancellationToken);

Realistic Exampleโ€‹

Scenarioโ€‹

An internal IT support copilot runs in ASP.NET Core. The business already has a C# service DeviceInventoryService used by the admin portal. The team adds an MCP server exposing:

  • find_devices_by_user
  • get_device_compliance
  • list_recent_repairs

The MCP layer stays thin: each tool validates input, calls the existing service, and returns a predictable DTO. Authorization still happens in the domain service using the authenticated user context supplied by the host.

Senior Tech vs Dev Conversationโ€‹

Senior Tech: Should the MCP server contain all the business logic?

Dev: No. The MCP server should adapt business logic, not become the business logic. If you move rules, authorization, and data access into the MCP layer, you create a second application surface that diverges from the original system. Keep the domain in domain services.

Senior Tech: How do we avoid exposing dangerous tools to every AI host?

Dev: Two layers. First, the server enforces authorization and can reject calls even if a host attempts them. Second, each host curates the visible tools for its scenario. A read-only assistant should not even see reset_device or delete_ticket.

Common Pitfallsโ€‹

PitfallWhat goes wrongPrevention
Rewriting domain logic in the MCP layerDuplicate rules and inconsistent behaviourKeep MCP adapter thin over existing services
Using anonymous request dictionariesWeak validation and poor schema qualityUse typed request/response models
No correlation IDs in logsHard to trace multi-host issuesEmit trace or correlation IDs on every invocation
Exposing admin-only tools broadlyUnsafe execution surfaceCurate visible tools per host and enforce auth server-side

References and Next Stepsโ€‹