Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions developers/applescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ end tell

Read Only is the default and the right level for most connections: reporting and export scripts
need nothing more. Raise a connection to Read & Write with **External Clients** on its **Options**
tab only when a script has to write to it.
tab only when a script has to write to it. [What Read Only allows](/developers#what-read-only-allows)
has what counts as a read and what is refused at every level.

Read & Write is not a way past Safe Mode. A statement that drops or truncates always raises a
confirmation naming the app that sent it, whatever the level says, and Safe Mode's authentication
Expand Down Expand Up @@ -139,7 +140,7 @@ fails with error -10000.

`row limit` defaults to 500 and caps at 10000. `timeout` defaults to 30 seconds and caps at 600.
Several statements in one call are refused, as are statements that read files or run server-side
code.
code and the requests TablePro builds for its own grid.

SQL Server is the exception to one statement per call: send the whole script, and it runs batch by
batch at its `GO` lines, as the query editor runs it. The record is the first result set,
Expand All @@ -148,7 +149,7 @@ than one. `row limit` caps each result set on its own.

```applescript
tell application "TablePro"
set r to run query "EXEC sp_help 'dbo.orders'" in connection "Warehouse"
set r to run query "SELECT TOP 5 * FROM dbo.orders; SELECT COUNT(*) FROM dbo.orders" in connection "Warehouse"
set sets to results of r
repeat with one in sets
columns of one
Expand Down Expand Up @@ -227,7 +228,7 @@ end try
|---|---|
| `-1728` | No connection or tab by that name or id |
| `-50` | The request is malformed: several statements in one call, or no database to run against |
| `-10000` | Refused by Safe Mode or by **External Clients**, or cancelled at the confirmation |
| `-10000` | Refused by Safe Mode, by **External Clients** or by a [check that holds at every level](/developers#what-read-only-allows), or cancelled at the confirmation |

## Related

Expand Down
27 changes: 27 additions & 0 deletions developers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,33 @@ Each request lands in the activity log with the token behind it, and a statement
SHA-256 digest rather than as text. Open **Settings > MCP** and click **View Activity** to
read it.

### What Read Only allows

**Read Only** is a check on the statement's text, made before the statement reaches the driver. A
statement from an MCP client, a script or a shortcut runs only when TablePro can tell for certain
that it reads, by the rules in [What counts as a read](/features/safe-mode#what-counts-as-a-read).
Anything else fails with "This connection is read only for external clients."

These fail at every level, **Read & Write** included:

- Text holding more than one statement, or ending inside a block it opened, with "Send one
statement at a time." A SQL Server script is the exception: it runs batch by batch at its `GO`
lines.
- A statement that reads or writes files, or runs code on the server, such as `COPY`, `ATTACH`,
`LOAD`, `VACUUM INTO`, DuckDB's file readers like `read_csv`, ClickHouse's `file`, `url` and `s3`,
and T-SQL that runs a string through `EXEC` or `sp_executesql`.
- A request in the form TablePro builds for its own grid on engines such as BigQuery,
Elasticsearch and Typesense. Send the statement it stands for.

The AI assistant's `execute_query` makes the same three refusals. The assistant answers to the
connection's AI policy and Safe Mode rather than to **External Clients**; see
[Tool calling](/features/ai-assistant#tool-calling).

A statement that clears the check runs as the connection's login, with every privilege that login
holds. Those privileges are the final limit. For a connection where an unwanted write matters, save
it with a login that can only read, or set **External Clients** to **Blocked** and **AI Policy** to
**Never**.

## Publish it

Listed integrations appear at [tablepro.app/integrations](https://tablepro.app/integrations), and
Expand Down
6 changes: 3 additions & 3 deletions developers/mcp-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ A call that has to open a connection with a pre-connect script also waits for th

`confirm_destructive_operation` needs `admin` as well, so it takes a Full Access token, and **Read Only** refuses it outright whatever the token carries. No token skips the user's approval.

Statements that read or write files, or that run server-side code, are refused on every tool. So is more than one statement in a single call, and a statement a `GO 5` line would run five times, except in a SQL Server script sent to [`execute_query`](#execute_query).
**Read Only** refuses any statement that is not a read, with `denied`. At every level, every tool refuses a statement that reads or writes files or runs server-side code, and a request in the form TablePro builds for its own grid, also with `denied`. More than one statement in a call fails with `invalid_argument`, and so does a statement a `GO 5` line would run five times, except in a SQL Server script sent to [`execute_query`](#execute_query). Examples of each, and the limit the connection's login sets, are in [What Read Only allows](/developers#what-read-only-allows).

## Connections

Expand Down Expand Up @@ -155,9 +155,9 @@ Without filters this returns the engine's fast estimate unless `exact` is set. W

### `execute_query`

One statement, 100 KB at most. `DROP` and `TRUNCATE` are refused here; use `confirm_destructive_operation`. With `read_only: true`, a statement that is not a read gets `-33007` before the user is asked anything, even on a connection set to **Read & Write** and with a token that carries `tools:write`. On MongoDB, a statement is a read only when it is one call with literal arguments, as [Safe Mode](/features/safe-mode#what-counts-as-a-read) describes; any other JavaScript is a write and needs `tools:write`. When the request carries a `progressToken`, the tool emits progress at 0.0 (resolving), 0.3 (executing) and 1.0 (done).
One statement, 100 KB at most. `DROP` and `TRUNCATE` are refused here; use `confirm_destructive_operation`. With `read_only: true`, a statement that is not a read fails with `denied` before the user is asked anything, even on a connection set to **Read & Write** and with a token that carries `tools:write`. On MongoDB, a statement is a read only when it is one call with literal arguments, as [Safe Mode](/features/safe-mode#what-counts-as-a-read) describes; any other JavaScript is a write and needs `tools:write`. When the request carries a `progressToken`, the tool emits progress at 0.0 (resolving), 0.3 (executing) and 1.0 (done).

On SQL Server, send the whole script instead. A variable exists only in the batch that declares it, so a `DECLARE` and the `SELECT`s that read it belong in one call. The script is cut into batches at its `GO` lines, as the query editor cuts it, and `GO 5` runs a batch five times. The gates judge the script by its worst statement: one write anywhere needs `tools:write` and Safe Mode, and one `DROP` refuses the call. `DECLARE` counts as a write.
On SQL Server, send the whole script instead. A variable exists only in the batch that declares it, so a `DECLARE` and the `SELECT`s that read it belong in one call. The script is cut into batches at its `GO` lines, as the query editor cuts it, and `GO 5` runs a batch five times. The gates judge the script by its worst statement: one write anywhere needs `tools:write` and Safe Mode, and one `DROP` refuses the call. `DECLARE` and `EXEC` of a procedure count as writes, `sp_help` included. `EXEC` of a string and `sp_executesql` run server-side code and are refused.

The top-level fields describe the first result set, with `rows_affected` counting the whole script. When the script returns more than one result set, `result_sets[]` lists every one in order, each with `columns`, `rows`, `row_count` and `is_truncated`. `max_rows` caps each result set on its own. `status_message` says when the script left a transaction open.

Expand Down
2 changes: 1 addition & 1 deletion developers/tokens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,4 @@ Request limits are keyed by token id, and `subscriptions/listen` is exempt. Fail
| Create, change, or read other tokens | no |
| Create or edit connection records | no |

Statements that reach the filesystem or run server-side code are refused before they reach the driver, on every tool. The reachable surface is the [tool catalog](/developers/mcp-tools), the [resources](/developers/mcp-resources), and the [prompts](/developers/mcp-prompts). Nothing outside those lists is exposed.
Statements that reach the filesystem or run server-side code are refused before they reach the driver, on every tool, along with the rest of [what no external caller may send](/developers#what-read-only-allows). The reachable surface is the [tool catalog](/developers/mcp-tools), the [resources](/developers/mcp-resources), and the [prompts](/developers/mcp-prompts). Nothing outside those lists is exposed.
2 changes: 2 additions & 0 deletions features/ai-assistant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ In Edit and Agent modes each tool call appears as a card in the reply. Read-only

Safe Mode **Silent** auto-approves write tools and **Read-Only** auto-denies them. Destructive operations are the exception: `confirm_destructive_operation` always needs a click, Silent does not auto-approve it, **Always for this connection** is refused for it, and the model must pass the verbatim phrase `I understand this is irreversible`.

`execute_query` refuses what [external clients cannot send at any level](/developers#what-read-only-allows), several statements in one call among them. Text stored in the database can steer the model, so on a connection where an unwanted write matters, connect with a login that can only read: the database holds that limit whatever the model sends.

Every provider can call tools. Claude Agent reaches them through the MCP server, so that has to be on, and local models depend on the model.

A reply pauses after 25 tool calls, keeps everything it has done, and offers **Continue** for a fresh budget or **Adjust Limit** to change the number, 5 to 200, under **Agent** in **Settings > AI**. Every call is another request with the schema attached, so a higher limit costs tokens.
Expand Down
4 changes: 2 additions & 2 deletions features/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ Every request needs a token, including one from your own Mac. The bundled bridge

- Clients work through your saved connections. Passwords never leave the Keychain and are not readable over MCP.
- AI access is set per connection, including blocking a connection from external clients entirely. A blocked connection is not even listed.
- Tokens carry scopes and can be limited to named connections. An unauthenticated local caller is read-only and can never be more.
- Tokens carry scopes and can be limited to named connections. A request without one gets `401`, from your own Mac too. See [No token, no access](/developers/tokens#no-token-no-access).
- Writes go through the connection's [Safe Mode](/features/safe-mode#external-clients). `DROP` and `TRUNCATE` need a full-access token and the user's approval every time.
- Statements that read or write files, or run server-side code, are refused before they reach the driver.
- Statements that read or write files, or run server-side code, are refused before they reach the driver. [What Read Only allows](/developers#what-read-only-allows) lists the rest.
- The reachable surface is the [tool catalog](/developers/mcp-tools), the [resources](/developers/mcp-resources) and the [prompts](/developers/mcp-prompts). Nothing else.
- Reaching the server from another machine means forwarding the port over SSH yourself, and owning what that exposes.

Expand Down
4 changes: 3 additions & 1 deletion features/safe-mode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ It does not sit in front of reading metadata. Loading the sidebar, opening a tab

A statement runs as a read only when TablePro can tell for certain that it reads. Anything it cannot tell counts as a write: confirmed at the Alert and Safe Mode levels, blocked at Read-Only.

`EXPLAIN` and `DESCRIBE` are reads until an option runs the statement. With `ANALYZE` they count as the statement they explain, so `EXPLAIN ANALYZE` of a `SELECT` is a read and of a `DELETE` is a write. An option TablePro does not recognize, or one in quotes, counts as running the statement, and an `EXPLAIN` whose statement cannot be found counts as a write. `ANALYZE` of a table stores statistics and is a write.

On MongoDB, a read is one call on a collection or database with literal arguments, such as `db.orders.find({status: "open"}).sort({at: -1}).limit(50)`, an `aggregate` pipeline without `$out` or `$merge`, `countDocuments`, `distinct`, `explain`, `show collections` and `use`. Any other JavaScript counts as a write: a variable, a function, `forEach`, `runCommand`. Run several reads at once and each one is checked. Shell state lasts as long as the connection, so a function you redefine in one statement runs redefined in every later one. A read still cannot write through it: the MongoDB driver refuses any write while it runs a statement as a read, once the plugin in **Settings > Extensions** is updated for this release.

Redis reads come from a fixed list of read commands. etcd reads are `get`, `watch`, and the commands that only list or report, such as `member list` and `endpoint health`. SAP HANA is read like any other SQL database.
Expand Down Expand Up @@ -120,7 +122,7 @@ This is a floor on TablePro's own behavior, not on the database. It stops the ap

A write from an external client (Raycast, Cursor, Claude Desktop, any MCP client) clears three locks in order:

1. **External Clients** (per connection: **Blocked** / **Read Only** / **Read & Write**), next to **Safe Mode** under **Access** on the connection form's **Options** tab. A Read Only connection rejects a write before the request reaches the database.
1. **External Clients** (per connection: **Blocked** / **Read Only** / **Read & Write**), next to **Safe Mode** under **Access** on the connection form's **Options** tab. A Read Only connection rejects a write before the request reaches the database. The database login still decides what a statement that passes may do; see [What Read Only allows](/developers#what-read-only-allows).
2. **Token scope** (per integration: `readOnly` / `readWrite` / `fullAccess`), issued by the [pairing flow](/developers/pairing). The effective permission is `MIN(token.scope, connection.externalAccess)`.
3. **Safe Mode**, per query. Everything on this page then applies, Touch ID prompts and confirmation dialogs included.

Expand Down
Loading