MCP 学习实战笔记

从一个只会写接口的后端视角,理解 MCP 到底是什么


一、MCP 是什么(大白话)

MCP = 让 AI 能调用你接口的规范

你平时写 API 给前端调:

前端点击按钮 → 调你的接口 → 你返回数据 → 前端展示

MCP 把"前端"换成"AI":

你和 AI 聊天 → AI 想查数据 → 调你的接口 → 你返回数据 → AI 告诉你结果

三个核心概念(和后端对照)

MCP 概念 与后端对照 说明
Tool Controller Action(POST 接口) AI 主动调用的函数,比如"查询订单"、"发送通知"
Resource GET 接口(只读) AI 可以被动读取的数据,比如"读取文件"
Prompt 预设模板 用户主动选的工作流模板,比如"写周报"
JSON-RPC HTTP 请求/响应 MCP 底层通信协议,代替 HTTP
STDIO 标准输入输出 默认通信方式(不走 HTTP)
Streamable HTTP HTTP + SSE 可选通信方式,支持远程访问

一句话: MCP 对你来说就是——不需要写 Controller + HTTP 那一套,用 C# 写个类加个 [McpServerTool],AI 就能调你的代码。


二、项目搭建

2.1 新建项目(VS)

  1. 打开 Visual Studio → 创建新项目 → 控制台应用 (Console App)
  2. 项目名 McpDemo → .NET 8 或 .NET 9

2.2 安装 NuGet 包

通过 NuGet 包管理器安装(勾选"包括预发行版"):

包 1:ModelContextProtocol          // MCP 官方 C# SDK
包 2:Microsoft.Extensions.Hosting  // .NET 通用主机(提供依赖注入)

⚠️ 坑 1:特性名已更新

早期博客写的是 [McpToolType][McpTool],最新 SDK v1.x 已改为:

  • 类上:[McpToolType][McpServerToolType]
  • 方法上:[McpTool][McpServerTool]

三、核心代码

3.1 Program.cs —— 程序入口

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;

// 创建一个空壳子程序的构建器
// 等效于 ASP.NET Core 的 WebApplication.CreateBuilder()
// 区别:不启动 Kestrel(不需要 Web 服务器),只要依赖注入
var builder = Host.CreateEmptyApplicationBuilder(null);

// 注册你自己的 Service(业务逻辑层,和平时一样)
// 如果 Tool 类构造函数需要注入,必须在这里注册
builder.Services.AddSingleton<OrderService>();

// 添加 MCP 服务器
builder.Services.AddMcpServer()                   // 告诉程序"我要当 MCP Server"
    .WithStdioServerTransport()                   // 走标准输入输出,不走 HTTP 端口
    .WithToolsFromAssembly();                      // 自动扫描所有 [McpServerToolType] 类

// 构建并启动,开始监听 AI 客户端的请求
await builder.Build().RunAsync();
代码 对后端的解释
Host.CreateEmptyApplicationBuilder 创建空壳程序(不含 Web 服务器),等效于 WebApplication.CreateBuilder,只是不启动 Kestrel
AddMcpServer() 告诉程序"我要当 MCP Server"
WithStdioServerTransport() 通信方式选标准输入输出(不走 HTTP 端口)
WithToolsFromAssembly() 扫描当前程序集,自动注册所有加了 [McpServerToolType] 的类。等效于 app.MapControllers()
Build().RunAsync() 构建并启动,开始监听 AI 客户端的请求

3.2 WeatherTools.cs —— 第一个 Tool

using ModelContextProtocol;             // MCP 核心库
using ModelContextProtocol.Server;      // 包含 [McpServerToolType] 和 [McpServerTool]
using System.ComponentModel;          // 包含 Description 特性

namespace McpDemo;

// [McpServerToolType] 标记这个类是"存放 Tool 的容器"
// 等效于 [ApiController]
[McpServerToolType]
public class WeatherTools
{
    // [McpServerTool] 标记这个方法是一个可被 AI 调用的 Tool
    // Description 是给 AI 看的说明,AI 根据它决定什么时候调这个方法
    // 等效于 [HttpGet("GetWeather")] + Swagger 上的接口说明
    [McpServerTool, Description("获取指定城市的天气信息")]
    public Task<string> GetWeather(
        // 参数上的 Description 告诉 AI 这个参数该传什么值
        [Description("城市名,如 Beijing、Shanghai")] string city)
    {
        // ====== 你的业务逻辑,和写 Service 层一模一样 ======
        var result = $"当前{city}气温25°C,晴";

        // 返回字符串给 AI,不是返回 JSON 给前端
        return Task.FromResult(result);
    }
}

MCP ↔ WebAPI 对照表:

MCP WebAPI 类比
[McpServerToolType] [ApiController]
[McpServerTool] [HttpGet][HttpPost]
Description("获取城市天气") Swagger 上的接口说明
GetWeather(string city) GetWeather(string city)
return "当前北京..." return Ok("当前北京...")
Task<string> IActionResult

3.3 OrderTools.cs —— 带依赖注入(跟你项目一样)

// ====== 这部分就是你平时写的 Service 层,完全不变 ======
public class OrderService
{
    // 模拟查数据库
    public async Task<string> GetOrderStatus(string orderNo)
    {
        await Task.Delay(100);                               // 模拟数据库查询延迟
        return $"订单 {orderNo} 状态:已发货,预计明天到达";
    }
}

// ====== 下面的就是你的 "Controller",但改成了 MCP Tool ======
[McpServerToolType]
public class OrderTools
{
    // 依赖注入——和你写 Controller 一模一样
    private readonly OrderService _orderService;

    // 构造函数注入:OrderService 由 DI 容器自动创建传进来
    public OrderTools(OrderService orderService)
    {
        _orderService = orderService;
    }

    // 定义一个 Tool:查询订单状态
    [McpServerTool, Description("查询订单状态")]
    public async Task<string> QueryOrder(
        [Description("订单号")] string orderNo)
    {
        // 调 Service——和 Controller 里调 Service 一模一样
        return await _orderService.GetOrderStatus(orderNo);
    }
}

⚠️ 坑 2:必须注册 Service 到 DI 容器

OrderTools 的构造函数需要 OrderService,如果不在 Program.cs 里注册就会报错:

// 必须在 Program.cs 加上这行
builder.Services.AddSingleton<OrderService>();

3.4 完整的 Program.cs(带 DI 注册)

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;

var builder = Host.CreateEmptyApplicationBuilder(null);

// 注册你自己的业务 Service(Tool 构造函数注入会用到)
builder.Services.AddSingleton<OrderService>();

// 配置 MCP Server
builder.Services.AddMcpServer()
    .WithStdioServerTransport()     // 通信方式:STDIO(不走 HTTP)
    .WithToolsFromAssembly();        // 自动注册所有 Tool

// 启动!
await builder.Build().RunAsync();

四、调试测试

4.1 MCP Inspector(官方调试工具,等效于 Swagger UI)

# 一行命令启动 Inspector,自动打开浏览器
npx @modelcontextprotocol/inspector dotnet run

浏览器打开 http://localhost:6274 → 左侧输入 dotnet run → 点 Connect → 看到 Tool 列表 → 选一个填参数 → 点 Run Tool 测试。

4.2 HTTP 模式 + Postman(备用方案)

WithStdioServerTransport() 改成 WithHttpTransport(),然后用 Postman 调。

列出所有 Tool:

POST http://localhost:5000/mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

调用 GetWeather Tool:

POST http://localhost:5000/mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "GetWeather",
    "arguments": {
      "city": "北京"
    }
  }
}

⚠️ 坑 3:Node.js 版本过低

MCP Inspector 要求 Node.js >= 22.7.5

报错 Class extends value undefined is not a constructor 就是版本问题。
解决:nodejs.org 下载最新 LTS 安装覆盖。

⚠️ 坑 4:Console.WriteLine 会破坏 STDIO 通信

STDIO 模式下,标准输出(stdout)被用于传输 JSON-RPC 消息。

Console.WriteLine("日志"); —— 不要用!

Console.Error.WriteLine("日志"); —— 用 stderr 或者 ILogger


五、完整代码文件一览

Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;

var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Services.AddSingleton<OrderService>();
builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();
await builder.Build().RunAsync();

WeatherTools.cs

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace McpDemo;

[McpServerToolType]
public class WeatherTools
{
    [McpServerTool, Description("获取指定城市的天气信息")]
    public Task<string> GetWeather(
        [Description("城市名,如 Beijing、Shanghai")] string city)
    {
        var result = $"当前{city}气温25°C,晴";
        return Task.FromResult(result);
    }
}

OrderTools.cs

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace McpDemo;

public class OrderService
{
    public async Task<string> GetOrderStatus(string orderNo)
    {
        await Task.Delay(100);
        return $"订单 {orderNo} 状态:已发货,预计明天到达";
    }
}

[McpServerToolType]
public class OrderTools
{
    private readonly OrderService _orderService;

    public OrderTools(OrderService orderService)
    {
        _orderService = orderService;
    }

    [McpServerTool, Description("查询订单状态")]
    public async Task<string> QueryOrder(
        [Description("订单号")] string orderNo)
    {
        return await _orderService.GetOrderStatus(orderNo);
    }
}

六、踩坑汇总(快速查阅)

# 原因 解决
1 特性名报错 旧博客用 [McpToolType],新版 SDK 已改名 [McpServerToolType][McpServerTool]
2 DI 报错找不到服务 Tool 构造函数需要 OrderService,但没注册 builder.Services.AddSingleton<OrderService>()
3 Inspector 启动报错 Node.js 版本低于 22.7.5 去 nodejs.org 下载最新 LTS
4 STDIO 通信中断 用了 Console.WriteLine 污染了 stdout 改用 Console.Error.WriteLine 或 ILogger
5 模式选错 开发用 HTTP,部署用 STDIO,搞混了 开发用 STDIO + Inspector,远程部署用 WithHttpTransport()

七、关键名词速查

名词 解释
MCP Model Context Protocol,让 AI 连接外部系统的开放协议,由 Anthropic 提出
Kestrel ASP.NET Core 自带的 Web 服务器,MCP 不走 HTTP 时不需要它
STDIO 标准输入输出通信,MCP Server 默认方式,不走端口
Streamable HTTP HTTP + SSE 通信方式,适用于远程 MCP Server
JSON-RPC 2.0 MCP 底层使用的通信协议,代替 HTTP REST
McpServer C# SDK 提供的高层 API,用来注册 Tool、管理生命周期
Inspector MCP 官方的 Web 调试工具,等效于 Swagger UI
Host AI 应用程序,如 Claude Desktop、VS Code、Cursor
Client Host 内部维护的、连接到某个 Server 的连接对象
Server 你写的程序,提供 Tool / Resource / Prompt
Tool AI 能调用的函数,等效于你的 API 接口
Resource AI 能读取的只读数据,等效于 GET 接口
Prompt 预定义的提示词模板,等效于业务模板