dataform-context-mcp 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataform_context_mcp-0.5.0/LICENSE +21 -0
- dataform_context_mcp-0.5.0/PKG-INFO +408 -0
- dataform_context_mcp-0.5.0/README.en.md +383 -0
- dataform_context_mcp-0.5.0/pyproject.toml +68 -0
- dataform_context_mcp-0.5.0/pyproject.toml.orig +58 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/__init__.py +3 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/cli.py +229 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/compile.py +103 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/db.py +405 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/golden.py +111 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/indexer.py +58 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/layers.py +25 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/lineage.py +188 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/model.py +42 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/server.py +360 -0
- dataform_context_mcp-0.5.0/src/dataform_context_mcp/staleness.py +42 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vincent Gossiaux
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataform-context-mcp
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Deterministic, self-hosted MCP server exposing structured Dataform pipeline context (lineage, schemas, layers) to coding agents
|
|
5
|
+
Keywords: dataform,bigquery,mcp,lineage,analytics-engineering
|
|
6
|
+
Author: Vincent Gossiaux
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Dist: mcp[cli]>=2.0.0
|
|
18
|
+
Requires-Dist: sqlglot>=30.17.0
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Project-URL: Homepage, https://github.com/vgossiaux/dataform-context-mcp
|
|
21
|
+
Project-URL: Repository, https://github.com/vgossiaux/dataform-context-mcp
|
|
22
|
+
Project-URL: Issues, https://github.com/vgossiaux/dataform-context-mcp/issues
|
|
23
|
+
Project-URL: Changelog, https://github.com/vgossiaux/dataform-context-mcp/tags
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<h1 align="center">
|
|
28
|
+
<img src="https://raw.githubusercontent.com/vgossiaux/dataform-context-mcp/main/docs/assets/logo-lockup.png" alt="Dataform Context" width="420">
|
|
29
|
+
</h1>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<strong>Language:</strong>
|
|
34
|
+
<a href="README.md">Français</a> |
|
|
35
|
+
<a href="README.en.md">English</a>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://img.shields.io/badge/Python-3.12%2B-3776AB?logo=python&logoColor=white" alt="Python 3.12+" />
|
|
40
|
+
<img src="https://img.shields.io/badge/MCP-stdio-6E56CF" alt="MCP stdio" />
|
|
41
|
+
<img src="https://img.shields.io/badge/Dataform-3.x-4285F4?logo=googlecloud&logoColor=white" alt="Dataform 3.x" />
|
|
42
|
+
<img src="https://img.shields.io/badge/runtime-no%20network%20%C2%B7%20no%20LLM-2EA44F" alt="No network, no LLM" />
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<strong>Give your coding agent (Claude Code, Cursor) reliable, always-fresh knowledge
|
|
47
|
+
of your Dataform pipeline — table and column lineage, schemas, impact analysis —
|
|
48
|
+
instead of letting it read <code>.sqlx</code> files one by one and hallucinate the DAG.</strong>
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Install with Claude Code
|
|
54
|
+
|
|
55
|
+
Prerequisites (once per machine): [uv](https://docs.astral.sh/uv/) (`brew install uv`)
|
|
56
|
+
and `@dataform/cli` ≥ 3.0 (`npm i -g @dataform/cli`).
|
|
57
|
+
|
|
58
|
+
At the root of **your Dataform repo**, create or extend `.mcp.json`
|
|
59
|
+
(template: [`.mcp.json.example`](.mcp.json.example)):
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"dataform-context": {
|
|
65
|
+
"type": "stdio",
|
|
66
|
+
"command": "/opt/homebrew/bin/uvx",
|
|
67
|
+
"args": ["--from", "dataform-context-mcp@latest", "dataform-context", "serve"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That's it: no clone, no `--repo` (the server indexes the current directory). Open a
|
|
74
|
+
Claude Code session in the repo and type `/mcp`: `dataform-context` should show as
|
|
75
|
+
connected. First call takes ~15 s (initial compilation), then ~10 ms.
|
|
76
|
+
|
|
77
|
+
> [!IMPORTANT]
|
|
78
|
+
> **Absolute path to `uvx` is required**: a bare `"command": "uvx"` fails (`ENOENT`)
|
|
79
|
+
> when the agent is launched from a non-login shell whose PATH lacks
|
|
80
|
+
> `/opt/homebrew/bin`. Same caution applies in CI.
|
|
81
|
+
|
|
82
|
+
**Verify from the agent**: just ask "run check_setup" — the tool diagnoses the whole
|
|
83
|
+
installation (dataform CLI, compilation, index, lineage coverage, goldens). Or copy
|
|
84
|
+
[`integrations/claude-code/commands/dataform-context-verify.md`](integrations/claude-code/commands/dataform-context-verify.md)
|
|
85
|
+
into your repo's `.claude/commands/` to get `/dataform-context-verify`.
|
|
86
|
+
|
|
87
|
+
Recommended: add the agent-instruction block to the repo's `CLAUDE.md` (see
|
|
88
|
+
[Getting the agent to adopt the tools](#getting-the-agent-to-adopt-the-tools)).
|
|
89
|
+
|
|
90
|
+
### Pin a version or work offline
|
|
91
|
+
|
|
92
|
+
`@latest` makes `uvx` check PyPI on every server start: fixes reach you with no action on
|
|
93
|
+
your side, at the cost of a mandatory network access at start-up (1 to 3 s). Offline, the
|
|
94
|
+
server does not start. To pin a version, or to ride out an outage, replace `@latest` with an
|
|
95
|
+
exact version:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
"args": ["--from", "dataform-context-mcp@0.5.0", "dataform-context", "serve"]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A pinned version no longer receives fixes: remember to bump it.
|
|
102
|
+
|
|
103
|
+
## Install with Cursor
|
|
104
|
+
|
|
105
|
+
Same server, standard MCP stdio. Copy
|
|
106
|
+
[`integrations/cursor/mcp.json.example`](integrations/cursor/mcp.json.example) to your
|
|
107
|
+
repo's `.cursor/mcp.json`, and the rule
|
|
108
|
+
[`integrations/cursor/rules/dataform-context.mdc`](integrations/cursor/rules/dataform-context.mdc)
|
|
109
|
+
to `.cursor/rules/`. Commands
|
|
110
|
+
[`dataform-context-verify.md`](integrations/cursor/commands/dataform-context-verify.md)
|
|
111
|
+
and
|
|
112
|
+
[`dataform-context-golden-init.md`](integrations/cursor/commands/dataform-context-golden-init.md)
|
|
113
|
+
go into `.cursor/commands/` for `/dataform-context-verify` and
|
|
114
|
+
`/dataform-context-golden-init` — same protocol as the Claude Code side (Cursor
|
|
115
|
+
supports custom commands and, since v1.5, MCP elicitation). All integration material is
|
|
116
|
+
summarized in [`integrations/README.md`](integrations/README.md) (French).
|
|
117
|
+
|
|
118
|
+
## Install with Codex CLI
|
|
119
|
+
|
|
120
|
+
Prerequisites: [uv](https://docs.astral.sh/uv/) and `@dataform/cli` ≥ 3.0.
|
|
121
|
+
|
|
122
|
+
At the root of your repo, create or extend `.codex/config.toml`
|
|
123
|
+
(template: [`integrations/codex/config.toml.snippet`](integrations/codex/config.toml.snippet)):
|
|
124
|
+
|
|
125
|
+
```toml
|
|
126
|
+
[mcp_servers.dataform-context]
|
|
127
|
+
command = "/opt/homebrew/bin/uvx"
|
|
128
|
+
args = ["--from", "dataform-context-mcp@latest", "dataform-context", "serve"]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`.codex/config.toml` only loads for a **"trusted"** project: the first time you launch
|
|
132
|
+
`codex` in the repo, answer "yes" to the trust prompt.
|
|
133
|
+
|
|
134
|
+
Add the block from [`integrations/codex/AGENTS.md.snippet.md`](integrations/codex/AGENTS.md.snippet.md)
|
|
135
|
+
to your `AGENTS.md`, then copy [`integrations/codex/skills/`](integrations/codex/skills/)
|
|
136
|
+
into `.codex/skills/` to get the `dataform-context-verify` and
|
|
137
|
+
`dataform-context-golden-init` skills.
|
|
138
|
+
|
|
139
|
+
## Install with Antigravity
|
|
140
|
+
|
|
141
|
+
Copy [`integrations/antigravity/mcp_config.json.example`](integrations/antigravity/mcp_config.json.example)
|
|
142
|
+
to `.agents/mcp_config.json`, the rule
|
|
143
|
+
[`integrations/antigravity/rules/dataform-context.md`](integrations/antigravity/rules/dataform-context.md)
|
|
144
|
+
to `.agents/rules/`, and the workflows
|
|
145
|
+
[`integrations/antigravity/workflows/`](integrations/antigravity/workflows/) to
|
|
146
|
+
`.agents/workflows/` to get `/dataform-context-verify` and
|
|
147
|
+
`/dataform-context-golden-init`.
|
|
148
|
+
|
|
149
|
+
## Install with Windsurf
|
|
150
|
+
|
|
151
|
+
⚠️ Unlike the other clients, Windsurf's MCP config is **global per machine**, not
|
|
152
|
+
project-scoped. Merge
|
|
153
|
+
[`integrations/windsurf/mcp_config.json.snippet`](integrations/windsurf/mcp_config.json.snippet)
|
|
154
|
+
into `~/.codeium/windsurf/mcp_config.json` (once per machine). Rules and workflows
|
|
155
|
+
stay project-scoped and commit normally: copy
|
|
156
|
+
[`integrations/windsurf/rules/dataform-context.md`](integrations/windsurf/rules/dataform-context.md)
|
|
157
|
+
to `.windsurf/rules/` and
|
|
158
|
+
[`integrations/windsurf/workflows/`](integrations/windsurf/workflows/) to
|
|
159
|
+
`.windsurf/workflows/`.
|
|
160
|
+
|
|
161
|
+
## Install with Copilot (VS Code)
|
|
162
|
+
|
|
163
|
+
Copy [`integrations/copilot/mcp.json.example`](integrations/copilot/mcp.json.example)
|
|
164
|
+
to `.vscode/mcp.json` (⚠️ not a root `.mcp.json` — schema collision with Claude
|
|
165
|
+
Code/Cursor), the block from
|
|
166
|
+
[`integrations/copilot/copilot-instructions.snippet.md`](integrations/copilot/copilot-instructions.snippet.md)
|
|
167
|
+
to `.github/copilot-instructions.md`, and the prompt files
|
|
168
|
+
[`integrations/copilot/prompts/`](integrations/copilot/prompts/) to
|
|
169
|
+
`.github/prompts/` to get `/dataform-context-verify` and
|
|
170
|
+
`/dataform-context-golden-init`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Why
|
|
175
|
+
|
|
176
|
+
A coding agent working on a Dataform repo reads `.sqlx` files one by one. Observed
|
|
177
|
+
consequences in real conditions: invented columns or tables, upstream dependencies
|
|
178
|
+
missed during refactors, underestimated downstream impact — the
|
|
179
|
+
`staging → intermediate → marts → assertions` cascade is never seen as a whole.
|
|
180
|
+
|
|
181
|
+
| Without a system | With dataform-context-mcp |
|
|
182
|
+
|---|---|
|
|
183
|
+
| The agent greps `ref()` calls and guesses the DAG | The DAG comes from the Dataform compiler itself (`dependencyTargets`) |
|
|
184
|
+
| "What breaks if I change this?" = partial re-reading | `impact_analysis`: full blast radius, assertions included, in one call |
|
|
185
|
+
| A column's origin gets lost across CTEs | `get_column_lineage`: full chain with SQL transformation expressions |
|
|
186
|
+
| An unresolvable lineage looks like "no dependency" | Explicit statuses + `complete: false` + `warnings` — **never a false empty** |
|
|
187
|
+
| Context frozen at read time | Lazy re-indexing on content hash at every call |
|
|
188
|
+
|
|
189
|
+
**Deterministic and self-hosted**: no LLM, no network calls from the server, no warehouse
|
|
190
|
+
access at runtime. Same files → same index → same answers.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
.sqlx + includes/ ──▶ dataform compile --json ──▶ CompiledGraph parsing
|
|
194
|
+
│
|
|
195
|
+
Local SQLite (~/.cache/dataform-context-mcp/) ◀┘
|
|
196
|
+
• actions, layers, documented columns
|
|
197
|
+
• table-level edges (source: compiler dependencyTargets)
|
|
198
|
+
• column-level edges (sqlglot, explicit statuses)
|
|
199
|
+
│
|
|
200
|
+
MCP stdio server (7 tools) ◀────────────────────┘
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## The 8 tools
|
|
204
|
+
|
|
205
|
+
| Tool | Example question to ask the agent |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `get_table_context(name)` | "Describe the ref_brand table" |
|
|
208
|
+
| `get_upstream(name, depth)` | "What does mart_kpis depend on?" |
|
|
209
|
+
| `get_downstream(name, depth)` | "Who reads staging_events?" |
|
|
210
|
+
| `find_tables_by_layer(layer)` | "List the mart tables" |
|
|
211
|
+
| `get_column_lineage(table, column, …)` | "Where does the total_amount column come from?" |
|
|
212
|
+
| `impact_analysis(name, column?)` | "What breaks if I rename page_type?" |
|
|
213
|
+
| `check_setup()` | "Check that dataform-context is properly installed" |
|
|
214
|
+
| `refresh_index()` | "Force a reindex" |
|
|
215
|
+
|
|
216
|
+
- **Tolerant name resolution**: `my_table`, `dataset.my_table`, full canonical name or
|
|
217
|
+
`.sqlx` file path — with suggestions on errors.
|
|
218
|
+
- **Layers discovered dynamically** from file paths
|
|
219
|
+
(`definitions/transforms/<NN_name>/`, `definitions/sources/`…) — no hardcoded
|
|
220
|
+
convention.
|
|
221
|
+
- **Every response embeds `index_meta`**: freshness, source hash, counts, last
|
|
222
|
+
compilation status.
|
|
223
|
+
|
|
224
|
+
## Getting the agent to adopt the tools
|
|
225
|
+
|
|
226
|
+
Block to add to the Dataform repo's `CLAUDE.md` (or Cursor rules):
|
|
227
|
+
|
|
228
|
+
> ## Pipeline context: dataform-context MCP
|
|
229
|
+
> Before reading `.sqlx` files or modifying a table: `get_table_context` (schema +
|
|
230
|
+
> neighbors), `get_upstream`/`get_downstream` (DAG), `impact_analysis` (mandatory before
|
|
231
|
+
> any table or column refactor), `find_tables_by_layer` (layer scope),
|
|
232
|
+
> `get_column_lineage` (column origin). These tools are generated from
|
|
233
|
+
> `dataform compile`: they are authoritative for the DAG, unlike a partial file read.
|
|
234
|
+
> `complete: false` = unknown lineage, not "no dependency". After editing `.sqlx`
|
|
235
|
+
> files, the index refreshes itself (content hash).
|
|
236
|
+
|
|
237
|
+
## Reading column lineage responses — the "never a false empty" contract
|
|
238
|
+
|
|
239
|
+
Static extraction has known limits (MERGE, `SELECT *` over an undocumented source,
|
|
240
|
+
multi-statement scripts). The absolute rule: **an empty lineage is only presented as
|
|
241
|
+
"no dependency" when it is certain.** Otherwise, it says so:
|
|
242
|
+
|
|
243
|
+
- Every table carries an **extraction status** (`ok`, `partial`, `failed`,
|
|
244
|
+
`not_attempted`, `source`), always with a reason.
|
|
245
|
+
- `get_column_lineage` returns `complete: false` + `warnings` (opaque tables
|
|
246
|
+
encountered) when the lineage is unknown beyond some point — distinct from
|
|
247
|
+
`complete: true` + `edges: []` (true absence, e.g. `CURRENT_DATE()`).
|
|
248
|
+
- `impact_analysis(column=…)` returns `possibly_affected`: downstream tables whose
|
|
249
|
+
column impact is unknown. **Never exclude them from a refactor.**
|
|
250
|
+
|
|
251
|
+
Category details and surfacing: [`docs/lineage-limits.md`](docs/lineage-limits.md) (French).
|
|
252
|
+
|
|
253
|
+
<details>
|
|
254
|
+
<summary><strong>CLI (without an agent)</strong></summary>
|
|
255
|
+
|
|
256
|
+
From a local clone (`uv sync` first), or via
|
|
257
|
+
`uvx --from dataform-context-mcp@latest dataform-context …`:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
dataform-context index # compile + (re)build the current repo's index
|
|
261
|
+
dataform-context report # summary: layers, edges, lineage coverage
|
|
262
|
+
dataform-context report --table my_table # JSON context of one table
|
|
263
|
+
dataform-context serve # MCP server (stdio) — used by .mcp.json
|
|
264
|
+
dataform-context validate-golden --golden goldens.json # manual oracle
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`--repo /path` on any command to target another repo. `--db /path` to relocate the
|
|
268
|
+
index (default: `~/.cache/dataform-context-mcp/<hash>.db`).
|
|
269
|
+
</details>
|
|
270
|
+
|
|
271
|
+
<details>
|
|
272
|
+
<summary><strong>Index freshness (lazy re-indexing)</strong></summary>
|
|
273
|
+
|
|
274
|
+
- On **every** tool call, the server hashes the content of `definitions/**`,
|
|
275
|
+
`includes/**` and `workflow_settings.yaml`. Unchanged hash → ~10 ms response.
|
|
276
|
+
Changed hash → recompile + reindex (~2–15 s), then respond. The agent always works
|
|
277
|
+
on the current state of the files, including its own in-session edits.
|
|
278
|
+
- If compilation fails (a file broken mid-edit), the **last good index** keeps being
|
|
279
|
+
served, with `index_meta.compile_status: "error"` and the message. Never an empty
|
|
280
|
+
index.
|
|
281
|
+
- The index lives outside the repo (`~/.cache/dataform-context-mcp/`) — nothing to
|
|
282
|
+
gitignore.
|
|
283
|
+
</details>
|
|
284
|
+
|
|
285
|
+
<details>
|
|
286
|
+
<summary><strong>Golden sets: validate lineage on your repo</strong></summary>
|
|
287
|
+
|
|
288
|
+
Without an external source of truth, the oracle is human: you hand-trace a few columns
|
|
289
|
+
you know well, and the tool must find exactly those edges. Format
|
|
290
|
+
(`golden_columns.json`, a list of entries):
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
[
|
|
294
|
+
{
|
|
295
|
+
"table": "my_dataset.my_table",
|
|
296
|
+
"column": "my_column",
|
|
297
|
+
"direction": "upstream",
|
|
298
|
+
"depth": 1,
|
|
299
|
+
"expected_edges": ["upstream_ds.upstream_table.col -> my_dataset.my_table.my_column"],
|
|
300
|
+
"expect_complete": true
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`validate-golden` prints PASS/FAIL per entry with a readable diff (missing / extra
|
|
306
|
+
edges) and exits 1 on any mismatch — CI-friendly. Tip: cover 1 passthrough,
|
|
307
|
+
1 aggregation, 1 chain of 3+ tables, 1 incremental table, 1 tricky case (UNNEST/macro).
|
|
308
|
+
|
|
309
|
+
**Guided construction**: copy the command matching your tool, then run
|
|
310
|
+
`/dataform-context-golden-init` — the agent proposes traces (cross-checked against the
|
|
311
|
+
source SQL), you validate them through interactive questions, and the file is written
|
|
312
|
+
and validated automatically.
|
|
313
|
+
|
|
314
|
+
- Claude Code: [`integrations/claude-code/commands/dataform-context-golden-init.md`](integrations/claude-code/commands/dataform-context-golden-init.md) → `.claude/commands/`
|
|
315
|
+
- Cursor: [`integrations/cursor/commands/dataform-context-golden-init.md`](integrations/cursor/commands/dataform-context-golden-init.md) → `.cursor/commands/`
|
|
316
|
+
- Codex CLI: [`integrations/codex/skills/dataform-context-golden-init/`](integrations/codex/skills/dataform-context-golden-init/) → `.codex/skills/`
|
|
317
|
+
- Antigravity: [`integrations/antigravity/workflows/dataform-context-golden-init.md`](integrations/antigravity/workflows/dataform-context-golden-init.md) → `.agents/workflows/`
|
|
318
|
+
- Windsurf: [`integrations/windsurf/workflows/dataform-context-golden-init.md`](integrations/windsurf/workflows/dataform-context-golden-init.md) → `.windsurf/workflows/`
|
|
319
|
+
- Copilot (VS Code): [`integrations/copilot/prompts/dataform-context-golden-init.prompt.md`](integrations/copilot/prompts/dataform-context-golden-init.prompt.md) → `.github/prompts/`
|
|
320
|
+
</details>
|
|
321
|
+
|
|
322
|
+
<details>
|
|
323
|
+
<summary><strong>Governance & audit</strong></summary>
|
|
324
|
+
|
|
325
|
+
Designed to pass a corporate security review before deployment on a client repo:
|
|
326
|
+
|
|
327
|
+
- **Exhaustive runtime dependencies**: `mcp` (official Model Context Protocol SDK) and
|
|
328
|
+
`sqlglot` — exact pins in `uv.lock`; everything else is stdlib (`sqlite3`,
|
|
329
|
+
`argparse`, `hashlib`, `difflib`).
|
|
330
|
+
- **Zero network calls from the server**: reads the repo's files + local
|
|
331
|
+
`dataform compile --json` shell-out. No warehouse access, no telemetry. Only `uvx`
|
|
332
|
+
reaches PyPI at start-up to serve the latest published version (see "Pin a version or
|
|
333
|
+
work offline").
|
|
334
|
+
- **Zero LLM at runtime**: deterministic parsing (Dataform compiler + sqlglot).
|
|
335
|
+
- **Local data only**: SQLite index in `~/.cache/dataform-context-mcp/`.
|
|
336
|
+
- **Trust boundary = the indexed repo**: `dataform compile` runs the target repo's
|
|
337
|
+
JavaScript (`includes/`, `*.js`) with the user's privileges on every re-index. Only
|
|
338
|
+
index Dataform repos you trust; never point `--repo` at an unreviewed clone.
|
|
339
|
+
- **Cache contents**: the SQLite index holds the compiled SQL of every action
|
|
340
|
+
(`query`, `incremental_query`). The file is created with mode `0600` (owner-readable
|
|
341
|
+
only). To purge: `rm -rf ~/.cache/dataform-context-mcp/`.
|
|
342
|
+
- **This repo contains no client metadata**: synthetic fixtures, aggregated reports
|
|
343
|
+
only.
|
|
344
|
+
</details>
|
|
345
|
+
|
|
346
|
+
<details>
|
|
347
|
+
<summary><strong>Known limits</strong></summary>
|
|
348
|
+
|
|
349
|
+
- Column lineage is incomplete by construction for: `SELECT *` over a source without a
|
|
350
|
+
documented schema, MERGE/DML, multi-statement scripts — always **surfaced**
|
|
351
|
+
(statuses, `warnings`, `possibly_affected`), never hidden. Lever: documenting the
|
|
352
|
+
`columns` of declarations in their `config {}` mechanically unlocks `SELECT *`
|
|
353
|
+
expansion (measured: +28 coverage points on one pilot repo).
|
|
354
|
+
- Observed coverage on two pilot repos: 91% and 63% of tables `ok` — the second gap is
|
|
355
|
+
structural (staging doing `SELECT *` over undocumented sources), analyzed in
|
|
356
|
+
[`docs/lineage-limits.md`](docs/lineage-limits.md).
|
|
357
|
+
- `dataform compile` (Node) is a runtime dependency: if missing, indexing fails
|
|
358
|
+
cleanly and the previous index keeps being served.
|
|
359
|
+
</details>
|
|
360
|
+
|
|
361
|
+
<details>
|
|
362
|
+
<summary><strong>Code architecture & tests</strong></summary>
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
src/dataform_context_mcp/
|
|
366
|
+
├── compile.py # subprocess dataform compile --json; structured errors
|
|
367
|
+
├── model.py # CompiledGraph dataclasses (Target, Action, ...)
|
|
368
|
+
├── layers.py # layer inference from file paths
|
|
369
|
+
├── staleness.py # content hash of source files
|
|
370
|
+
├── db.py # SQLite: DDL, rebuild, name resolution, recursive traversals
|
|
371
|
+
├── lineage.py # sqlglot column extraction, explicit statuses, topological order
|
|
372
|
+
├── indexer.py # ensure_fresh: lazy reindex + last-good fallback
|
|
373
|
+
├── server.py # the 7 MCP tools (stdio)
|
|
374
|
+
└── cli.py # index | report | serve | validate-golden
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Local development:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
git clone git@github.com:vgossiaux/dataform-context-mcp.git
|
|
381
|
+
cd dataform-context-mcp
|
|
382
|
+
uv sync && uv run pytest # 74 tests
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Tests rely on a **synthetic, compilable mini Dataform repo**
|
|
386
|
+
(`tests/fixtures/mini_repo/`) — no test touches a real repo.
|
|
387
|
+
`uv run pytest -m "not integration"` runs without Node.
|
|
388
|
+
- PyPI publishing: [`docs/publishing.md`](docs/publishing.md).
|
|
389
|
+
</details>
|
|
390
|
+
|
|
391
|
+
<details>
|
|
392
|
+
<summary><strong>Quick troubleshooting</strong></summary>
|
|
393
|
+
|
|
394
|
+
| Symptom | Cause | Fix |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| `/mcp`: server error `ENOENT ... uv` | PATH without homebrew (non-login shell) | Absolute path `/opt/homebrew/bin/uvx` in `.mcp.json` |
|
|
397
|
+
| `compile error` in `index_meta` | A `.sqlx` doesn't compile | Run `dataform compile` in the repo to see the error; the previous index keeps being served |
|
|
398
|
+
| `not_found` with suggestions | Approximate table name | Pick a suggestion, or use `dataset.table` |
|
|
399
|
+
| Slow first call (~15 s) | Initial compilation + extraction | Normal; subsequent calls ~10 ms |
|
|
400
|
+
</details>
|
|
401
|
+
|
|
402
|
+
## Roadmap
|
|
403
|
+
|
|
404
|
+
- License (prerequisite for going open source).
|
|
405
|
+
- **Batch, offline, self-hosted LLM** semantic enrichment of undocumented columns —
|
|
406
|
+
never at MCP runtime.
|
|
407
|
+
- Mermaid/graphviz graph export; PreToolUse hook suggesting `impact_analysis` before
|
|
408
|
+
`.sqlx` edits.
|