首页
/ Directus MCP schema 工具解析:让 LLM 读懂数据库结构的只读发现机制

Directus MCP schema 工具解析:让 LLM 读懂数据库结构的只读发现机制

2026-09-05 23:38:01作者:晏闻田Solitary

在 Directus 的 AI/MCP 集成中,schema 工具是所有 LLM 交互的起点:它以只读方式返回一份"经过精心裁剪"的 schema 概览,让模型先弄清项目里有哪些集合(collections)、字段与关系,再决定调用哪个 CRUD 工具。读完本篇,你可以完整理解该工具的两种操作模式(Discovery / Detailed)的输入输出契约、响应中每个字段的生成规则,以及它在 MCP 工具注册表中作为 root 工具被挂载的完整链路。

工具定位:只读发现,不做修改

prompt.md 开宗明义地界定了这个工具的边界:

Retrieve essential Directus schema information to understand the data structure - collections, fields, and relationships. This is a READ-ONLY discovery tool designed to help you explore and comprehend existing schema. It is for schema exploration and understanding only. For schema modifications, use the dedicated collections, fields, and relations tools.

也就是说,schema 工具只负责"理解",不负责"变更"——修改结构时应使用专门的 collectionsfieldsrelations 工具(均位于 api/src/ai/tools/ 目录下,并在 工具清单ALL_TOOLS 数组中统一注册)。文档还特别强调,该工具返回的不是 Directus API 的原始 schema 响应,而是一份为 LLM 理解深度优化过的紧凑结构。

这一"只读"定位在源码中有三重印证(见 schema 工具实现):

  • readOnly: true:注册表据此判定该调用无需审批(见 registry.ts 的 #isReadOnly);
  • annotations: { title: 'Directus - Schema', readOnlyHint: true }:向 MCP 客户端声明工具注解;
  • exposure: 'root':表示它是注册表模式下可直接调用的根级工具,而非需要先 searchexecute 的目录工具。

另外,工具定义中的 instructions 字段通过 requireText 在运行时直接读取同目录下的 prompt.md 全文——这意味着本文解析的这份 Markdown 文档,正是实际下发给 LLM 的工具说明文本,而非普通的开发者笔记。

输入输出契约:一个可选参数,两种返回形态

工具使用 Zod 定义了三套 schema(index.ts L60-L86):

export const SchemaValidateSchema = z.strictObject({
  keys: z.array(z.string()).optional(),
});

export const SchemaInputSchema = z.object({
  keys: z
    .array(z.string())
    .optional()
    .describe(
      'Collection names to get detailed schema for. If omitted, returns a lightweight list of all collections.',
    ),
});
  • 输入:唯一参数是 keys(字符串数组,可选),指定要深入查看的集合名;
  • 输出SchemaOutputSchema 是一个联合类型,data 要么是轻量概览(collections / collection_folders / notes),要么是 集合名 → 字段名 → 字段概览 的三层映射。这个联合类型直接对应文档描述的两种操作模式。

Discovery 模式(默认):轻量总览

用法:不传参数或传空 keys 数组:

{}

返回:轻量 schema 概览,包含三个字段:

  • collections:按字母排序的真实集合名(对应数据库表)数组;
  • collection_folders:按字母排序的文件夹名数组。文件夹是纯 UI 概念,不是真实表,与 directus_files 中的文件文件夹也完全不同——它只用于在 UI 中把不同集合分组展示;
  • notes:集合与文件夹的描述(如有)。

重要提示:文件夹与集合共享同一个命名空间。创建新集合前应先检查 collection_folders 数组以避免命名冲突(例如,已存在 website 文件夹时就不能再创建 website 集合)。

示例响应

{
  "collections": ["categories", "contacts", "organizations", "pages", "posts", "products"],
  "collection_folders": ["content", "marketing", "website"],
  "notes": {
    "contacts": "People at the organizations you work with",
    "organizations": "Your clients and customers",
    "pages": "Static pages with page builder blocks",
    "posts": "Blog posts and articles",
    "content": "Content management folder"
  }
}

适用场景:初次探索、快速建立对现有数据结构的整体认知。

实现细节:如何区分文件夹与真实集合

handler 中的 Discovery 分支 调用 CollectionsService.readByQuery() 拉取全部集合条目后,用一条规则做区分:collection.schema 为空的就是 UI 文件夹(真实集合必有数据库表,即 schema 非空)。notes 来自 collection.meta.note,且有一处细节值得注意——以 $t 开头的 note 会被过滤掉,因为这类值是 Directus 的国际化翻译占位符(如 $t:folder_content),直接下发只会让 LLM 困惑。

单元测试 覆盖了这条路径:keysundefined[] 时均走 Discovery 分支;含 schema 的条目进入 collectionsschema: null 的进入 collection_folders;无 note 的集合、空集合列表等边界情况也都断言了输出形状,并用 schema.output.safeParse() 验证响应符合声明的 Zod 输出 schema。

Detailed 模式:指定集合的深度剖析

用法:通过 keys 指定要检查的集合:

{ "keys": ["products", "categories", "users"] }

返回:一个以集合名为键的对象,内含字段与关系详情,包括:

  • 字段定义(类型、必填性、只读性);
  • 关系映射(外键、连接表);
  • 界面(interface)配置与选项(如下拉 choices);
  • 字段元数据与约束说明(note);
  • JSON 字段(repeater/list 结构)的嵌套字段结构,支持递归嵌套。

示例响应(含普通字段、界面选项与 JSON 嵌套结构):

{
  "posts": {
    "id": { "type": "uuid", "primary_key": true, "readonly": true },
    "title": { "type": "string", "required": true },
    "status": {
      "type": "string",
      "interface": { "type": "select-dropdown", "choices": ["draft", "published", "archived"] }
    },
    "category": {
      "type": "uuid",
      "relation": { "type": "m2o", "related_collections": ["categories"] }
    }
  },
  "block_faqs": {
    "headline": { "type": "text", "interface": { "type": "input-rich-text-html" } },
    "faqs": {
      "type": "json",
      "interface": { "type": "list" },
      "fields": {
        "title": { "type": "text", "interface": { "type": "input-multiline" } },
        "answer": { "type": "text", "interface": { "type": "input-multiline" } }
      }
    }
  }
}

适用场景:在操作数据或修改 schema 之前,对特定集合做深入分析。

实现细节:字段概览的裁剪规则

Detailed 分支(index.ts L141-L238)一次性加载三份数据:CollectionsServiceFieldsService.readAll()RelationsService.readAll() 的结果组成一个 SchemaToolSnapshot,随后逐字段构建 fieldOverviewOutput。构建过程体现了"为 LLM 裁剪"的设计意图:

  1. 只处理请求的集合if (!args.keys?.includes(field.collection)) return;——即使全量加载了字段,输出也只包含 keys 中指定的集合;
  2. 跳过纯 UI 字段type === 'alias'meta.specialno-data 的字段(如 presentation-divider 分隔线)被直接剔除——它们没有对应数据,对模型毫无信息量。测试用例 should skip UI-only alias fields 明确验证了这一点;
  3. choices 只保留取值select-dropdown 等接口的 options.choices 会被映射为纯值数组(如 ["active", "inactive"]),源码注释写明原因:"Only return the value of the choice to reduce size and potential for confusion",即去掉 label/text 等装饰字段以压缩体积、避免混淆(对应测试 should handle fields with choices);
  4. JSON 字段的递归展开type === 'json'meta.options.fields 存在时(典型如 repeater/list 界面),调用 processNestedFields() 递归展开子字段,maxDepth: 5 封顶防止过深结构撑爆上下文。深度嵌套测试 构造了 5 层 JSON 嵌套,断言第 5 层之后 fields 为空对象——与文档中"includes recursive nesting"的描述一致;
  5. collection-item-dropdown 特殊处理:这类字段存储的是 { collection, key } 组合,processCollectionItemDropdown 会查找 selectedCollection 的主键类型,输出形如 { collection: { value: 'posts', type: 'string' }, key: { type: 'uuid' } } 的结构,让 LLM 准确理解该 JSON 字段的实际形态。

关系映射:m2o / o2m / m2m / m2a 的推导逻辑

文档承诺 Detailed 模式返回"relationship mappings (foreign keys, junction tables)",其实现核心是两个辅助函数。

第一步:从 meta.special 判定关系类型getRelationType):

meta.special 标记 判定结果
m2ofile m2o(多对一)
o2m o2m(一对多)
m2mfiles m2m(多对多)
m2a m2a(多对任意)

第二步:在关系快照中反查结构细节buildRelationInfo 分发到四个构建函数):

  • M2O:目标集合直接取自本字段的外键表——按 relation.related_collectionrelation.schema.foreign_key_tablefield.schema.foreign_key_table 的优先级回退取值;
  • O2M:一对多字段本身是 alias,需要在关系快照中找到"反向关系"——即 meta.one_collection / meta.one_field 指向当前字段的那条记录,输出对侧集合与 many_field
  • M2M:先找到连接表中 meta.one_field 等于当前字段的 junction 关系,再通过 meta.junction_field 定位另一侧关系得到目标集合(找不到时回退为 directus_files,这也是文件字段被归入 M2M 的原因),并附带 junction: { collection, many_field, junction_field, sort_field? } 完整描述连接表结构;
  • M2A:在连接表中寻找带 one_allowed_collections 的多态关系,输出允许的集合列表与完整 junction 描述(含 one_collection_field,默认为 collection)。

relationships 测试组 对上述四种关系逐一构造了 mock 数据并断言输出,其中 "M2M with files" 用例验证了 special: ['files'] 的字段最终映射为 collection: 'directus_files'、junction 为 posts_files 的结果。

在 MCP 服务端如何被暴露与调用

schema 工具并不是孤立存在的,理解它的分发链路有助于把握文档中"Recommended Workflow"的运行背景:

  1. 注册schema 与其他 10 个工具一起进入 ALL_TOOLSapi/src/ai/tools/index.ts);
  2. 挂载:每次 MCP 请求到达时,DirectusMCP.handleRequest 以当前请求的 accountabilityschema 等上下文执行 new ToolRegistry(ALL_TOOLS).mount(...),实现权限感知的工具可见性过滤(如 admin 标记的工具对非管理员隐藏,见 registry.ts 的 #isToolVisible);
  3. 两种工具模式:通过查询参数 tool_mode=registry 切换。server.ts L316-L324toMcpTool 揭示了 prompt.md 的实际去向:
    • legacy 模式(默认):工具描述直接取 tool.instructions,也就是 prompt.md 全文——模型在 tools/list 时即读到"Discovery/Detailed 双模式 + 工作流建议"的完整说明;
    • registry 模式:描述只取简短的 tool.description,改为挂载 outputSchema;此时根级工具集是 search + execute + schema 三件套(getRootTools),schemaexposure: 'root' 而无需先 search 即可直接调用,其余工具则遵循"search → execute"两段式调用。
  4. 执行CallToolRequestSchema 处理器按模式分发到 mountedRegistry.executeRoot(...)execute(...),输入先经 validateSchema(strictObject,拒绝未知字段)校验,结果在 registry 模式下还会附带 structuredContent

结合文档末尾的 Recommended Workflow,一次典型的 AI 数据操作会话即:

  1. Discover:不带 keys 调用 schema,拿到全部集合与文件夹总览;
  2. Analyze:根据用户需求从总览中挑出相关集合;
  3. Detail:带具体集合名再次调用 schema,获取字段/关系/嵌套结构;
  4. Implement:基于 schema 认知调用 items 等 CRUD 工具读写数据,或用 collections/fields/relations 工具变更结构。

小结

Directus 的 MCP schema 工具是一个典型的"面向 LLM 的信息裁剪"设计:prompt.md 定义的行为契约(双模式、命名空间冲突提醒、推荐工作流)与 index.ts 的实现(文件夹/集合区分、no-data 字段过滤、choices 值化、5 层递归上限、四种关系反查)一一对应,并由 index.test.ts 中 15+ 个用例锁定行为。若你要在自己的 Directus 项目里接入 AI 助手,理解这套"先发现、后剖析、再操作"的工具链设计,比记住某个具体字段更有价值。

登录后查看全文
热门项目推荐
相关项目推荐