使用 .net10 构建 AI 友好的 RSS 订阅机器人

本文详细讲解:从 Minimal API 到 Model Context Protocol:构建 CSDN RSS 订阅器的演进之路。

随着人工智能技术的快速发展,Model Context Protocol (MCP) 正在成为连接应用与 AI 助手 的 新一代标准协议 。本文将以构建一个 CSDN RSS 订阅器 为例,探讨从传统 Minimal API 到 MCP 架构的演进过程。

1. 简洁高效的起点

使用 WebApplication.CreateSlimBuilder 和 Minimal API 构建基础应用服务:

  • Minimal API 是 ASP.NET Core 6 引入的一种更简洁的方式来构建 HTTP API,它极大地减少了样板代码的数量。

  • WebApplication.CreateSlimBuilder 是 ASP.NET Core 8 引入的构建器方法,是一个用于创建轻量级 Web 应用程序构建器,使用最少的 WebApplicationBuilder 默认值初始化类的新实例。

这种方法允许开发者从最小的基础开始,只添加实际需要的服务和中间件,从而获得更好的性能表现。

csharp 复制代码
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddHttpClient();

var app = builder.Build();

app.MapGet("/api/rss", async (HttpClient http) => {
    var rss = await http.GetStringAsync("https://rss.csdn.net/chaitsimplelove/rss/map");
    return Results.Ok(rss);
});

await app.RunAsync();

这种写法对于小型项目非常友好,代码集中在一个文件中,易于理解和快速开发。然而,当项目规模增长时,单一文件会变得难以维护。

2. 分层架构改进

为了提高可维护性,我们将 Minimal API 重构为分层架构:

csharp 复制代码
// Program.cs
using Ai4c.ACP.Server.RssBot.Services;
using QuickStart.ServiceDefaults;

var builder = WebApplication.CreateSlimBuilder(args);

// Add services to the container.
builder.AddServiceDefaults();
// 注册 McpServer 服务
builder.Services.AddMcpServer()
    .WithToolsFromAssembly()
    .WithHttpTransport();

builder.Services.AddHttpClient();
builder.Services.AddScoped<IRssService, RssService>();

var app = builder.Build();

app.MapDefaultEndpoints(); // 将 endpoints 移到单独文件

// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.MapMcp();
await app.RunAsync();
  • 定义 RSS 字段映射类
csharp 复制代码
// Models/CsdnRssFeed.cs
namespace Ai4c.ACP.Server.RssBot.Models;

// 说明:遵循 csdn rss version="2.0"
public sealed class CsdnRssFeed
{
    public string Title { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public string Link { get; set; } = string.Empty;
    public string Language { get; set; } = string.Empty;
    public string Generator { get; set; } = string.Empty;
    public string Copyright { get; set; } = string.Empty;
    public List<CsdnRssItem> Items { get; set; } = [];

    public sealed class CsdnRssItem
    {
        public string Title { get; set; } = string.Empty;
        public string Link { get; set; } = string.Empty;
        public string Guid { get; set; } = string.Empty;
        public string Author { get; set; } = string.Empty;
        public DateTime PubDate { get; set; }
        public string Description { get; set; } = string.Empty;
        public string Category { get; set; } = string.Empty;
    }
}
  • IRssService 关注点分离
csharp 复制代码
public interface IRssService
{
    Task<CsdnRssFeed> GetCsdnRssFeedAsync(string rssUrl);
}
  • IRssService 实现
csharp 复制代码
// Services/RssService.cs
using System.Xml.Linq;

namespace Ai4c.ACP.Server.RssBot.Services;

public partial class RssService(HttpClient httpClient) : IRssService
{
    private string GetElementValue(XElement parent, string elementName)
    {
        var element = parent.Element(elementName);
        if (element == null) return string.Empty;

        // 处理 CDATA
        var cdata = element.FirstNode as XCData;
        return cdata?.Value ?? element.Value ?? string.Empty;
    }
}

---

using Ai4c.ACP.Server.RssBot.Models;
using System.Xml.Linq;

namespace Ai4c.ACP.Server.RssBot.Services;

public partial class RssService
{
    public async Task<CsdnRssFeed> GetCsdnRssFeedAsync(string rssUrl)
    {
        var response = await httpClient.GetAsync(rssUrl);
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var doc = XDocument.Parse(content);

        var channel = doc.Element("rss")?.Element("channel");
        if (channel == null)
            throw new InvalidOperationException("Invalid RSS format");

        var feed = new CsdnRssFeed
        {
            Title = GetElementValue(channel, "title"),
            Description = GetElementValue(channel, "description"),
            Link = GetElementValue(channel, "link"),
            Language = GetElementValue(channel, "language"),
            Generator = GetElementValue(channel, "generator"),
            Copyright = GetElementValue(channel, "copyright")
        };

        var items = channel.Elements("item");
        foreach (var item in items)
        {
            feed.Items.Add(new CsdnRssFeed.CsdnRssItem
            {
                Title = GetElementValue(item, "title"),
                Link = GetElementValue(item, "link"),
                Guid = GetElementValue(item, "guid"),
                Author = GetElementValue(item, "author"),
                PubDate = DateTime.TryParse(GetElementValue(item, "pubDate"), out var pubDate) ? pubDate : DateTime.MinValue,
                Description = GetElementValue(item, "description"),
                Category = GetElementValue(item, "category")
            });
        }

        return feed;
    }
}
  • Endpoints 目录添加 RssEndpoints.cs
csharp 复制代码
// Endpoints/RssEndpoints.cs
using Ai4c.ACP.Server.RssBot.Services;
using Microsoft.AspNetCore.Mvc;

namespace Ai4c.ACP.Server.RssBot.Endponts;

public static class RssEndpoints
{
    // 默认 rss map 地址
    private const string DefaultRssUrl = "https://rss.csdn.net/chaitsimplelove/rss/map";

    public static void MapRssEndpoints(this WebApplication app)
    {
        // 获取 RSS 订阅内容
        app.MapGet("/api/rss", async ([FromServices] IRssService rssService, [FromQuery] string? url = null) =>
        {
            try
            {
                var rssUrl = string.IsNullOrWhiteSpace(url) ? DefaultRssUrl : url;
                var feed = await rssService.GetCsdnRssFeedAsync(rssUrl);

                var apiResp = new ApiResponse(200, true, "ok", feed);
                return Results.Ok(apiResp);
            }
            catch (Exception ex)
            {
                string error = $"Failed to fetch RSS feed: {ex.Message}";
                //var apiResp = new ApiResponse(500, false, error);
                return Results.BadRequest(error);
            }
        });

        // 获取最新博客文章
        app.MapGet("/api/rss/latest", async ([FromServices] IRssService rssService, [FromQuery] string ? url = null, [FromQuery] int count = 5) =>
        {
            try
            {
                var rssUrl = string.IsNullOrWhiteSpace(url) ? DefaultRssUrl : url;
                var feed = await rssService.GetCsdnRssFeedAsync(rssUrl);
                var latestPosts = feed.Items
                    .OrderByDescending(i => i.PubDate)
                    .Take(count)
                    .ToList();

                var apiResp = new ApiResponse(200, true, "ok", latestPosts);
                return Results.Ok(apiResp);
            }
            catch (Exception ex)
            {
                string error = $"Failed to fetch latest posts: {ex.Message}";
                //var apiResp = new ApiResponse(500, false, error);
                return Results.BadRequest(error);
            }
        });
    }

    record ApiResponse(int Code, bool Success, string Msg, object? Data = null);
}

这种结构将关注点分离,使得代码更易维护和测试。

  • 测试验证浏览器访问接口地址:
bash 复制代码
http://localhost:5050/api/rss/latest

显示信息如下:

经测试,基于 Minimal API 接口的 RSS 订阅成功!

3. 向 Model Context Protocol 演进

Model Context Protocol (MCP) 是一种新兴的标准协议,旨在为 AI 助手和应用程序之间提供标准化的通信方式 ,号称 AI时代的 USB-C 接口。它允许 AI 助手直接与应用程序交互,获取信息、执行操作,甚至参与决策过程。

接下来,我们需要在项目中安装 nuget 包:

csharp 复制代码
<ItemGroup>
  <PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.5.0-preview.1" />
</ItemGroup>

说明:此处演示使用 MCP 的 SSE/Streamable HTTP 模式,所以安装 ModelContextProtocol.AspNetCore nuget 包,如果是本地工具 STDIO 模式,可以使用 ModelContextProtocol nuget 包即可。

3.1 MCP 基础架构

MCP 的核心是一个典型的 C/S 架构,其中主机应用程序可以连接到多个服务器:

MCP 由三个核心组件构成:Host、Client 和 Server。让我们通过一个实际场景来理解这些组件如何协同工作:

假设你正在使用 Claude Desktop (Host) 询问:"我桌面上有哪些文档?"

  • MCP 主机 (MCP Hosts):Claude Desktop 作为 Host,负责接收你的提问并与 Claude 模型交互。
  • MCP 客户端 (MCP Clients):当 Claude 模型决定需要访问你的文件系统时,Host 中内置的 MCP Client 会被激活。这个 Client 负责与适当的 MCP Server 建立连接。
  • MCP 服务器 (MCP Servers):在这个例子中,文件系统 MCP Server 会被调用。它负责执行实际的文件扫描操作,访问你的桌面目录,并返回找到的文档列表。

整个流程是这样的:你的问题 → Claude Desktop(Host) → Claude 模型 → 需要文件信息 → MCP Client 连接 → 文件系统 MCP Server → 执行操作 → 返回结果 → Claude 生成回答 → 显示在 Claude Desktop 上。

除了核心组成,还包括以下部分:

  • 本地数据源 (Local Data Sources):您的计算机的文件、数据库和 MCP 服务器可以安全访问的服务
  • 远程服务 (Remote Services):可通过互联网访问的外部系统(例如,通过 API),MCP 服务器可以连接到这些系统

这种架构设计使得 Claude 可以在不同场景下灵活调用各种工具和数据源,而开发者只需专注于开发对应的 MCP Server,无需关心 Host 和 Client 的实现细节。

了解 MCP 更多信息,请查看:

3.2 RSS 订阅器的 MCP 实现

让我们将 CSDN RSS 订阅器 改造为符合 MCP 标准的服务:

csharp 复制代码
// Tools/RssSubscriber.cs
using Ai4c.ACP.Server.RssBot.Models;
using Ai4c.ACP.Server.RssBot.Services;
using Microsoft.AspNetCore.Mvc;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Ai4c.ACP.Server.RssBot.Tools;

// RSS订阅器工具
[McpServerToolType]
public sealed class RssSubscriber(ILogger<RssSubscriber> logger)
{
    private const string DefaultRssUrl = "https://rss.csdn.net/chaitsimplelove/rss/map";

    [McpServerTool(Name = "csdn_rss_subscriber"), Description("call this function to csdn rss subscriber.")]
    public async Task<CsdnRssFeed?> CsdnRssSubscriberToolAsync([FromServices] IRssService rssService,
         [Description("csdn rss url")] string url = DefaultRssUrl)
    {
        try
        {
            var rssUrl = string.IsNullOrWhiteSpace(url) ? DefaultRssUrl : url;
            logger.LogDebug($"订阅CSDN RSS:{rssUrl}");

            var feed = await rssService.GetCsdnRssFeedAsync(rssUrl);
            return feed;
        }
        catch (Exception ex)
        {
            string error = $"Failed to fetch RSS feed: {ex.Message}";
            logger.LogError(ex, error);
            return null;
        }
    }

    [McpServerTool(Name = "csdn_rss_latest"), Description("call this function to csdn rss latest.")]
    public async Task<List<CsdnRssFeed.CsdnRssItem>?> CsdnRssLatestToolAsync([FromServices] IRssService rssService,
         [Description("csdn rss url")] string url = DefaultRssUrl,
         [Description("csdn rss count")] int count = 5)
    {
        try
        {
            var rssUrl = string.IsNullOrWhiteSpace(url) ? DefaultRssUrl : url;
            logger.LogDebug($"订阅CSDN RSS:{rssUrl}");

            var feed = await rssService.GetCsdnRssFeedAsync(rssUrl);
            var latestPosts = feed.Items
                .OrderByDescending(i => i.PubDate)
                .Take(count)
                .ToList();

            return latestPosts;
        }
        catch (Exception ex)
        {
            string error = $"Failed to fetch latest posts: {ex.Message}";
            logger.LogError(ex, error);
            return null;
        }
    }
}

3.3 MCP 服务注册

在 Program.cs 中注册 MCP 服务端点:

csharp 复制代码
using Ai4c.ACP.Server.RssBot.Services;
using QuickStart.ServiceDefaults;

var builder = WebApplication.CreateSlimBuilder(args);

// Add services to the container.
builder.AddServiceDefaults();

// 注册 McpServer 服务
builder.Services.AddMcpServer()
    .WithToolsFromAssembly()
    .WithHttpTransport();

// 注册 RSS 相关服务
builder.Services.AddHttpClient();
builder.Services.AddScoped<IRssService, RssService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapDefaultEndpoints();
app.UseHttpsRedirection();
app.MapMcp(); //使用 MCP

await app.RunAsync();

4. AI 助手集成示例

  • 启动 MCP Server 服务:
  • 浏览器输入服务监听地址:

通过 MCP,AI 助手可以直接与我们的 RSS 订阅器交互:

这里我们可以使用本地工具 MCP Inspector 测试。

  • 连接 mcp server 服务:
  • 执行 tool 订阅交互

AI 助手可以获得结构化的响应:

json 复制代码
{
  "success": true,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[{\"title\":\"基于 .NET Garnet 1.0.91 实现高性能分布式锁(使用 Lua 脚本)\",\"link\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155992850\",\"guid\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155992850\",\"author\":\"ChaITSimpleLove\",\"pubDate\":\"2025-12-17T11:08:18+08:00\",\"description\":\"本文介绍了在 .NET10 环境下使用 Garnet 数据库实现分布式锁的方法。通过GarnetClient与Lua脚本配合,实现了具备自动续期功能的分布式锁 GarnetDistributedLock,并提供了完整的测试用例验证其功能正确性与并发安全性,适用于需要跨进程或跨机器同步访问资源的场景。\",\"category\":\"\"},{\"title\":\"轻量级身份认证和授权管理插件 MiniAuth\",\"link\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155934365\",\"guid\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155934365\",\"author\":\"ChaITSimpleLove\",\"pubDate\":\"2025-12-15T10:39:08+08:00\",\"description\":\"MiniAuth 是一款轻量级 ASP.NET Core Identity 管理插件,支持 JWT、Cookie 认证,兼容多种数据库与应用类型。一行代码集成,提供用户、角色、权限管理界面,非侵入式设计,跨平台部署,助力快速构建安全认证系统。\",\"category\":\"\"},{\"title\":\"基于 .net 开发的细粒度权限管理库 Casbin.NET ,支持 ACL, RBAC, ABAC 访问\",\"link\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155918863\",\"guid\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155918863\",\"author\":\"ChaITSimpleLove\",\"pubDate\":\"2025-12-15T07:30:00+08:00\",\"description\":\"Casbin 是一个强大且高效的开源访问控制库,Casbin.NET 是 Casbin 在 .NET 平台上的实现版本,提供了完整的访问控制功能。它支持多种授权模型(如 ACL、RBAC、ABAC 等),基于 PERM 元模型(Policy, Effect, Request, Matchers)进行策略 enforcement。该库处于 production-ready 状态,可用于实际生产环境中的权限管理和访问控制需求。\",\"category\":\"\"},{\"title\":\"从美食城的思考,如何构建.net高并发系统?\",\"link\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155679749\",\"guid\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155679749\",\"author\":\"ChaITSimpleLove\",\"pubDate\":\"2025-12-08T00:18:30+08:00\",\"description\":\"基于拆分层(服务/数据拆分)解决扩展瓶颈;缓冲层(缓存+MQ)化解流量洪峰;防御层(网关+应用防护)三大核心理念构建高并发安全防线。遵循\\\"网关防外敌,应用防内乱\\\"原则,实现系统稳定高效运行,彻底排除内忧外患,让高并发不再是难题。\",\"category\":\"\"},{\"title\":\"如何定位 linux 环境 .net 进程资源占用高的程序代码\",\"link\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155677505\",\"guid\":\"https://blog.csdn.net/ChaITSimpleLove/article/details/155677505\",\"author\":\"ChaITSimpleLove\",\"pubDate\":\"2025-12-07T21:27:34+08:00\",\"description\":\"本文给出Linux下.NET高CPU完整现场排查链:top -H锁定线程→dotnet-dump抓dump→dotnet-dump analyze导出符号化堆栈,秒级定位到C#文件名与行号;附一键脚本与回传分析方案,三分钟指出热点代码。\",\"category\":\"\"}]"
      }
    ]
  }
}

到此处,说明 MCP Server 服务就可以使用了。只需把该 MCP Server 挂载开放公网访问,就可以被各种 AI Agent(智能体)使用。

5. MCP 架构的优势

  • 5.1 标准化接口

MCP 提供了一套标准化的接口规范,使不同厂商的 AI 助手都能无缝集成应用功能。

  • 5.2 上下文感知

通过 MCP,AI 助手可以理解应用的完整上下文,包括可用资源、功能能力和交互历史。

  • 5.3 动态发现

AI 助手可以在运行时动态发现应用的功能,无需预先编程。

  • 5.4 安全可控

MCP 支持细粒度的权限控制,确保 AI 助手只能访问授权的功能。

6. 架构演进的价值对比

特性 Minimal API MCP(AI时代的USB-C)
开发速度 快 中等
可维护性 低(小项目)/差(大项目) 高
AI 集成能力 无 强
标准化程度 低 高
可发现性 差 好
上下文感知 无 强

7. 总结

从 Minimal API 到 Model Context Protocol 的演进代表了 AI 时代应用架构的重要转变:

  1. 从被动服务到主动协作:应用不再只是被动响应请求,而是主动与 AI 助手协作
  2. 从隐式接口到显式契约:通过 MCP 明确定义应用的能力和资源
  3. 从人类为中心到 AI 友好:设计考虑 AI 助手的使用场景
  4. 从静态功能到动态发现:支持运行时功能发现和调用

对于像 CSDN RSS 订阅器 这样的应用,MCP 架构不仅能满足传统 API 需求,还能为 AI 助手提供强大的集成能力。通过 MCP,我们可以让 AI 助手直接获取最新文章、搜索相关内容,甚至根据用户偏好推荐文章,真正实现智能化服务。

这种演进过程体现了软件架构适应 AI 时代的必然趋势。随着 MCP 标准的不断完善和普及,未来的应用开发将更多地考虑与 AI 助手的协作,创造出更加智能和便捷的用户体验。

相关推荐
styshoo1 分钟前
NVIDIA Dynamo Snapshot 介绍
人工智能
用户8314550980311 分钟前
如一 Agent 架构解读(一):数字分身的执行模型——Run、Turn 与四个时间尺度
人工智能
Kyops1 分钟前
GPT-6 把地图和交互组件带进 ChatGPT,答案终于可以直接操作了
人工智能
布吉岛的石头5 分钟前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer
OCR_133716212755 分钟前
智能交通底层逻辑:国内车牌分类规范与AI识别适配技术解析
大数据·人工智能·分类
Vincy1237 分钟前
腾讯开源 WeKnora 深度评估:3 万 Star 的 RAG 框架,你的 Milvus 能直接用吗?
人工智能
染指111012 分钟前
140.Agent-多Agent框架-Agent执行Skills流程
数据库·人工智能·设计模式·langchain·agents
小慧姐姐呀20 分钟前
把大模型塞进桌面端:一次本地离线 AI 推理的选型与工程实践
人工智能
Chengyunlai22 分钟前
告诉模型输出 JSON,就一定能得到正确的 JSON 吗?
人工智能
万联WANFLOW31 分钟前
从 ARTEX 事件看 AI Agent 安全:工具调用链为何成为新的风险入口?
人工智能·安全·测试