使用 Terraform 管理 Onyx MCP 服务器:onyx_mcp_server资源配置与最佳实践
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
Onyx 支持接入 MCP(Model Context Protocol)服务器,让外部工具通过标准协议挂载到 Agent 上。本文以 onyx_mcp_server 资源文档 为核心,系统讲解如何在terraform-provider-onyx中创建、认证、授权与导入 MCP 服务器,并深入mcp_server_resource.go与write_only.go等源码,剖析其配置校验、凭证生命周期与 API 调用链。读完本文,你将能够用纯声明式配置管理 Onyx 的 MCP 服务器接入,包括共享令牌、按用户密钥、Craft 可用性与访问控制,并规避 Terraform 与浏览器式登录、敏感信息状态存储等关键陷阱。
一、资源定位与适用边界
onyx_mcp_server描述的是"Onyx 连接到的 MCP 服务器",其价值在于把该服务器的工具挂载到 Agent 上。在 Onyx 的整个 MCP 体系里,此资源只负责"服务器本身"的注册与授权,不负责服务器暴露的工具清单——工具由 Onyx 主动调用服务器后自行学习(discover),与工具选择(tool selection)以及 Craft 审批策略(approval policies)相关的配置,都只对 Onyx 已经发现过的工具生效。
文档明确划定了本资源可管理的能力边界:
- 支持无需交互式登录的认证方式:
NONE(无凭证)与API_TOKEN(令牌)。 - 拒绝 OAuth 服务器:文档声明 "An OAuth server is refused while the plan is built",因为 OAuth 流程需要浏览器往返(browser round-trip),这是 Terraform 无法执行的。从客户端源码 mcp_server.go 可看到完整的认证类型枚举,除
NONE、API_TOKEN外还有OAUTH与PT_OAUTH,后两者均被拒于 plan 阶段(详见下文"配置校验"一节)。
因此,对于需要 OAuth 交互式登录的服务器,应先在 Onyx 管理后台手工添加,再用 Terraform 管理部署的其余部分——这是官方文档给出的明确指引。
二、完整示例:三种典型用法
原文档提供了三个覆盖不同认证与授权形态的完整配置示例,应作为实战起点(三个示例可直接合并到同一.tf文件中):
# 一个无需任何凭证的公共 MCP 服务器。 resource "onyx_mcp_server" "docs" { name = "Docs" description = "Public documentation search" server_url = "https://mcp.example.com/mcp" } # 一个使用共享 API 令牌的服务器。Onyx 返回令牌时会被掩码处理,因此 # 配置文件是令牌的唯一记录:轮换令牌时请改这里,不要在 UI 中改。 resource "onyx_mcp_server" "weather" { name = "Weather" server_url = "https://weather.example.com/mcp" auth_type = "API_TOKEN" auth_performer = "ADMIN" api_token = var.weather_api_token # 仅允许 Craft agent 访问该服务器。 available_in_craft = true is_public = false } # 每个用户各自提供密钥的服务器。模板声明用户需要填写的字段, # admin_credentials 是应用该配置的管理员自己的值。 resource "onyx_mcp_server" "tickets" { name = "Tickets" server_url = "https://tickets.example.com/mcp" auth_type = "API_TOKEN" auth_performer = "PER_USER" auth_template_headers = { "X-Api-Key" = "{api_key}" } admin_credentials = { api_key = var.tickets_admin_api_key } }三个示例分别对应三类部署形态:
| 形态 | auth_type | auth_performer | 凭证字段 |
|---|---|---|---|
| 公共无凭证 | NONE(默认) | ADMIN(默认) | 无需设置 |
| 共享令牌 | API_TOKEN | ADMIN | api_token(或api_token_wo) |
| 按用户密钥 | API_TOKEN | PER_USER | auth_template_headers+admin_credentials(或_wo变体) |
三、Schema 全解:必填、可选与只读属性
原文档给出的 Schema 定义已相当完整,下表在保留全部字段的基础上补充了默认值与底层含义(字段默认值均来自 mcp_server_resource.go 的 Schema 定义):
Required(必填)
| 参数 | 类型 | 说明 |
|---|---|---|
name | String | 显示名称。Onyx 不要求唯一,两个服务器可以同名 |
server_url | String | Onyx 调用该服务器的 URL。无论 SSRF 保护级别如何,Onyx 都会拒绝 loopback 与 link-local 地址,因此部署在 Onyx 宿主本机的服务器无法通过主机名被访问 |
Optional(可选)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
description | String | "" | 自由文本描述 |
transport | String | STREAMABLE_HTTP | STREAMABLE_HTTP或已弃用的SSE |
auth_type | String | NONE | NONE或API_TOKEN |
auth_performer | String | ADMIN | 凭证提供方:ADMIN表示单一共享令牌,PER_USER表示每个用户各自提供令牌 |
api_token | String(Sensitive) | — | 共享 API 令牌,用于API_TOKEN+ADMIN组合。Onyx 返回时掩码,Terraform 永不读回:配置值是唯一记录,导入的服务器没有该值。优先使用api_token_wo,二者不能同时设置 |
api_token_wo | String(Sensitive,Write-only) | — | 仅存于配置中的共享令牌。每次 apply 都会发送,状态中不存储任何内容。与api_token_wo_version配合轮换。需要 Terraform 1.11 或更高版本 |
api_token_wo_version | Number | — | api_token_wo的轮换计数器。Terraform 不存储 write-only 值,无法感知密钥变化;提升该数字使下次 apply 发送当前值。不要用密钥本身派生它——与密钥不同,该数字保留在 state 中 |
auth_template_headers | Map of String(Sensitive) | — | 用于PER_USER的请求头模板。值中的{placeholder}声明每个用户需填写的字段。共享令牌场景下由 Onyx 自行写入该模板;若请求未声明,Onyx 会保留已有值——因此从按用户切换到共享令牌后,原按用户头仍会残留,需重建服务器才能清零 |
admin_credentials | Map of String(Sensitive) | — | auth_template_headers占位符的值,PER_USER下必填、其他形态下被拒绝(共享令牌走api_token)。Onyx 按"应用该配置的身份"而非"服务器"存储它们,返回时掩码。优先使用admin_credentials_wo,二者不能同时设置 |
admin_credentials_wo | Map of String(Sensitive,Write-only) | — | 仅存于配置的模板字段值。Terraform 每次 apply 发送、不存储任何内容。与admin_credentials_wo_version配合轮换。需要 Terraform 1.11+ |
admin_credentials_wo_version | Number | — | admin_credentials_wo的轮换计数器,语义同api_token_wo_version |
is_public | Boolean | true | 是否所有用户都可用。为false时仅users与groups指定的对象可用 |
groups | Set of Number | — | 服务器非公开时允许使用的用户组 id。Onyx 拒绝内置的Admin组,遇到该场景应改用公开服务器。该列表由配置"拥有":从配置中移除会清空服务器上的组(包括管理后台添加的) |
users | Set of String | — | 服务器非公开时允许使用的用户 id(UUID)。同样由配置"拥有",移除即清空 |
available_in_craft | Boolean | false | Craft agent 是否可以使用该服务器。该字段由 Onyx 存放在独立端点,因此设置它需要额外一次 API 调用 |
Read-Only(只读)
| 参数 | 类型 | 说明 |
|---|---|---|
id | String | 服务器 id,由 Onyx 分配 |
owner | String | 配置该服务器的身份。对 Terraform 运行而言是 API key 的合成地址,而非真实邮箱 |
status | String | 连接状态,由 Onyx 自行流转:CREATED、AWAITING_AUTH、FETCHING_TOOLS、CONNECTED或DISCONNECTED |
tool_count | Number | Onyx 在该服务器上已发现的工具数量 |
last_refreshed_at | String | Onyx 最近一次列出该服务器工具的时间 |
注意:Write-only 参数(
*_wo)依赖 Terraform 1.11 及以后版本才支持的 Write-only Arguments 特性,使用前请确认 CLI 版本满足要求。
四、配置校验:Apply 之前的本地交叉检查
ValidateConfig(mcp_server_resource.go)在 plan 构建阶段即执行全部本地校验,无需已配置的客户端,其检查顺序与组合逻辑值得关注:
- 先校验
auth_performer,再校验auth_type:因为后续检查依赖 performer 是否已知,且对 Onyx 不认识的 performer,无论auth_type解析为何值都是错误的。performer 必须是ADMIN或PER_USER,否则直接报Unknown authentication performer。 - 拒绝 OAuth:当
auth_type为OAUTH或PT_OAUTH时直接报错,提示需要在 Onyx 管理后台添加服务器。这正是前文"OAuth 被拒绝于 plan 阶段"的源码级实现。 auth_type合法值:仅允许NONE与API_TOKEN,其余值报Unknown authentication type。- 认证矩阵交叉检查(核心逻辑,按 performer 分支):
auth_type = NONE:任何凭证类字段(api_token/api_token_wo、admin_credentials/admin_credentials_wo、auth_template_headers)一旦被设置即报Credentials set on a server that takes none;ADMIN共享令牌:必须设置api_token或api_token_wo(否则报Missing api_token);不允许设置auth_template_headers(Onyx 会自行写入共享令牌的模板)与admin_credentials(共享令牌本身就是凭证);PER_USER按用户:必须设置auth_template_headers(声明用户填写字段的模板)与admin_credentials/admin_credentials_wo(应用管理员自己的字段值);禁止设置api_token/api_token_wo(那是共享令牌专用)。
源码中eitherAttributeIsSet(write_only.go)把普通敏感字段与其 write-only 孪生字段折叠为一次"是否存在"判断:任一侧有值即视为已设置,仅当两侧都未知时才返回 unknown——这保证了api_token与api_token_wo互斥但等效。配套的ConflictsWith校验器(stringvalidator.ConflictsWith与mapvalidator.ConflictsWith)则确保成对字段不能同时出现。
五、凭证生命周期:掩码、write-only 与轮换
本资源在凭证处理上有三个设计要点,直接决定了你的使用方式:
1. Onyx 返回的凭证永远被掩码。客户端模型注释明确指出:管理员的 API 令牌在回读时是一串 bullet 字符(mcp_server.go),因此没有任何响应字段适合回写进 upsert。资源在刷新时刻意跳过api_token与admin_credentials(applyRemoteMCPServer),以免把一屏掩码写进 state 覆盖真实配置值。
2. Write-only 孪生字段让密钥彻底离开 state。Terraform 会把 write-only 值从 plan 与 state 中剥离,密钥只存在于配置文件(write_only.go)。由于 Onyx 的 API 在更新时会整体替换字段,resolveWriteOnly保证每次 apply 都能拿到配置中的值发送,不会因更新而清空已存密钥。该机制由markWriteOnlySource/writeOnlySourceMarked通过 private state 标记记录来源,确保刷新时不会把密钥误写回 state。
3. 轮换通过版本计数器触发。Terraform 无法 diff 一个它从不存储的值,所以单独修改_wo字段不会产生任何 plan。writeOnlyVersionAttribute(write_only.go)为此提供了配套的*_wo_version计数器:提升数字才会产生 diff,从而驱动下一次 apply 发送当前密钥;同时用AlsoRequires校验器强制该计数器必须伴随对应_wo字段使用。文档特别警告:不要用密钥本身派生版本号——版本号留在 state 中,密钥不在。
六、访问控制:公开、用户、组与 Craft 可用性
is_public = true(默认):所有用户可用;false时仅users与groups所列对象可用。二者均可同时配置,形成白名单。groups使用用户组数字 id,且Onyx 拒绝内置Admin组,遇到"全员可用"需求请直接设is_public = true。- 这两个集合遵循"配置即权威"原则:从配置中删除某个用户/组,apply 时会同步清空服务器上的对应项——包括在管理后台手工添加的。实现上,
writeFromModel(mcp_server_resource.go)在配置缺省时发送空列表而非省略字段,因为 Onyx 把"缺省"解读为"保持原样",而配置语义是"没有访问列表";若不显式发送空列表,从配置中移除的列表会残留在服务器上,并在下次 read 时与已删除它们的 plan 产生永久 diff。 available_in_craft走独立端点:upsert 请求体(MCPServerWrite)不携带该字段,只有 PATCH 端点(/admin/mcp/server/{id},MCPServerPatch)接受它,因此完整定义一台服务器需要两次调用(mcp_server.go)。资源在创建/更新后会调用applyCraftAvailability补齐该字段,并容忍"服务器已建好但 PATCH 失败"的中间态——先记录 id 再报错,避免留下孤儿服务器。
七、工具发现与状态流转
资源本身不含工具清单。Onyx 通过调用服务器来学习其工具:tool_count反映已发现工具数量,last_refreshed_at记录最近一次工具列表刷新时间,status则由 Onyx 独立流转CREATED → AWAITING_AUTH → FETCHING_TOOLS → CONNECTED(或DISCONNECTED)。这与 Onyx 后端 MCP 服务器生命周期管理一致——连接建立、工具拉取、鉴权等待均由服务端异步完成,Terraform 只负责注册与配置。正因如此,文档强调:工具选择与 Craft 审批策略只对 Onyx已经见过的工具生效,配置中引用未发现工具会被拒绝。
八、导入既有服务器
资源支持terraform import,按数字 id 导入(与后端 APIGET /admin/mcp/servers/{id}的寻址方式一致):
#!/bin/sh # 按数字服务器 id 导入。凭证返回时为掩码状态,因此导入的服务器 # 不携带任何凭证:请在下次 apply 前把 api_token 或 admin_credentials # 补回配置文件中。 terraform import onyx_mcp_server.weather 3导入后需要特别留意凭证状态:掩码机制意味着导入的服务器没有凭证记录,若不补回api_token/admin_credentials,后续 apply 可能因缺少凭证而失败或被 Onyx 拒绝(Onyx 会直接拒绝掩码值)。
九、底层 API 调用链
从 mcp_server.go 可以完整还原资源的 REST 调用链(均为/admin管理端点):
| 操作 | HTTP 方法与路径 | 说明 |
|---|---|---|
| 创建 / 更新 | POST /admin/mcp/servers/create | UpsertMCPServer,同一请求体通过existing_server_id区分新建与更新,返回摘要(仅 server id),完整记录需再读一次 |
| 读取 | GET /admin/mcp/servers/{id} | GetMCPServer,404 表示不存在 |
| PATCH | PATCH /admin/mcp/server/{id} | PatchMCPServer,仅补available_in_craft |
| 删除 | DELETE /admin/mcp/server/{id} | DeleteMCPServer,真实删除,重复删除返回 404 |
资源生命周期(Create/Read/Update/Delete/ImportState,见 mcp_server_resource.go)严格对应上述端点;Read遇到 404 会从 state 中移除资源,Delete同样容忍 404(幂等删除)。
十、测试验证:行为即规格
仓库中的验收测试直接印证了上述行为:
- mcp_server_resource_test.go 验证了:无凭证服务器的默认值(
auth_type = NONE、auth_performer = ADMIN、transport = STREAMABLE_HTTP、is_public = true)、available_in_craft在 create 时通过后续 PATCH 生效、未设置的集合(groups/users/auth_template_headers)保持未设置以避免永久 diff,以及重命名、清空描述、翻转标志后的更新行为。 - 同一文件的
TestAccMCPServerResourceAPIToken验证了共享令牌的完整生命周期:创建 → 不轮换的 apply 产生空 plan → 轮换后新值生效,并断言共享令牌场景下 Onyx 自行写入的模板头为Authorization: Bearer {api_key}。 - 测试还确认了
server_url只需通过结构性校验即可创建(数据库写入 + URL 结构检查,不会真正连接服务器),但必须为外部地址——这与文档中"Onyx 拒绝 loopback 地址"的约束一致。
小结
onyx_mcp_server是terraform-provider-onyx中把外部 MCP 工具接入 Onyx Agent 体系的关键资源。使用时要始终牢记三条主线:认证矩阵(NONE/API_TOKEN×ADMIN/PER_USER)决定了凭证字段的合法组合,OAuth 必须走管理后台;凭证只活在配置里(掩码回读 + write-only 孪生字段 + 版本计数器轮换),state 中永远没有明文密钥;配置即权威(users/groups列表删除即清空,缺省列表会被显式置空以保持一致)。掌握这些规则后,你就能把 MCP 服务器接入纳入完全声明式的 IaC 工作流。
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考