describe_table
ChatGPTReturn the schema of one table — columns, types, sample values, row count. Use this when you have a known table name but don't know its columns yet. For business-level descriptions (what each column means) prefer get_table_metadata; this tool returns the raw schema view. Errors (returned as {"error": "..."}): - Unknown table → call list_tables first to discover valid names. - Disabled / errored data source → tell the user; admin must re-enable the source in DataAssist-IO Data Sources. Example: describe_table("orders") → {"name": "orders", "row_count": 1234, "columns": [{"name": "id", "type": "bigint", ...}, ...]}
describe_table
ChatGPTReturn the schema of one table — columns, types, sample values, row count. Use this when you have a known table name but don't know its columns yet. For business-level descriptions (what each column means) prefer get_table_metadata; this tool returns the raw schema view. Errors (returned as {"error": "..."}): - Unknown table → call list_tables first to discover valid names. - Disabled / errored data source → tell the user; admin must re-enable the source in DataAssist-IO Data Sources. Example: describe_table("orders") → {"name": "orders", "row_count": 1234, "columns": [{"name": "id", "type": "bigint", ...}, ...]}
get_sample_data
ChatGPTReturn the first N rows of a table as JSON (clamped: 1 ≤ N ≤ 100). Useful for sanity-checking what real data looks like — what's an amount column actually denominated in, what date format does the org use, are there nulls, etc. Cheaper than query_data for "show me a few rows" because you don't have to write a SELECT. Errors (returned as {"error": "..."}): - Unknown / disabled table → call list_tables first. - Pending-column references / forbidden keywords → these can't happen here (we run SELECT *), but the same envelope shape is used so callers can handle errors uniformly. Example: get_sample_data("orders", limit=5) → [{"id": 1, "amount": 49.99, "ordered_at": "2026-05-20", ...}, ...]
get_sample_data
ChatGPTReturn the first N rows of a table as JSON (clamped: 1 ≤ N ≤ 100). Useful for sanity-checking what real data looks like — what's an amount column actually denominated in, what date format does the org use, are there nulls, etc. Cheaper than query_data for "show me a few rows" because you don't have to write a SELECT. Errors (returned as {"error": "..."}): - Unknown / disabled table → call list_tables first. - Pending-column references / forbidden keywords → these can't happen here (we run SELECT *), but the same envelope shape is used so callers can handle errors uniformly. Example: get_sample_data("orders", limit=5) → [{"id": 1, "amount": 49.99, "ordered_at": "2026-05-20", ...}, ...]
get_table_metadata
ChatGPTReturn the BUSINESS-LEVEL description of a table's columns. Each column carries human-curated metadata: label (display name), description (what it means), category (grouping), plus the inferred type and a few sample values. Use this before writing queries so you map the user's natural-language question to the right columns. Prefer this over describe_table for any user-facing answer — the metadata is what the org's admin chose to expose. describe_table is the raw schema fallback when this returns sparse info. Errors (returned as {"error": "..."}): - Unknown table → call list_tables to discover valid names. - Disabled / errored source → tell the user the admin needs to fix it. Example: get_table_metadata("orders") → {"name": "orders", "description": "All customer purchase events", "columns": [ {"name": "amount", "label": "Order total (USD)", "description": "Pre-tax order amount in USD", "category": "money", "inferred_type": "decimal"}, ...]}
get_table_metadata
ChatGPTReturn the BUSINESS-LEVEL description of a table's columns. Each column carries human-curated metadata: label (display name), description (what it means), category (grouping), plus the inferred type and a few sample values. Use this before writing queries so you map the user's natural-language question to the right columns. Prefer this over describe_table for any user-facing answer — the metadata is what the org's admin chose to expose. describe_table is the raw schema fallback when this returns sparse info. Errors (returned as {"error": "..."}): - Unknown table → call list_tables to discover valid names. - Disabled / errored source → tell the user the admin needs to fix it. Example: get_table_metadata("orders") → {"name": "orders", "description": "All customer purchase events", "columns": [ {"name": "amount", "label": "Order total (USD)", "description": "Pre-tax order amount in USD", "category": "money", "inferred_type": "decimal"}, ...]}
list_tables
ChatGPTList all data tables the connected organization has exposed for MCP. Returns a JSON array; each entry has at least table_name, row_count, description. Call this FIRST when you don't know what data exists — every subsequent tool needs a table name from this list. Empty-state: returns an object of the form {"tables": [], "message": "no tables exposed — direct the user to..."}. Don't invent table names; tell the user how to add data via the DataAssist-IO Data Sources page. Example follow-up: 1. discover list_tables() → [{"table_name": "orders", "row_count": 1234, ...}, ...] 2. understand get_table_metadata("orders") 3. answer query_data("SELECT product, SUM(amount) FROM orders GROUP BY product")
list_tables
ChatGPTList all data tables the connected organization has exposed for MCP. Returns a JSON array; each entry has at least table_name, row_count, description. Call this FIRST when you don't know what data exists — every subsequent tool needs a table name from this list. Empty-state: returns an object of the form {"tables": [], "message": "no tables exposed — direct the user to..."}. Don't invent table names; tell the user how to add data via the DataAssist-IO Data Sources page. Example follow-up: 1. discover list_tables() → [{"table_name": "orders", "row_count": 1234, ...}, ...] 2. understand get_table_metadata("orders") 3. answer query_data("SELECT product, SUM(amount) FROM orders GROUP BY product")
query_data
ChatGPTRun a validated read-only SELECT against the org's data. Strictly read-only: only a single SELECT is allowed. DDL, DML (INSERT/UPDATE/DELETE), GRANT/REVOKE, MySQL HANDLER, system schemas (information_schema, mysql, performance_schema), and DataAssist-IO's own internal tables (orgs, users, audit_logs, etc.) are all rejected by the validator. Best practice — answer with SQL, not row dumps: Prefer SELECT region, SUM(amount) AS total FROM orders GROUP BY region ORDER BY total DESC LIMIT 10 over SELECT * FROM orders followed by client-side aggregation. Aggregations push the work to the DB, keep results small, and produce answers a human can read directly. Limits: `limit` (default 1000, max 10000) is applied automatically — you do NOT need to add a trailing `LIMIT` yourself, though one inside the query is fine. Queries are killed after 30 seconds. Errors (returned as {"error": "..."}): "Table 'X' is not in the allowed tables list" → call `list_tables` to see what's actually exposed. "Column 'X' is not in the allowed columns ... It may be pending admin review" → that column was added to the source but the admin hasn't approved it. Use a column from get_table_metadata. "Forbidden keyword" → you tried DML/DDL or a banned construct; rewrite as a pure SELECT. "Query exceeded timeout of 30 seconds" → narrow the WHERE clause, add an index-friendly filter, or aggregate harder. Examples: Aggregate query_data("SELECT status, COUNT(*) FROM orders GROUP BY status") Filter + sort query_data("SELECT id, amount FROM orders " "WHERE amount > 100 ORDER BY amount DESC LIMIT 50") Join query_data("SELECT c.name, SUM(o.amount) AS spend " "FROM customers c JOIN orders o ON o.customer_id = c.id " "GROUP BY c.name ORDER BY spend DESC LIMIT 10")
query_data
ChatGPTRun a validated read-only SELECT against the org's data. Strictly read-only: only a single SELECT is allowed. DDL, DML (INSERT/UPDATE/DELETE), GRANT/REVOKE, MySQL HANDLER, system schemas (information_schema, mysql, performance_schema), and DataAssist-IO's own internal tables (orgs, users, audit_logs, etc.) are all rejected by the validator. Best practice — answer with SQL, not row dumps: Prefer SELECT region, SUM(amount) AS total FROM orders GROUP BY region ORDER BY total DESC LIMIT 10 over SELECT * FROM orders followed by client-side aggregation. Aggregations push the work to the DB, keep results small, and produce answers a human can read directly. Limits: `limit` (default 1000, max 10000) is applied automatically — you do NOT need to add a trailing `LIMIT` yourself, though one inside the query is fine. Queries are killed after 30 seconds. Errors (returned as {"error": "..."}): "Table 'X' is not in the allowed tables list" → call `list_tables` to see what's actually exposed. "Column 'X' is not in the allowed columns ... It may be pending admin review" → that column was added to the source but the admin hasn't approved it. Use a column from get_table_metadata. "Forbidden keyword" → you tried DML/DDL or a banned construct; rewrite as a pure SELECT. "Query exceeded timeout of 30 seconds" → narrow the WHERE clause, add an index-friendly filter, or aggregate harder. Examples: Aggregate query_data("SELECT status, COUNT(*) FROM orders GROUP BY status") Filter + sort query_data("SELECT id, amount FROM orders " "WHERE amount > 100 ORDER BY amount DESC LIMIT 50") Join query_data("SELECT c.name, SUM(o.amount) AS spend " "FROM customers c JOIN orders o ON o.customer_id = c.id " "GROUP BY c.name ORDER BY spend DESC LIMIT 10")