Skip to content

MCP tools

BitBoard exposes dashboards through the Model Context Protocol so connected agents can read and edit them the same way a person can. This page is the full call reference, generated directly from the MCP tool registry.

To connect your agent, see Connect your agent. Every tool below operates on a single dashboard identified by dashboardId, except list_dashboards and dashboard_create, which are account-level.

List the caller’s dashboards. Returns slim records (id, title, updated_at). For the full read shape (manifest + blocks + files) call dashboard_get.

Inputs: none.

Read the dashboard shape: row fields + blocks + derived manifest. The manifest is projected from blocks at read time (connections from sql.config.source / code.config.connections / mcp.config.connector_id; queries from blocks with config.output_name; params union of every block’s config.params).

Each block’s source is omitted by default — the read returns structure, config, run state, and layout but NOT authored bodies. Bodies are reference-by-default: when the agent needs a specific block’s source, call dashboard_get_block_source(blockId). Pass include_source=true to inline every block’s source in this response (rare; useful for export / migration scripts).

Inputs

  • dashboardId (string, required)
  • include_source (boolean, optional) — Default false. When true, each block’s source is inlined. Prefer dashboard_get_block_source for single-block reads.

Fetch one block’s authored source body by id. Companion to dashboard_get, which omits source by default. Returns {id, block_type, source, config, run_status, run_error?}. Use this when you actually need to read or revise a specific block’s body — not as a bulk discovery tool.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)

Walk a displayed block’s upstream chain (the closure of blocks reachable through ref / refs / config.source) and return them in topological order (deepest source first, the seed block last). Use this to introspect lineage before editing a render block — it tells you which SQL block produced the rows the render reads, and which connector the SQL hits.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)

Return block-type capabilities and contracts. Use this before authoring blocks so behavior is runtime-discoverable.

Inputs

  • dashboardId (string, optional)

Create a new dashboard owned by the caller. A dashboard is a grid of displayed blocks (SQL + chart + render + filters) rendered at /dashboards/. Returns the dashboard row (id, owner_id, title, data_dir, visibility, …). Typical follow-up sequence: dashboard_add_block (sql + chart) → dashboard_set_displayed → dashboard_set_layout.

Inputs

  • title (string, optional)
  • visibility (private | link_view, optional)

Set the dashboard’s visibility. ‘private’ = owner-only; ‘link_view’ = anyone with the URL can view (anonymous, read-only). Owner-only operation.

Inputs

  • dashboardId (string, required)
  • visibility (private | link_view, required)

Soft-delete the dashboard. Owner-only.

Inputs

  • dashboardId (string, required)

Push tabular data into the dashboard as a queryable table. Creates or replaces a named table. Accepts row-object format or columnar rows with explicit columns. The data becomes queryable from SQL blocks via config.source = <table name>.

Inputs

  • dashboardId (string, required) — Dashboard ID
  • table (string, required) — Table name (valid SQL identifier, e.g. ‘sales’, ‘monthly_revenue’)
  • rows ((object | array)[], required) — Array of row objects (e.g. [{“month”:“Jan”,“revenue”:100}]) or row arrays when used with columns.
  • columns (string[], optional) — Required when rows are arrays. Optional when rows are objects.

Append rows to an existing pushed table without re-uploading the prior dataset. Use this for curation patterns: backfilling new periods, recording corrections, or extending a running table from a fresh batch.

Strict column alignment: the rows’ columns must be a subset of the existing schema. Missing columns become null; extra columns reject. Call dashboard_push_data to replace the table when changing shape (adding a new column counts as shape change). Rejects if the table does not yet exist — call push_data first to create.

Bounded by the dashboard upload limit; an append that would push the merged parquet past that cap fails without corrupting the existing file.

Inputs

  • dashboardId (string, required)
  • table (string, required) — Existing table name (must already be pushed).
  • rows ((object | array)[], required) — Row objects keyed by column name, or row arrays aligned to columns.
  • columns (string[], optional) — Required when rows are arrays; optional when rows are objects (defaults to first row’s keys).

Import CSV text into the dashboard as a queryable table. Parses the CSV, creates or replaces a named table, and adds a starter SQL block that queries it via config.source. The starter block is auto-run by default (pass auto_run: false to defer). Use this instead of push_data when you have raw CSV content (e.g. from a file read).

Inputs

  • dashboardId (string, required)
  • csv_content (string, required) — Raw CSV text with a header row followed by data rows.
  • table_name (string, optional) — Table name for the imported data. If omitted, defaults to ‘imported’.
  • auto_run (boolean, optional, default True) — If true (default), the starter SQL block is run immediately so subsequent dashboard_get calls show real data.

Mint a short-lived signed URL the agent’s host can PUT a file to, without the bytes flowing through MCP or your context. Returns {upload_url, token, max_bytes, expires_at, filename}. After the PUT completes, call dashboard_finalize_upload(token) to convert the staged file to Parquet and register it as a dashboard source.

Use this when the agent has access to a local file (Claude Code stdio) or can shell out an HTTP PUT (Claude.ai web, ChatGPT).

Inputs

  • dashboardId (string, required)
  • filename (string, required) — Original filename (basename only). Used to infer file_type during finalize and as a default table name suggestion.

Promote a staged upload (already PUT to the signed URL) into a queryable dashboard source. Converts CSV/TSV/JSON/JSONL/Parquet to Parquet, stores it under blobs/data/, and adds a starter SQL block. The starter block is auto-run by default (pass auto_run: false to defer). Same return shape as dashboard_import_csv.

Inputs

  • dashboardId (string, required)
  • token (string, required)
  • table_name (string, optional) — Table name for the imported data. Defaults to a slug derived from the original filename.
  • file_type (csv | tsv | json | jsonl | parquet, optional) — Override the auto-detected file type. Defaults to detection by filename extension.
  • auto_run (boolean, optional, default True) — If true (default), the starter SQL block is run immediately so subsequent dashboard_get calls show real data.

List every queryable source in the dashboard with run state. The catalog dashboard_sample_rows and dashboard_query_sql read against — use this BEFORE authoring SQL to confirm (a) the source you want exists, (b) its producer has run, (c) its column shape.

Each entry: {name, origin, producer_block_id, block_type, run_status, row_count, columns}. origin is 'block' for block outputs or 'upload' for uploaded files. run_status ∈ {‘complete’, ‘stale’, ‘running’, ‘error’, null}; queryable means 'complete'. A source with run_status anything else needs dashboard_run_block(producer_block_id) before it can be sampled or queried.

Inputs

  • dashboardId (string, required)

Return full detail for a single source in the dashboard: schema, columns, row_count for block outputs and uploads; engine and table list for databases. Use this after dashboard_get when you need a source’s columns before authoring SQL. Returns metadata only — call dashboard_sample_rows(name) to inspect concrete row values.

Inputs

  • dashboardId (string, required)
  • name (string, required) — Source name from catalog.sources[].name

Return a small sample of rows from a dashboard source for inspection. Use this when you need to see concrete values; for planning queries, prefer dashboard_describe_source which returns schema + row_count without bytes. Supports block-output and upload sources. For database sources, write a SQL block with LIMIT instead — this tool will reject.

Inputs

  • dashboardId (string, required)
  • name (string, required) — Source name from catalog.sources[].name
  • n (integer, optional, default 20) — Number of rows to return (1-100, default 20).

Run a read-only SQL query against the dashboard’s catalog without authoring a block. Same engine resolution, validation, and access checks as dashboard_run_block — diverges only at the persist step: no block is created, no parquet output is written, no DAG node is added. The call is recorded in the audit log as an adhoc_query event so the read activity stays visible.

Use this for inspection: peeking at a source’s distribution, checking a join shape before authoring a real block, sanity-checking row counts. For analysis that downstream blocks should consume, author a SQL block via dashboard_add_block + dashboard_run_block.

Inputs

  • dashboardId (string, required)
  • sql (string, required) — Read-only SQL (SELECT / WITH / EXPLAIN). Mutations are rejected.
  • source (string, optional) — Optional catalog.sources entry name. When set, routes to that source’s engine (e.g. a connected Postgres). When omitted, runs against the full DuckDB catalog (uploads + block outputs), allowing cross-source CTEs.
  • n (integer, optional, default 100) — Max rows to return (1-500, default 100).

List dashboard data ingestion records from Postgres.

Inputs

  • dashboardId (string, required)
  • limit (integer, optional, default 25)

Add a block to the dashboard. Producers set config.output_name; consumers set config.source to a sibling block’s output name. Block types and their conventions:

  • sql: runs against a connector (config.source=connector name) or an upstream output (config.source=output_name). Sets config.output_name so downstream blocks reference the rows it produces. Placeholders: write $name for every parameter (whether the destination is a connector or an upstream block). Declare each one in config.inputs so the cascade walker knows the dependency. The engine translates $name to the destination dialect at execution — psycopg sees %(name)s, DuckDB binds $name natively. Don’t write %(name)s directly; it still works for legacy SQL but isn’t the documented surface.
  • code: Python sandbox. Must define def transform(df): ... returning a DataFrame, scalar, image, or HTML(…).
  • mcp: calls a connector tool; payload materializes as a queryable output.
  • chart (DASHBOARD DEFAULT for visualizations): Python transform that returns HTML(…). Same sandbox and def transform(df) contract as code. config.source names the upstream output_name; df arrives as a DataFrame of those rows. The agent owns the SVG/HTML — read bitboard://chart-design-guide first (or call get_chart_design_guide) for palette, type voice, axis rules, forbidden patterns, and reference layouts. Use this for any standard data visualization.
  • render (escape hatch): identical runtime to chart, but no design guide. Use it for things the guide explicitly forbids or doesn’t cover — custom controls that pilot filters via bb.setFilter, narrative HTML, off-rail styling. The agent and user lose visual consistency here; reach for chart first.
  • filter-daterange / param-enum (dashboard): control blocks. Their source is the JSON-encoded selected value (e.g. ‘“7d”’ or ’{“start”:”…”,“end”:”…”}’). Subscribers reference them via config.source. To verify the filter→SQL wiring at authoring time, call dashboard_run_block(blockId=<downstream_sql>, filter_overrides={<filter_block_id>: <value>}). The override binds for one call without persisting; the filter’s stored value is unchanged.
  • markdown / document: non-runnable consumers.

Inputs

  • dashboardId (string, required)
  • type (code | markdown | sql | mcp | chart | document | render | filter-daterange | param-enum, required)
  • source (string, required)
  • config (object, optional)
  • index (integer, optional)
  • inputs (object[], optional) — Optional. Declare this block’s typed upstreams explicitly. Each entry: {name, role, from}. role ∈ {data, param, connector, literal}. from is {producer: } or {connection: } or {literal: }. When omitted, the platform derives inputs from your source text + config.source (SQL placeholders, {{var}} tokens, params[‘name’] refs). When supplied, the platform stores your declaration verbatim and surfaces a warning if it disagrees with what your source actually references. Each item is an object with:
    • name (string, required)
    • role (data | param | connector | literal, required)
    • from (object, optional)
  • include_source (boolean, optional) — Default false. The response normally returns only {block_id, block_type, run_status, run_error?} — the source / config / metadata you just sent are NOT echoed back. Set true if you need the full block shape returned (rare; usually you still have the body in your own context).

Update a block’s source and/or config and/or inputs in the dashboard. Fields are replaced, not merged: pass the full new config object. Updating any of source/config/inputs re-derives the upstream edge set (the platform parses the new source to populate config.inputs when the agent doesn’t supply one explicitly). Use this to iterate on a block without deleting + re-adding it.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)
  • source (string, optional)
  • config (object, optional)
  • inputs (object[], optional) — Optional explicit list of typed inputs. Same shape as dashboard_add_block.inputs. When omitted, the platform re-derives from the new source text. Each item is an object with:
    • name (string, required)
    • role (data | param | connector | literal, required)
    • from (object, optional)
  • include_source (boolean, optional) — Default false. Response is {block_id, block_type, run_status, run_error?}. Set true to receive the full updated block back.

Remove a single block from the dashboard. Soft-deletes the block row, drops its edges so the cascade walker stops considering it, and clears its layout entry so the grid doesn’t keep an empty slot. Owner-only. Use this to clean up exploratory probe blocks rather than leaving them as hidden dead weight in the DAG.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)

Promote or demote a block as a dashboard root. Displayed blocks render in the dashboard’s grid (forest of trees); non-displayed blocks live as internal nodes consumed by a displayed root via refs / config.source. A newly displayed block is placed as its own full-width row at the bottom; use dashboard_set_layout to arrange. Setting displayed=False removes it from the layout. Pass title to set the frame label at the same time.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)
  • displayed (boolean, required)
  • title (string, optional)
  • include_source (boolean, optional) — Default false. Response is {block_id, block_type, run_status, run_error?}. Set true to receive the full block back.

Set the dashboard’s full row layout in one call. The layout is an ordered list of rows (top to bottom); the cards in a row split the 12-column width evenly. Each row needs items (1–4 displayed block_ids, left to right). Optional cols (one integer per item, each >= 2, summing to 12) overrides the even split. Idempotent: pass the complete desired layout — only displayed blocks may appear, each at most once. Width comes from row membership, so you rarely need cols; just group blocks into rows in the order you want. Height: optional h is the row’s height in pixels. Omit it for the auto default (short for a row of only controls/markdown, ~290px otherwise). Set it to deviate — good bands (these include the cell’s title bar, ~65px): a single-number / KPI tile ~200; a standard chart ~290; a dense chart or table taller. Content fits inside the row — a chart redraws to fill it, and anything taller than the row scrolls inside it (it won’t clip or resize the row), so pick a comfortable height, not a pixel-perfect one.

Inputs

  • dashboardId (string, required)
  • rows (object[], required) Each item is an object with:
    • items (string[], required)
    • cols (integer[], optional)
    • h (integer, optional)

Run a block and persist its output. Execution is eager: the result is written to the block’s output store, run_status becomes ‘complete’ (or ‘error’), and the cascade refreshes downstream consumers so their next read sees the new value. This is the default path — use it when you want the change to stick. The returned shape is {columns, rows} for DataFrames, {html} for HTML, {value} for scalars.

Two opt-in scratch overrides bypass persistence entirely (no DB write, no output artifact, no edge mutation, no run_status change). Use them to audition something against the live graph without committing:

  • source: inline a new body for THIS block. Runs the override against the existing block’s config, refs, and upstreams. Use to audition a chart’s Python or a SQL query’s text without the mutate-to-test loop (update → run → update back). When the body is right, call dashboard_update_block to commit it.
  • filter_overrides: substitute sibling filter / param block values for one call only. Keys are filter block_ids; values are the substituted values. The cascade binds them into this SQL block’s params as if the filter had been set live. Use to prove ‘filter value X actually reaches SQL Y’ without the persisted update(filter) → run(filter) → run(sql) recipe. Unknown / non-producer block_ids are silently ignored. If both source and filter_overrides are passed, the inline-source path wins for that call.

Inputs

  • dashboardId (string, required)
  • blockId (string, required)
  • params (object, optional)
  • source (string, optional) — Optional inline source body. When provided, the block’s persisted source is bypassed for this call only — the override runs against the existing block’s config, refs, and upstreams.
  • filter_overrides (object, optional) — Optional. Map of {filter_block_id: substituted_value} applied during this single run. The cascade binds these values as if the filter blocks had been set live. Nothing persists. Use to verify filter→SQL flow at authoring time without the persisted update→run→update dance.

List every connection the user has wired up — both database connections (Postgres, Snowflake, etc.) and MCP connections (PostHog, Stripe, etc.). Same list the user sees in the Connections page; agent and user speak the same vocabulary.

Each entry: {id, name, type, engine, status}. type ∈ {‘database’, ‘mcp’} — read it to pick the downstream contract:

  • type=database → write a SQL block with config.source set to the connection’s name. The DuckDB engine routes to the right database.
  • type=mcp → write an mcp block with config.connection_id= plus a tool name from dashboard_list_connection_tools.

engine is the specific kind (e.g. ‘postgres’, ‘snowflake’, ‘posthog’). Use this to filter / decide which engine-specific SQL dialect to write.

Inputs

  • dashboardId (string, required)

List callable tools for an MCP connection (only meaningful for type='mcp' connections from dashboard_list_connections; database connections expose tables, not tools). Schema-light by default — call dashboard_describe_connection_tool with the chosen tool’s name for its parameter manifest.

Accepts connectionId (preferred) or the legacy connectorId argument.

Inputs

  • dashboardId (string, required)
  • connectionId (string, optional) — Connection ID from dashboard_list_connections.
  • q (string, optional) — Optional case-insensitive search query
  • limit (integer, optional, default 50)
  • offset (integer, optional, default 0)
  • includeInputSchema (boolean, optional, default False) — Include full input schema for each tool. Prefer dashboard_describe_connection_tool for the single-tool case; this flag is for bulk dumps.

Return one MCP connection tool’s parameter manifest. Companion to dashboard_list_connection_tools: list returns names + descriptions, describe returns the parameters for one tool. Use this to author an mcp block — the manifest tells you every arg’s name, type, required flag, and one-line description.

Default response shape: {name, local_name, connector_id, connector_name, description, parameters: [{name, type, required, description}]}

Pass includeFullSchema=true for the raw JSON Schema blob (rare; needed only for nested constraints / enums / oneOf).

Accepts connectionId (preferred) or the legacy connectorId argument.

Inputs

  • dashboardId (string, required)
  • connectionId (string, required)
  • toolName (string, required)
  • includeFullSchema (boolean, optional, default False)

Deprecated alias for dashboard_list_connections. Use the new name; this entry will be removed.

Inputs

  • dashboardId (string, required)

List callable tools for connected account-level connectors. Results are paginated and schema-light by default (cheap discovery). When you’ve picked a tool to author an mcp block against, call dashboard_describe_connector_tool (or its dashboard_* alias) with that tool’s name to get its full input schema — single-target lookup, no need to paginate the full list with includeInputSchema=true.

Inputs

  • dashboardId (string, required)
  • connectorId (string, optional)
  • q (string, optional) — Optional case-insensitive search query
  • limit (integer, optional, default 50)
  • offset (integer, optional, default 0)
  • includeInputSchema (boolean, optional, default False) — Include full input schema for each tool. Prefer dashboard_describe_connector_tool for the single-tool case; this flag is for bulk dumps.

Return one connector tool’s parameter manifest. Companion to dashboard_list_connector_tools: list returns names + descriptions, describe returns the parameters for one tool. Use this to author an mcp block — the manifest tells you every arg’s name, type, required flag, and one-line description, which is what you need to build the call.

Default response shape: {name, local_name, connector_id, connector_name, description, parameters: [{name, type, required, description}]}

Pass includeFullSchema=true to also receive the raw JSON Schema input_schema blob — for the rare case you need nested constraints, enums, oneOf / allOf, or defaults. The raw schema can be very large (PostHog’s execute-sql is 54KB); prefer the manifest unless you actually need the body.

toolName accepts either the fully-qualified <connector>.<tool> form or the bare local name. Raises if either the connector or the tool isn’t found.

Inputs

  • dashboardId (string, required)
  • connectorId (string, required)
  • toolName (string, required)
  • includeFullSchema (boolean, optional, default False) — Default false. Set true to receive the raw input_schema blob alongside the parameter manifest. Prefer the default for authoring.