The Model Context Protocol (MCP) has quickly become one of the most important standards for integrating AI assistants with external tools, APIs, and enterprise systems. Instead of building custom integrations for every AI application, MCP provides a standardized way for AI agents to discover capabilities and invoke tools.
While creating a simple MCP server is relatively straightforward, building one that is secure, scalable, observable, and production-ready requires additional architectural considerations. This article walks through the process of building an MCP server in ASP.NET Core 10 while covering authentication, dependency injection, structured logging, validation, and deployment best practices.
Understanding MCP Servers
What Is an MCP Server?
An MCP server exposes tools, resources, and prompts that AI assistants can consume through a standardized protocol. Instead of exposing traditional REST endpoints directly to AI agents, the MCP server acts as an intelligent bridge between enterprise services and AI applications.
Typical responsibilities include:
Exposing business capabilities as AI tools
Validating incoming requests
Executing backend services
Returning structured responses
Managing authentication and authorization
For enterprise applications, this separation helps prevent AI clients from directly accessing internal APIs.
Why ASP.NET Core 10 Is a Good Choice
ASP.NET Core 10 offers several advantages for hosting MCP servers:
High-performance request processing
Built-in dependency injection
Native OpenTelemetry integration
Minimal API support
Modern authentication middleware
Cloud-native deployment capabilities
Excellent container support
These features make it suitable for production AI workloads that require scalability and maintainability.
Project Architecture
A production-ready MCP server should separate protocol handling from business logic.
AI Client
│
▼
MCP Server
│
┌───────────────┐
│ Tool Handlers │
└───────────────┘
│
Business Services
│
Repositories
│
Database / External APIs
Keeping these layers separate allows business services to remain reusable outside the MCP implementation.
Creating the ASP.NET Core 10 Project
Create a new Web API project.
dotnet new webapi -n ProductCatalogMcp
Register application services.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.Run();
This setup keeps service registration centralized and enables dependency injection throughout the application.
Implementing an MCP Tool
Suppose an AI assistant needs to retrieve product information.
Create a service interface.
public interface IProductService
{
Task<ProductDto?> GetProductAsync(int id);
}
Implementation:
public class ProductService : IProductService
{
public Task<ProductDto?> GetProductAsync(int id)
{
return Task.FromResult<ProductDto?>(new ProductDto
{
Id = id,
Name = "Laptop",
Price = 1499
});
}
}
Expose the tool.
app.MapPost("/tools/product", async (
int id,
IProductService service) =>
{
var product = await service.GetProductAsync(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
Although this example is simple, the important design choice is keeping the endpoint thin. The endpoint delegates business logic to a service instead of embedding logic directly in the request handler, making the application easier to test and maintain.
Building a Real-World Workflow
Consider an internal inventory system where an AI assistant helps support engineers.
The workflow might look like this:
The AI agent receives a user request.
The agent discovers available MCP tools.
It invokes the Product Lookup tool.
The MCP server validates the request.
The business service retrieves inventory data.
The server returns structured JSON.
The AI assistant summarizes the result for the user.
This approach keeps enterprise APIs isolated while allowing AI agents to perform useful business tasks.
Dependency Injection and Service Design
Avoid placing database or HTTP calls inside MCP handlers.
Instead:
MCP Endpoint
│
▼
Application Service
│
▼
Repository
│
▼
Database
This separation improves:
Testability
Maintainability
Code reuse
Dependency management
Error Handling
Production systems should never expose raw exceptions.
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new
{
Error = "An unexpected error occurred."
});
});
});
Returning consistent error responses simplifies debugging for both AI clients and developers while preventing accidental exposure of internal implementation details.
Join the conversation! Your thoughts help the community grow.