model2data-studio 0.1.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,24 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ .DS_Store
6
+ node_modules/
7
+ web/dist/
8
+ # Built by web/scripts/dbml-core.mjs on `npm run dev` and `npm run build`.
9
+ web/public/dbml-core-*.mjs
10
+ .pytest_cache/
11
+ *.duckdb
12
+
13
+ # TypeScript incremental build info
14
+ *.tsbuildinfo
15
+
16
+ # Local secrets: database URLs, Supabase keys. `migrations/env.py` is a
17
+ # different thing entirely and stays tracked — these patterns only match files
18
+ # literally named `.env`.
19
+ .env
20
+ .env.*
21
+ !.env.example
22
+
23
+ # Claude Code agent worktrees (launch.json is intentionally tracked)
24
+ .claude/worktrees/
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JB Analytica
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.
22
+
23
+ ---
24
+
25
+ This licence covers the model2data-studio command line client and MCP server in
26
+ this directory only. The model2data studio application they talk to (the API,
27
+ the web app and everything else in the repository around this directory) is
28
+ proprietary and is not licensed by this file; see the repository's own LICENSE.
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.5
2
+ Name: model2data-studio
3
+ Version: 0.1.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. The two
73
+ levels above it are for things the CLI does not do — **Generate and save** adds
74
+ writing the project back, for a pipeline that pushes a schema up rather than
75
+ pulling data down, and **Full access** adds deleting a project and managing its
76
+ share links. A key that leaks out of a CI log can only do what it was given.
77
+
78
+ ## Commands
79
+
80
+ ```bash
81
+ model2data-studio projects # id, name, rows, last edited
82
+ model2data-studio whoami # the plan behind the key, and what it has used
83
+
84
+ model2data-studio generate --project <id> --out seeds/
85
+ model2data-studio generate --project <id> --out . --format dbt --adapter duckdb
86
+ ```
87
+
88
+ `generate` takes every setting from the saved project — row counts, per-table
89
+ overrides, seed, locale, name — and each flag overrides one of them:
90
+
91
+ | | |
92
+ | --- | --- |
93
+ | `--format csv\|dbt` | seed CSVs, or a whole dbt project. dbt export is a Pro feature. |
94
+ | `--rows N` | the default row count for every table |
95
+ | `--rows-for TABLE=N` | one table's count, on top of the project's. Repeatable. |
96
+ | `--seed N` | pin the data. Same seed, same rows, forever. |
97
+ | `--as-of YYYY-MM-DD` | pin the day dates and timestamps are anchored on. Defaults to the project's, then today. |
98
+ | `--locale CODE` | which country's people and addresses to generate |
99
+ | `--business-hours` / `--no-business-hours` | weight timestamps toward weekdays and working hours. Pro. |
100
+ | `--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. |
101
+ | `--seasonality N` | strength of an annual cycle peaking in Q4, `0.0`-`1.0`. Pro. |
102
+ | `--skew N` | how unevenly child rows spread over their parents, `0.0`-`1.0`. Pro. |
103
+ | `--name NAME` | the dbt project's name |
104
+ | `--adapter duckdb\|postgres` | the warehouse the dbt project targets |
105
+ | `--force` | replace files already in `--out` |
106
+ | `--check` | compare against `--out` instead of writing to it |
107
+
108
+ Every run also prints a row count for each table it generated, read back out
109
+ of the CSVs — a quick way to see what actually landed without opening a seed.
110
+
111
+ **Shaping flags name one field at a time.** `--growth` on its own overrides
112
+ just the growth trend; business hours and seasonality still come from the
113
+ project, the same way `--rows-for orders=9` overrides one table without
114
+ resetting every other one to the base count. Shaping is a Pro feature — a free
115
+ plan's key gets a 402 naming the plan boundary if the project or a flag asks
116
+ for anything but the uniform defaults.
117
+
118
+ **Pin a seed.** A project without one generates different rows every run, which
119
+ means a diff on every build and no way back to yesterday's data. If the project
120
+ has no seed and you pass no `--seed`, the CLI picks one and prints it, so a run
121
+ that surprised you can at least be reproduced.
122
+
123
+ **Pin the day too.** Dates and timestamps are generated relative to an anchor
124
+ day, so a seed alone reproduces its rows only for as long as that day is the
125
+ same. The studio pins one whenever a project's seed is on — **Dates as of**, in
126
+ the generation settings — and the CLI reads it back, which is what makes a run
127
+ here reproduce the rows the studio previewed rather than merely yesterday's CLI
128
+ run. `--as-of` overrides it. A project with no day pinned anchors on today — at
129
+ midnight, so every run on the same day agrees — and the CLI prints the day it
130
+ used, the same way it prints a picked seed. A pipeline that `--check`s
131
+ committed fixtures, or that needs a failing build to be reproducible on a
132
+ laptop next week, wants the day pinned one way or the other; with `--seed`, the
133
+ two together name one dataset for good.
134
+
135
+ **The project must have been opened in the studio at least once** since the
136
+ studio started saving its parse. The API never parses DBML — the browser does,
137
+ and posts the result — so a project with no stored parse has nothing to generate
138
+ from, and the CLI says so rather than failing later.
139
+
140
+ **`--check` never writes into `--out`.** It regenerates into a scratch
141
+ directory and diffs it against what is already there, printing what changed,
142
+ what is missing and what is left over, then exits 1 if they differ — so a CI
143
+ job can fail a build whose committed fixtures nobody regenerated after a schema
144
+ change, instead of shipping them stale. Exits 0 and prints `Up to date: N
145
+ files` when they match.
146
+
147
+ ## Regenerating dbt seeds in CI
148
+
149
+ ```yaml
150
+ name: Seeds
151
+
152
+ on: [push]
153
+
154
+ jobs:
155
+ seeds:
156
+ runs-on: ubuntu-latest
157
+ steps:
158
+ - uses: actions/checkout@v4
159
+ - uses: astral-sh/setup-uv@v5
160
+
161
+ - name: Regenerate the seeds from the model
162
+ env:
163
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
164
+ run: |
165
+ uvx model2data-studio generate \
166
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
167
+ --out seeds/ --force --seed 1 --as-of 2026-01-01
168
+
169
+ - name: Build against them
170
+ run: uv run dbt build --target ci
171
+ ```
172
+
173
+ `--force` because the checkout already holds the last run's files, and `--seed`
174
+ with `--as-of` because a pipeline that regenerates its fixtures should still be
175
+ able to explain a failure — with a pinned seed and a pinned day, a failing build
176
+ is reproducible on a laptop, however long after it ran.
177
+
178
+ Nothing is committed back: the seeds exist for the length of the job, and the
179
+ model in the studio is the thing under version control. If you would rather
180
+ commit them, add a step that opens a pull request when `git status` comes back
181
+ dirty — that turns a schema change into a reviewable diff of the data it
182
+ produces.
183
+
184
+ ## Failing CI when the committed fixtures are stale
185
+
186
+ If seeds are committed rather than regenerated on every run, `--check` is the
187
+ step that catches someone editing the schema in the studio and forgetting to
188
+ pull the new fixtures into the repo:
189
+
190
+ ```yaml
191
+ - name: Check the committed seeds match the model
192
+ env:
193
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
194
+ run: |
195
+ uvx model2data-studio generate \
196
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
197
+ --out seeds/ --seed 1 --as-of 2026-01-01 --check
198
+ ```
199
+
200
+ It never writes into `seeds/` — only into a scratch directory it diffs against
201
+ what's there — so this is safe to run against the checkout as-is, no `--force`
202
+ needed. `--as-of` is what lets the check pass on any day: the committed seeds
203
+ were generated relative to one day, and without it a schema with a date column
204
+ would compare against rows anchored on today and fail at the first midnight.
205
+
206
+ ## The MCP server: generating from an agent instead of a shell
207
+
208
+ The same account, the same key, the same API — for the editor rather than the
209
+ terminal. The studio is where a schema is *designed*, with a diagram and a
210
+ preview to design it against; the agent in your editor is where the data is
211
+ wanted, and asking it for "two hundred believable orders from my webshop model"
212
+ should not mean pasting DBML into a chat.
213
+
214
+ ```bash
215
+ uvx --from 'model2data-studio[mcp]' model2data-studio mcp # the SDK is an extra, see below
216
+ ```
217
+
218
+ `uvx` fetches it on first use, so there is nothing to install beforehand. With
219
+ the package installed (`pip install 'model2data-studio[mcp]'`),
220
+ `model2data-studio-mcp` starts the same server. For Claude Code:
221
+
222
+ ```bash
223
+ claude mcp add model2data-studio \
224
+ --env MODEL2DATA_STUDIO_KEY=m2d_... \
225
+ -- uvx --from 'model2data-studio[mcp]' model2data-studio mcp
226
+ ```
227
+
228
+ and for anything that reads a JSON config (Claude Desktop, Cursor, Zed):
229
+
230
+ ```json
231
+ {
232
+ "mcpServers": {
233
+ "model2data-studio": {
234
+ "command": "uvx",
235
+ "args": ["--from", "model2data-studio[mcp]", "model2data-studio", "mcp"],
236
+ "env": { "MODEL2DATA_STUDIO_KEY": "m2d_..." }
237
+ }
238
+ }
239
+ }
240
+ ```
241
+
242
+ `MODEL2DATA_STUDIO_URL` works here too, for a self-hosted studio.
243
+
244
+ Five tools:
245
+
246
+ | | |
247
+ | --- | --- |
248
+ | `account` | the plan and its caps, worth reading before asking for a lot of rows |
249
+ | `list_projects` | the ids to pass to everything else |
250
+ | `describe_project` | one project's tables, columns, refs and enums as text, generation hints included |
251
+ | `preview_data` | generate and return the rows themselves, capped at fifty a table |
252
+ | `write_data` | generate and write the files, as `generate --out` does |
253
+
254
+ `preview_data` is the one worth having. An agent that can *read* generated rows
255
+ can act on them — write an assertion against them, notice that `shipped_at`
256
+ precedes `ordered_at`, spot a column coming out as placeholder sentences. It
257
+ answers with the rows inline and says how many there really are; `write_data` is
258
+ for when it has decided and wants the fixtures on disk.
259
+
260
+ Both go through the same `export.resolve` the CLI's `generate` does, so an agent
261
+ and a pipeline asking for the same project with the same arguments get the same
262
+ rows — including the part where an unpinned project has a seed picked for it and
263
+ is told which one, so the answer can be reproduced afterwards.
264
+
265
+ The MCP SDK is an optional extra rather than a dependency, on purpose: the
266
+ reason this package is two dependencies is that it installs on CI runners next
267
+ to a build that has nothing to do with the studio, and a runner regenerating
268
+ fixtures does not need an MCP SDK.
269
+
270
+ ## Developing
271
+
272
+ ```bash
273
+ cd cli
274
+ uv sync --extra dev
275
+ uv run pytest
276
+ uv run ruff check .
277
+ ```
278
+
279
+ The tests drive the real Typer app, and the MCP tools through the server's own
280
+ `call_tool`, answering their HTTP with an `httpx.MockTransport` — so there is
281
+ nothing to run and nothing to reach.
282
+
283
+ ## Releasing
284
+
285
+ Bump `version` in `pyproject.toml`, `__version__` in
286
+ `model2data_studio/__init__.py` and both versions in `server.json` (the release
287
+ workflow refuses a tag that disagrees with any of them), merge, then tag the
288
+ merge commit on `main` and push the tag:
289
+
290
+ ```bash
291
+ git tag cli-v0.1.0 && git push origin cli-v0.1.0
292
+ ```
293
+
294
+ `.github/workflows/release-cli.yml` runs the tests, builds, publishes to PyPI
295
+ through trusted publishing (no token stored anywhere) and then lists the new
296
+ version in the MCP registry.
297
+
298
+ <!-- mcp-name: io.github.JB-Analytica/model2data-studio -->
@@ -0,0 +1,269 @@
1
+ # model2data-studio CLI
2
+
3
+ Pull a saved [model2data studio](https://studio.jbanalytica.com) project and
4
+ write its data, so a build can regenerate its fixtures instead of carrying a CSV
5
+ that was right once.
6
+
7
+ The schema is designed in the studio, where a diagram and a preview make it
8
+ worth designing. This is the part that runs afterwards, unattended, from the
9
+ same saved project — so the seeds a test suite runs against are the ones the
10
+ model says they should be, rather than whatever was committed the day someone
11
+ last remembered to export.
12
+
13
+ It is deliberately thin: no DBML parser, no generator, no schema knowledge. The
14
+ studio's API does all of that; this only knows how to ask, and where to put the
15
+ answer.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install model2data-studio # or run it without installing:
21
+ uvx model2data-studio --help
22
+ ```
23
+
24
+ It needs an API key from a **Pro** account on the studio: see
25
+ [pricing](https://www.jbanalytica.com/model2data/pricing/). The client is MIT
26
+ licensed; the studio it talks to is not open source.
27
+
28
+ ## Configuration
29
+
30
+ Two environment variables, both overridable by a flag:
31
+
32
+ | | flag | default |
33
+ | --- | --- | --- |
34
+ | `MODEL2DATA_STUDIO_KEY` | `--key` | required |
35
+ | `MODEL2DATA_STUDIO_URL` | `--url` | `https://studio.jbanalytica.com` |
36
+
37
+ Create a key in the studio's account menu. The CLI sends it as a bearer token
38
+ and nothing else — it never prints it, and it is the only thing that identifies
39
+ the account, so put it in your CI provider's secrets rather than in a file.
40
+
41
+ Leave the key's access on **Generate and export**, which is the default and
42
+ covers everything below: reading a project, generating, exporting. A key with
43
+ **Read only** access is refused by `generate` with a 403 saying so. The two
44
+ levels above it are for things the CLI does not do — **Generate and save** adds
45
+ writing the project back, for a pipeline that pushes a schema up rather than
46
+ pulling data down, and **Full access** adds deleting a project and managing its
47
+ share links. A key that leaks out of a CI log can only do what it was given.
48
+
49
+ ## Commands
50
+
51
+ ```bash
52
+ model2data-studio projects # id, name, rows, last edited
53
+ model2data-studio whoami # the plan behind the key, and what it has used
54
+
55
+ model2data-studio generate --project <id> --out seeds/
56
+ model2data-studio generate --project <id> --out . --format dbt --adapter duckdb
57
+ ```
58
+
59
+ `generate` takes every setting from the saved project — row counts, per-table
60
+ overrides, seed, locale, name — and each flag overrides one of them:
61
+
62
+ | | |
63
+ | --- | --- |
64
+ | `--format csv\|dbt` | seed CSVs, or a whole dbt project. dbt export is a Pro feature. |
65
+ | `--rows N` | the default row count for every table |
66
+ | `--rows-for TABLE=N` | one table's count, on top of the project's. Repeatable. |
67
+ | `--seed N` | pin the data. Same seed, same rows, forever. |
68
+ | `--as-of YYYY-MM-DD` | pin the day dates and timestamps are anchored on. Defaults to the project's, then today. |
69
+ | `--locale CODE` | which country's people and addresses to generate |
70
+ | `--business-hours` / `--no-business-hours` | weight timestamps toward weekdays and working hours. Pro. |
71
+ | `--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. |
72
+ | `--seasonality N` | strength of an annual cycle peaking in Q4, `0.0`-`1.0`. Pro. |
73
+ | `--skew N` | how unevenly child rows spread over their parents, `0.0`-`1.0`. Pro. |
74
+ | `--name NAME` | the dbt project's name |
75
+ | `--adapter duckdb\|postgres` | the warehouse the dbt project targets |
76
+ | `--force` | replace files already in `--out` |
77
+ | `--check` | compare against `--out` instead of writing to it |
78
+
79
+ Every run also prints a row count for each table it generated, read back out
80
+ of the CSVs — a quick way to see what actually landed without opening a seed.
81
+
82
+ **Shaping flags name one field at a time.** `--growth` on its own overrides
83
+ just the growth trend; business hours and seasonality still come from the
84
+ project, the same way `--rows-for orders=9` overrides one table without
85
+ resetting every other one to the base count. Shaping is a Pro feature — a free
86
+ plan's key gets a 402 naming the plan boundary if the project or a flag asks
87
+ for anything but the uniform defaults.
88
+
89
+ **Pin a seed.** A project without one generates different rows every run, which
90
+ means a diff on every build and no way back to yesterday's data. If the project
91
+ has no seed and you pass no `--seed`, the CLI picks one and prints it, so a run
92
+ that surprised you can at least be reproduced.
93
+
94
+ **Pin the day too.** Dates and timestamps are generated relative to an anchor
95
+ day, so a seed alone reproduces its rows only for as long as that day is the
96
+ same. The studio pins one whenever a project's seed is on — **Dates as of**, in
97
+ the generation settings — and the CLI reads it back, which is what makes a run
98
+ here reproduce the rows the studio previewed rather than merely yesterday's CLI
99
+ run. `--as-of` overrides it. A project with no day pinned anchors on today — at
100
+ midnight, so every run on the same day agrees — and the CLI prints the day it
101
+ used, the same way it prints a picked seed. A pipeline that `--check`s
102
+ committed fixtures, or that needs a failing build to be reproducible on a
103
+ laptop next week, wants the day pinned one way or the other; with `--seed`, the
104
+ two together name one dataset for good.
105
+
106
+ **The project must have been opened in the studio at least once** since the
107
+ studio started saving its parse. The API never parses DBML — the browser does,
108
+ and posts the result — so a project with no stored parse has nothing to generate
109
+ from, and the CLI says so rather than failing later.
110
+
111
+ **`--check` never writes into `--out`.** It regenerates into a scratch
112
+ directory and diffs it against what is already there, printing what changed,
113
+ what is missing and what is left over, then exits 1 if they differ — so a CI
114
+ job can fail a build whose committed fixtures nobody regenerated after a schema
115
+ change, instead of shipping them stale. Exits 0 and prints `Up to date: N
116
+ files` when they match.
117
+
118
+ ## Regenerating dbt seeds in CI
119
+
120
+ ```yaml
121
+ name: Seeds
122
+
123
+ on: [push]
124
+
125
+ jobs:
126
+ seeds:
127
+ runs-on: ubuntu-latest
128
+ steps:
129
+ - uses: actions/checkout@v4
130
+ - uses: astral-sh/setup-uv@v5
131
+
132
+ - name: Regenerate the seeds from the model
133
+ env:
134
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
135
+ run: |
136
+ uvx model2data-studio generate \
137
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
138
+ --out seeds/ --force --seed 1 --as-of 2026-01-01
139
+
140
+ - name: Build against them
141
+ run: uv run dbt build --target ci
142
+ ```
143
+
144
+ `--force` because the checkout already holds the last run's files, and `--seed`
145
+ with `--as-of` because a pipeline that regenerates its fixtures should still be
146
+ able to explain a failure — with a pinned seed and a pinned day, a failing build
147
+ is reproducible on a laptop, however long after it ran.
148
+
149
+ Nothing is committed back: the seeds exist for the length of the job, and the
150
+ model in the studio is the thing under version control. If you would rather
151
+ commit them, add a step that opens a pull request when `git status` comes back
152
+ dirty — that turns a schema change into a reviewable diff of the data it
153
+ produces.
154
+
155
+ ## Failing CI when the committed fixtures are stale
156
+
157
+ If seeds are committed rather than regenerated on every run, `--check` is the
158
+ step that catches someone editing the schema in the studio and forgetting to
159
+ pull the new fixtures into the repo:
160
+
161
+ ```yaml
162
+ - name: Check the committed seeds match the model
163
+ env:
164
+ MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
165
+ run: |
166
+ uvx model2data-studio generate \
167
+ --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
168
+ --out seeds/ --seed 1 --as-of 2026-01-01 --check
169
+ ```
170
+
171
+ It never writes into `seeds/` — only into a scratch directory it diffs against
172
+ what's there — so this is safe to run against the checkout as-is, no `--force`
173
+ needed. `--as-of` is what lets the check pass on any day: the committed seeds
174
+ were generated relative to one day, and without it a schema with a date column
175
+ would compare against rows anchored on today and fail at the first midnight.
176
+
177
+ ## The MCP server: generating from an agent instead of a shell
178
+
179
+ The same account, the same key, the same API — for the editor rather than the
180
+ terminal. The studio is where a schema is *designed*, with a diagram and a
181
+ preview to design it against; the agent in your editor is where the data is
182
+ wanted, and asking it for "two hundred believable orders from my webshop model"
183
+ should not mean pasting DBML into a chat.
184
+
185
+ ```bash
186
+ uvx --from 'model2data-studio[mcp]' model2data-studio mcp # the SDK is an extra, see below
187
+ ```
188
+
189
+ `uvx` fetches it on first use, so there is nothing to install beforehand. With
190
+ the package installed (`pip install 'model2data-studio[mcp]'`),
191
+ `model2data-studio-mcp` starts the same server. For Claude Code:
192
+
193
+ ```bash
194
+ claude mcp add model2data-studio \
195
+ --env MODEL2DATA_STUDIO_KEY=m2d_... \
196
+ -- uvx --from 'model2data-studio[mcp]' model2data-studio mcp
197
+ ```
198
+
199
+ and for anything that reads a JSON config (Claude Desktop, Cursor, Zed):
200
+
201
+ ```json
202
+ {
203
+ "mcpServers": {
204
+ "model2data-studio": {
205
+ "command": "uvx",
206
+ "args": ["--from", "model2data-studio[mcp]", "model2data-studio", "mcp"],
207
+ "env": { "MODEL2DATA_STUDIO_KEY": "m2d_..." }
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ `MODEL2DATA_STUDIO_URL` works here too, for a self-hosted studio.
214
+
215
+ Five tools:
216
+
217
+ | | |
218
+ | --- | --- |
219
+ | `account` | the plan and its caps, worth reading before asking for a lot of rows |
220
+ | `list_projects` | the ids to pass to everything else |
221
+ | `describe_project` | one project's tables, columns, refs and enums as text, generation hints included |
222
+ | `preview_data` | generate and return the rows themselves, capped at fifty a table |
223
+ | `write_data` | generate and write the files, as `generate --out` does |
224
+
225
+ `preview_data` is the one worth having. An agent that can *read* generated rows
226
+ can act on them — write an assertion against them, notice that `shipped_at`
227
+ precedes `ordered_at`, spot a column coming out as placeholder sentences. It
228
+ answers with the rows inline and says how many there really are; `write_data` is
229
+ for when it has decided and wants the fixtures on disk.
230
+
231
+ Both go through the same `export.resolve` the CLI's `generate` does, so an agent
232
+ and a pipeline asking for the same project with the same arguments get the same
233
+ rows — including the part where an unpinned project has a seed picked for it and
234
+ is told which one, so the answer can be reproduced afterwards.
235
+
236
+ The MCP SDK is an optional extra rather than a dependency, on purpose: the
237
+ reason this package is two dependencies is that it installs on CI runners next
238
+ to a build that has nothing to do with the studio, and a runner regenerating
239
+ fixtures does not need an MCP SDK.
240
+
241
+ ## Developing
242
+
243
+ ```bash
244
+ cd cli
245
+ uv sync --extra dev
246
+ uv run pytest
247
+ uv run ruff check .
248
+ ```
249
+
250
+ The tests drive the real Typer app, and the MCP tools through the server's own
251
+ `call_tool`, answering their HTTP with an `httpx.MockTransport` — so there is
252
+ nothing to run and nothing to reach.
253
+
254
+ ## Releasing
255
+
256
+ Bump `version` in `pyproject.toml`, `__version__` in
257
+ `model2data_studio/__init__.py` and both versions in `server.json` (the release
258
+ workflow refuses a tag that disagrees with any of them), merge, then tag the
259
+ merge commit on `main` and push the tag:
260
+
261
+ ```bash
262
+ git tag cli-v0.1.0 && git push origin cli-v0.1.0
263
+ ```
264
+
265
+ `.github/workflows/release-cli.yml` runs the tests, builds, publishes to PyPI
266
+ through trusted publishing (no token stored anywhere) and then lists the new
267
+ version in the MCP registry.
268
+
269
+ <!-- mcp-name: io.github.JB-Analytica/model2data-studio -->
@@ -0,0 +1,17 @@
1
+ """The model2data studio command line client.
2
+
3
+ A schema is designed once, so the studio itself is a place you visit and leave.
4
+ This is the part that stays: it pulls a saved project and writes its data, which
5
+ is what turns "I made some fixtures" into "CI rebuilds the fixtures every run"
6
+ and a committed, quietly rotting CSV into something regenerated from the model
7
+ it came from.
8
+
9
+ Deliberately thin. It holds no parser, no generator and no schema knowledge —
10
+ the studio's API does all of that, and this only knows how to ask.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ __version__ = "0.1.0"
16
+
17
+ __all__ = ["__version__"]