site logo

Marico's space

在构建时将现有 ASP.NET Core API 转换为 MCP 工具

AI技术与应用 2026-06-08 14:48:27 6

你的 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。

为什么用它而不是手写 Server

官方 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 服务。

保持工具 Surface 精简

两个附加功能帮助控制响应体积。[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,有些边界情况值得提前了解:

  • 目标框架是 .NET 10,基于 ModelContextProtocol.AspNetCore 1.4.0。
  • 主要面向 Controller,同时支持 Minimal API。最丰富的行为在 Controller 路径下。
  • 输出塑形是 尽力而为:格式错误的 JSON 会原样透传,而不是被重新塑形。
  • Token 计数是 启发式估算,不是提供商精确的计费数字。

这些都不影响核心使用场景,但在采用前你应该知道。

试试看

MCP 在 .NET 生态正在升温:Visual Studio 2026 内置 MCP 支持,Aspire 也在跟进,SDK 已经到 1.0。如果你已经有个 ASP.NET Core API,这是从"我有端点"到"智能体能调用"的最短路径。

  • 给仓库点个 Star:github.com/norequest/McpIt
  • 从 NuGet 安装:nuget.org/packages/McpIt

一个 [McpTool] 特性标记,零反射,零代理,零手写 Server。这就是卖点。