Analytics tools
Datasets, charts, dashboards, and running SQL / NL→SQL queries.
25 tools. Each is callable over MCP with a Bearer agx_ key that holds the required scope. The catalog is public (tools/list isn't scope-filtered); the scope below is enforced when the tool is called (tools/call) — a key without it is refused (default-deny).
get_chart
Get one chart's config. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
chart_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_chart",
"arguments": {
"chart_id": "\u2026"
}
}
}
get_dashboard
Get one dashboard + the charts it contains. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dashboard_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_dashboard",
"arguments": {
"dashboard_id": "\u2026"
}
}
}
get_dashboard_with_data
Get a dashboard AND every chart's plotted data in one call — the batch loader. Optional dashboard-wide date filter: date_preset (today/yesterday/last_7_days/last_30_days/this_month/this_quarter/this_year/last_month/…) OR date_start+date_end (YYYY-MM-DD). The filter applies per chart via its date-column mapping. Each chart's data is under _chart_data (with a per-chart error if that one failed). Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dashboard_id | string | ✓ | |
date_preset | string | e.g. last_30_days, this_month (omit for all-time). | |
date_start | string | YYYY-MM-DD (custom range; needs date_end). | |
date_end | string | YYYY-MM-DD (custom range; needs date_start). | |
fresh | boolean | Bypass the analytics cache. |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_dashboard_with_data",
"arguments": {
"dashboard_id": "\u2026"
}
}
}
get_data_model
The org's grounded analysis data model: tables + columns/types, the REAL enum values per column, custom-field sql_refs (e.g. (custom_fields->>'budget')::numeric), the join graph, and deal/lifecycle stages. Read this BEFORE writing SQL for run_query so you use real column/value/stage names instead of guessing. Org database only — for external DBs use list_data_sources + describe_analytics_table. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
force_refresh | boolean | Rebuild the cached manifest (after a schema change). |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_data_model",
"arguments": {}
}
}
get_dataset
Get one dataset with its SQL + column manifest. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dataset_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_dataset",
"arguments": {
"dataset_id": "\u2026"
}
}
}
create_chart
Create a chart on a dataset. chart_type ∈ bar/line/pie/area/table/stacked_bar/horizontal_bar/donut/scatter/kpi/… chart_config holds dimensions[], metrics[], breakdownBy, filters[], topN. Requires analytics:write. RENDER CONTRACT (charts render BLANK if violated): (1) EVERY chart — including kpi and table — needs ≥1 entry in dimensions[]; a chart with dimensions:[] shows 'No dimensions/metrics configured'. For a single-value KPI, add a constant column to the dataset (e.g. "'Total'::text AS scope") and use it as the sole dimension so it groups to one row. (2) Every metric/dimension alias MUST be a lowercase snake_case identifier ([a-z0-9_], no spaces/slashes/caps). The engine sanitizes aliases and Postgres lowercases them, so the returned data key = lowercase(sanitized alias); the UI looks up series/KPI values by the RAW alias, so "Sign Ups" → key sign_ups → mismatch → empty chart. Use alias:"sign_ups", "ad_spend", "r_org_conv". RATIO / COST-PER / AOV (derived) metrics: use chart_config.calculatedMetrics = [{alias, metricA, operator:'+|-|*|/|%', metricB (or constantB)}] — computed POST-aggregation as metricA <op> metricB (division guards /0 → NULL), so it is a period-accurate SUM(a)/SUM(b). metricA/metricB reference the (sanitized) metric aliases. For a ratio KPI: metrics=[SUM(num) with hidden:true, SUM(den) with hidden:true] + calculatedMetrics=[{alias:'ratio', metricA:'num', operator:'/', metricB:'den'}]; the scorecard displays the calculated metric (first visible metric or, if base metrics are hidden, the calculated one).
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | |
chart_type | string | ✓ | |
query_id | string | The dataset id this chart visualizes (required unless chart_type='text'). | |
chart_config | object | dimensions[] (≥1 required, even for kpi/table), metrics[] (function ∈ COUNT/SUM/AVG/MIN/MAX/COUNT_DISTINCT; mark a base metric hidden:true to keep it out of the display but usable by calculatedMetrics), calculatedMetrics[] (derived ratio/cost/AOV: {alias, metricA, operator, metricB|constantB} computed post-aggregation = period-accurate SUM(a)/SUM(b)), breakdownBy ({field, alias} — a bare column name also works), filters[], topN, valueFormat, sortByColumn (a dimension/metric ALIAS string + sortDirection 'asc'|'desc'). ALL aliases must be lowercase snake_case identifiers matching the dataset columns. | |
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_chart",
"arguments": {
"name": "\u2026",
"chart_type": "\u2026"
}
}
}
create_dashboard
Create a dashboard composing existing charts (by chart_ids). Optional layout (grid positions + per-chart date-filter mapping). Requires analytics:write. For the dashboard's global date filter to drive a chart, that chart's layout entry MUST set filter_mappings.date_range.column to a date column its dataset's SELECT actually exposes — map it to a column that does not exist in the chart's own query and that chart errors ('column base.<col> does not exist'). When charts come from different sources with different date columns, build them on ONE dataset with a single shared date column (a daily calendar-spine) so one filter can anchor all. CUSTOM FILTER DROPDOWNS (industry, size, …) beyond the built-in date filter go in filters (array of controls); a chart responds to a control ONLY if its layout filter_mappings[<control id>] = {column:'<col>'} points at a real column in that chart's dataset. Leave a chart unmapped to exclude it (e.g. keep ad-spend charts OUT of an industry filter when spend has no industry). Give the dataset the filterable dimension as a real column and grain the rows by it.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | |
description | string | ||
chart_ids | array<string> | Chart ids to include. | |
layout | object | {chart_layouts: {<chart_id>: {position:{x,y}, size:{width,height}, filter_mappings:{date_range:{column:'<date_col>'}, <control_id>:{column:'<col>'}}}}}. GRID UNITS: 12 columns; rowHeight is 15px with a per-chart minimum of 8 rows (120px). Set size.height in these units: kpi/scorecard/table height:8 (120px), line/bar/area charts height:16 (240px). Small heights like 2–4 render 30–60px → KPI numbers overflow and plots flatten. x/y are also in grid units (stack kpi rows h8 at y0/y8/y16, then charts h16). | |
filters | array<object> | Custom filter controls beyond the built-in date filter: [{id, name, type:'single_select'|'multi_select'|'text_search'|'numeric_range', field?}]. Use a stable id (e.g. 'industry') and reference that same id in each chart's layout.filter_mappings to wire it to a column. Options are auto-populated from distinct column values. | |
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_dashboard",
"arguments": {
"name": "\u2026"
}
}
}
create_dataset
Save a reusable dataset from a read-only SQL SELECT. The column manifest is auto-extracted. Charts are built on datasets. Requires analytics:write.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | |
sql | string | ✓ | A read-only SELECT. |
description | string | ||
integration_id | string | Build the dataset on an external database (from list_data_sources). Omit for the org DB. | |
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_dataset",
"arguments": {
"name": "\u2026",
"sql": "\u2026"
}
}
}
update_chart
Update a chart's name/type/config/dataset/sharing. Requires analytics:write. Same RENDER CONTRACT as create_chart: dimensions[] must be non-empty (even for kpi/table) and every alias must be a lowercase snake_case identifier matching the dataset columns, or the chart renders blank.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
chart_id | string | ✓ | |
name | string | ||
chart_type | string | ||
query_id | string | ||
chart_config | object | ||
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_chart",
"arguments": {
"chart_id": "\u2026"
}
}
}
update_dashboard
Update a dashboard's name/description/layout/chart_ids/filters/sharing. Requires analytics:write. layout and filters each REPLACE the stored value, so send the COMPLETE object each time (layout: preserve every chart's filter_mappings + use grid-unit sizes kpi/table height:8, charts height:16 at 15px/row — a partial layout drops mappings and resets heights; filters: send the full control array).
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dashboard_id | string | ✓ | |
name | string | ||
description | string | ||
chart_ids | array<string> | ||
layout | object | ||
filters | array<object> | Custom filter controls (REPLACES stored): [{id, name, type:'single_select'|'multi_select'|...}]; wire each to charts via layout.filter_mappings[<id>]={column}. | |
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_dashboard",
"arguments": {
"dashboard_id": "\u2026"
}
}
}
update_dataset
Update a dataset's name/description/sql/sharing (changing sql re-extracts columns). Requires analytics:write.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dataset_id | string | ✓ | |
name | string | ||
description | string | ||
sql | string | ||
integration_id | string | Move the dataset onto an external database (or 'current_org' for the org DB). | |
is_shared | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_dataset",
"arguments": {
"dataset_id": "\u2026"
}
}
}
delete_chart
Delete a chart. Requires analytics:write.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
chart_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_chart",
"arguments": {
"chart_id": "\u2026"
}
}
}
delete_dashboard
Delete a dashboard (its charts are not deleted). Requires analytics:write.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dashboard_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_dashboard",
"arguments": {
"dashboard_id": "\u2026"
}
}
}
delete_dataset
Delete a dataset (its dependent charts cascade). Requires analytics:write.
Required scope: analytics:write
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dataset_id | string | ✓ |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_dataset",
"arguments": {
"dataset_id": "\u2026"
}
}
}
list_analytics_tables
List the tables available to query for analytics (name + column count). Call describe_analytics_table for a table's columns and row count. Use to discover what you can build a dataset/query from. Pass integration_id (from list_data_sources) to list an external database's tables instead. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
integration_id | string | External database integration. Omit for the org DB. |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_analytics_tables",
"arguments": {}
}
}
list_charts
List saved charts. Optional dataset_id filters to charts on one dataset. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
dataset_id | string |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_charts",
"arguments": {}
}
}
list_dashboards
List saved dashboards. Requires analytics:read.
Required scope: analytics:read
Input
No input parameters.
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_dashboards",
"arguments": {}
}
}
list_data_sources
List the databases you can analyze: the org database plus every connected external database integration (PostgreSQL/MySQL/…), each with its integration_id. External DBs have no pre-loaded schema — discover it live: list_analytics_tables(integration_id) → describe_analytics_table → preview → run_query. Note generate_sql/get_data_model are org-DB only; for external DBs hand-write SQL from the discovered schema. Requires analytics:read.
Required scope: analytics:read
Input
No input parameters.
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_data_sources",
"arguments": {}
}
}
list_datasets
List saved analytics datasets (saved SQL queries). Requires analytics:read.
Required scope: analytics:read
Input
No input parameters.
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_datasets",
"arguments": {}
}
}
describe_analytics_table
Get a table's columns + types (including custom fields). Pass integration_id to introspect a table in an external database. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
table_name | string | ✓ | |
integration_id | string | External database integration. Omit for the org DB. |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe_analytics_table",
"arguments": {
"table_name": "\u2026"
}
}
}
describe_entity_fields
The writable field schema for ONE CRM entity (contact / company / deal / task / note / …): its standard columns plus every custom field with its slug (field_name), type, and — for dropdown/multiselect — the EXACT allowed option values. Read this BEFORE a create_/update_ call so you set custom_fields.<field_name> to a real slug and a valid dropdown option instead of guessing (an off-list value is stored silently and never shows in the UI). Lightweight, entity-scoped alternative to get_data_model. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
entity_type | string | ✓ | Entity singular: contact, company, deal, task, note, product, activity. |
force_refresh | boolean | Rebuild the cached schema manifest first. |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe_entity_fields",
"arguments": {
"entity_type": "\u2026"
}
}
}
generate_sql
Turn a natural-language question into grounded, validated, read-only SQL — the SAME NL→SQL agent the in-product dataset builder uses (it knows your real schema, enum values, custom-field sql_refs, join graph, and deal/lifecycle stages, and self-corrects against the DB). Prefer this over hand-writing SQL. mode: validate (default, self-corrects + returns sample rows), preview (more rows), generate_only (no DB). Feed the returned sql to run_query or create_dataset. Org database only — for external DBs discover schema via list_data_sources + describe_analytics_table and hand-write SQL. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
question | string | ✓ | The business question in plain English. |
mode | enum(validate, preview, generate_only) |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "generate_sql",
"arguments": {
"question": "acme"
}
}
}
preview_analytics_table
Preview the first rows of a table. Pass integration_id for a table in an external database. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
table_name | string | ✓ | |
integration_id | string | External database integration. Omit for the org DB. | |
limit | integer |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "preview_analytics_table",
"arguments": {
"table_name": "\u2026"
}
}
}
run_chart
Get server-side aggregated data for a chart (GROUP BY dimensions + metrics, breakdownBy, filters, topN). Provide chart_id (saved chart) OR (dataset_id and/or sql) plus a chart_config. Returns the plotted rows. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
chart_id | string | A saved chart (uses its dataset + config). | |
dataset_id | string | Aggregate a saved dataset. | |
sql | string | Ad-hoc base SELECT to aggregate. | |
integration_id | string | With ad-hoc sql: aggregate against an external database. Saved charts/datasets carry their own. | |
chart_config | object | dimensions[], metrics[], breakdownBy ({field, alias} — a bare column name also works), filters[], topN, sortByColumn (a dimension/metric ALIAS string, with sortDirection 'asc'|'desc'; sortByMetric is the deprecated name for the same string). | |
limit | integer | ||
fresh | boolean |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "run_chart",
"arguments": {}
}
}
run_query
Run a read-only SQL query and get rows back — the way to ANALYZE data. Provide sql (ad-hoc SELECT), dataset_id (a saved dataset), OR question (plain English — the NL→SQL agent grounds + validates it, then it's executed; the response includes the generated_sql). Only SELECTs are allowed (DROP/DELETE/etc. blocked); org-scoped. Paginated via limit/offset. Tip: call get_data_model first to learn real columns/values. Requires analytics:read.
Required scope: analytics:read
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
sql | string | A read-only SELECT (ad-hoc). | |
dataset_id | string | Run a saved dataset. | |
question | string | Natural-language question; auto-converted to SQL and run (org database only). | |
integration_id | string | Run against a connected external database (from list_data_sources). Omit for the org DB. | |
limit | integer | ||
offset | integer | ||
fresh | boolean | Bypass cache. |
Example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "run_query",
"arguments": {}
}
}