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)
- 打开 Visual Studio → 创建新项目 → 控制台应用 (Console App)
- 项目名
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 | 预定义的提示词模板,等效于业务模板 |
浙公网安备 33010602011771号