Dashboard 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 generated reference for BitBoard’s stable dashboard tools.
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.
Connected apps and repositories can add dynamic tools for the authenticated user. Agents should discover those tools at runtime instead of hard-coding their names. See MCP overview for how the surfaces fit together.
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 dashboard from a declarative spec. Pass blocks and layout in one call — this is the normal path.
Each block carries a client-supplied key. Set displayed and title inline and no follow-up calls are needed: the spec expresses everything dashboard_add_block, dashboard_set_displayed and dashboard_set_layout would have done. Inter-block wiring is by config.output_name / config.source. Returns key_to_id and a per-key blocks map so you can see what landed without a follow-up read.
Pass hydrate: true to also run the graph and return with data. Omit it to create the structure only — hydration runs queries against your connections, so it’s opt-in.
Omitting blocks gives an empty dashboard, for when the shape isn’t known yet. That costs one round trip per block, one per set_displayed, and one for set_layout — on a six-block dashboard that’s ten calls instead of one.
Inputs
title(string, optional)visibility(private|link_view, optional)hydrate(boolean, optional) — Run the graph after applying, so the dashboard comes back with data. Omit for structure only — hydration runs queries against your connections.hydrate_stale_only(boolean, optional) — With hydrate, skip blocks already marked complete. Use for ‘fill in what’s missing’ rather than ‘refresh the data’.blocks(object[], optional) — Optional declarative block tree. Each entry: {key, type, source, config, displayed?, title?}.keyis a client-side stable identifier used for layout refs and diff identity. Producer blocks setconfig.output_name; consumers pointconfig.sourceat that output_name. Each item is an object with:key(string, required)type(string, required)source(string, optional)config(object, optional)displayed(boolean, optional)title(string, optional)
layout(object, optional) — Optional row layout.itemsreferences spec block keys (or, on update, existing block_ids).
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, chunk_url, chunk_size, token, max_bytes, expires_at, filename}. For files <= chunk_size, PUT the whole body to upload_url. For larger files, PUT each chunk_size-byte slice to chunk_url?index=N&final=0 in order, sending the last slice with final=1. After the upload 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). The chunked path lets uploads finish behind HTTP proxies (Render, Cloudflare, etc.) that cap individual request bodies.
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 ONE block to a dashboard that already exists.
Building a dashboard, or adding more than one block, goes through the spec on dashboard_create / dashboard_update instead — one call for the whole set rather than one per block, plus one per set_displayed. If you’re calling this a second time in a row, use a spec.
Producers set config.output_name; consumers set config.source to a sibling’s output name. Blocks added here are addressable by block_id in a later spec.
Inputs
dashboardId(string, required)type(code|markdown|sql|mcp|chart|document|render|table|filter-daterange|param-enum, required)source(string, required)config(object, optional)title(string, optional) — Optional tile title. Recommended for presentation blocks (chart/table/render/…) so the cell shows a real label instead of a derived fallback. Ignored for hidden compute blocks.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 ONE existing block as a dashboard root.
When building or extending a dashboard, set displayed and title inline in the spec on dashboard_create / dashboard_update instead — this call exists for adjusting a board that already exists, not for constructing one.
Displayed blocks render in the grid; non-displayed ones are internal nodes consumed by a displayed root. A newly displayed block is placed as its own full-width row at the bottom.
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.
When building or extending a dashboard, pass layout in the spec on dashboard_create / dashboard_update instead — it takes the same shape and saves a round trip. This call is for rearranging a board that already exists.
Rows are ordered top to bottom; 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. Optional h is the row height in pixels — a KPI tile ~200, a standard chart ~290, a dense chart or table taller.
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 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.
Connections
Section titled “Connections”dashboard_list_connections
Section titled “dashboard_list_connections”List every connection the user has wired up — databases, MCP connections, and repositories. 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’, ‘repository’} — read it to pick the downstream contract:
- type=database → write a SQL block with config.source set to the connection’s
name. BitBoard routes the query to the selected database. - type=mcp → write an
mcpblock with config.connection_id=plus a tool name from dashboard_list_connection_tools. - type=repository → call its namespaced tools from MCP discovery.
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.
dashboard_delete_blocks
Section titled “dashboard_delete_blocks”Soft-delete a list of blocks in one transaction. Returns {deleted, not_found} so the caller can confirm what landed. Edges to and from each deleted block are dropped so the cascade walker stops considering them. Owner-only. Prefer this over many dashboard_delete_block calls when wiping a set of orphan or experimental blocks.
Inputs
dashboardId(string, required)blocks(string[], required) — List of block_ids to soft-delete.
dashboard_update
Section titled “dashboard_update”Apply a declarative spec to an existing dashboard. Same shape as the blocks/layout fields of dashboard_create.
The spec is a FRAGMENT: keys it names are inserted or updated in place, and everything else is left alone. So extending a dashboard costs a spec of just the blocks you’re adding — you never restate the board to add to it.
Deletion is always explicit, never implied by omission:
delete: block keys or block_ids to remove.prune: true: also remove keyed blocks the spec omits, for the ‘make it exactly this’ case. Never touches blocks without a stored key, since a spec can’t name them.
Removing a block that another block still reads is rejected, and names the dependents. Positions are preserved for existing keys and appended for new ones. Layout, when supplied, is replaced wholesale; omit it to leave the layout untouched. Returns {added, updated, deleted, key_to_id}.
Inputs
dashboardId(string, required)blocks(object[], optional) Each item is an object with:key(string, required)type(string, required)source(string, optional)config(object, optional)displayed(boolean, optional)title(string, optional)
layout(object, optional)delete(string[], optional) — Blocks to remove, by spec key or block_id. Omission fromblocksdoes NOT delete — say so here.hydrate(boolean, optional) — Run the graph after applying, so the dashboard comes back with data. Omit for structure only — hydration runs queries against your connections.hydrate_stale_only(boolean, optional) — With hydrate, skip blocks already marked complete. Use for ‘fill in what’s missing’ rather than ‘refresh the data’.prune(boolean, optional) — Default false. When true, also removes keyed blocks absent fromblocks, making the dashboard exactly the spec. Never removes blocks without a stored key.