An AI app is only as useful as the tools it can reach. The Model Context Protocol (MCP) gives those tools a standard shape. A server lists what it can do, and any MCP-aware client, such as an agent, VS Code, or the MCP Inspector, can discover and call it. The official C# SDK is now on 1.x, so .NET developers can build servers with the ASP.NET Core pieces they already know.

In this article we build a small inventory server with two tools and run it locally. It takes about ten minutes.

What you need

Step 1: Create the project

dotnet new web -n InventoryMcp
cd InventoryMcp
dotnet add package ModelContextProtocol.AspNetCore

The ModelContextProtocol.AspNetCore package is the one for HTTP-based servers. It brings in the core SDK as a dependency.

Step 2: Wire up the server

Replace the contents of Program.cs with this:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp("/mcp");

app.Run();

AddMcpServer registers the server, WithHttpTransport makes it reachable over HTTP, and WithToolsFromAssembly scans your project for tools. MapMcp exposes everything at /mcp.

Step 3: Add two tools

Create a file named InventoryTools.cs:

using System.ComponentModel;
using ModelContextProtocol.Server;

[McpServerToolType]
public static class InventoryTools
{
    // Demo data. In a real server this would call your ERP or a database.
    private static readonly StockInfo[] Data =
    [
        new("PLANT-1020", "MAT-9920", 450, 500),
        new("PLANT-1020", "MAT-1104", 80, 120),
        new("PLANT-3040", "MAT-9920", 1200, 300),
    ];

    [McpServerTool, Description("Get stock on hand and reorder level for one material at one plant.")]
    public static StockInfo? GetStock(
        [Description("Plant ID, for example PLANT-1020")] string plantId,
        [Description("Material number, for example MAT-9920")] string materialNumber)
        => Data.FirstOrDefault(s => s.PlantId == plantId && s.MaterialNumber == materialNumber);

    [McpServerTool, Description("List the materials at a plant that are below their reorder level.")]
    public static StockInfo[] ListLowStock(
        [Description("Plant ID, for example PLANT-1020")] string plantId)
        => Data.Where(s => s.PlantId == plantId && s.OnHand < s.ReorderLevel).ToArray();
}

public record StockInfo(string PlantId, string MaterialNumber, int OnHand, int ReorderLevel);

Two attributes do the work. [McpServerToolType] marks the class, and [McpServerTool] marks each method that becomes a tool. The SDK builds the input schema from the method signature, so you do not write any JSON schema by hand.

Step 4: Run it and try it

Start the server on a fixed port so the URL is easy to remember:

dotnet run --urls http://localhost:5000

In a second terminal, start the MCP Inspector:

npx @modelcontextprotocol/inspector

In the Inspector, choose the Streamable HTTP transport, enter http://localhost:5000/mcp, and connect. Open the Tools tab and list the tools. You should see GetStock and ListLowStock with their descriptions. Call ListLowStock with PLANT-1020 and you get both materials back, since each one is below its reorder level.

Why the descriptions matter

The model never sees your C# code. It sees the tool name, the description, and the parameter descriptions. If those are vague, the model will pick the wrong tool or pass the wrong values. Put an example value in each parameter description, as the code above does, and the calls get noticeably more accurate.

Before you use it for real

In the next article we connect an AI agent written in C# to this server, so the agent can answer stock questions on its own.

Resources