MCP tools: remote-custom-data
remote-custom-data
Registered when: deployment configured (CUSTOM_DATA_CLUSTER_URL) + org: customData.enabled (the org flag defaults ON, but no tools exist until the deployment is configured); key names data, no persona.
alter_table
Add columns or dedupe keys to a table, or switch its mode. Additive and safe on a live table. Leaving append_only needs confirm "_append_only". Dropping or renaming a column is not done here: use query with the DDL and confirm. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
add_columns | object[] | no | |
add_dedupe_keys | string[][] | no | |
mode | "normal" \ | "append_only" | no |
confirm | string | no | |
dry_run | boolean | no | |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"alter_table","arguments":{"database":"<database>","table":"<table>"}}
Related: data_status, describe, query, explain, create_table, find_rows, insert_rows, update_rows.
create_table
Create a table from a column list without writing DDL; returns the DDL it ran. Every table gets id (identity primary key), created_at and updated_at. dedupe_keys adds unique keys that insert_rows can upsert on (the durable dedupe a bot should rely on). mode "append_only" refuses UPDATE/DELETE. row_scope "project" adds project_id and limits rows to the caller's projects. dry_run=true returns the DDL without creating anything. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
name | string | yes | New table name (lowercase_snake, at most 40 characters). |
columns | object[] | yes | |
dedupe_keys | string[][] | no | Unique keys, each a list of column names, e.g. [["email"]]. |
mode | "normal" \ | "append_only" | no |
row_scope | "none" \ | "project" | no |
dry_run | boolean | no | |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"create_table","arguments":{"database":"<database>","name":"<name>","columns":[]}}
Related: data_status, describe, query, explain, alter_table, find_rows, insert_rows, update_rows.
data_status
Whether custom data is on for this API key's organization, and that these data tools are available to it. Call first. The organization is always the key's own; no tool takes an orgId. Workflow: data_status, then describe, then query / find_rows / insert_rows. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
No parameters.
Example call:
{"name":"data_status","arguments":{}}
Related: describe, query, explain, create_table, alter_table, find_rows, insert_rows, update_rows.
delete_rows
Delete rows by primary key (keys: [{id: 1}, ...]) or by where (column -> value equality). Deleting every row is refused (CD-WARN-FULLTABLE) unless allow_full_table=true. append_only tables refuse deletes. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
where | object | no | |
keys | object[] | no | |
allow_full_table | boolean | no | |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"delete_rows","arguments":{"database":"<database>","table":"<table>"}}
Related: data_status, describe, query, explain, create_table, alter_table, find_rows, insert_rows.
describe
Show the schema of one custom database as compact DDL: tables, columns with types, keys, indexes, relations, row counts and descriptions, only what your data role can see. Read this before writing SQL or rows. Returns {ddl, schema_digest, ...}; the digest changes only when the schema does, so skip re-reading an unchanged schema. history=true adds the DDL change log. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
format | "ddl" \ | "json" | no |
history | boolean | no | Include the DDL change log. |
Example call:
{"name":"describe","arguments":{"database":"<database>"}}
Related: data_status, query, explain, create_table, alter_table, find_rows, insert_rows, update_rows.
explain
Show the Postgres plan and estimated cost of one SQL statement without running it (analyze=true runs a read inside the timeout for real timings). Use when a query is slow or failed with CD-PG-57014 (timeout): look for Seq Scan on large tables and add an index with query. Same SQL rules and $1..$n params as query. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
sql | string | yes | The statement to plan. |
params | any[] | no | Values for $1..$n, in order. Never paste values into the SQL text. |
analyze | boolean | no | Run it (reads only) for actual timings. |
timeout_ms | integer | no |
Example call:
{"name":"explain","arguments":{"database":"<database>","sql":"<sql>"}}
Related: data_status, describe, query, create_table, alter_table, find_rows, insert_rows, update_rows.
export_rows
Export a table (optionally filtered with the find_rows query form in query) as CSV, JSON or NDJSON text, bounded by the row and byte caps. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
format | "csv" \ | "json" \ | "ndjson" |
query | object | no | {select, where, order_by, limit} as in find_rows. |
Example call:
{"name":"export_rows","arguments":{"database":"<database>","table":"<table>","format":"csv"}}
Related: data_status, describe, query, explain, create_table, alter_table, find_rows, insert_rows.
find_rows
Read rows from one table without writing SQL. where is a filter: {column, op, value} with op one of eq, ne, lt, lte, gt, gte, like, ilike, in, not_in, is_null, not_null; combine with {and: [...]}, {or: [...]}, {not: {...}}. select lists columns or aggregates {fn: count|sum|avg|min|max, column, as}; group_by, order_by [{column, direction}], limit (default 1000), offset. Returns the rows and the SQL it compiled to. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
select | string \ | object[] | no |
where | object | no | |
group_by | string[] | no | |
order_by | object[] | no | |
limit | integer | no | |
offset | integer | no |
Example call:
{"name":"find_rows","arguments":{"database":"<database>","table":"<table>"}}
Related: data_status, describe, query, explain, create_table, alter_table, insert_rows, update_rows.
import_rows
Bulk insert or upsert CSV, JSON (array of objects) or NDJSON into a table in one transaction (bounded: 10,000 rows, 5 MB; larger is CD-LIMIT-IMPORT). column_map renames source fields to columns; mode "upsert" with on_conflict dedupes. dry_run=true returns the first mapped rows and errors without writing. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
format | "csv" \ | "json" \ | "ndjson" |
data | string \ | object[] | no |
attachmentId | string | no | Canonical uploaded UTF-8 CSV/JSON/NDJSON id. Existing file-read authorization and import limits apply. |
column_map | object | no | |
empty_as_null | boolean | no | |
mode | "insert" \ | "upsert" | no |
on_conflict | object | no | |
dry_run | boolean | no | |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"import_rows","arguments":{"database":"<database>","table":"<table>","format":"csv"}}
Related: data_status, describe, query, explain, create_table, alter_table, find_rows, insert_rows.
insert_rows
Insert rows given as JSON objects (column -> value). on_conflict {columns: [dedupe key columns], action: "nothing" | "update", update_columns?} upserts on a unique key instead of failing with CD-PG-23505: use it so a retried bot run never duplicates rows. Do not send id, created_at or updated_at. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
rows | object[] | yes | |
on_conflict | object | no | |
returning | boolean | no | Return the inserted rows. |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"insert_rows","arguments":{"database":"<database>","table":"<table>","rows":[]}}
Related: data_status, describe, query, explain, create_table, alter_table, find_rows, update_rows.
query
Run PostgreSQL 16 SQL in one custom database, in ONE transaction: SELECT (CTEs, window functions, LATERAL, JSONB operators), INSERT/UPDATE/DELETE/MERGE with RETURNING and ON CONFLICT, and DDL (CREATE TABLE/INDEX/VIEW, ALTER TABLE). Call describe first: never guess table or column names. Pass values as $1..$n in params, never inside the SQL text. Send either sql (one statement) or statements (several, run in order, all rolled back if one fails). Do not send BEGIN/COMMIT/SAVEPOINT (each call is already a transaction), COPY, CREATE EXTENSION, GRANT/REVOKE or role statements: they are refused. UPDATE/DELETE without WHERE is refused unless allow_full_table=true; DROP and TRUNCATE need confirm set to the object name. dry_run=true runs everything and rolls back (sequences still advance). The goodqa.* schema is read only. Results: {statements: [{rows, row_count, truncated, ...}]}, at most limit rows (default 1000). On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
sql | string | no | One SQL statement. Use $1..$n placeholders for values. |
params | any[] | no | Values for $1..$n, in order. Never paste values into the SQL text. |
statements | object[] | no | Several statements run in order in one transaction (instead of sql). |
dry_run | boolean | no | Run and roll back: test a write or DDL without keeping it. |
allow_full_table | boolean | no | Allow UPDATE/DELETE without a WHERE clause. |
confirm | string | no | Object name, required for DROP and TRUNCATE. |
limit | integer | no | Max rows returned per statement (default 1000). |
timeout_ms | integer | no |
Example call:
{"name":"query","arguments":{"database":"<database>"}}
Related: data_status, describe, explain, create_table, alter_table, find_rows, insert_rows, update_rows.
update_rows
Update rows matching where (column -> value equality) with set (column -> new value). expect (column -> value) makes the update apply only if those values still hold (optimistic concurrency). An empty where changes every row and is refused (CD-WARN-FULLTABLE) unless allow_full_table=true. Call describe first: never guess table or column names. On failure the result is {error: {code, message, fix, position?}}: read fix and retry with it. Codes: CD-PG- (a Postgres error, e.g. CD-PG-42P01 unknown table, CD-PG-42703 unknown column, CD-PG-23505 duplicate key, CD-PG-42501 your data role lacks the privilege, CD-PG-57014 timeout), CD-DENY- (refused before running, e.g. CD-DENY-SCHEMA, CD-DENY-FN, CD-DENY-TXN, CD-DENY-COPY, CD-DENY-EXT), CD-WARN-FULLTABLE, CD-LIMIT-*, CD-FORBIDDEN / CD-NOT-FOUND (no access, or no such database).
| Parameter | Type | Required | Description |
|---|---|---|---|
database | string | yes | Custom database key (lowercase_snake, e.g. "outreach"). Unqualified table names resolve in this database. |
table | string | yes | Table name inside the database (lowercase_snake). Run describe first to see the real names. |
where | object | yes | |
set | object | yes | |
expect | object | no | |
allow_full_table | boolean | no | |
operationId | string | no | Idempotency key: retrying with the same operationId and the same arguments returns the stored answer instead of applying twice (24 h). Reusing it with different arguments is a 409; while the first call runs (or if its outcome was lost) it is a 409 for up to 2 minutes, then a retry runs, so read the rows back first. |
Example call:
{"name":"update_rows","arguments":{"database":"<database>","table":"<table>","where":{},"set":{}}}
Related: data_status, describe, query, explain, create_table, alter_table, find_rows, insert_rows.