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.
@@ -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.