当前位置: 首页 > news >正文

Resource 与 Tool 的边界

MCP 中的 Resource 更适合表达“已经存在、可以被引用、可以被读取的上下文对象”。例如:

mssql://local/db/sales/schema/dbo/table/Orders mssql://local/db/sales/schema/dbo/procedure/GetOrders mssql://local/db/sales/schema/dbo/view/OrderSummary mssql://local/db/sales/schema/dbo/table/Orders/indexes

这些 URI 表示的是数据库中的稳定对象。用户可以通过@引用它们,Agent 客户端也可以通过资源浏览、搜索、补全等方式把它们加入上下文。相比之下,Tool 更适合表达“动作”。例如搜索对象、解析依赖、执行只读查询、诊断 SQL 错误、分析执行计划等,都更适合作为工具。

因此,一个 SQL Server MCP 不应该只暴露 tools,也不应该把数据库中所有对象一股脑注册为 resources。更合理的设计是:用 resources 表达数据库对象,用 resource templates 表达可参数化的资源路径,用 tools 帮助模型发现、解析和读取相关资源。

代码层面可以先把资源注册集中起来,避免所有逻辑散落在 server 初始化代码中:

// resources/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerSqlEntryResources } from "./entries.js"; import { registerSqlResourceTemplates } from "./templates.js"; import { registerSqlResourceTools } from "./tools.js"; export function registerResources(server: McpServer) { registerSqlEntryResources(server); registerSqlResourceTemplates(server); registerSqlResourceTools(server); }

这个结构的重点不是文件怎么命名,而是把三类事情分清楚:入口资源、动态资源模板、模型可调用的资源辅助工具。

不要把所有数据库对象平铺到 resources/list

SQL Server 中的对象数量可能非常多。一个实例下面可能有多个 database,每个 database 下有多个 schema,每个 schema 下又有大量 table、view、procedure、function、index。如果resources/list一次性返回所有对象,不仅性能差,也会让客户端资源列表变得不可用。

更优雅的做法是让resources/list只返回少量入口资源或高价值资源。例如:

mssql://local mssql://local/db/sales mssql://local/db/sales/schemas mssql://local/db/sales/schema/dbo mssql://local/db/sales/schema/dbo/tables mssql://local/db/sales/schema/dbo/views mssql://local/db/sales/schema/dbo/procedures

这些资源更像目录入口。用户或客户端读取mssql://local/db/sales/schema/dbo/tables时,Server 可以返回该 schema 下的表清单;读取具体表 URI 时,Server 再返回表结构、列、主键、外键、索引摘要、行数估计等内容。

入口资源可以这样写:

// resources/entries.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerSqlEntryResources(server: McpServer) { server.registerResource( "SQL Server Local", "mssql://local", { title: "SQL Server Local", description: "Root resource for the local SQL Server instance.", mimeType: "text/markdown", }, async (uri) => ({ contents: [ { uri: uri.toString(), mimeType: "text/markdown", text: [ "# SQL Server: local", "", "Available databases:", "", "- sales", "- hr", "- finance", "", "Use resource templates to access schemas, tables, views, and procedures.", ].join("\n"), }, ], }) ); server.registerResource( "Sales Database", "mssql://local/db/sales", { title: "sales", description: "Entry resource for the sales database.", mimeType: "text/markdown", }, async (uri) => ({ contents: [ { uri: uri.toString(), mimeType: "text/markdown", text: [ "# Database: sales", "", "Common entry points:", "", "- mssql://local/db/sales/schemas", "- mssql://local/db/sales/schema/dbo/tables", "- mssql://local/db/sales/schema/dbo/views", "- mssql://local/db/sales/schema/dbo/procedures", ].join("\n"), }, ], }) ); }

这里的关键点是:静态注册的 resource 不需要覆盖整个数据库。它们更像“导航入口”。真正大量的数据库对象,应该交给 Resource Template 动态读取。

例如:

mssql://local/db/sales/schema/dbo/tables

可以返回:

# Tables in dbo - dbo.Customers - dbo.Orders - dbo.OrderItems - dbo.Products

而:

mssql://local/db/sales/schema/dbo/table/Orders

可以返回:

CREATE TABLE dbo.Orders ( OrderId int NOT NULL PRIMARY KEY, CustomerId int NOT NULL, CreatedAt datetime2 NOT NULL, Status nvarchar(32) NOT NULL ); -- Foreign Keys -- FK_Orders_Customers: CustomerId -> dbo.Customers.CustomerId -- Indexes -- PK_Orders -- IX_Orders_CustomerId -- IX_Orders_CreatedAt

这种方式让resources/list保持轻量,同时保留资源浏览的层次感。

使用 Resource Templates 表达动态资源

大量数据库对象不适合全部预注册,但它们适合通过 Resource Template 暴露。Resource Template 可以告诉客户端:Server 支持某一类 URI 结构,客户端可以根据参数构造具体资源。

对于 SQL Server MCP,可以设计如下模板:

mssql://{server}/db/{database} mssql://{server}/db/{database}/schema/{schema} mssql://{server}/db/{database}/schema/{schema}/table/{table} mssql://{server}/db/{database}/schema/{schema}/view/{view} mssql://{server}/db/{database}/schema/{schema}/procedure/{procedure} mssql://{server}/db/{database}/schema/{schema}/function/{function} mssql://{server}/db/{database}/schema/{schema}/table/{table}/indexes mssql://{server}/db/{database}/schema/{schema}/table/{table}/sample?limit={limit}

代码可以这样表达:

// resources/templates.ts import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerSqlResourceTemplates(server: McpServer) { server.registerResource( "SQL Table", new ResourceTemplate( "mssql://{server}/db/{database}/schema/{schema}/table/{table}", { list: undefined, complete: { server: completeServers, database: completeDatabases, schema: completeSchemas, table: completeTables, }, } ), { title: "SQL Table", description: "SQL Server table schema resource.", mimeType: "application/sql", }, async (uri, variables) => { const ddl = await getTableDefinition({ serverName: String(variables.server), database: String(variables.database), schema: String(variables.schema), table: String(variables.table), }); return { contents: [ { uri: uri.toString(), mimeType: "application/sql", text: ddl, }, ], }; } ); server.registerResource( "SQL Procedure", new ResourceTemplate( "mssql://{server}/db/{database}/schema/{schema}/procedure/{procedure}", { list: undefined, complete: { server: completeServers, database: completeDatabases, schema: completeSchemas, procedure: completeProcedures, }, } ), { title: "SQL Procedure", description: "SQL Server stored procedure definition resource.", mimeType: "application/sql", }, async (uri, variables) => { const sql = await getProcedureDefinition({ serverName: String(variables.server), database: String(variables.database), schema: String(variables.schema), procedure: String(variables.procedure), }); return { contents: [ { uri: uri.toString(), mimeType: "application/sql", text: sql, }, ], }; } ); }

这段代码体现了一个重要原则:tableprocedure本身就是 resource。它们不是describe_table工具返回的一段普通文本,而是有 URI、有 MIME 类型、有标题和描述的上下文对象。

但是需要注意,Resource Template 本身只说明“可以这样访问资源”,不等于客户端一定会自动提供完美的路径补全。路径补全、树形浏览、搜索体验,仍然取决于具体 Agent 客户端的实现。

用 Completion 支持类似文件路径的补全体验

如果希望用户输入@mssql://时像输入文件路径一样逐层补全,MCP Server 需要配合实现 completion 能力。比如用户输入:

@mssql://

客户端可以请求 server 参数补全,Server 返回:

local dev prod

用户选择local后继续输入:

@mssql://local/db/

Server 再返回:

sales hr finance

继续选择sales后,Server 返回 schema:

dbo reporting audit

最后补全 table:

Customers Orders OrderItems Products

对应代码可以先写成简单版本:

async function completeServers(value: string) { const servers = ["local", "dev", "prod"]; return servers.filter((x) => x.startsWith(value)); } async function completeDatabases(value: string, context?: any) { const serverName = context?.arguments?.server ?? "local"; const databases = await listDatabases(serverName); return databases.filter((x) => x.startsWith(value)); } async function completeSchemas(value: string, context?: any) { const serverName = context?.arguments?.server ?? "local"; const database = context?.arguments?.database; if (!database) return []; const schemas = await listSchemas(serverName, database); return schemas.filter((x) => x.startsWith(value)); } async function completeTables(value: string, context?: any) { const serverName = context?.arguments?.server ?? "local"; const database = context?.arguments?.database; const schema = context?.arguments?.schema; if (!database || !schema) return []; const tables = await listTables(serverName, database, schema); return tables.filter((x) => x.startsWith(value)); } async function completeProcedures(value: string, context?: any) { const serverName = context?.arguments?.server ?? "local"; const database = context?.arguments?.database; const schema = context?.arguments?.schema; if (!database || !schema) return []; const procedures = await listProcedures(serverName, database, schema); return procedures.filter((x) => x.startsWith(value)); }

这个流程中,MCP Server 负责提供可补全的数据,Agent Client 负责识别用户正在输入资源引用,并决定何时调用补全接口、如何展示结果、是否缓存结果、是否显示成树形结构。

换句话说,MCP Server 提供“资源语义”和“补全能力”,但@mssql://的最终交互体验,是 MCP Server 和 Agent Client 共同完成的。

仅靠 Resource 还不够:模型也需要主动探索资源

有一个更复杂但非常真实的场景:

用户:帮我看看 @mssql://local/db/sales/schema/dbo/procedure/GetOrders 为什么出错。

用户手动把GetOrders这个存储过程作为资源加入上下文。Agent 读取了 procedure 的代码,发现里面引用了dbo.Ordersdbo.Customers。这时模型需要继续查看这些表的定义,才能判断错误原因。

问题来了:模型怎么知道dbo.Orders的资源 URI?又应该通过什么方式读取它?

从 MCP 的语义上看,resources/read是 Client/Host 调用 MCP Server 的通用资源读取能力,而 Tool 才是模型主动调用的动作。如果 Agent Host 没有把通用read_resource(uri)暴露给模型,那么模型虽然知道自己需要更多上下文,却不一定能主动读取相关资源。

因此,SQL Server MCP 最好不要只提供静态 resources,还应该提供一组面向模型探索的工具,例如:

resolve_database_object(name, contextUri) get_object_dependencies(objectUri) get_referenced_by(objectUri) search_database_objects(query, contextUri) read_database_resource(uri)

这些工具不是为了取代 resources,而是为了让模型能够在分析过程中发现和请求更多 resources。

例如,模型看到存储过程里有dbo.Orders,可以先调用对象解析工具:

server.registerTool( "resolve_database_object", { title: "Resolve SQL Object", description: "Resolve a SQL object name to a MCP resource URI.", inputSchema: { name: z.string(), contextUri: z.string().optional(), }, }, async ({ name, contextUri }) => { const object = await resolveObjectName(name, contextUri); return { content: [ { type: "text", text: `Resolved ${name} to ${object.uri}`, },
http://www.jsqmd.com/news/1088013/

相关文章:

  • 影刀RPA新手教程:子流程封装完全指南——参数传递、复用设计与调试技巧
  • KMS_VL_ALL_AIO:你的Windows和Office智能激活解决方案
  • UE4SS终极指南:如何用Lua脚本系统彻底改变虚幻引擎游戏开发体验
  • 软考机考模拟系统深度拆解(从考场底层协议到答题延迟优化)
  • 10分钟极速配置黑苹果:OpCore Simplify终极指南
  • 某红书App X-s参数逆向分析:从Hook到算法复现的完整实战
  • GAN如何生成合法SQL与JSON?微软离散数据生成方案解析
  • 终极魔兽世界宏工具指南:GSE-Advanced-Macro-Compiler完整教程
  • 终极星露谷物语农场规划器:免费在线设计你的完美农场
  • 瑞萨FSP电机传感器模块实战:霍尔与感应式角度速度检测详解
  • 瑞萨PG-FP6编程器芯片支持全解析与量产烧录实战指南
  • QMCDecode终极指南:3分钟解锁QQ音乐加密文件的完整方案
  • Chrome V8引擎0day漏洞深度解析与应急响应指南
  • TPFanCtrl2终极指南:如何在Windows 10/11上实现ThinkPad风扇128级精准控制
  • 软考AI新科目教材对比测评(含5大出版社+3套教辅):哪本真正匹配2024年最新考试大纲?权威数据告诉你答案
  • B站视频永久保存指南:m4s转MP4完整解决方案
  • 软考新大纲隐藏规则曝光(内部教研组闭门会议纪要节选):案例分析题评分细则重大调整
  • 2026年GEO优化系统源码架构与高性能实践
  • 我用 Codex 做周报自动化,第一件事是防止它胡写
  • 速存!一键扒光短视频水印的神器来了!
  • D3KeyHelper:暗黑3智能按键辅助工具,优化你的游戏操作流程
  • 【FI】SAP ODN实战:从配置到调优的完整指南
  • 跨平台兼容方案:在macOS上无缝运行Windows应用
  • BetterNCM安装器:5分钟解锁网易云音乐插件化新体验
  • 大模型MoE稀疏激活原理与工程实践:从1.8万亿参数到2%计算真相
  • RA8P1 OSPI接口配置与调试:从基础原理到实战避坑指南
  • SOP —— 构建RBD模拟的基石
  • 3分钟上手Aimmy:免费AI瞄准辅助工具让游戏体验全面提升
  • 3步搞定离线漫画库:哔咔漫画下载器的终极使用指南
  • 早高峰ETC车道频现读卡失败 全绿监控为何抓不住毫秒级淤堵