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

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

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

1. 简洁高效的起点

使用 WebApplication.CreateSlimBuilderMinimal API 构建基础应用服务:

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

  • WebApplication.CreateSlimBuilderASP.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>

说明:此处演示使用 MCPSSE/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,无需关心 HostClient 的实现细节。

了解 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 APIModel Context Protocol 的演进代表了 AI 时代应用架构的重要转变:

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

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

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

相关推荐
高洁012 小时前
DNN案例一步步构建深层神经网络
人工智能·神经网络·算法·机器学习·transformer
找方案2 小时前
我的 all-in-rag 学习笔记:初识 RAG—— 让 AI 从 “闭卷考试“ 变 “开卷考“
人工智能·笔记·学习·rag·all-in-rag
专注VB编程开发20年2 小时前
vb.net宿主程序通过统一接口直接调用,命名空间要一致
服务器·前端·.net
小小8程序员2 小时前
图文排版天花板:Adobe InDesign 2025 安装步骤 高效排版指南:样式 + 自动化,效率翻倍
人工智能
说私域2 小时前
社交新零售下开源AI智能名片链动2+1模式商城小程序的创新与实践
人工智能·开源·零售
子午2 小时前
【蔬菜识别系统】Python+TensorFlow+Vue3+Django+人工智能+深度学习+卷积网络+resnet50算法
人工智能·python·深度学习·蔬菜识别
天***88963 小时前
在线教育小程序定制开发,知识付费系统AI问答网课录播APP
人工智能·小程序
qq7422349844 小时前
VitePress静态网站从零搭建到GitHub Pages部署一站式指南和DeepWiki:AI 驱动的下一代代码知识平台
人工智能·python·vue·github·vitepress·wiki
式5164 小时前
线性代数(五)向量空间与子空间
人工智能·线性代数·机器学习