Lightdash MCP 查询结果契约(Result Contracts)完全指南:从 SQL 轮询到渲染 Chart 的类型化数据流

发布时间:2026/9/18 1:36:24
Lightdash MCP 查询结果契约(Result Contracts)完全指南:从 SQL 轮询到渲染 Chart 的类型化数据流 Lightdash MCP 查询结果契约Result Contracts完全指南从 SQL 轮询到渲染 Chart 的类型化数据流【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash导读Lightdash 的 MCPModel Context Protocol能力让 Claude 等 AI 客户端可以执行语义层查询run_metric_query、运行只读 SQLrun_sql、轮询异步结果get_query_result并渲染图表render_chart。但当你在自定义 HTML/React Artifact 中消费这些工具时真正需要处理的是structuredContent.result—— 一套精确的、区分「已完成 / 运行中 / 终态错误」的响应形状。本文基于仓库中的 result-contracts.md 文档结合 McpService.ts 的真实实现完整讲解每种响应形状的字段语义、CSV 文本块与类型化结构体之间的边界、轮询纪律以及链接与空值处理帮你写出健壮的、能正确处理 0 行结果与长轮询的 Artifact 集成代码。本文面向的场景是「自定义 Artifact 直接调用 Lightdash MCP 工具」普通聊天对话或 Lightdash 内置 Chart App 不需要这些细节SKILL.md 中明确说明了这一点。1. 理解外层信封structuredContent.result与CallToolResult的关系文档开篇就强调了一个最容易踩坑的前提这些形状描述的是structuredContent.result而不是整个CallToolResult。一次完整的 MCP 工具调用结果CallToolResult可能包含content一组带类型的文本块。查询工具会把 CSV 或状态/错误文本放在第一个 text 块中后续块可能包含 applied-parameters 说明、queryUuid: id、[Scope: ...]或兼容性警告structuredContent.result宿主暴露的类型化结果优先于解析 CSV使用isError工具失败标志需要检查但不能只看它——终态查询状态error/cancelled/expired可能以isError: false或完全缺省的方式正常返回_meta宿主/应用元数据不要假设你的 Artifact 桥接层会暴露它。从源码看Lightdash 服务端正是按这套结构构造响应的。例如 McpService.ts#L1310-L1338 中构造指标查询结果时content数组包含 CSV 文本块或 0 行提示语、可选的 applied-parameters 块、独立的queryUuid文本块而structuredContent中则放入{ result: { status: done, queryUuid, rows, fields, exploreUrl } }。其中把queryUuid单独作为一个文本块发出源码注释给出了明确动机render_chart需要 queryUuid而只暴露content不暴露structuredContent的客户端无法从中恢复它可能臆造一个无效 IDMcpService.ts#L1304-L1307。两个必须区分的语义宿主可选地省略structuredContent纯文本桥接层查询返回 0 行——这是成功状态下的空数据。这两者完全不同前者是协议层缺失后者是业务结果为空。纯文本桥接层必须从状态文本中跟随状态并且只对真正的数据块使用 CSV 解析器引号定界符、引号与换行在 CSV 中都是合法的。如果桥接层丢弃了必要的状态或 UUID 信息应明确报集成错误而不是靠猜测。2. 已完成 SQLrun_sql同步完成 /get_query_result轮询完成run_sql返回如下形状{ status: done, rows: ArrayRecordstring, unknown, columns: string[], rowCount: number, sqlRunnerUrl: string | null }关键语义rows以列名column name为键columns给出它们的顺序rowCount是返回的行数初始同步完成的run_sql不包含queryUuid——查询已完成时不要要求一个 UUID如果 SQL 需要轮询完成的get_query_result会在这个形状上追加queryUuid第一个 content 块包含带列名表头的 CSV空结果时则是类似Query returned 0 rows. Columns: ...的文本——不要把这句空结果提示语传给 CSV 解析器结构化的空结果仍然包含status: done、rows: []、columns、rowCount: 0和sqlRunnerUrlLightdash 会对 SQL 施加请求的行数限制因此请提交完整的 SELECT 语句畸形 SQL 可能在生成的 LIMIT 附近产生错误。源码佐证位于 McpService.ts#L1341-L1424 的buildSqlQueryResultResponsecolumns取自result.columns的reference字段rows通过cell.value.raw取出原始值当rows.length 0时构造Query returned 0 rows.Columns: ${columns.join(, )}的提示文本同时structuredContent.result仍携带status: done、rows: []、columns、rowCount: 0与sqlRunnerUrlMcpService.ts#L1382-L1423。另外注意includeStatus参数控制是否附加queryUuid——这正是文档所说「同步完成不带 queryUuid、轮询完成追加 queryUuid」的实现机制。3. 已完成的指标查询Metric Queryrun_metric_query以及get_query_result返回的指标查询完成结果{ status: done, queryUuid: string, rows: ArrayRecordstring, unknown, fields: Recordstring, unknown, exploreUrl: string | null }关键语义rows以字段 IDfield ID为键fields是 Lightdash 的字段元数据映射。请用 ID 访问值与元数据用于展示标签display label与格式化第一个文本块是 CSV但表头是展示标签DISPLAY LABEL而不是稳定的字段 ID且重复的展示标签是可能的——不要靠猜把 CSV 列映射回字段 ID后续文本块可能包含 applied-parameters 说明和queryUuid: id请把它们与 CSV 分开空结果使用Query returned 0 rows.和rows: []调用render_chart时必须从该结果或其 UUID 文本块中复制本次查询的确切 UUID绝不要复用其他查询/会话的 ID。源码佐证buildMetricQueryPollResultMcpService.ts#L1280-L1339中CSV 表头通过getItemLabelWithoutTableName(item)生成即展示标签rows为空时用Query returned 0 rows.作为 body并始终追加queryUuid: ${queryUuid}文本块structuredContent.result携带完整的rows按字段 ID 键控与fields元数据映射。对于 Artifact 渲染SKILL.md 给出的原则是数据访问用稳定 ID展示用标签且不要把标签当作唯一标识符。4. 运行中状态所有查询启动工具与get_query_result的轮询形状{ status: running, queryUuid: string, nextPollAfterMs: number, heartbeatAt: string }此刻还没有任何完成的行。文本响应中同样包含 UUID、等待说明以及不要重新提交原始查询的警告。应遵从nextPollAfterMs当前为 1000 ms而不是硬编码立即重试循环。heartbeatAt是最近一次 Lightdash 状态检查的 ISO 时间戳。服务端实现里getRunningQueryResponseMcpService.ts#L822-L841用new Date().toISOString()生成heartbeatAt把MCP_QUERY_POLL_INTERVAL_MS同时写进文本提示与nextPollAfterMs而状态映射getPollingStatusMcpService.ts#L803-L820把后台的QueryHistoryStatusPENDING/QUEUED/EXECUTING/READY/ERROR/CANCELLED/EXPIRED翻译为running/done/error/cancelled/expired五态这正是所有查询工具与get_query_result共享的状态机。4.1 轮询纪律Polling Discipline从 SKILL.md 与文档可以提炼出完整的轮询流程用run_sql或run_metric_query启动一次查询保存返回的queryUuid与原始 project/agent 作用域先检查isError对结构化结果按result.status分支running保留queryUuid显示「查询仍在运行…」等待nextPollAfterMs再用同一个 UUID 与作用域调用get_query_result。永远不要发明一个 ID 或为查进度重启原查询done停止轮询并渲染数据。空的rows数组是成功完成但零行不是解析失败error/cancelled/expired停止轮询并显示返回的错误/状态这些可能是没有isError: true的正常 MCP 结果。两个重要的超时语义初次查询启动调用与每次轮询调用在服务端都最多可等待约 50 秒——请为宿主桥接层的 timeout 留出余量。这是服务端轮询的等待时间不是仓库执行超时仓库warehouse超时来自 Lightdash 连接配置。heartbeatAt记录的是 Lightdash 最近一次确认查询仍在运行的检查时间不是仓库进度百分比。此外轮询调用若断连或超时只重试get_query_result同一个 UUID使用有界的重试/退避避免重叠轮询在终态、Artifact 销毁或用户取消时停止。停止本地轮询并不会取消仓库执行。如果用户在一个旧调用还在途时启动了新查询应忽略过期响应如果初次启动调用在收到 UUID 前就丢失了响应不要自动重新提交仓库可能已经在执行它。应报告结果未知重试可能造成重复执行校验失败应在启动下一个查询前修正应用/仓库错误是终态的直到被修正不要把这类错误放进瞬时轮询重试循环。测试佐证McpService.queryPolling.test.ts 覆盖了轮询相关的行为与配置。5. 终态错误error/cancelled/expiredget_query_result可以用一个正常的 CallToolResult返回{ status: error | cancelled | expired, queryUuid: string, error: string | null }这里isError可能缺省。停止轮询并展示终态状态/错误绝不能仅仅因为isError为 false 或缺失就把终态误判为成功。与之相对启动期、校验、授权与执行期的异常则返回isError: true错误文本在content[0]且没有 structuredContent——永远不要把这些文本当作 CSV 解析。纯文本桥接层必须同时浮现错误标志与状态文本如果必要信息被隐藏应明确失败而不是猜测。源码佐证run_metric_query的 catch 分支McpService.ts#L3170-L3185以及run_sql的同类分支都以{ content: [{ type: text, text: Error running ... }], isError: true }返回而终态状态则通过getPollingStatus正常映射进structuredContent.result两者是截然不同的两条路径。6. 值类型与链接语义6.1 结构化行的值类型结构化行中的数字与布尔值不会预先字符串化不要盲目套parseFloat/parseInt。字符串、ISO 日期字符串与null都是合法值。CSV 只是格式化后的文本表示不能替代带类型的结构化值。保留null不要悄悄把它转成 0 或空字符串。6.2 查询专属链接sqlRunnerUrl与exploreUrl是 Lightdash 返回的可空、查询专属链接当 Artifact 提供「检查/编辑」动作时使用返回的链接链接为null时隐藏该动作不要构造项目级链接来冒充本次查询例如在 SQL Runner 与 Explore 场景下分别使用各自的buildSqlRunnerUrl/buildMetricExploreUrl生成的、与 queryUuid 绑定的链接不要在助手最终措辞里自行添加 SQL Runner 链接平台已经提供了查询专属的延续动作当一轮中有多个工具运行时措辞链接可能指向错误的查询。7.render_chart内置 MCP App 的输出而非 Artifact 图表数据{ status: done, queryUuid: string, exploreUrl: string | null, echartsOption: {} | null }空对象{}是轻量占位符。完整的 chart option 与 rows/fields 存在于内置 MCP App 的_meta.result中Artifact 桥接层可能拿不到这些元数据null表示没有 ECharts option例如表格或 0 行结果不是查询失败content 里是简短的渲染状态消息不是查询 CSV。该工具只支持已完成的run_metric_query结果它拒绝 SQL Runner /run_sql的查询 UUID且不会执行或轮询查询。自定义 HTML/React Artifact 应从查询工具获取 rows 并自己渲染可视化不要依赖内置图表框架也不要依赖元数据送达模型。源码佐证render_chart与run_metric_query一样受runMetricQueryEnabled开关控制McpService.ts#L3190-L3204并在渲染空结果时返回Result rendered for queryUuid: id, but the query returned 0 rows.的提示McpService.ts#L1187-L1207。run_sql与 SQL 轮询只返回数据从不返回 Lightdash chart 产物——自定义 Artifact 可以自行可视化这些数据但绝不要向render_chart发送 SQL 查询 UUID。8. 工具选择与行数限制的实践前提在启动查询前应明确两条规则详见 SKILL.md能用语义层表达的问题优先选run_metric_query先用grep_fields/get_metadata发现字段 ID并显式提供必要参数——省略的参数可能静默采用默认值其他仓库方言的只读 SELECT 用run_sql读取每个工具的 schema 了解其有效行数上限部署配置可以改变上限。从源码看指标查询的限制来自this.lightdashConfig.ai.copilot.maxQueryLimitMcpService.ts#L879并通过getValidAiQueryLimit校验后注入 MetricQuery。另外Artifact 自身应跟踪 loading / success / empty / error 四态——一个 pending 中的查询不是空数据集。9. 安全渲染与收尾检查把返回的标签、单元格值与错误消息都视为不可信的展示数据使用转义文本或 React 的常规渲染而不是原始 HTML 注入使用结构化值类型而不是用parseFloat/parseInt强制转换一切显式处理null与空结果。一句话总结本文的核心契约场景关键判断点易错点SQL 同步完成status: done无queryUuid不要强求 UUIDSQL 轮询完成追加queryUuidCSV 表头是列名指标查询完成rows按字段 ID 键控 fields元数据CSV 表头是展示标签可能重复运行中nextPollAfterMsheartbeatAt不要立即重试、不要重提原查询终态错误error/cancelled/expiredisError可能缺省不要误判为成功工具异常isError: true无 structuredContent不要解析错误文本为 CSV空结果status: donerows: []是成功不是解析失败render_chart仅限已完成的 metric 查询{}/null是占位/无图不是失败如需按上述契约继续深入建议进一步阅读同一技能目录下的 SKILL.md、契约原文 result-contracts.md以及实现端 McpService.ts 与轮询测试 McpService.queryPolling.test.ts。【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考