authentik 的 Agent 友好架构:从 llms.txt 文档索引到 code-mode MCP 的落地实践
【免费下载链接】authentikThe authentication glue you need.项目地址: https://gitcode.com/GitHub_Trending/au/authentik
本文基于仓库中的设计文档 2026-06-24-authentik-llm-architecture-design.md 编写,并结合
website/下已落地的 Docusaurus 插件源码展开。文中涉及的 Layer 2、Layer 3 及authentik-agent-marketplace为设计文档中标注的外部配套仓库/规划内容,当前 monorepo 内可以验证的是 Layer 1 的实现与文档中描述的其余层设计。
authentik 是一个开源身份认证平台,版本迭代频繁,AI 编码 Agent(如 Claude Code、Cursor)基于预训练数据的知识往往滞后于真实版本。为此,authentik 设计了一套"三层 Agent 友好架构":用随每个 release 自动再生的文档产物喂给 Agent,让 Agent 优先做实时检索(live retrieval)而不是依赖训练数据。读完本文,你将掌握这套架构的完整设计(llms.txt 文档索引 → marketplace 技能 → code-mode MCP 服务器)、Layer 1 在仓库中的真实实现细节,以及"给登录流加验证码"这一贯穿三层的验收用例。
为什么需要"检索优先":问题与设计目标
文档开篇就点明了动机:authentik 在两个 release 之间的变化非常大,Agent 的预训练知识经常过期。因此目标不是把更多内容塞进模型上下文,而是:
- 让 Agent 通过一个廉价的索引(docs 侧是
llms.txt,API 侧是schema.yml)起步; - 按需按需拉取/执行(fetch/execute on demand),而不是一次性把内容或工具灌进上下文。
两条检索面遵循同样的形状:先给 Agent 一个轻量索引,再让它按需取内容。这样"稳态维护成本趋近于零"——因为索引和 API 面都来自 authentik 每个 release 自动再生的产物。
三类问题的路由
设计文档给出了清晰的请求路由:
| 问题类型 | 例子 | 路由 |
|---|---|---|
| 文档问题 | "如何配置 SAML?"、"App 和 Provider 有什么区别?" | L1 + L2,不需要实例 |
| 实例问题 | "显示最近 10 次失败的登录"、"我运行的是哪个版本?" | L3 |
| 混合/操作问题 | "给我的登录流加个验证码"、"重置我的管理员密码" | L2 读文档理解概念,L3 写代码执行 |
非目标(YAGNI)
设计文档明确划定了不做的事,防止架构膨胀:
- 不做第三方域名上的主
llms.txt(docs/integrations 通过互链解决); - 不做预计算的 docs→API 映射注册表(code-mode 的
search对实时 spec 的检索覆盖了它); - 不做 tool-per-endpoint 的 MCP:authentik 的 API 有数百个端点,每个端点暴露一个工具会淹没上下文且每个 release 都要维护;code-mode 把工具面固定在约 3 个工具上,与 API 规模无关;
- 不采用社区
authentik-mcp(原始 HTTP、手写 tool-per-endpoint、4 倍重复、无守卫,每次 release 维护负担高,且形态错误)。
Layer 1 — Docusaurusllms.txt插件(仓库内已实现)
Layer 1 从docusaurus-plugin-llms移植并精简,落在共享主题包里。仓库中真实的目录是 website/docusaurus-theme/llms-txt/(注意与设计文档中的docusaurus-theme/llms-txt/相对位置对应,位于website/之下),与releases/、redirects/平级,以源码形式发布(无构建步骤),与既有模式一致。
文件组成
plugin.mjs— Docusaurus 插件工厂(默认导出);node.mjs— 文件发现、MDX 解析、URL 解析逻辑;generate.mjs— 各类输出(根索引、全文、分组索引、单页.md)的字符串组装;common.mjs— 选项与数据类型;markdown.mjs— MDX → 干净 Markdown 的清洗管线;- 配套
*.test.mjs单元测试与__fixtures__/样例文件。
在 website/docusaurus-theme/package.json 的exports中注册./llms-txt/plugin、./llms-txt/node、./llms-txt/common等入口。
Hook 选择:postBuild而非loadContent
插件使用postBuild钩子,而不是主题其他插件常用的loadContent/contentLoaded模式。这是有意为之:postBuild能拿到最终解析好的路由 URL 列表(routesPaths),而准确的最终 URL 必须等构建后阶段才有。在 plugin.mjs 中可以看到postBuild里用props.routesPaths调用buildLLMSOutputs并写入props.outDir。
有意思的是,插件同时实现了loadContent(用于 dev server)——它用routesPaths: []走resolveDocumentUrlFromSource的源码级 URL 解析,并通过configureWebpack把生成的产物挂到 dev server 的静态目录上,保证开发环境也能预览 llms.txt 产物。
三级"索引的索引"输出
按照 llmstxt.org 约定,对 docs 和 integrations两个站点(docs.goauthentik.io、integrations.goauthentik.io两个子域)分别生成:
/llms.txt— 分组根索引。头部带指向姊妹站点/llms.txt的交叉链接;docs 按主题分组,integrations 按类别分组(类别由categories.mjs驱动)。仓库中该头部由 generate.mjs 的 buildHeader 生成,交叉链接渲染为Related: label行。<dir>/llms.txt— 每个主题/类别一个索引(如integrations.goauthentik.io/cloud-providers/llms.txt、docs.goauthentik.io/add-secure-apps/llms.txt),只索引该子树,并向上交叉链接到父索引。实现在generatePerGroupIndexes(generate.mjs)。/llms-full.txt— 站点全文拼接。设计文档强调它并不冗余:这是给 RAG 索引种子和批量/离线下载的最佳单一载荷。插件既然已经遍历了整个站点,生成它的成本很低(generateFullText把所有页面的清洗内容按## 标题拼接,页间用---分隔)。- 每页
<page>.md—最后一跳(last-hop)载荷,索引链接指向这些.md文件(llmstxt.org 的.md后缀约定)。URL 后缀由 generate.mjs 的 applyMdExtension 处理——根首页的载荷是/index.md,其余页面是<url>.md。
每页.md载荷:核心而非可选
这是设计评审中"决定性的修正":没有每页.md,Agent 走完整条索引链后没有小块内容可拉取——只剩过大的llms-full.txt或渲染后的 HTML。所以每页.md的产出是核心功能。
难点在于 authentik 的 MDX 不是普通 Markdown,它使用了:
- partial imports(
import X from '_shared.mdx'); - 自定义 remark 指令(
:::ak-version,以及 enterprise/preview/support 徽章)。
直接拷贝.mdx会泄露未解析的 import(硬性失败——内容直接缺失)和指令噪音。因此清洗步骤必须:
- 在 React 组件注入之前拿到解析后的 MDX AST;
- 内联 partial imports(复用源插件的
resolvePartialImports思路); - 剥离自定义指令(丢弃
:::ak-*/ 徽章节点——对 Agent 是噪音); - 剥离 frontmatter,然后序列化为干净的 Markdown。
残留的 JSX 是可接受的——现代 LLM 能解析它,真正的风险是信息丢失而非残留 JSX。这是一次性、构建稳定的投入。
源码中的真实实现
清洗管线实现在 markdown.mjs 的cleanMdxToMarkdown:
- partial 内联:
inlinePartials用正则匹配import X from '..._partial.mdx',resolve出真实路径读取内容(跳过 frontmatter),再把 JSX 用法<X />替换为正文;对 Markdown 转义的下划线(\_partial.mdx)会先反转义再解析,并用chain集合防止 partial 循环导入自身; - 节点剥离:
stripNodesPlugin用unist-util-visit遍历 AST,把mdxjsEsm、mdxJsxFlowElement、mdxTextExpression等节点替换为其文本子节点,把containerDirective/leafDirective/textDirective指令节点解包为纯文本; - admonition 围栏:
stripAdmonitionFences逐行处理:::note等围栏标记(保留内文),且感知代码块——绝不改动围栏代码内部的内容; - 正则兜底:当严格 MDX 解析抛错(格式复杂的 JSX 等)时,走
regexClean正则兜底并计入统计日志。plugin.mjs 里会汇总输出(N skipped — no route; M used the regex fallback)。
解析过程中的描述提取也相当讲究:node.mjs的extractDescription会跳过标题、MDX import/export、admonition/JSX、CVE reporter 署名和纯列表块,cleanDescriptionText会剥离 blockquote 标记、-- 署名行、列表符号、图片、链接(保留链接文本)、粗斜体与行内代码,最后firstSentence截到第一句,让索引行是一条干净的短描述。
插件接线
website/docusaurus-theme/config.js 提供createLLMSPlugin(options)工厂,返回["@goauthentik/docusaurus-theme/llms-txt/plugin", options]元组。两个站点各自调用:
- docs(website/docs/docusaurus.config.esm.mjs):
sections: [{ path: ".", routeBasePath: "/" }]、groupBy: "topic"、categories: topics(来自 website/docs/topics.mjs,如core → Core Concepts、glossary → Glossary等显示标签)、regroup: [["core/glossary", "glossary"]]把术语表从 Core Concepts 中拆成独立小节,crossLinks指向 integrations 的llms.txt。 - integrations(website/integrations/docusaurus.config.esm.mjs):
groupBy: "category"、categories来自自己的 categories.mjs,overviewPages: ["index", "applications"]把落地页作为## Overview内联为散文而非链接行,并ignoreFiles: ["**/template/**"]跳过脚手架模板。
选项速查(来自common.mjs的LLMSPluginOptions)
| 选项 | 类型 | 说明 |
|---|---|---|
sections | { path, routeBasePath, label? }[] | 要扫描的一个或多个 docs 根(必填,为空会抛错) |
siteUrl | string | 覆盖站点 URL(优先级最高) |
title/description | string | 覆盖站点标题/标语 |
ignoreFiles | string[] | 额外的 glob 排除 |
crossLinks | { label, url }[] | 头部姊妹站点链接 |
groupBy | "topic" \| "category" | 根索引的分组方式 |
categories | [slug, label][] | 分组显示名覆盖 |
regroup | [pathPrefix, groupSlug][] | 把某子树拆分/并入指定组 |
overviewPages | string[] | 内联进根索引## Overview的页面 |
siteUrl的解析还有个细节:resolveSiteUrl会检查 Netlify 环境变量CONTEXT与DEPLOY_PRIME_URL,在 deploy-preview / branch-deploy 时把链接指向部署预览源,而不是硬编码生产子域,避免预览构建里的链接指向错误来源。
移植时的增删取舍
- 保留:两个核心生成器(索引 + 全文)、基于路由的 URL 解析、glob ignore、排序、section/category 分组、partial import 解析 + 指令剥离、批量处理。
- 删除:blog 包含、
pathTransformation(路由解析已覆盖)、customLLMFiles、keepFrontMatter、addPaths/ignorePaths。 - 依赖:
gray-matter+minimatch(主题本已使用fast-glob)。
Layer 2 — Marketplace 技能(authentik-agent-marketplace)
Layer 2 位于设计文档标注的外部仓库authentik-agent-marketplace(当前 monorepo 中不含其源码),其形态是两个角色拆分的插件,每个插件是一组技能。关键设计原则是:技能是"指针 + 方法",绝不是知识倾倒(knowledge dumps)。
ak-admin(12 个技能)— 按 authentik 对象模型组织:concepts、applications、providers、sources、flows-stages、authenticators-mfa、policies-rbac、users-directory、outposts、events-monitoring、troubleshooting、operations。ak-dev(11 个技能)— 面向为 authentik 做贡献:dev-environment、backend、frontend、docs、testing、linting、contributing、community、de-slop。
每个技能由name+description+ Purpose + "When to invoke" + "Not this skill" 组成,并且每个技能都携带"优先检索而非预训练,authentik 随 release 变化"的指令。
两条接缝(seams)
连接三层的正是这一对接线:
- L2 → L1(文档):每个技能只指向稳定的根入口 URL(
docs.goauthentik.io/llms.txt或integrations.goauthentik.io/llms.txt),并指示 Agent动态遍历链接、拉取页面.md。技能散文里不写死深路径,因此能扛住文档重组。Layer 1 生成的llms.txtURL 就是这些技能的入口。 - L2 → L3(实例):
ak-admin技能追加一行转向指令——"要检查或修改在线实例,使用 code-mode MCP:先search找端点,再在execute/execute_write里写ak.request(...)。先从文档学习概念。"其中events-monitoring指向execute;flows-stages、users-directory等指向execute_write。
设计文档还提到,.mcp.json注册和SessionStart依赖安装钩子已在仓库中为 Layer 3 服务器搭好了脚手架(属外部仓库内容,当前 monorepo 中不可见)。
Layer 3 — Code-mode MCP 服务器
核心思路一句话:不要以 N 个工具暴露 authentik 的 API,而是暴露"可搜索的 OpenAPI spec + 一个代码沙箱",让 Agent 对着一个经过认证的ak.request(...)辅助函数写代码。这就是 code-mode 模式(Cloudflare、Ronacher 提出)——LLM 写代码远比发工具调用在行,而且代码面把数百端点的 API 折叠成一个固定的约 3 工具、token 占用近乎不变的表面:它不会随 API 增长而增长,且免费跟随运行实例的版本。
三个工具(v1 与 v2 共用)
search(query)— 查询解析后的schema.yml(所有$ref已内联),只返回匹配的操作:method + path + summary + param / request / response 的 schema 切片。这是唯一随 API 规模伸缩的输出,且返回切片,从不返回整份 spec。(它取代了此前find_endpoint/describe_endpoint这对工具——search直接返回 Agent 构造调用所需的参数/响应 schema。)execute(code)— 在沙箱中运行 Agent 的 JS/TS,沙箱唯一能力是一个绑定的ak.request(method, path, { query, body })辅助函数。只读:拒绝任何非 GET 动词。execute_write(code)— 同一个沙箱,完整ak.request(所有动词)。每次调用都需要确认。因为它也携带读操作,所以一条"查找→创建→绑定"的混合链路可以作为一个确认块运行——code-mode 的可组合性得以保留。工具名本身也在审计轨迹中表明了意图。
Agent 循环示意:search("captcha stage")→ 读取端点 → 写一个调用ak.request(...)的代码块,按需链式调用,放进execute或execute_write。
v1 — 本地 stdio 服务器(可构建目标)
位于authentik-agent-marketplace/mcp-servers/code-mode/(npm workspace 已搭好脚手架,.mcp.json+SessionStart依赖安装钩子已存在):
- 配置/认证:
AUTHENTIK_URL+AUTHENTIK_TOKEN环境变量(社区authentik-mcp验证过的部署模型)。token 携带管理员自己的权限。 - Schema 来源:启动时拉取
<AUTHENTIK_URL>/api/v3/schema/,让发现逻辑总是匹配运行实例的版本,另有内置schema.yml作为兜底。这正是零维护属性——发现逻辑跟随实例,这里无需再生成任何东西。 - 沙箱——"绑定即边界":进程内
node:vm/worker_thread,全局对象剥离到只剩ak+console——没有fs,没有通用fetch。ak.request是唯一的出口,在execute中它仅限 GET。对抗性隔离刻意做弱,因为信任模型是"管理员用自己的 token 对自己的实例运行代码"——Agent 本来也能通过任何精选调用做同样的事,所以真正的控制是绑定(只读默认 + 写入闸门),而不是 VM(Ronacher 的观点)。 - 写入闸门:
execute_write触发 MCP 确认(elicitation)后才运行;批准后仅该次调用写武装。每次execute_write都在本地记录日志。 - 暴露的工具:只有
search、execute、execute_write。 @goauthentik/api不是关键路径:ak.request是对schema.yml路径的通用认证 fetch;生成的客户端Configuration/runtime 只是可选的传输便利,不是调用面本身。
v2 — authentik 原生 OAuth 端点(已设计,v1 验证后构建)
这是 authentik 作为身份提供商的天然优势,几乎 1:1 映射到 enterprise-mcp 参考架构:
- 传输:authentik 在产品内提供远程 MCP 端点(HTTP/SSE),如
/mcp。 - 认证:MCP 客户端向 authentik 自身执行 OAuth(authentik 就是 OIDC 提供方)——没有 API token 交接。Agent以已认证用户的身份行动。
- 授权 = authentik 自己的 RBAC:
ak.request在服务端以用户身份运行,authentik 现有的 per-object / role 权限决定代码能读写什么。无需发明新的 scope 系统——复用 authentik 已强制的内容。(MCP 层的 OAuth scope 仍可作为execute_write的粗粒度开关。) - 沙箱:服务端运行,因此有真正的隔离可用(isolate/worker 池),不像 v1 的进程内 VM。
- 审计:每次
execute/execute_write写入 authentik现有的事件日志——与events-monitoring管理技能查询的是同一条轨迹。
设计文档给出的诚实警告:v2 是产品/后端工作、随 release 门控,有真实的安全面(面向公众的代码执行挂在 IdP 后面)。它是路线图设计,必须由 v1 先行验证——在 v1 的 captcha 转折测试通过之前不要启动它。
验收测试:"给我的登录流加验证码"
这一条混合任务能锻炼完整的 L1→L2→L3 回路,也是大规模铺开前的门禁。用 code-mode 的术语讲:
flows-stages技能解释 stages/flows 概念(内容来自 Layer 1 发布的页面.md);- Agent 调用
search("captcha stage", "flow stage binding"); - 写出一个
execute_write块:创建 captcha stage、找到目标 flow、POST 绑定——一次确认调用,没有 per-endpoint 工具。
MVP 可以对着一个 mock 服务器和一小片schema.yml切片运行。它验证的是:schema 可搜索、代码可写、确认闸门可用,三个层真正串起来。
构建顺序(依赖排序)
设计文档给出 10 步、含 4 个门禁的推进路线,核心逻辑是"先索引、后载荷、再技能、最后原生端点":
- L1 — 核心索引 + 全文:
postBuild插件生成/llms.txt、<topic>/llms.txt、/llms-full.txt(docs 站点)。关键路径。 - 🛑 门禁 — 索引健全性:对真实 docs 快照——每个链接可解析、分组正确、全文包含内容。
- L1 — 每页
.md:partial 解析 + 指令剥离,所有索引链接指向.md。关键路径。 - 🛑 门禁 — 内容质量:抽 5 个不同类型的页面——无残留 import、无徽章杂物、Markdown 可读;喂给 LLM 确认它能列出步骤。
- L2 —
ak-docs技能骨架:入口 URL + 遍历-拉取方法。(门禁 2 修复入口 URL 后即可并行。) - L3 v1 — code-mode 服务器核心:
search(基于schema.yml)+execute(只读沙箱)+execute_write(需确认)。(可与 L2 并行,只依赖 spec;在转折测试关键路径上。) - 🛑 门禁 — captcha MVP 端到端(转折点):Agent 走 L2 → 读 captcha
.md→searchspec → 写一个execute_write块(mock 服务器即可)。失败时诊断的是 schema/散文清晰度,而不是工具。 - L1 — integrations 子域。(设计文档标注已随 PR #23360 一起上线。)
- L2 — 把
ak-admin/ak-dev技能接到llms.txt入口(L2→L1),并加 code-mode 转向行(L2→L3)。可并行。 - L3 v2 — authentik 原生 OAuth 端点。只有v1 转折点通过后才做;产品/后端投入,单独 spec + 计划。
横切关注点:安全、分发与维护
安全
- v1:本地 stdio 把管理员 token 留在自己的环境里;沙箱只暴露
ak(无fs/fetch);execute仅 GET;execute_write每次调用确认并记日志。绑定即边界。 - v2:对 authentik 做 OAuth;授权是 authentik 自己在用户身份下的 RBAC;每次调用都审计进事件日志。
分发
Claude Code + Cursor 的 manifest 已在 marketplace 中;ak-admin/ak-dev两个插件同时服务两者。
维护论点
docs(.md+ 索引)和 API 面(对实时schema.yml的search)每个 release 都直接来自 authentik;L2 是薄胶水,L3 没有 per-endpoint 代码要打补丁。稳态人工维护趋近于零。
待规划阶段解决的开问题
设计文档保留了明确的开放问题,可作为后续实施的路线图注记:
- L3:
search如何对schema.yml的操作做排序/过滤(对 path+summary+tags 做关键词匹配;每次命中返回多少 schema 切片而不撑爆上下文)。 - L3:
node:vmvsworker_thread的最终选择,全局如何剥离到只剩ak+console;execute_write的 MCP 确认(elicitation)如何在 Claude Code / Cursor 之间呈现。 - L3:
ak.request的传输——纯认证fetchvs@goauthentik/api的Configurationruntime;启动时拉取/api/v3/schema/vs 内置schema.yml兜底如何选择。 - L2 接线:确认每个
ak-admin技能的单条转向行,以及稳定的llms.txt入口 URL(只依赖已上线的 Layer 1)。
结语
这套架构的核心价值可以浓缩为三句话:docs 教概念(Layer 1 + Layer 2),code-mode 执行动作(Layer 3),而两个检索面都由 authentik 每个 release 自动再生——Agent 的知识不再依赖"最后一次训练时点的快照",而是始终跟随运行实例的当前版本。对于要复刻这套模式的团队,Layer 1 是唯一在仓库内完整落地的部分,其 llms-txt 插件 从postBuild路由解析、partial 内联、指令剥离到三级索引输出,提供了可以直接参照的完整实现样例。
【免费下载链接】authentikThe authentication glue you need.项目地址: https://gitcode.com/GitHub_Trending/au/authentik
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考