Skip to main content

model2data-studio CLI

Pull a saved model2data studio project and write its data, so a build can regenerate its fixtures instead of carrying a CSV that was right once.

The schema is designed in the studio, where a diagram and a preview make it worth designing. This is the part that runs afterwards, unattended, from the same saved project — so the seeds a test suite runs against are the ones the model says they should be, rather than whatever was committed the day someone last remembered to export.

It is deliberately thin: no DBML parser, no generator, no schema knowledge. The studio's API does all of that; this only knows how to ask, and where to put the answer.

Install

pip install model2data-studio          # or run it without installing:
uvx model2data-studio --help

It needs an API key from a Pro account on the studio: see pricing. The client is MIT licensed; the studio it talks to is not open source.

Configuration

Two environment variables, both overridable by a flag:

flag default
MODEL2DATA_STUDIO_KEY --key required
MODEL2DATA_STUDIO_URL --url https://studio.jbanalytica.com

Create a key in the studio's account menu. The CLI sends it as a bearer token and nothing else — it never prints it, and it is the only thing that identifies the account, so put it in your CI provider's secrets rather than in a file.

Leave the key's access on Generate and export, which is the default and covers everything below: reading a project, generating, exporting. A key with Read only access is refused by generate with a 403 saying so. The two levels above it are for things the CLI does not do — Generate and save adds writing the project back, for a pipeline that pushes a schema up rather than pulling data down, and Full access adds deleting a project and managing its share links. A key that leaks out of a CI log can only do what it was given.

Commands

model2data-studio projects              # id, name, rows, last edited
model2data-studio whoami                # the plan behind the key, and what it has used

model2data-studio generate --project <id> --out seeds/
model2data-studio generate --project <id> --out . --format dbt --adapter duckdb

generate takes every setting from the saved project — row counts, per-table overrides, seed, locale, name — and each flag overrides one of them:

--format csv|dbt seed CSVs, or a whole dbt project. dbt export is a Pro feature.
--rows N the default row count for every table
--rows-for TABLE=N one table's count, on top of the project's. Repeatable.
--seed N pin the data. Same seed, same rows, forever.
--as-of YYYY-MM-DD pin the day dates and timestamps are anchored on. Defaults to the project's, then today.
--locale CODE which country's people and addresses to generate
--business-hours / --no-business-hours weight timestamps toward weekdays and working hours. Pro.
--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.
--seasonality N strength of an annual cycle peaking in Q4, 0.0-1.0. Pro.
--skew N how unevenly child rows spread over their parents, 0.0-1.0. Pro.
--name NAME the dbt project's name
--adapter duckdb|postgres the warehouse the dbt project targets
--force replace files already in --out
--check compare against --out instead of writing to it

Every run also prints a row count for each table it generated, read back out of the CSVs — a quick way to see what actually landed without opening a seed.

Shaping flags name one field at a time. --growth on its own overrides just the growth trend; business hours and seasonality still come from the project, the same way --rows-for orders=9 overrides one table without resetting every other one to the base count. Shaping is a Pro feature — a free plan's key gets a 402 naming the plan boundary if the project or a flag asks for anything but the uniform defaults.

Pin a seed. A project without one generates different rows every run, which means a diff on every build and no way back to yesterday's data. If the project has no seed and you pass no --seed, the CLI picks one and prints it, so a run that surprised you can at least be reproduced.

Pin the day too. Dates and timestamps are generated relative to an anchor day, so a seed alone reproduces its rows only for as long as that day is the same. The studio pins one whenever a project's seed is on — Dates as of, in the generation settings — and the CLI reads it back, which is what makes a run here reproduce the rows the studio previewed rather than merely yesterday's CLI run. --as-of overrides it. A project with no day pinned anchors on today — at midnight, so every run on the same day agrees — and the CLI prints the day it used, the same way it prints a picked seed. A pipeline that --checks committed fixtures, or that needs a failing build to be reproducible on a laptop next week, wants the day pinned one way or the other; with --seed, the two together name one dataset for good.

The project must have been opened in the studio at least once since the studio started saving its parse. The API never parses DBML — the browser does, and posts the result — so a project with no stored parse has nothing to generate from, and the CLI says so rather than failing later.

--check never writes into --out. It regenerates into a scratch directory and diffs it against what is already there, printing what changed, what is missing and what is left over, then exits 1 if they differ — so a CI job can fail a build whose committed fixtures nobody regenerated after a schema change, instead of shipping them stale. Exits 0 and prints Up to date: N files when they match.

Regenerating dbt seeds in CI

name: Seeds

on: [push]

jobs:
  seeds:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5

      - name: Regenerate the seeds from the model
        env:
          MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
        run: |
          uvx model2data-studio generate \
            --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
            --out seeds/ --force --seed 1 --as-of 2026-01-01

      - name: Build against them
        run: uv run dbt build --target ci

--force because the checkout already holds the last run's files, and --seed with --as-of because a pipeline that regenerates its fixtures should still be able to explain a failure — with a pinned seed and a pinned day, a failing build is reproducible on a laptop, however long after it ran.

Nothing is committed back: the seeds exist for the length of the job, and the model in the studio is the thing under version control. If you would rather commit them, add a step that opens a pull request when git status comes back dirty — that turns a schema change into a reviewable diff of the data it produces.

Failing CI when the committed fixtures are stale

If seeds are committed rather than regenerated on every run, --check is the step that catches someone editing the schema in the studio and forgetting to pull the new fixtures into the repo:

      - name: Check the committed seeds match the model
        env:
          MODEL2DATA_STUDIO_KEY: ${{ secrets.MODEL2DATA_STUDIO_KEY }}
        run: |
          uvx model2data-studio generate \
            --project ${{ vars.MODEL2DATA_PROJECT_ID }} \
            --out seeds/ --seed 1 --as-of 2026-01-01 --check

It never writes into seeds/ — only into a scratch directory it diffs against what's there — so this is safe to run against the checkout as-is, no --force needed. --as-of is what lets the check pass on any day: the committed seeds were generated relative to one day, and without it a schema with a date column would compare against rows anchored on today and fail at the first midnight.

The MCP server: generating from an agent instead of a shell

The same account, the same key, the same API — for the editor rather than the terminal. The studio is where a schema is designed, with a diagram and a preview to design it against; the agent in your editor is where the data is wanted, and asking it for "two hundred believable orders from my webshop model" should not mean pasting DBML into a chat.

uvx --from 'model2data-studio[mcp]' model2data-studio mcp   # the SDK is an extra, see below

uvx fetches it on first use, so there is nothing to install beforehand. With the package installed (pip install 'model2data-studio[mcp]'), model2data-studio-mcp starts the same server. For Claude Code:

claude mcp add model2data-studio \
  --env MODEL2DATA_STUDIO_KEY=m2d_... \
  -- uvx --from 'model2data-studio[mcp]' model2data-studio mcp

and for anything that reads a JSON config (Claude Desktop, Cursor, Zed):

{
  "mcpServers": {
    "model2data-studio": {
      "command": "uvx",
      "args": ["--from", "model2data-studio[mcp]", "model2data-studio", "mcp"],
      "env": { "MODEL2DATA_STUDIO_KEY": "m2d_..." }
    }
  }
}

MODEL2DATA_STUDIO_URL works here too, for a self-hosted studio.

Five tools:

account the plan and its caps, worth reading before asking for a lot of rows
list_projects the ids to pass to everything else
describe_project one project's tables, columns, refs and enums as text, generation hints included
preview_data generate and return the rows themselves, capped at fifty a table
write_data generate and write the files, as generate --out does

preview_data is the one worth having. An agent that can read generated rows can act on them — write an assertion against them, notice that shipped_at precedes ordered_at, spot a column coming out as placeholder sentences. It answers with the rows inline and says how many there really are; write_data is for when it has decided and wants the fixtures on disk.

Both go through the same export.resolve the CLI's generate does, so an agent and a pipeline asking for the same project with the same arguments get the same rows — including the part where an unpinned project has a seed picked for it and is told which one, so the answer can be reproduced afterwards.

The MCP SDK is an optional extra rather than a dependency, on purpose: the reason this package is two dependencies is that it installs on CI runners next to a build that has nothing to do with the studio, and a runner regenerating fixtures does not need an MCP SDK.

Developing

cd cli
uv sync --extra dev
uv run pytest
uv run ruff check .

The tests drive the real Typer app, and the MCP tools through the server's own call_tool, answering their HTTP with an httpx.MockTransport — so there is nothing to run and nothing to reach.

Releasing

Bump version in pyproject.toml, __version__ in model2data_studio/__init__.py and both versions in server.json (the release workflow refuses a tag that disagrees with any of them), merge, then tag the merge commit on main and push the tag:

git tag cli-v0.1.0 && git push origin cli-v0.1.0

.github/workflows/release-cli.yml runs the tests, builds, publishes to PyPI through trusted publishing (no token stored anywhere) and then lists the new version in the MCP registry.

Release files for model2data-studio 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for model2data-studio 0.1.0
File Size Uploaded
model2data_studio-0.1.0.tar.gz 102.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for model2data-studio 0.1.0
File Interpreter ABI Platform
model2data_studio-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 129.6 kB

Release files / model2data_studio-0.1.0.tar.gz

Download URL model2data_studio-0.1.0.tar.gz
Size 102.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5155d6b9eb5520e4c92af11b16a17276ead17f81b37eae84c802fdc182afbcab
BLAKE2b-256 checksum
How to use checksums
c6644748ac7b05876b87ec7aabe06f61a39719fcaccbb503f48fd8de21b0b1d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / model2data_studio-0.1.0-py3-none-any.whl

Download URL model2data_studio-0.1.0-py3-none-any.whl
Size 27.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
db2637674a67015fd2f473d435da8d8ab76830e22dd44e1f235e541f758e8c5d
BLAKE2b-256 checksum
How to use checksums
8c7efec461d84e9c5ed15e3d2cd49da9b7362075c18b792249b3d67342154916
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page