Build MCP Servers in ASP.NET Core 9 | AI Tool Integration

Learn to build MCP servers in ASP.NET Core 9 to expose enterprise APIs and business logic as AI-discoverable tools. Covers authentication, hosting, and a UAE expense-approval example.

0

What Is MCP and Why It Matters for .NET Teams

Model Context Protocol, or MCP, is an open standard that defines how AI agents, IDEs, and other clients can discover and invoke tools. Think of it as REST for AI: your API exposes endpoints; your MCP server exposes tools that AI systems can discover, understand, and call autonomously.

The protocol is backed by Anthropic and has implementations for Python, JavaScript, and now .NET via the official Microsoft library. For enterprises running ASP.NET Core, this is a game-changer. You don’t need to build a custom agent framework or rewrite your business logic. You wrap your existing APIs, database queries, and workflows in an MCP server, and suddenly any MCP client (GitHub Copilot, VS Code, custom agents, Claude) can invoke them.

A practical scenario: a UAE company has a CRM system, an expense database, and approval workflows in .NET. An employee asks an AI agent, “Approve the expense report for employee 1234.” The agent discovers your MCP server, calls the approval tool, and processes the request. No middleware. No custom integration layer. The protocol handles discovery and invocation.

MCP Protocol Fundamentals

MCP defines three primary concepts:

  • Tools: Functions the server exposes. A tool has a name, description, input schema (JSON Schema), and a handler. The client calls a tool with arguments; the server executes and returns a result.
  • Resources: Data or content the server exposes for context. Resources have URIs, descriptions, and content types. Clients can read or list them. Example: a resource representing a customer record or a document.
  • Prompts: Reusable prompt templates the server provides. A prompt has a name, description, and optional arguments. Useful for standardized workflows or domain-specific instructions.

For most enterprise integrations, you’ll focus on tools. A tool is the bridge between your .NET logic and the AI ecosystem.

Setting Up Your First MCP Server in ASP.NET Core 9

Let’s build a minimal MCP server. You’ll need .NET 9 SDK and the official MCP library for .NET from the modelcontextprotocol/csharp-sdk repository.

dotnet new console -n McpExpenseServer
cd McpExpenseServer
dotnet add package ModelContextProtocol

The ModelContextProtocol package includes hosting, dependency injection, and attribute-based tool discovery. For HTTP-based servers in ASP.NET Core, you’ll also add:

dotnet add package ModelContextProtocol.AspNetCore

Create a tool class that exposes an expense approval operation:

using ModelContextProtocol.Sdk.Attributes;
using System.ComponentModel;

[McpServerToolType]
public class ExpenseTools
{
    [McpServerTool]
    [Description("Approve an expense report. Requires expense ID and optional approval notes.")]
    public async Task<string> ApproveExpense(
        [Description("Unique expense ID")] string expenseId,
        [Description("Optional approval notes")] string approvalNotes = "")
    {
        // Call your .NET business logic here
        var result = await ApproveExpenseInDatabase(expenseId, approvalNotes);
        return result;
    }
    
    private async Task<string> ApproveExpenseInDatabase(string expenseId, string notes)
    {
        // Your actual database call or API invocation
        // This is where you integrate with your existing .NET infrastructure
        return $"Expense {expenseId} approved. Notes: {notes}";
    }
}

Then in your Program.cs, register the tool and start the server:

using ModelContextProtocol.Sdk.Server;

var builder = Host.CreateDefaultBuilder(args);

builder.ConfigureServices(services =>
{
    services.AddMcpServer(options =>
    {
        options.ServerInfo = new ServerInfo
        {
            Name = "ExpenseApprovalServer",
            Version = "1.0.0"
        };
    })
    .WithToolsFromAssembly();
});

var host = builder.Build();
await host.RunAsync();

The WithToolsFromAssembly() call discovers every class marked with [McpServerToolType] and registers every [McpServerTool] method as a discoverable tool. When an MCP client connects, it discovers the “ApproveExpense” tool and can invoke it with the required arguments.

Integrating with Your Enterprise .NET Stack

In a real enterprise scenario, your MCP server doesn’t live in isolation. It sits between AI clients and your existing infrastructure. Here’s a realistic pattern:

using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

public class ExpenseService
{
    private readonly AppDbContext _db;
    private readonly IAuthorizationService _authService;
    
    public ExpenseService(AppDbContext db, IAuthorizationService authService)
    {
        _db = db;
        _authService = authService;
    }
    
    public async Task<string> ApproveExpenseAsync(string expenseId, string userId, string notes)
    {
        // Authorization check
        var canApprove = await _authService.CanUserApproveAsync(userId);
        if (!canApprove)
            return "Error: User not authorized to approve expenses.";
        
        // Fetch and update
        var expense = await _db.Expenses.FindAsync(expenseId);
        if (expense == null)
            return "Error: Expense not found.";
        
        expense.Status = "Approved";
        expense.ApprovedBy = userId;
        expense.ApprovalNotes = notes;
        expense.ApprovedAt = DateTime.UtcNow;
        
        await _db.SaveChangesAsync();
        
        return $"Expense approved. Amount: AED {expense.Amount}. Approved by: {userId}";
    }
}

Your MCP tool then uses this service via dependency injection:

using ModelContextProtocol.Sdk.Attributes;
using System.ComponentModel;

[McpServerToolType]
public class ExpenseTools
{
    private readonly ExpenseService _expenseService;
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public ExpenseTools(ExpenseService expenseService, IHttpContextAccessor httpContextAccessor)
    {
        _expenseService = expenseService;
        _httpContextAccessor = httpContextAccessor;
    }
    
    [McpServerTool]
    [Description("Approve an expense report in the enterprise system.")]
    public async Task<string> ApproveExpense(
        [Description("Unique expense ID")] string expenseId,
        [Description("Optional approval notes")] string approvalNotes = "")
    {
        // Get user context from the HTTP request
        var userId = _httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value ?? "system";
        
        var result = await _expenseService.ApproveExpenseAsync(expenseId, userId, approvalNotes);
        return result;
    }
}

This pattern keeps your MCP layer thin. All business logic stays in your existing service classes, repositories, and DbContext. The MCP server is just the protocol adapter.

Authentication and Authorization

MCP clients connect via stdio or HTTP. Authentication depends on your transport layer. For enterprise deployments, you need to ensure that only authorized users and clients can invoke sensitive tools.

If hosting over HTTP in ASP.NET Core, use standard authentication middleware:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://your-identity-provider.com";
        options.Audience = "your-api-audience";
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ExpenseApprover", policy =>
        policy.RequireClaim("role", "expense_approver"));
});

builder.Services.AddMcpServer(options =>
{
    options.ServerInfo = new ServerInfo
    {
        Name = "ExpenseApprovalServer",
        Version = "1.0.0"
    };
})
.WithToolsFromAssembly();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

app.MapMcp("/api/mcp");
app.Run();

The MapMcp("/api/mcp") call sets up your MCP endpoint. ASP.NET Core’s authentication middleware runs before the MCP handler, so the authenticated user context is available in your tools via IHttpContextAccessor or the request context.

For stdio transport (local development or agent-to-server), you can pass the authenticated user context through environment variables set by the calling process.

The key principle: authenticate the client, authorize the tool invocation, and pass user context into your service layer so audit logs and business rules apply consistently.

Hosting and Deployment Options

You have several options for hosting an MCP server:

Standalone ASP.NET Core App

Run it as a traditional web service on a VM or container in your data center or Azure App Service. Expose an HTTP endpoint that MCP clients connect to. This is straightforward for enterprises already running .NET infrastructure.

Azure Functions

Host the MCP handler as a function. Clients call an HTTPS endpoint; the function processes the MCP request and returns the response. This is serverless, scales automatically, and integrates with Azure authentication and Key Vault.

using Azure.Functions.Worker;
using Azure.Functions.Worker.Http;
using ModelContextProtocol.Sdk.Server;

public class McpFunction
{
    private readonly ExpenseService _expenseService;
    
    public McpFunction(ExpenseService expenseService)
    {
        _expenseService = expenseService;
    }
    
    [Function("HandleMcpRequest")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "mcp")] HttpRequestData req)
    {
        // Azure Functions with MCP requires hosting the MCP server
        // within the function runtime. Consult the official documentation
        // for the latest patterns.
        
        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(new { message = "MCP endpoint" });
        return response;
    }
}

Docker Container

Containerize your MCP server and deploy to Kubernetes, Docker Swarm, or any container orchestration platform. This works well for enterprises with existing DevOps practices.

FROM mcr.microsoft.com/dotnet/runtime:9.0
WORKDIR /app
COPY bin/Release/net9.0/publish/ .
EXPOSE 5000
ENTRYPOINT ["dotnet", "McpExpenseServer.dll"]

A Practical Example: UAE Expense Approval Tool

Let’s build a concrete example that demonstrates the pattern. The scenario: an employee’s expense report needs approval. An AI agent calls the MCP server to check if the employee is valid and the amount is within policy, then approves if conditions are met.

using ModelContextProtocol.Sdk.Attributes;
using System.ComponentModel;
using System.Collections.Generic;

[McpServerToolType]
public class ExpenseApprovalTools
{
    // Tool 1: Get employee details
    [McpServerTool]
    [Description("Retrieve employee details by ID. Returns name, department, and expense limit.")]
    public async Task<string> GetEmployee(
        [Description("Employee ID")] string employeeId)
    {
        // Mock data; replace with real database query
        var employees = new Dictionary<string, object>
        {
            { "E001", new { Name = "Ahmed Al-Mansouri", Department = "Finance", ExpenseLimit = 5000 } },
            { "E002", new { Name = "Fatima Al-Zahra", Department = "Operations", ExpenseLimit = 3000 } }
        };
        
        if (employees.TryGetValue(employeeId, out var employee))
        {
            return $"Employee found: {employee}";
        }
        
        return "Employee not found.";
    }
    
    // Tool 2: Approve expense
    [McpServerTool]
    [Description("Approve an expense report. Validates employee, amount, and updates status.")]
    public async Task<string> ApproveExpense(
        [Description("Expense report ID")] string expenseId,
        [Description("Employee ID")] string employeeId,
        [Description("Expense amount in AED")] decimal amount,
        [Description("Expense description")] string description)
    {
        // Validation: check employee limit
        if (amount > 5000)
        {
            return $"Expense AED {amount} exceeds limit. Rejected.";
        }
        
        // Mock approval update
        var approvalTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
        var result = $"Expense {expenseId} approved. Employee: {employeeId}, Amount: AED {amount}, Description: {description}, Approved at: {approvalTime} UTC";
        
        return result;
    }
}

This tool class exposes two operations: “GetEmployee” and “ApproveExpense”. An AI agent can call them in sequence: first fetch the employee to check their limit, then approve the expense if it’s within policy. The agent handles the orchestration; your MCP server provides the individual operations.

Testing Your MCP Server

Use the official MCP CLI or a client library to test. If you’re using VS Code or a compatible IDE with MCP support, you can configure it to connect to your server. See the official C# SDK documentation for client setup and testing patterns.

{
  "mcpServers": {
    "expense-approval": {
      "command": "dotnet",
      "args": ["run", "--project", "./McpExpenseServer"],
      "env": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

The IDE will discover your tools and display them in the MCP client interface. You can invoke them directly and see responses.

Key Takeaways

MCP is a lightweight, open protocol that turns your .NET APIs and business logic into AI-discoverable tools. Building an MCP server in ASP.NET Core 9 is straightforward: define your tools using attributes, implement handlers, integrate with your existing services, add authentication, and deploy.

For UAE enterprises, this unlocks a powerful pattern: your existing CRM, ERP, approval workflows, and data queries become first-class citizens in the AI ecosystem. AI agents, VS Code, and future clients can discover and invoke them autonomously. You don’t need a custom agent framework or extensive middleware. The protocol handles the discovery and invocation.

Start small. Expose one or two critical tools from your existing .NET stack. Test with a local MCP client. Then scale: containerize, deploy to Azure, add more tools, and watch your enterprise logic become part of the AI-driven future.

What is Model Context Protocol (MCP)?

MCP is an open standard that allows AI agents, IDEs, and other clients to discover and invoke tools exposed by servers. Think of it as REST for AI: your MCP server describes available tools and their input schemas, clients discover them, and invoke them with arguments. It’s backed by Anthropic and has implementations for multiple languages including .NET.

Do I need to rewrite my existing .NET APIs to use MCP?

No. Your MCP server sits as a thin adapter layer on top of your existing services, repositories, and DbContext. Your business logic remains unchanged. The MCP server simply exposes your existing operations as discoverable tools that AI clients can invoke.

How do I handle authentication and authorization in an MCP server?

If hosting over HTTP, use standard ASP.NET Core authentication middleware (JWT, OAuth, etc.) to authenticate clients. For tool-level authorization, check user claims or roles before executing the handler. Pass the authenticated user context into your service layer so audit logs and business rules apply consistently.

What are the deployment options for an MCP server built in .NET?

You can run it as a standalone ASP.NET Core app on a VM or App Service, deploy it as an Azure Function for serverless scaling, or containerize it and run on Kubernetes or Docker Swarm. Choose based on your enterprise infrastructure and DevOps practices.

Can an MCP server expose resources and prompts, or just tools?

MCP defines three main concepts: tools (functions), resources (data or content), and prompts (reusable templates). Most enterprise integrations focus on tools, but you can implement resources and prompts as well. Resources are useful for exposing customer records, documents, or configuration data; prompts are useful for standardized workflows.

Leave a Reply

Your email address will not be published. Required fields are marked *