Skip to main content

vibedasher-mcp

An MCP server that exposes the Vibedasher data engine — datasets, CSV upload + ETL, headless SQL query, and dashboard (viz) management — as tools a customer's AI (Claude Code, Cursor, ...) can drive directly.

This is the control plane (VD-601, PIVOT/PLAN.md EP-6). It wraps the published vibedasher Python SDK and calls only the public, metered /api/v1/* API.

Install: pip install 'vibedasher-mcp>=0.6.0', then vibedasher login — no API key to mint. Live on PyPI since 2026-08-09, as are vibedasher (PyPI) and @vibedasher/client (npm).

The eject tools (VD-602) ship in this package, not separately: eject_viz, eject_viz_files, eject_instructions and get_viz_files are registered by register_eject_tools() in eject.py. They pull a viz's code plus an SDK-wiring manifest so your AI can recreate the dashboard natively in your stack.

Tools

Tool What it does
list_datasets List datasets the key can read (id, name, cleanSQLName, status).
get_dataset(dataset_id) One dataset's metadata + column schema.
upload_dataset(name, csv_content, ...) Inline. Create a CSV dataset from CSV text you pass as an argument, upload, load, poll to READY. Every byte is a tool argument — ~260k tokens per MB — so this is the lane for a small table you are writing by hand.
upload_dataset_from_file(name, csv_path, ...) From disk. Same flow, but the bytes are read from an absolute local path and never enter the caller's context; you get back datasetId, cleanSQLName, sourcePath, sourceBytes. Use this for any CSV that is already a file. stdio only; absolute paths only; refuses a directory, a device, a missing or unreadable path, and anything over 512 MiB (VIBEDASHER_MCP_MAX_UPLOAD_BYTES).
run_query(sql, dataset_ids, params, type) Explore. Inline alias-only SQL across your datasets (plural, joins allowed) → typed columns + rows in the response. type="wasm" returns a plan of presigned Parquet URLs instead of rows — the credential-free data lane for a standalone build, and the only call that builds a dataset's wasm extract.
run_query_to_file(sql, output_path, dataset_ids, params, format, overwrite) Extract. Same query, but the rows are written to a local CSV/JSONL file and never enter the response — you get back path, rowCount, columns, bytes, truncated. stdio only; absolute paths only; never clobbers without overwrite=True.
list_vizzes(include_unpublished=False) List dashboards.
get_viz(viz_id) One viz's metadata.
create_viz(name, dataset_ids, seed_id/dashboard_config, ...) Create a viz (AI dashboard-build entry).
build_viz(viz_id, branch) Compile a viz branch into a renderable bundle.
eject_viz(viz_id, branch, out_dir, overwrite, max_chars) (eject) The viz's runnable source tree + package.json + Tailwind config + wiring manifest. Pass out_dir — see Delivery.
eject_viz_files(viz_id, paths, branch, out_dir, ...) (eject) Ejected file contents on demand — the follow-up call when eject_viz came back paged.
eject_instructions() (eject) How to recreate an ejected dashboard in your app — the query contract, auth modes, region sharding, and the host-bundler traps.
get_viz_files(viz_id, branch, out_dir, ...) (eject) Raw file tree for a viz branch. Same delivery budget as eject_viz.

Delivery: an ejected tree does not fit in one response

Measured against prod on 2026-08-18: eject_viz(1275) is 44 files / 517,351 characters (~129k tokens) and eject_viz(1282) is 45 files / 556,363 characters (~139k tokens). MCP clients cap a tool result, so the single-shot payload was either dropped outright by a strict client or ate the agent's entire context. Every eject response now carries a delivery block, and the three file tools take the same three parameters.

Mode How Cost
disk (preferred) out_dir=<ABSOLUTE path> ~30k characters regardless of tree size — measured 32,308 for viz 1275
paged (default) omit out_dir; files fills to max_chars (60,000), the rest land in delivery.pendingPaths 10 calls for viz 1275, none over budget
unbounded max_chars=0 the pre-0.2.0 payload, on request only
eject_viz(viz_id=1275, out_dir="/abs/path/to/app/dashboard")   # one call, whole tree on disk

out_dir resolves on the machine running the MCP server; with the standard stdio launch (command: vibedasher-mcp) that is your machine, because the client spawns the server as a local subprocess. Relative paths are refused, existing files are refused unless overwrite=True, and any path escaping out_dir aborts the write before a byte lands.

Paging is the fallback, not the goal: round trips do not reduce total tokens (the content still crosses the model), they only keep each individual response deliverable. Hand delivery.pendingPaths straight back to eject_viz_files(viz_id, paths=...) and repeat until it is empty. A response with a non-empty pendingPaths is a page, not a tree.

Override the budget per call with max_chars or globally with VIBEDASHER_MCP_MAX_RESPONSE_CHARS.

Three data lanes

Pick by what you will do with the rows:

Lane Call What comes back Use it for
Explore run_query(sql, ...) the rows, inline aggregates, samples, schema checks — anything the agent will read
Extract run_query_to_file(sql, output_path=<absolute>, format="csv"|"jsonl") {path, rowCount, columns, bytes, truncated} — never the rows anything loaded, charted or shipped from disk; thousands of rows and up
Static asset run_query(sql, type="wasm") a plan of presigned Parquet URLs, no rows a dashboard you host yourself, querying in-browser with DuckDB-WASM and no key in the bundle

run_query_to_file writes on the machine running the MCP server, which with the standard stdio launch is the agent's own machine (the client spawns the server as a local subprocess). It is refused over --http, where the path would resolve on the server's disk. Both lanes that return counts honour the backend's 100,000-row cap: truncated: true means the rows — in the response or in the file — are a prefix. Parquet output is deliberately not offered: the wasm lane already returns Parquet the server built, and pyarrow is a 40 MB wheel to install for one option.

run_query reuses the SDK's hand-written query() transport (VD-301): the caller never sees inline-vs-presigned delivery, MessagePack, or retries — one call in, typed rows out. Each dataset id resolves server-side to that dataset's cleanSQLName alias under the VD-203 RLS/alias-rewrite; SQL references only aliases.

Auth

Two lanes. The first one is the default because it asks nothing of you.

Sign in through your browser (first run)

pip install 'vibedasher-mcp>=0.6.0'   # `login` does not exist below 0.6.0
vibedasher login                      # opens a browser; approve; done

vibedasher login opens your browser, captures the authorization code on a loopback listener bound to 127.0.0.1, and stores a short-lived refresh token in your OS keyring (with a 0600 file fallback where no keyring is available). There is no key to mint, copy, paste or rotate, and nothing lands in a config file or your shell history. Later runs are silent until the session lapses, at which point login asks again. The refresh token is single-use and rotates on every refresh; if a rotated-away copy is ever presented again — someone read the keyring or the credentials file — the whole session is revoked on the spot and the next run asks you to login again.

Two companions: vibedasher whoami prints the account, user, region and the date you will be asked to sign in again; vibedasher logout revokes this machine's session and forgets it.

The MCP client block then carries no credential at all:

{
  "mcpServers": {
    "vibedasher": {
      "command": "vibedasher-mcp",
      "env": { "VIBEDASHER_REGION": "eu-central-1" }
    }
  }
}

VIBEDASHER_REGION is optional here — login records the region the account actually lives in — but pinning it costs nothing and makes the shard explicit.

API key (CI, servers, headless agents)

Anywhere a browser cannot open, an API key is still the answer. Mint one in the console (Settings → API keys) or via POST /v1/api-keys, and give it to the server as an environment variable:

export VIBEDASHER_API_KEY=...           # X-Api-Key, handled by the SDK's create_client
export VIBEDASHER_REGION=eu-central-1   # or us-east-1
# export VIBEDASHER_BASE_URL=...        # optional override (on-prem/staging)
{
  "mcpServers": {
    "vibedasher": {
      "command": "vibedasher-mcp",
      "env": {
        "VIBEDASHER_API_KEY": "...",
        "VIBEDASHER_REGION": "eu-central-1"
      }
    }
  }
}

An API key authenticates as the user who created it, has no expiry unless one is set, and is a bearer secret in plaintext on whatever disk holds that config. That is a fine trade for a build server and a poor one for your laptop — which is why it is the second lane and not the first.

Either way, the server checks before it serves

vibedasher-mcp resolves a credential — stored session first, then VIBEDASHER_API_KEY — at startup. With neither, it refuses to start and prints what to do on stderr, so a credential-less server never connects and never advertises tools it cannot serve. Neither credential is ever logged or echoed in a tool result.

Metering

Every tool hits the public, metered endpoints (create_dataset is credit-gated; run_query is metered via the usage/credits event). The MCP adds no side channel and bypasses no metering.

Run

pip install 'vibedasher-mcp>=0.6.0'   # (monorepo dev: also make `vibedasher` importable)
vibedasher login                       # sign in once, in a browser
vibedasher-mcp                         # stdio MCP server
# or:  python -m vibedasher_mcp

Wiring Claude Code in one line, on a machine that has signed in:

claude mcp add vibedasher --env VIBEDASHER_REGION=eu-central-1 -- vibedasher-mcp

Then ask your agent for something. It has 64 tools; a good first prompt is "run whoami, then list_datasets, and build me a dashboard from the most interesting one — verify with get_build_status and probe_viz before telling me it's done."

Two verifiers, two worlds, and they do not overlap:

probe_viz render_probe
reads the GENERATED tree (config.ts + pages/*.tsx) nothing — it opens the published page
runs each tile's own SQL, server-side a real browser
answers does the emitted SQL match the declared binding did it paint, with what numbers, with what errors, and did it leak into the host
on a hand-authored tree reports hand_authored_tree and checks nothing this is the one
never claims that a viewer saw anything that a number is correct

render_probe walks the WHOLE dashboard, not one screenful: it scrolls the frame to the bottom in steps (re-settling after each one, so a lazily-mounted tile is counted and a permanently skeletal one still goes red) and, when the tree exposes the generated page control [data-selector="page"] [data-value], opens every page and sweeps each. Tiles are unioned by node identity, so a tile carrying three conventions is one tile and two conventions marking different tiles add up. tiles.covers states in one sentence what the count is a count OF — all pages fully scrolled, one page fully scrolled, or one viewport — and whatever it could not walk stays in notVerified. Pass scroll=False / traverse_pages=False to narrow it; the caveat comes back when you do.

render_probe needs a browser, so Playwright is an extra:

pip install 'vibedasher-mcp[render]>=0.6.0' && python -m playwright install chromium

Without it the tool returns checked: false and that install command. It never reports a pass it did not earn.

For the JSON mcpServers form of both lanes, see Auth.

Development

The package depends on the sibling SDK at ../sdk/py. Tests wire that path automatically (tests/conftest.py), so from packages/mcp:

python -m pytest tests/ -q

Note: fastmcp is a client-side dependency of this standalone package; it is not bundled into the API Lambdas, so the export_requirements.py step in the root CLAUDE.md does not apply here.

Release files for vibedasher-mcp 0.8.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 vibedasher-mcp 0.8.0
File Size Uploaded
vibedasher_mcp-0.8.0.tar.gz 656.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vibedasher-mcp 0.8.0
File Interpreter ABI Platform
vibedasher_mcp-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / vibedasher_mcp-0.8.0.tar.gz

Download URL vibedasher_mcp-0.8.0.tar.gz
Size 656.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8acea3df535540f6847376a2ea0fd4b683d0e1b65445c174f2b6bf5d0e6f5ed6
BLAKE2b-256 checksum
How to use checksums
be0f9fcd39c7106425febb8a59c9243250c1e96196357e7a8726f3fb0f7d16f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / vibedasher_mcp-0.8.0-py3-none-any.whl

Download URL vibedasher_mcp-0.8.0-py3-none-any.whl
Size 367.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
607faa477f3b5c60996d5ca1411d6b8371ab50abda9fd5e5d6e1ecf41eafede3
BLAKE2b-256 checksum
How to use checksums
3824e4848810ccbf9482341a5370b63567d68d6369589aaa3f5bfecddc9be472
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

0.9.6

2 release files

0.9.5

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

This release

0.8.0 This release

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

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