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.
Discovery
Section titled “Discovery”list_dashboards
Section titled “list_dashboards”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.
dashboard_get
Section titled “dashboard_get”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’ssourceis inlined. Prefer dashboard_get_block_source for single-block reads.
dashboard_get_block_source
Section titled “dashboard_get_block_source”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)
dashboard_get_block_subtree
Section titled “dashboard_get_block_subtree”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)
dashboard_get_capabilities
Section titled “dashboard_get_capabilities”Return block-type capabilities and contracts. Use this before authoring blocks so behavior is runtime-discoverable.
Inputs
dashboardId(string, optional)
Lifecycle
Section titled “Lifecycle”dashboard_create
Section titled “dashboard_create”Create a new dashboard owned by the caller. A dashboard is a grid of displayed blocks (SQL + chart + render + filters) rendered at /dashboards/
Inputs
title(string, optional)visibility(private|link_view, optional)
dashboard_set_visibility
Section titled “dashboard_set_visibility”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)
dashboard_delete
Section titled “dashboard_delete”Soft-delete the dashboard. Owner-only.
Inputs
dashboardId(string, required)
Loading data
Section titled “Loading data”dashboard_push_data
Section titled “dashboard_push_data”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 IDtable(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.
dashboard_append_rows
Section titled “dashboard_append_rows”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).
dashboard_import_csv
Section titled “dashboard_import_csv”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, defaultTrue) — If true (default), the starter SQL block is run immediately so subsequent dashboard_get calls show real data.
dashboard_create_upload_url
Section titled “dashboard_create_upload_url”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.
dashboard_finalize_upload
Section titled “dashboard_finalize_upload”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, defaultTrue) — If true (default), the starter SQL block is run immediately so subsequent dashboard_get calls show real data.
Inspecting sources
Section titled “Inspecting sources”dashboard_list_sources
Section titled “dashboard_list_sources”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)
dashboard_describe_source
Section titled “dashboard_describe_source”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
dashboard_sample_rows
Section titled “dashboard_sample_rows”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[].namen(integer, optional, default20) — Number of rows to return (1-100, default 20).
dashboard_query_sql
Section titled “dashboard_query_sql”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, default100) — Max rows to return (1-500, default 100).
dashboard_get_ingest_runs
Section titled “dashboard_get_ingest_runs”List dashboard data ingestion records from Postgres.
Inputs
dashboardId(string, required)limit(integer, optional, default25)
Authoring blocks
Section titled “Authoring blocks”dashboard_add_block
Section titled “dashboard_add_block”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
$namefor every parameter (whether the destination is a connector or an upstream block). Declare each one inconfig.inputsso the cascade walker knows the dependency. The engine translates$nameto the destination dialect at execution — psycopg sees%(name)s, DuckDB binds$namenatively. Don’t write%(name)sdirectly; 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
sourceis 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, calldashboard_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}.fromis {producer:} or {connection: } or {literal: }. When omitted, the platform derives inputsfrom yoursourcetext +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).
dashboard_update_block
Section titled “dashboard_update_block”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 asdashboard_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.
dashboard_delete_block
Section titled “dashboard_delete_block”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)
dashboard_set_displayed
Section titled “dashboard_set_displayed”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.
dashboard_set_layout
Section titled “dashboard_set_layout”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)
Running blocks
Section titled “Running blocks”dashboard_run_block
Section titled “dashboard_run_block”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 bothsourceandfilter_overridesare 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.
Connections
Section titled “Connections”dashboard_list_connections
Section titled “dashboard_list_connections”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
mcpblock 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)
dashboard_list_connection_tools
Section titled “dashboard_list_connection_tools”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 querylimit(integer, optional, default50)offset(integer, optional, default0)includeInputSchema(boolean, optional, defaultFalse) — Include full input schema for each tool. Preferdashboard_describe_connection_toolfor the single-tool case; this flag is for bulk dumps.
dashboard_describe_connection_tool
Section titled “dashboard_describe_connection_tool”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, defaultFalse)
dashboard_list_connected_connectors
Section titled “dashboard_list_connected_connectors”Deprecated alias for dashboard_list_connections. Use the new name; this entry will be removed.
Inputs
dashboardId(string, required)
dashboard_list_connector_tools
Section titled “dashboard_list_connector_tools”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 querylimit(integer, optional, default50)offset(integer, optional, default0)includeInputSchema(boolean, optional, defaultFalse) — Include full input schema for each tool. Preferdashboard_describe_connector_toolfor the single-tool case; this flag is for bulk dumps.
dashboard_describe_connector_tool
Section titled “dashboard_describe_connector_tool”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, defaultFalse) — Default false. Set true to receive the rawinput_schemablob alongside the parameter manifest. Prefer the default for authoring.