
你的 ASP.NET Core API 已经在线上跑了,现在有个 AI 智能体想调用它。官方的 MCP C# SDK(Model Context Protocol,模型上下文协议)是个好基础,但它要求你为每个要暴露的操作手写一个 [McpServerTool] 类。等于把原来在 Controller 里定义一遍的东西,又写一遍。McpIt 就是来解决这个重复劳动的——加一个特性标记,工具在编译时就给你生成好了。
官方的 MCP C# SDK(Model Context Protocol,模型上下文协议)是微软背书的,质量没问题,但它期望你自己手写一个 [McpServerTool] 类来暴露每个操作。而这个操作你其实已经在 Controller 里定义过了:路由、HTTP 谓词、参数、验证、业务逻辑全在 Controller Action 里。再写一个平行的类去复制这些定义,然后永远保持两边同步——这正是那种会让代码腐烂的重复劳动。
如果你有十个端点想暴露给 AI,你就要维护十个额外的类。哪天在 Controller 里改了个参数名,工具那边的 schema 就漂移了,直到你手动想起来去改。这种维护负担,就是 McpIt 要消除的税。
先看一个普通的 ASP.NET Core Controller Action:
[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{ [HttpGet("{id}")] public string GetOrder(int id) => $"order-{id}";
} 再看看加上 McpIt 之后同一个 Action 暴露给 AI 智能体的样子。只需要加一个特性标记和一个用于描述的 <summary>:
using McpIt;
using Microsoft.AspNetCore.Mvc; [ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{ /// <summary>Gets an order by its id.</summary> [HttpGet("{id}")] [McpTool(Name = "getOrder")] public string GetOrder(int id) => $"order-{id}";
} 这就是全部改动。McpIt 在编译时为 getOrder 生成 MCP 工具类。它读取 Action 的 HTTP 谓词、路由模板、参数和 XML 摘要,然后用这些信息构建工具的输入 schema 和安全提示。id 路由参数自动变成带类型的工具参数。<summary> 自动成为工具描述。
对于 MCP 客户端来说,调用 tools/list 会返回:
{ "tools": [ { "name": "getOrder", "description": "Gets an order by its id.", "inputSchema": { "type": "object", "properties": { "id": { "type": "integer" } }, "required": ["id"] }, "annotations": { "readOnlyHint": true, "idempotentHint": true } } ]
} 注意 annotations 部分。因为 GetOrder 是 GET 请求,McpIt 自动把工具标记为只读和幂等。无需手写类,无需重复 schema。
三件事让 McpIt 与其他方案不同。
它是一个源码生成器,不是运行时层。 工具类在编译时就存在了。启动时不会有反射扫描你的程序集,也不会有运行时动态构建的代理对象。这意味着它支持 AOT(Ahead-of-Time,预编译):在 Native AOT 和 trimming 环境下正常工作,而反射-heavy 的方案会挂掉。
它在进程内调用你的 Action。 当智能体调用 getOrder 时,McpIt 直接调用你真正的 GetOrder 方法。你的路由、模型绑定、验证和业务逻辑完全按照 HTTP 调用时的方式运行。不存在第二轮 HTTP 请求回到你自己的服务器。另一个同类库 Api.ToMcp 每次工具调用都发起一个内部 HTTP 自调用。McpIt 完全跳过网络层。
它同时支持 Controller 和 Minimal API。 在 Controller Action 或 Minimal API 端点上标记 [McpTool] 都可以。Api.ToMcp 只支持 Controller。
官方 SDK 提供了传输层、协议层和服务端基础设施,这是实实在在的价值。McpIt 建立在 ModelContextProtocol.AspNetCore 之上,让官方 SDK 来服务这些工具。McpIt 增加的是生成步骤,所以你再也不用手写那些重复端点定义的工具类。
暴露是可选的。只有你用 [McpTool] 标记的端点才会变成工具,所以不会不小心把整个 API 全部暴露给智能体。而且安全提示来自 HTTP 谓词:GET 和 HEAD 被标记为只读和幂等,而 POST、PUT、PATCH 和 DELETE 被标记为破坏性操作。暴露破坏性操作会触发编译警告,除非你用 [McpTool(AllowDestructive = true)] 明确确认。编译时诊断(MCPGEN001 表示缺少描述,MCPGEN002 表示未确认的破坏性操作)在发布前就保证工具 surface 的诚实。
dotnet add package McpIt McpIt 会可传递地引入官方 MCP SDK,所以不需要你手动添加 ModelContextProtocol.AspNetCore。[McpTool] 和 [McpToolOutput] 特性在小的 McpIt.Abstractions 包里,也是可传递引入的。
在 Program.cs 里接入:
using McpIt; var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddMcpServer() .WithHttpTransport(o => o.Stateless = true) .WithToolsFromAssembly(); // discovers the tools McpIt generated builder.Services.AddMcpEndpoints(); // in-process invoker for the generated tools var app = builder.Build();
app.MapControllers();
app.MapMcp("/mcp"); // MCP server at /mcp; your API stays where it is
app.Run(); 你的 REST API 原封不动运行,同时在 /mcp 提供 MCP 服务。
两个附加功能帮助控制响应体积。[McpToolOutput] 塑造返回内容:Fields 把响应投影到你指定的上层 JSON 属性列表,MaxLength 截断结果。
[HttpGet("{id}")]
[McpTool]
[McpToolOutput(Fields = new[] { "id", "status" }, MaxLength = 500)]
public Order GetOrder(int id) { ... } 另一个是 token 报表工具。智能体在用户提问前就把每个工具的名称、描述和输入 schema 加载到上下文里,所以臃肿的工具 surface 是真实的、重复的上下文成本。mcp-token-report 可以测量这个:
dotnet tool install -g McpIt.TokenReport.Tool mcp-token-report http://localhost:5199/mcp # live server or a saved tools-list.json
mcp-token-report http://localhost:5199/mcp --markdown # Markdown table for CI artifacts
mcp-token-report http://localhost:5199/mcp --budget 2000 # exit 1 if over budget 它完全离线且确定性,所以可以用 --budget 来卡 CI 构建。计数来自离线启发式分词器:是用于比较工具和发现膨胀的估算值,不是精确的计费数字。
McpIt 是 v1.0.0,有些边界情况值得提前了解:
ModelContextProtocol.AspNetCore 1.4.0。这些都不影响核心使用场景,但在采用前你应该知道。
MCP 在 .NET 生态正在升温:Visual Studio 2026 内置 MCP 支持,Aspire 也在跟进,SDK 已经到 1.0。如果你已经有个 ASP.NET Core API,这是从"我有端点"到"智能体能调用"的最短路径。
一个 [McpTool] 特性标记,零反射,零代理,零手写 Server。这就是卖点。