model2data-studio 0.1.0__tar.gz → 0.4.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.
Files changed (27) hide show
  1. model2data_studio-0.4.0/PKG-INFO +483 -0
  2. model2data_studio-0.4.0/README.md +454 -0
  3. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/model2data_studio/__init__.py +1 -1
  4. model2data_studio-0.4.0/model2data_studio/building.py +367 -0
  5. model2data_studio-0.4.0/model2data_studio/cli.py +1395 -0
  6. model2data_studio-0.4.0/model2data_studio/client.py +483 -0
  7. model2data_studio-0.4.0/model2data_studio/export.py +537 -0
  8. model2data_studio-0.4.0/model2data_studio/mcp_server.py +857 -0
  9. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/pyproject.toml +1 -1
  10. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/server.json +2 -2
  11. model2data_studio-0.4.0/tests/test_building.py +603 -0
  12. model2data_studio-0.4.0/tests/test_cli.py +2022 -0
  13. model2data_studio-0.4.0/tests/test_export.py +434 -0
  14. model2data_studio-0.4.0/tests/test_mcp_server.py +649 -0
  15. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/uv.lock +1 -1
  16. model2data_studio-0.1.0/PKG-INFO +0 -298
  17. model2data_studio-0.1.0/README.md +0 -269
  18. model2data_studio-0.1.0/model2data_studio/cli.py +0 -452
  19. model2data_studio-0.1.0/model2data_studio/client.py +0 -197
  20. model2data_studio-0.1.0/model2data_studio/export.py +0 -191
  21. model2data_studio-0.1.0/model2data_studio/mcp_server.py +0 -377
  22. model2data_studio-0.1.0/tests/test_cli.py +0 -928
  23. model2data_studio-0.1.0/tests/test_export.py +0 -137
  24. model2data_studio-0.1.0/tests/test_mcp_server.py +0 -301
  25. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/.gitignore +0 -0
  26. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/LICENSE +0 -0
  27. {model2data_studio-0.1.0 → model2data_studio-0.4.0}/model2data_studio/archive.py +0 -0
@@ -0,0 +1,483 @@
1
+ Metadata-Version: 2.5
2
+ Name: model2data-studio
3
+ Version: 0.4.0
4
+ Summary: Generate a model2data studio project's data from the command line, so CI can rebuild fixtures instead of committing them.
5
+ Project-URL: Homepage, https://www.jbanalytica.com/model2data/
6
+ Project-URL: Studio, https://studio.jbanalytica.com
7
+ Project-URL: Pricing, https://www.jbanalytica.com/model2data/pricing/
8
+ Author: JB Analytica
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: dbml,dbt,fixtures,mcp,synthetic data,test data
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: typer>=0.12.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: mcp>=2.0.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
26
+ Provides-Extra: mcp
27
+ Requires-Dist: mcp>=2.0.0; extra == 'mcp'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # model2data-studio CLI
31
+
32
+ Pull a saved [model2data studio](https://studio.jbanalytica.com) project and
33
+ write its data, so a build can regenerate its fixtures instead of carrying a CSV
34
+ that was right once.
35
+
36
+ The schema is designed in the studio, where a diagram and a preview make it
37
+ worth designing. This is the part that runs afterwards, unattended, from the
38
+ same saved project — so the seeds a test suite runs against are the ones the
39
+ model says they should be, rather than whatever was committed the day someone
40
+ last remembered to export.
41
+
42
+ It is deliberately thin: no DBML parser, no generator, no schema knowledge. The
43
+ studio's API does all of that; this only knows how to ask, and where to put the
44
+ answer.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install model2data-studio # or run it without installing:
50
+ uvx model2data-studio --help
51
+ ```
52
+
53
+ It needs an API key from a **Pro** account on the studio: see
54
+ [pricing](https://www.jbanalytica.com/model2data/pricing/). The client is MIT
55
+ licensed; the studio it talks to is not open source.
56
+
57
+ ## Configuration
58
+
59
+ Two environment variables, both overridable by a flag:
60
+
61
+ | | flag | default |
62
+ | --- | --- | --- |
63
+ | `MODEL2DATA_STUDIO_KEY` | `--key` | required |
64
+ | `MODEL2DATA_STUDIO_URL` | `--url` | `https://studio.jbanalytica.com` |
65
+
66
+ Create a key in the studio's account menu. The CLI sends it as a bearer token
67
+ and nothing else — it never prints it, and it is the only thing that identifies
68
+ the account, so put it in your CI provider's secrets rather than in a file.
69
+
70
+ Leave the key's access on **Generate and export**, which is the default and
71
+ covers everything below: reading a project, generating, exporting. A key with
72
+ **Read only** access is refused by `generate` with a 403 saying so. **Generate
73
+ and save** adds writing the project back, which `push`, `project create`, `model push`,
74
+ `metrics push` and `dashboard push` need (`project create` also needs a key that is not
75
+ limited to one project) — for a
76
+ pipeline that pushes a model up rather than pulling data down — and **Full access** adds
77
+ deleting a project and managing its share links, which the CLI never does. A
78
+ key that leaks out of a CI log can only do what it was given.
79
+
80
+ ## Commands
81
+
82
+ ```bash
83
+ model2data-studio projects # id, name, rows, last edited
84
+ model2data-studio whoami # the plan behind the key, and what it has used
85
+
86
+ model2data-studio generate --project <id> --out seeds/
87
+ model2data-studio generate --project <id> --out . --format dbt --adapter duckdb
88
+
89
+ model2data-studio push --project <id> shop.model2data.yml # save a model as the project's
90
+
91
+ model2data-studio project create Shop --model shop.model2data.yml --metrics shop.metrics.yml
92
+ model2data-studio model check shop.model2data.yml # exits 1 on an error; no key needed
93
+ model2data-studio model push <id> shop.model2data.yml # replace the model, keep the rest
94
+ model2data-studio model guide # how to write one: the model spec
95
+
96
+ model2data-studio metrics list --project ID # what each metric computes
97
+ model2data-studio metrics check --project shop.metrics.yml # exits 1 on an error, for CI
98
+ model2data-studio metrics values --project ID --seed 42 --as-of 2026-10-01
99
+ model2data-studio metrics suggest --project shop.model2data.yml -o shop.metrics.yml
100
+ model2data-studio metrics check shop.metrics.yml --model shop.model2data.yml # or --project <id>
101
+ model2data-studio metrics push <id> shop.metrics.yml # replace the metrics file, keep the rest
102
+ model2data-studio metrics guide # how to write one: the metrics spec
103
+
104
+ model2data-studio dashboard pull <id> --out dashboards/ # one <name>.dashboard.yml each
105
+ model2data-studio dashboard push <id> dashboards/*.dashboard.yml
106
+ model2data-studio dashboard check dashboards/*.dashboard.yml --project <id> # for CI
107
+ ```
108
+
109
+ `generate` takes every setting from the saved project — row counts, per-table
110
+ overrides, seed, locale, name — and each flag overrides one of them:
111
+
112
+ | | |
113
+ | --- | --- |
114
+ | `--format csv\|dbt` | seed CSVs, or a whole dbt project. dbt export is a Pro feature. |
115
+ | `--rows N` | the default row count for every table |
116
+ | `--rows-for TABLE=N` | one table's count, on top of the project's. Repeatable. |
117
+ | `--seed N` | pin the data. Same seed, same rows, forever. |
118
+ | `--as-of YYYY-MM-DD` | pin the day dates and timestamps are anchored on. Defaults to the project's, then today. |
119
+ | `--locale CODE` | which country's people and addresses to generate |
120
+ | `--business-hours` / `--no-business-hours` | weight timestamps toward weekdays and working hours. Pro. |
121
+ | `--growth N` | relative change in activity across the window, e.g. `0.5` for the end being half again as busy as the start. Pro. |
122
+ | `--seasonality N` | strength of an annual cycle peaking in Q4, `0.0`-`1.0`. Pro. |
123
+ | `--skew N` | how unevenly child rows spread over their parents, `0.0`-`1.0`. Pro. |
124
+ | `--name NAME` | the dbt project's name |
125
+ | `--adapter duckdb\|postgres` | the warehouse the dbt project targets |
126
+ | `--metrics FILE` / `--no-metrics` | the metrics file to send instead of the project's own, or none (the metrics are then the ones the model implies). A dbt export holds `<project>.metrics.yml`, `metric_values.json` and a test per metric under `data-tests/metrics/`. |
127
+ | `--defects PRESET` | break the data on purpose: `clean`, `messy`, `training` or `none`; the archive then holds `defects_report.json` (and, for dbt, `EXPECTED_FAILURES.md`), and the log lists the tests `dbt build` should fail. Defaults to the project's. Pro, bar `training` on a bundled example. |
128
+ | `--force` | replace files already in `--out` |
129
+ | `--check` | compare against `--out` instead of writing to it |
130
+
131
+ Every run also prints a row count for each table it generated, read back out
132
+ of the CSVs — a quick way to see what actually landed without opening a seed.
133
+
134
+ **Shaping flags name one field at a time.** `--growth` on its own overrides
135
+ just the growth trend; business hours and seasonality still come from the
136
+ project, the same way `--rows-for orders=9` overrides one table without
137
+ resetting every other one to the base count. Shaping is a Pro feature — a free
138
+ plan's key gets a 402 naming the plan boundary if the project or a flag asks
139
+ for anything but the uniform defaults.
140
+
141
+ **Pin a seed.** A project without one generates different rows every run, which
142
+ means a diff on every build and no way back to yesterday's data. If the project
143
+ has no seed and you pass no `--seed`, the CLI picks one and prints it, so a run
144
+ that surprised you can at least be reproduced.
145
+
146
+ **Pin the day too.** Dates and timestamps are generated relative to an anchor
147
+ day, so a seed alone reproduces its rows only for as long as that day is the
148
+ same. The studio pins one whenever a project's seed is on — **Dates as of**, in
149
+ the generation settings — and the CLI reads it back, which is what makes a run
150
+ here reproduce the rows the studio previewed rather than merely yesterday's CLI
151
+ run. `--as-of` overrides it. A project with no day pinned anchors on today — at
152
+ midnight, so every run on the same day agrees — and the CLI prints the day it
153
+ used, the same way it prints a picked seed. A pipeline that `--check`s
154
+ committed fixtures, or that needs a failing build to be reproducible on a
155
+ laptop next week, wants the day pinned one way or the other; with `--seed`, the
156
+ two together name one dataset for good.
157
+
158
+ **A project written as a model is generated from the model.** A project whose
159
+ text is a model2data model — a `.model2data.yml` document, spec 0.2.0, with a
160
+ top-level `model2data:` version key — is sent to the studio as that text, and
161
+ the studio reads it with the engine's own loader, exactly as `model2data --file`
162
+ would. A model the studio refuses fails the run with every issue at its path in
163
+ the document (`tables.orders.columns.status.generate.weights: ...`). The
164
+ project's settings, and every flag above, still win over the model's own `run:`
165
+ section.
166
+
167
+ **A DBML project must have been opened in the studio at least once** since the
168
+ studio started saving its parse. Nothing on the server reads DBML into what the
169
+ editor showed — the browser does, and saves the result beside the project — so
170
+ a DBML project with no stored parse has nothing to generate from, and the CLI
171
+ says so rather than failing later.
172
+
173
+ **`push` saves a model file as the project's text.** The studio checks the model
174
+ first and nothing is saved if it has an error (warnings are printed and do not
175
+ stop it). The project's name, settings and canvas positions are kept; the
176
+ browser's saved parse of the old text is cleared, since the model is read
177
+ as-is from then on. It needs a key with **Generate and save** access. It takes
178
+ a model, not DBML: convert a DBML file with the engine's
179
+ `model2data convert schema.dbml` first.
180
+
181
+ **`push` saves the metrics file beside the model too.** `shop.metrics.yml` next
182
+ to `shop.model2data.yml` is checked by the studio and saved as the project's
183
+ metrics file, or the file `--metrics` names. Without either — or with
184
+ `--no-metrics` — the project keeps the metrics file it already has. A metrics
185
+ file with an error saves nothing.
186
+
187
+ **`--check` never writes into `--out`.** It regenerates into a scratch
188
+ directory and diffs it against what is already there, printing what changed,
189
+ what is missing and what is left over, then exits 1 if they differ — so a CI
190
+ job can fail a build whose committed fixtures nobody regenerated after a schema
191
+ change, instead of shipping them stale. Exits 0 and prints `Up to date: N
192
+ files` when they match.
193
+
194
+ ## Metrics
195
+
196
+ A project's metrics file — `<stem>.metrics.yml` beside `<stem>.model2data.yml`,
197
+ metrics spec 0.1.0 — says what its numbers mean: `revenue` is the sum of
198
+ `orders.total_amount` where the order was paid, shipped or delivered. The studio
199
+ saves one per project (the Metrics tab); in a repository it is the file next to
200
+ the model. Without one, the metrics are the ones the model implies: a sum per
201
+ `measure` column and a row count per fact.
202
+
203
+ Every `metrics` command takes `--project`: a saved project's id **or** a local file (the id also still works as a plain argument, as it did before 0.2.1): the
204
+ model (`shop.model2data.yml`, or `shop.dbml`), or its metrics file, in which case
205
+ the model beside it is used. A local model's metrics are the `shop.metrics.yml`
206
+ beside it, unless `--metrics FILE` or `--no-metrics` says otherwise. **A local
207
+ file needs no API key**: checking metrics is free, and without a key `values`
208
+ runs on the free plan.
209
+
210
+ | | |
211
+ | --- | --- |
212
+ | `metrics list --project TARGET` | each metric's name, label, kind, what it computes, its filter and its date column; a metric the model implies rather than the file writes is marked `*` |
213
+ | `metrics check --project TARGET` | every issue as `file:line:column: error: path: message`, the shape a CI log viewer links; exits 1 on an error, 0 on warnings alone |
214
+ | `metrics check FILE.metrics.yml --model FILE` | the same, for a metrics file against a model file (YAML, JSON or DBML), wherever the two live; `--project ID` instead reads it against a saved project's model. Needs no key with `--model` |
215
+ | `metrics push PROJECT_ID FILE.metrics.yml` | checks the file against the project's model, then saves it as the project's metrics file, keeping the model, dashboards and settings; says which metrics were added and removed. Nothing is saved on an error |
216
+ | `metrics guide` | the guide to writing a metrics file, as markdown: the metrics spec, then what the studio adds (no key needed) |
217
+ | `metrics values --project TARGET` | generates the data (the same run `generate` makes with the same arguments) and prints each metric's known value. `--seed`, `--rows` and `--as-of` as for `generate`; `--by-month` adds each month; `--json` prints the values shaped like the export's `metric_values.json` (its `run` and `metrics`; the spec version and rounding note are the export's own) |
218
+ | `metrics suggest --project TARGET` | a metrics file of the metrics the model implies, to start one from: stdout, or `-o FILE` (`--force` to replace one) |
219
+
220
+ ```console
221
+ $ model2data-studio metrics values examples/ecommerce.model2data.yml --seed 42 --as-of 2026-10-01
222
+ ecommerce: seed 42, dates anchored on 2026-10-01, 100 rows a table
223
+ metric label value
224
+ ------------------- ------------------------ -----------------------
225
+ revenue Revenue 4,012.30
226
+ orders Orders 100
227
+ average_order_value Average order value 40.12 (exact 40.123)
228
+ cancelled_orders Cancelled orders 4
229
+ cancellation_rate Cancellation rate 4.00%
230
+ ...
231
+ ```
232
+
233
+ The values are **known answers**: what a correct query over the generated seeds
234
+ returns. A rounded value is followed by its exact one, since that is what the
235
+ query will return. Metric values are Pro, except on a bundled example; on a
236
+ plan without them `values` says so and exits 1.
237
+
238
+ ## Dashboards
239
+
240
+ A project's dashboards are files, `<name>.dashboard.yml`: the file name is the
241
+ dashboard's name. They name the project's metrics and columns, never a value;
242
+ the studio draws them from the project's generated data. The format is
243
+ model2data's open [dashboard spec](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/dashboards/README.md) (0.1.0),
244
+ with a JSON Schema at
245
+ `https://www.jbanalytica.com/model2data/spec/dashboards/0.1.0/dashboard.schema.json`
246
+ for editor completion: a file starts with
247
+
248
+ ```yaml
249
+ # yaml-language-server: $schema=https://www.jbanalytica.com/model2data/spec/dashboards/0.1.0/dashboard.schema.json
250
+ model2data-dashboard: 0.1.0
251
+ model: coffee_webshop
252
+ ```
253
+
254
+ This client does not read the files: it moves them to and from the studio, which
255
+ checks them and says what is wrong, by line and column. `dashboard guide` and
256
+ `dashboard check` without `--project` need no key. Since 0.3.0.
257
+
258
+ | | |
259
+ | --- | --- |
260
+ | `dashboard list PROJECT_ID` | the project's dashboards |
261
+ | `dashboard pull PROJECT_ID` | writes each one into `--out DIR` (the current folder by default); `--name NAME` for just that one. A file there that differs from the studio's is left alone, and nothing is written, unless `--force` |
262
+ | `dashboard push PROJECT_ID FILE...` | creates or replaces each dashboard by file name. Every file is checked first and nothing is saved if one has an error. Needs a key with **Generate and save** access |
263
+ | `dashboard check FILE... [--project ID]` | every error and warning as `file:line:column: error: message`; exits 1 on an error, 0 on warnings alone. With `--project`, names the project lacks are warnings |
264
+ | `dashboard guide [--project ID]` | the guide to writing one, as markdown: the dashboard spec, then what the studio adds; with a project, it ends with the metrics and columns that project offers |
265
+
266
+ A dashboard file that names something the project does not have is still saved:
267
+ that is a warning, not an error, so a dashboard can be sketched before the
268
+ metric it needs exists.
269
+
270
+ ## Building a project: model, metrics, dashboards
271
+
272
+ A whole project can be built from files, by a pipeline or an agent, without
273
+ opening the studio: a model (`<stem>.model2data.yml`, model2data's open
274
+ [model spec](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/README.md),
275
+ or DBML), a metrics file (the
276
+ [metrics spec](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/metrics/README.md))
277
+ and dashboard files. Each kind has a guide (the spec, served by the studio), a
278
+ check that says where each error is by line and column, and a save that checks
279
+ first and saves nothing on an error. Since 0.4.0.
280
+
281
+ | | |
282
+ | --- | --- |
283
+ | `project create NAME --model FILE [--metrics FILE]` | checks both, then creates the project (100 rows a table, seed 42, dates anchored on today) and prints its id and studio URL. The plan's project limit applies |
284
+ | `model check FILE` | every error and warning as `file:line:column: error: message`; exits 1 on an error. Needs no key |
285
+ | `model push PROJECT_ID FILE` | checks the model, then saves it as the project's model, keeping its metrics file, dashboards, settings and canvas; says how the tables changed, and whether the project's metrics file still reads against it |
286
+ | `model guide` | the guide to writing a model, as markdown (no key needed) |
287
+
288
+ DBML is saved as the model document it converts to, so the project generates
289
+ straight away. A save reads the project first and replaces only its own part;
290
+ if the project is saved elsewhere in between, it reads it again once rather
291
+ than writing over that save.
292
+
293
+ The order of work:
294
+
295
+ 1. `model guide`, write `shop.model2data.yml`, `model check` it until it is clean.
296
+ 2. `project create Shop --model shop.model2data.yml`: note the id it prints.
297
+ 3. `metrics guide`, write `shop.metrics.yml`, `metrics check shop.metrics.yml --project <id>`.
298
+ 4. `metrics push <id> shop.metrics.yml`, then `metrics values --project <id>` to see
299
+ that the numbers come out as intended.
300
+ 5. `dashboard guide --project <id>`, write `sales.dashboard.yml`,
301
+ `dashboard check sales.dashboard.yml --project <id>`.
302
+ 6. `dashboard push <id> sales.dashboard.yml`, and open the URL it prints.
303
+
304
+ ## Regenerating dbt seeds in CI
305
+
306
+ ```yaml
307
+ name: Seeds
308
+
309
+ on: [push]
310
+
311
+ jobs:
312
+ seeds:
313
+ runs-on: ubuntu-latest
314
+ steps:
315
+ - uses: actions/checkout@v4
316
+ - uses: astral-sh/setup-uv@v5
317
+
318
+ - name: Regenerate the seeds from the model
319
+ env:
320
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
321
+ run: |
322
+ uvx model2data-studio generate \
323
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
324
+ --out seeds/ --force --seed 1 --as-of 2026-01-01
325
+
326
+ - name: Build against them
327
+ run: uv run dbt build --target ci
328
+ ```
329
+
330
+ `--force` because the checkout already holds the last run's files, and `--seed`
331
+ with `--as-of` because a pipeline that regenerates its fixtures should still be
332
+ able to explain a failure — with a pinned seed and a pinned day, a failing build
333
+ is reproducible on a laptop, however long after it ran.
334
+
335
+ Nothing is committed back: the seeds exist for the length of the job, and the
336
+ model in the studio is the thing under version control. If you would rather
337
+ commit them, add a step that opens a pull request when `git status` comes back
338
+ dirty — that turns a schema change into a reviewable diff of the data it
339
+ produces.
340
+
341
+ ## Failing CI when the committed fixtures are stale
342
+
343
+ If seeds are committed rather than regenerated on every run, `--check` is the
344
+ step that catches someone editing the schema in the studio and forgetting to
345
+ pull the new fixtures into the repo:
346
+
347
+ ```yaml
348
+ - name: Check the committed seeds match the model
349
+ env:
350
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
351
+ run: |
352
+ uvx model2data-studio generate \
353
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
354
+ --out seeds/ --seed 1 --as-of 2026-01-01 --check
355
+ ```
356
+
357
+ It never writes into `seeds/` — only into a scratch directory it diffs against
358
+ what's there — so this is safe to run against the checkout as-is, no `--force`
359
+ needed. `--as-of` is what lets the check pass on any day: the committed seeds
360
+ were generated relative to one day, and without it a schema with a date column
361
+ would compare against rows anchored on today and fail at the first midnight.
362
+
363
+ ## The MCP server: generating from an agent instead of a shell
364
+
365
+ The same account, the same key, the same API — for the editor rather than the
366
+ terminal. The studio is where a schema is *designed*, with a diagram and a
367
+ preview to design it against; the agent in your editor is where the data is
368
+ wanted, and asking it for "two hundred believable orders from my webshop model"
369
+ should not mean pasting DBML into a chat.
370
+
371
+ ```bash
372
+ uvx --from 'model2data-studio[mcp]' model2data-studio mcp # the SDK is an extra, see below
373
+ ```
374
+
375
+ `uvx` fetches it on first use, so there is nothing to install beforehand. With
376
+ the package installed (`pip install 'model2data-studio[mcp]'`),
377
+ `model2data-studio-mcp` starts the same server. For Claude Code:
378
+
379
+ ```bash
380
+ claude mcp add model2data-studio \
381
+ --env MODEL2DATA_STUDIO_KEY=m2d_... \
382
+ -- uvx --from 'model2data-studio[mcp]' model2data-studio mcp
383
+ ```
384
+
385
+ and for anything that reads a JSON config (Claude Desktop, Cursor, Zed):
386
+
387
+ ```json
388
+ {
389
+ "mcpServers": {
390
+ "model2data-studio": {
391
+ "command": "uvx",
392
+ "args": ["--from", "model2data-studio[mcp]", "model2data-studio", "mcp"],
393
+ "env": { "MODEL2DATA_STUDIO_KEY": "m2d_..." }
394
+ }
395
+ }
396
+ }
397
+ ```
398
+
399
+ `MODEL2DATA_STUDIO_URL` works here too, for a self-hosted studio.
400
+
401
+ Nineteen tools:
402
+
403
+ | | |
404
+ | --- | --- |
405
+ | `account` | the plan and its caps, worth reading before asking for a lot of rows |
406
+ | `list_projects` | the ids to pass to everything else |
407
+ | `describe_project` | one project's tables, columns, refs and enums as text, generation hints included, and its metrics |
408
+ | `preview_data` | generate and return the rows themselves, capped at fifty a table |
409
+ | `write_data` | generate and write the files, as `generate --out` does |
410
+ | `list_metrics` | the project's metrics, a line each: what it computes, its filter, its date column |
411
+ | `metric_values` | each metric's known value over a generated run, overall and by month (`seed`, `rows`, `as_of` as for `preview_data`). Pro, bar a bundled example. |
412
+ | `get_model_guide` | how to write a model: the open model spec, then what the studio adds (no key needed) |
413
+ | `check_model` | a model's errors and warnings (YAML, JSON or DBML), each with its line and column, and what it holds (no key needed) |
414
+ | `create_project` | checks a model and, optionally, a metrics file, then creates a project from them; answers with its id and studio URL. Needs a key not limited to one project |
415
+ | `save_model` | checks a model, then makes it a project's model, keeping everything else; says how the tables changed |
416
+ | `get_metrics_guide` | how to write a metrics file: the open metrics spec, then what the studio adds (no key needed) |
417
+ | `check_metrics` | a metrics file's errors and warnings, against a project's model (`project_id`) or a model's text (`model_text`, no key needed), and the metrics it defines |
418
+ | `save_metrics` | checks a metrics file, then makes it a project's, keeping everything else; says which metrics were added and removed |
419
+ | `list_dashboards` | the project's dashboards, by name |
420
+ | `get_dashboard` | one dashboard file's text, to edit and save back |
421
+ | `get_dashboard_guide` | how to write a dashboard file: the open dashboard spec, then what the studio adds (no key needed); with `project_id`, ending with that project's metrics and columns |
422
+ | `check_dashboard` | a dashboard file's errors and warnings, each with its line and column, without saving (no key needed without `project_id`) |
423
+ | `save_dashboard` | checks a dashboard file, then creates or replaces it by name; a file with errors is not saved and the errors come back. Answers with the link to the project |
424
+
425
+ The server's instructions give an agent the order of work for building a
426
+ project from nothing: `get_model_guide` → write the model → `check_model` →
427
+ `create_project` → `get_metrics_guide` → write the metrics → `check_metrics` →
428
+ `save_metrics` → `metric_values` to sanity-check the numbers →
429
+ `get_dashboard_guide` → write a dashboard → `check_dashboard` →
430
+ `save_dashboard` → the studio URL (and share link) to open. Every check and save
431
+ is the studio's; the tools move text.
432
+
433
+ `preview_data` is the one worth having. An agent that can *read* generated rows
434
+ can act on them — write an assertion against them, notice that `shipped_at`
435
+ precedes `ordered_at`, spot a column coming out as placeholder sentences. It
436
+ answers with the rows inline and says how many there really are; `write_data` is
437
+ for when it has decided and wants the fixtures on disk.
438
+
439
+ `metric_values` is its other half: the numbers a correct query over those rows
440
+ returns, one quotable line per metric —
441
+ `revenue (Revenue): 4,012.30 — sum of orders.total_amount where orders.status in
442
+ paid, shipped, delivered; by month (orders.order_date): 2025-10 472.39, …` — so an
443
+ agent writing SQL against the seeds can check its own answer.
444
+
445
+ All of them go through the same `export.resolve` the CLI's `generate` does, so an agent
446
+ and a pipeline asking for the same project with the same arguments get the same
447
+ rows — including the part where an unpinned project has a seed picked for it and
448
+ is told which one, so the answer can be reproduced afterwards.
449
+
450
+ The MCP SDK is an optional extra rather than a dependency, on purpose: the
451
+ reason this package is two dependencies is that it installs on CI runners next
452
+ to a build that has nothing to do with the studio, and a runner regenerating
453
+ fixtures does not need an MCP SDK.
454
+
455
+ ## Developing
456
+
457
+ ```bash
458
+ cd cli
459
+ uv sync --extra dev
460
+ uv run pytest
461
+ uv run ruff check .
462
+ ```
463
+
464
+ The tests drive the real Typer app, and the MCP tools through the server's own
465
+ `call_tool`, answering their HTTP with an `httpx.MockTransport` — so there is
466
+ nothing to run and nothing to reach.
467
+
468
+ ## Releasing
469
+
470
+ Bump `version` in `pyproject.toml`, `__version__` in
471
+ `model2data_studio/__init__.py` and both versions in `server.json` (the release
472
+ workflow refuses a tag that disagrees with any of them), merge, then tag the
473
+ merge commit on `main` and push the tag:
474
+
475
+ ```bash
476
+ git tag cli-v0.4.0 && git push origin cli-v0.4.0
477
+ ```
478
+
479
+ `.github/workflows/release-cli.yml` runs the tests, builds, publishes to PyPI
480
+ through trusted publishing (no token stored anywhere) and then lists the new
481
+ version in the MCP registry.
482
+
483
+ <!-- mcp-name: io.github.JB-Analytica/model2data-studio -->