1. 为什么我决定用 .NET 自己写一个 MCP 数据库服务器
先聊个实际的场景。最近我在做一个内部数据问答机器人,团队成员希望直接在聊天界面里问“上个月的订单总额是多少”,AI 能自动查数据库并返回结果。第一反应是让大模型直接写 SQL,把 SQL 粘贴到数据库工具里执行。这个方案在演示时问题不大,但真到了生产环境,几乎没人敢放开权限——大模型生成的 SQL 可能查错表、漏掉关键过滤条件,更别提如果误操作执行了 DELETE 或 UPDATE,后果根本不是一句“抱歉”能解决的。
于是我把目光转向了 MCP。MCP 的全称是 Model Context Protocol,它的本质是给 AI 模型提供一套“外接工具”的标准接口。模型不需要知道数据库连接串、不用自己拼 SQL,它只需要按照协议调用服务器暴露出来的工具函数,由服务器去执行真正危险的操作,再把结果格式化返回给模型做总结。这种“模型负责理解意图、服务器负责执行操作”的架构,正好解决了数据库查询助手最核心的安全和可控性问题。
为什么我会选 .NET 而不是 Python?原因很实在:第一,我们公司的数据团队和业务系统本身就跑在 .NET 生态上,现成的数据库实体、仓储层、配置中心都可以复用;第二,.NET 的 Microsoft.Data.SqlClient 搭配 Dapper 在查询性能上表现很稳定,处理大数据集时比 Python 的 ORM 更让我放心;第三,MCP 官方 SDK 现在有完整的 C# 实现,跑起来并不比 Python 复杂多少。
这篇博文不是教你怎么用别人的现成 MCP 服务器,而是带你从零把一个能用的 MCP 数据库查询服务写出来。适合的读者是:已经了解 C# 基础语法、用过 ASP.NET Core 或控制台项目,想在自己的 .NET 服务里接入 AI 能力的开发者。整个过程我会从环境配置开始,一步步写到联调测试,最后把我在实际开发中踩过的坑也一并分享出来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先把 MCP 的工作机制搞清楚:工具、资源和原语
2.1 MCP 到底在“协议”什么
很多人刚开始接触 MCP 时会把它类比成 API 框架,其实不太准确。MCP 更接近一套“客户-服务器”模式,但它服务的目标不是普通 App,而是 AI 模型。整个架构里有三个角色:
- MCP 主机:也就是实际运行大模型的应用,比如 Claude Desktop、Cherry Studio、或者你自己写的一个聊天前端。
- MCP 客户端:和服务器建立连接的组件,在主机进程内运行,负责收发 JSON-RPC 消息。
- MCP 服务器:一个独立进程,通过标准输入输出或者 HTTP 协议暴露自己的工具列表,等待客户端调用。
整个流程是:主机启动后,由客户端向服务器发送 initialize 请求,服务器返回自己的能力和名称;随后客户端获取工具列表 tools/list,把工具的名称、描述、参数 Schema 全部发给模型。模型在回答问题时根据工具描述“看到”了能调用的函数,于是生成一条函数调用指令,客户端再转成 tools/call 请求发给服务器;服务器执行完毕后返回结果。
这中间最关键的就是 JSON-RPC 消息结构。MCP 2.0 对 JSON-RPC 的用法做了扩展,但核心还是那套轻量的请求-响应机制。这也意味着,即使你不用官方 SDK,只要按照协议规范手动构造 JSON 消息,也能实现一个最简单的 MCP 服务。不过实际开发中不建议这么干,因为 SDK 已经把传输层、会话管理和错误处理都封装好了。
2.2 数据库助手里需要哪个能力子集
MCP 协议定义了三种主要原语:工具(Tools)、资源(Resources) 和 提示词(Prompts)。在数据库查询助手的场景里,资源和提示词其实用不太上,核心完全集中在工具上。
工具的特点是“由模型自主决定是否调用”,也就是动态执行。你在服务器上定义好一个工具叫 query_database,模型会自行判断什么时候该用它、传入什么参数。这也带来了一个我在开发中反复强调的安全原则:工具的名字可以叫“查询”,但服务器内部必须有能力区分“只读查询”和“写操作”,最好在开发阶段直接屏蔽掉写操作。我用的是一个折中的方案:默认情况下,MCP 服务器用一个只读数据库账号连接,SQL 里一旦出现 INSERT、UPDATE、DELETE 等关键字,直接拒绝执行。这样即使模型抽风,也不会造成实质性的数据破坏。
2.3 SDK 选择和项目形态的决定
.NET 生态里做 MCP 服务器,最省心的做法是用官方 C# SDK。我在写的时候用的版本是 ModelContextProtocol 包,它内部的 API 设计是典型的 ASP.NET Core 风格,注册服务、添加工具、启动服务器几步走,熟悉依赖注入的朋友上手会很快。
项目形态上我建议用一个 ASP.NET Core Web 项目,而不是普通的控制台程序。原因有两个:一是 Web 项目里的配置系统、日志系统都是现成的,后续加鉴权、加健康检查都很方便;二是 MCP 服务器在开发阶段需要频繁联调,Web 项目可以通过 HTTP 端点暴露服务,用 MCP Inspector 这类工具调试起来比纯 stdio 模式直观得多。
3. 动手搭建:项目初始化和依赖安装
3.1 环境准备与项目创建
先交代一下我本机环境:Windows 11,安装了 .NET 8 SDK(建议最低 .NET 8,因为 MCP SDK 对较新运行时支持更好),装了 Docker Desktop 用来跑测试数据库,IDE 用的是 Visual Studio 2022 和 VS Code 都测试过。
打开终端,先建一个 ASP.NET Core Web API 项目:
bash复制dotnet new web -n McpDatabaseServer
cd McpDatabaseServer
接着安装 MCP Server SDK 和 SQL Server 相关包:
bash复制dotnet add package ModelContextProtocol.AspNetCore
dotnet add package Microsoft.Data.SqlClient
dotnet add package Dapper
这里我故意没有用 Microsoft.EntityFrameworkCore 这类重量级 ORM,原因后面会重点解释。先用 Dapper 让查询路径尽量短,减少不必要的映射开销,这在数据量大的场景下体感差异非常明显。
3.2 配置数据库连接
在 appsettings.json 里加上数据库连接字符串。注意我这里刻意使用了只读账号,这是安全意识的开端:
json复制{
"ConnectionStrings": {
"ReadOnlyDb": "Server=localhost,1433;Database=SalesDB;User Id=mcp_reader;Password=StrongPass;TrustServerCertificate=True;"
},
"Database": {
"MaxRows": 100,
"CommandTimeoutSeconds": 30
}
}
MaxRows 这个配置非常关键。MCP 服务器返回给模型的结果不能是无限大的结果集,否则模型上下文直接爆炸。我习惯默认限制 100 行,并且每条结果都拼上一个提示,“仅展示前100行,如需更精确数据请添加 WHERE 条件”,这样模型就会在后续对话中主动帮用户补过滤条件。实测下来,这个提示对模型的行为纠正非常有效。
4. 核心实现:从模型调用到 SQL 执行
4.1 定义工具类:MCP 的工具就是普通方法
MCP SDK 里定义工具的方式很简单,你写一个普通类,方法上加上 [McpTool] 特性,SDK 会自动把它暴露成工具。我把所有数据库操作都封装在 DatabaseTools.cs 里:
csharp复制using ModelContextProtocol;
using ModelContextProtocol.Server;
using Microsoft.Data.SqlClient;
using Dapper;
using System.ComponentModel;
using System.Text.Json;
public sealed class DatabaseTools
{
private readonly IConfiguration _config;
private readonly ILogger<DatabaseTools> _logger;
public DatabaseTools(IConfiguration config, ILogger<DatabaseTools> logger)
{
_config = config;
_logger = logger;
}
[McpTool("query_database", "通过只读方式查询关系型数据库,支持标准 T-SQL SELECT 语句,自动限制返回行数。")]
public async Task<string> QueryDatabaseAsync(
[Description("完整的 SELECT 查询语句,禁止包含 INSERT/UPDATE/DELETE 等写操作关键字")]
string sql,
CancellationToken ct = default)
{
// 安全检查 + 执行 + 格式化 JSON 返回
var safeSql = SqlSafetyValidator.ValidateSelect(sql);
if (!safeSql.IsValid)
{
return JsonSerializer.Serialize(new
{
success = false,
error = safeSql.ErrorMessage
});
}
var connStr = _config.GetConnectionString("ReadOnlyDb");
var maxRows = _config.GetValue<int>("Database:MaxRows");
try
{
using var conn = new SqlConnection(connStr);
var sqlToRun = SqlSafetyValidator.WrapWithTop(sql, maxRows);
var result = await conn.QueryAsync<dynamic>(sqlToRun, commandTimeout: 30);
var limited = result.Take(maxRows).ToList();
var output = new
{
success = true,
rowCount = limited.Count,
truncated = limited.Count == maxRows,
message = limited.Count == maxRows
? $"仅显示前 {maxRows} 行,可添加更精确的 WHERE 条件缩小范围"
: $"查询成功,共 {limited.Count} 行",
data = limited
};
return JsonSerializer.Serialize(output);
}
catch (Exception ex)
{
_logger.LogError(ex, "数据库查询失败,SQL: {Sql}", sql);
return JsonSerializer.Serialize(new
{
success = false,
error = "查询执行失败,请检查 SQL 语法,或确认表和字段名是否正确",
detail = ex.Message
});
}
}
[McpTool("get_schema", "获取数据库中所有表名,以及指定表的字段信息。")]
public async Task<string> GetSchemaAsync(
[Description("表名,可选;为空时返回所有表列表")]
string? tableName = null,
CancellationToken ct = default)
{
// 实现见下文
}
}
这里有几个细节可以展开聊一下。
首先,方法的参数我用 [Description] 特性描述了含义和约束,这些描述会被 MCP 协议转换为 JSON Schema 传给模型。模型其实是靠这些描述来决定“该怎么调用”和“该传什么”的,所以描述写得越清楚,模型翻车的概率越低。我甚至会在描述里直接写上常见错误示例,比如“不要使用 SELECT *,性能差且结果集过大”,实测效果相当好。
其次,返回值统一用 JSON 字符串,而不是直接返回对象。MCP 协议的工具返回结果本质上就是一个文本内容,模型拿到文本后再理解并生成回答。JSON 是最好的中间格式,因为模型读结构化数据的理解准确率远高于读纯文本表格。我习惯在 JSON 里同时包含 success、rowCount、truncated 和 message 字段,message 是给模型看的引导性提示,data 才是真正的查询结果。
4.2 SQL 安全验证器:这是整个服务器的生命线
如果说整个 MCP 服务器里有一个模块是绝对不能省的,那一定是 SQL 安全验证。我在前文说到了只读账号,但只依靠数据库账号权限还不够——万一账号权限配置疏忽,或者未来有人把连接串换成了有写权限的账号,危险操作就等于裸奔了。
所以在应用层再叠一层保险。我写了一个 SqlSafetyValidator,重点检查以下几点:
csharp复制public static class SqlSafetyValidator
{
// 黑名单关键字:如果 SQL 中包含这些关键字,直接拒绝执行
private static readonly HashSet<string> BlockedKeywords = new(StringComparer.OrdinalIgnoreCase)
{
"INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE",
"TRUNCATE", "EXEC", "EXECUTE", "GRANT", "REVOKE", "MERGE"
};
public static (bool IsValid, string ErrorMessage) ValidateSelect(string sql)
{
if (string.IsNullOrWhiteSpace(sql))
return (false, "SQL 不能为空");
// 去掉字符串里的内容,避免误伤
var cleaned = RemoveStringLiterals(sql);
foreach (var keyword in BlockedKeywords)
{
if (Regex.IsMatch(cleaned, $@"\b{keyword}\b", RegexOptions.IgnoreCase))
return (false, $"检测到禁止的关键字 {keyword},只允许执行 SELECT 查询");
}
var trimmed = sql.TrimStart();
if (!trimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)
&& !trimmed.StartsWith("WITH", StringComparison.OrdinalIgnoreCase))
{
return (false, "只支持以 SELECT 或 WITH 开头的查询语句");
}
return (true, string.Empty);
}
private static string RemoveStringLiterals(string sql)
{
// 简单实现:去掉单引号包裹的字符串内容
return Regex.Replace(sql, "'[^']*'", "''");
}
}
这个验证器当然不是 100% 完美——比如 WITH 开头的 CTE 后面仍然可能隐藏写操作,但配合只读账号的双保险,已经把风险降到了可接受的范围。我甚至会在 catch 块里把执行异常的详细信息返回给模型,让模型自己反思并重写 SQL,这个“自愈流程”配合得非常顺。
4.3 Schema 查询工具:给模型一张“地图”
还有一个容易被忽视的关键工具:get_schema。模型本质上是“没看过数据库”的,它不知道你的库里有几张表、每个表有哪些字段。如果你不提供 Schema,模型生成 SQL 时就只能猜,猜错的概率非常高。哪怕你给它一个“订单总额”这种简单问题,它也可能把表名搞错——谁知道你的订单表是叫 Orders 还是 T_Order_2024 呢?
所以我的 MCP 服务器里固定提供一个查询表结构的工具。这个工具实现起来很简单:
csharp复制[McpTool("get_schema", "获取数据库中所有表结构信息,包括表名、字段名、数据类型、是否可空等。")]
public async Task<string> GetSchemaAsync(
[Description("表名,可选;为空时返回所有表列表")]
string? tableName = null,
CancellationToken ct = default)
{
var connStr = _config.GetConnectionString("ReadOnlyDb");
using var conn = new SqlConnection(connStr);
if (string.IsNullOrEmpty(tableName))
{
var tables = await conn.QueryAsync<string>(
"SELECT t.name FROM sys.tables t ORDER BY t.name");
return JsonSerializer.Serialize(new { success = true, tables });
}
var columns = await conn.QueryAsync(
@"SELECT c.name AS ColumnName,
ty.name AS DataType,
c.max_length,
c.is_nullable
FROM sys.columns c
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
JOIN sys.tables t ON c.object_id = t.object_id
WHERE t.name = @TableName
ORDER BY c.column_id",
new { TableName = tableName });
if (!columns.Any())
return JsonSerializer.Serialize(new { success = false, error = $"表 {tableName} 不存在" });
return JsonSerializer.Serialize(new { success = true, tableName, columns });
}
这里有一个我后来才悟出来的经验:模型的上下文窗口是有限的。如果数据库有几十张表,你把所有表的 Schema 一次性返回给模型,模型很快就“忘”了最早的那些表。所以 get_schema 的分步设计很重要:先返回所有表名,模型根据用户的问题猜测相关表,再精确查询某一张表的具体字段。这样既省 token 又提高了准确率。
4.4 为什么选 Dapper 而不是 EF Core
开头我说了不想用 EF Core,这里解释下原因。
EF Core 的好处是面向实体、有 LINQ 支持,代码写起来很优雅。但在 MCP 服务器的场景里,一个致命问题是你永远无法预知模型要查什么表、什么字段——你不能要求模型生成的 SQL 恰好能匹配你预先定义好的实体类。如果模型查了一张你根本没建实体的表,EF Core 根本处理不了。Dapper 不一样,它跑的是原生 SQL,返回的是 dynamic 类型,模型生成什么 SQL,Dapper 就执行什么 SQL,然后直接把原始行数据序列化为 JSON。这种“无模式”的灵活性,恰好是 AI 查询场景最需要的。
当然,代价是安全性更依赖你自己写的防护逻辑。所以我的方案是 Dapper 执行查询 + 安全验证器守住入口 + 只读数据库账号,三层防线缺一不可。
5. 把工具注册到 MCP 服务器并启动
5.1 注册 MCP 服务和工具
工具类写好后,接下来就是把它注册到 MCP 服务器中。在 Program.cs 里做三件事:加 MCP 服务、注册工具类、映射 MCP 端点。
csharp复制using ModelContextProtocol;
using ModelContextProtocol.AspNetCore;
using McpDatabaseServer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<DatabaseTools>();
builder.Services.AddMcpServer()
.WithTools<DatabaseTools>();
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapMcpServer();
app.MapHealthChecks("/health");
app.Run();
看着很简洁对吧?AddMcpServer 会自动从服务容器里把 DatabaseTools 中标记了 [McpTool] 的方法提取出来,转换成 MCP 工具列表;MapMcpServer() 会暴露两个端点:一个是用于 SSE(Server-Sent Events)的 /sse,一个是用于流式传输的 /mcp。
启动服务:
bash复制dotnet run
看到控制台输出 Now listening on: http://localhost:5000 就表示成功了。
5.2 用 MCP Inspector 联调:验证工具注册是否成功
写 MCP 服务器最痛苦的是什么?是你没法直接像调试 Web API 那样在浏览器里发个 URL 就能测试,因为 MCP 的请求是 JSON-RPC,而且工具调用需要先建会话、发初始化请求,然后才能列工具。
好在官方提供了 MCP Inspector 这个调试工具。启动方式也很简单:
bash复制npx @modelcontextprotocol/inspector
然后在浏览器打开 Inspector 的地址,在 Server 配置里选择 URL 模式,填上 http://localhost:5000/sse,点击连接。连接成功后,你可以:
- 查看服务器暴露出哪些工具
- 查看每个工具的 JSON Schema
- 手动模拟调用工具,填入参数并查看返回
我第一次用 Inspector 时,发现 query_database 工具暴露正常,但它对 sql 参数的描述显示为“无描述”——后来检查才发现忘了加 [Description] 特性。这个工具是排查 MCP 连接和参数问题的利器,早点学会它能省下大把时间。
5.3 进阶:支持 Streamable HTTP 传输
MCP 协议发展到现在,主推的传输方式已经从早期的 stdio 变成了 Streamable HTTP。ModelContextProtocol.AspNetCore 包已经内置了支持,app.MapMcpServer() 会自动映射 /mcp 端点支持 POST 消息和 SSE 响应。用这种传输方式的好处是:
- 不需要在客户端维护一个常驻进程,HTTP 的请求-响应模型更贴合 Web 场景
- 可以通过反向代理做负载均衡和鉴权
- 更容易嵌入到已有的微服务架构中
如果之后你想切换回 stdio 模式(比如直接在 Claude Desktop 里配置一个本地脚本作为服务器),只需要改注册代码,把 UseHttp 换成 UseStdio 即可。两种模式共用同一套工具定义,切换成本极低。
6. 在真实客户端里验证完整链路:自然语言到查询结果
6.1 用 Cherry Studio 做端到端测试
MCP Inspector 能验证协议层没有问题,但真正的考验是让大模型“自己决定”调用工具。我最常用的测试客户端是 Cherry Studio,因为它的 MCP 配置界面做得比较友好,而且支持 SSE endpoint 连接。
在 Cherry Studio 的 MCP 配置里,新增一个服务器,类型选 SSE,地址填 http://localhost:5000/sse,保存并刷新。之后新建一个会话,在模型设置里开启 MCP 工具,就能看到 query_database 和 get_schema 这两个工具了。
打开系统提示词,告诉模型“你是一个数据库查询助手,可以调用 MCP 工具获取数据库信息和执行 SQL 查询”,然后开始测试:
用户输入:上个季度每个月各产品线的销售总额分别是多少?
预期流程:
- 模型先调用
get_schema,看看有哪些表。 - 发现
OrderItems表里有ProductLine、Amount、OrderDate字段。 - 模型生成 SQL,比如
SELECT ProductLine, MONTH(OrderDate) AS Month, SUM(Amount) AS TotalAmount FROM OrderItems WHERE OrderDate >= '2025-07-01' AND OrderDate < '2025-10-01' GROUP BY ProductLine, MONTH(OrderDate) ORDER BY Month。 - 调用
query_database,拿到 JSON 结果。 - 模型把 JSON 转成人类可读的回答,加上简单分析和总结。
整个流程走下来,看到模型真的能自主调工具、自主决定 SQL、自主生成总结时,那种成就感是很强烈的——这就是“从文本到 SQL 再到洞察”的完整闭环。
6.2 测试中容易出现的几个典型失败模式
我在联调时遇到过几类高频失败,提前写出来给你避坑。
失败模式一:模型问“需要我查询数据库吗?”而不是直接调工具。 原因是系统提示词里没告诉模型它拥有可用的工具。解决办法是在系统提示词里明确写:“你可以调用下列 MCP 工具获取实时数据:query_database、get_schema。请直接调用,无需询问。”
失败模式二:模型 SQL 语法错误,导致服务器返回失败。 这时候模型会收到错误信息,高质量模型一般会自动修正后再调一次。如果你的模型不修正,可以在 query_database 返回的错误信息里加一句“请分析错误原因并重新构造正确的 SQL”。模型通常都会照做。
失败模式三:模型忘记加 LIMIT 或 WHERE,结果集过大。 这就是我在前面配置 MaxRows 的原因。即使模型没想着限制行数,服务器端也会强杀结果集并把截断提示随着 JSON 返回给它,模型据此补条件再查一次。
7. 运行过程中踩过的坑,和一些真心建议
7.1 SQL Server 连接串的坑:TrustServerCertificate 必须显式指定
新版 Microsoft.Data.SqlClient 默认强制加密连接,如果你本地 SQL Server 用的是自签名证书,不设置 TrustServerCertificate=True 就会一直报证书链错误。这个报错在普通 ASP.NET Core 项目里很直观,但放进 MCP 服务器里、经过 JSON 序列化传给模型后,错误信息已经绕过了一层,模型返回的可能是“查询失败,未知原因”这种没营养的话。排查起来极其隐蔽。我的经验是本地开发连接串一律加 TrustServerCertificate=True,生产环境再用真实的 CA 证书。
7.2 Docker 跑数据库时端口映射的坑
我用 Docker 跑 SQL Server 测试库时遇到过一个问题:容器内 SQL Server 默认监听 1433 端口,但我在 docker run 时把宿主机端口映射成了 11433:1433。连接串写 Server=localhost,1433 连不上,写 localhost,11433 才对。这种错位问题在 MCP 场景里特别容易忽略,因为报错会延迟到模型调用工具时才出现,不仔细看还以为是自己 SQL 写得不对。
7.3 让模型更“懂”你的数据库:描述信息就是学习资料
我的一个核心体会是:在 MCP 服务器开发里,花在写工具描述上的时间,和花在写代码上的时间一样重要。模型不像人可以通过读代码文档理解工具,它只能靠 JSON Schema 里的描述字段来理解“这个工具是干嘛的、参数应该怎么传”。所以每次发现模型调用工具出错,我第一反应不是去改模型配置,而是去改进工具描述,让描述更精确、更贴近用户提问的方式。
比如一开始我把工具描述写成“查询数据库”,模型就经常不传 WHERE 条件。后来我改成“查询关系型数据库,用 SELECT 语句,务必包含表名。当用户没有给出明确条件时,自行判断是否需要补充 WHERE 条件”,效果立竿见影,生成的 SQL 规范程度高了一截。
7.4 性能与可观测性:MCP 服务器也需要日志和度量
MCP 服务器自身跑的是后台服务,遇到问题不像普通 Web API 那样有访问日志可以查。所以在开发阶段我就加了 ILogger,每个工具调用都会记录入参、耗时、是否成功。调用链路上的关键节点,比如安全验证器拦截了哪些 SQL、结果集被截断了几次,我都会打日志。这些数据在优化系统提示词和工具描述时非常有用——你能直观看到模型“在想什么”,而不是猜。
我还会定期看 MaxRows 导致的截断次数。如果某个问题一直触发截断,说明用户经常提出过大的查询范围,可以考虑在工具描述里更强调“建议使用聚合函数或者增加过滤条件”。
8. 后续还能怎么玩:从只读查询到告警分析
最后说点扩展方向。目前这个 MCP 数据库助手只支持只读查询,后续可以基于同一个架构扩展出更多能力:
- 告警分析工具:新增一个
analyze_metrics工具,读取监控数据库里的指标数据,让模型可以回答“昨晚 API 平均响应时间为什么升高了”这类问题。 - 数据血缘追踪:通过查询系统表里的外键关系,让模型理解表与表之间的关联,生成 JOIN 时正确率会提升非常多。
- 多数据库适配:目前只在 SQL Server 上验证过,适配 PostgreSQL 或者达梦数据库的话,只需要替换 SqlClient 为对应驱动,Dapper 的接口不受影响,改动成本很低。
- 动态上下文注入:在用户提问之前,把业务核心表的 Schema 自动填充到模型上下文里,省去
get_schema的往返调用,提升响应速度。但要注意 token 消耗,最好限制在几张高频表。
说实话,MCP 这个生态还在快速演进中,SDK 的 API 也会持续调整。但整体思路是稳定的:把工具注册好、描述写清楚、安全守住、结果格式统一,不管协议怎么升级,你的服务器都能快速适配。如果看完这篇文章后你自己动手搭了一个,碰到什么奇怪的坑,欢迎来交流——这种面向未来的人机协作开发方式,值得投入时间。
