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
8 changes: 7 additions & 1 deletion connections/connection-form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Connection form
description: The four sections of the connection editor and the fields each driver adds
---

import LocalCredentialConsent from "/snippets/local-credential-consent.mdx";

The editor's sidebar lists the same four sections whatever the driver. Only Network is ever absent, and only for a driver that connects directly and cannot negotiate TLS.

<Frame caption="A PostgreSQL connection on the General tab">
Expand Down Expand Up @@ -33,10 +35,14 @@ The bar along the bottom carries **Test Connection**, **Cancel** and **Save**, p
| **Username** | Not pre-filled. Empty means the driver's own default |
| **Password** | Stored in the macOS Keychain. **Show password** reveals a saved one after Touch ID or your Mac's password. A connection whose password comes from a [password source](/features/connection-sharing#password-sources) shows the source here, with **Test Source** |
| **Prompt for password** | Stores nothing, asks on every connect. Reads **Prompt for API token** on API-only drivers |
| **Use ~/.pgpass** | PostgreSQL, Redshift, CockroachDB, and KingbaseES. Reads `~/.pgpass`, and reports underneath whether the file exists, has `chmod 0600`, and holds a matching line |
| **Use ~/.pgpass** | PostgreSQL, Redshift, CockroachDB, and KingbaseES. Off by default. See [Password file](/databases/postgresql#password-file) for lookup rules and file permissions |

SQLite, DuckDB, libSQL, and Beancount replace the host fields with a file path. **Browse…** picks a file that exists. **New…** names one that does not, and the file is made when you connect; Beancount opens only an existing ledger, so it has no **New…**.

## Sign-ins from this Mac

<LocalCredentialConsent />

## Network

**Connect via** is a single choice, and the fields under it belong to whichever transport is selected. A connection uses one transport or none.
Expand Down
2 changes: 2 additions & 0 deletions connections/ssh-tunneling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Pick an alias from the **Config Host** picker and `HostName`, `User`, `Port`, `I

`Match` blocks take the criteria ssh does, `localnetwork` and `tagged` included. A tunnel counts as `sessiontype none` with an empty `command`, and a criterion TablePro does not know keeps its block from applying.

`Match exec` runs your command through `/bin/sh` in the app's inherited environment. Exit status zero matches. The command gets five seconds, then two seconds to stop before it is killed. Edit `~/.ssh/config` to control these commands. Host and user values are checked before substitution, including names resolved through DNS.

`CanonicalizeHostname` completes a short name with each `CanonicalDomains` suffix in turn, skipping a name with more dots than `CanonicalizeMaxDots`, and applies to a host reached through a jump only when set to `always`. The completed name is the one dialed and the one its host key is stored under. A DNS alias (CNAME) replaces it only where a `CanonicalizePermittedCNAMEs` rule covers both names. With `CanonicalizeFallbackLocal no`, a name no suffix resolves stops the connection instead of being dialed as typed.

`IdentityFile` picks which agent key is offered, not only which key file is read, and a `.pub` is enough. That is how a 1Password or Secretive key gets pinned to a host. `IdentitiesOnly yes` then drops every agent key the config did not name, which is what keeps a thirty-key agent inside a server's `MaxAuthTries`.
Expand Down
21 changes: 17 additions & 4 deletions databases/beancount.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ description: Backend requirements, the table layout, source locations, includes,

import RegistryPlugin from "/snippets/registry-plugin.mdx";

Install `rledger` or Python Beancount before you connect. The plugin shells out to one of those two and projects the answer into the read-only SQL tables below.
Install `rledger` or Python Beancount before you connect. The plugin runs one of those two and projects the answer into read-only SQL tables.

<RegistryPlugin name="Beancount" plugin="Beancount Driver" />

## Backend requirements

The compatibility fixture exercises `rledger` 0.22.0 and 0.24.0, and Python Beancount 3.2.3. Other versions may work, but are not part of that tested contract. Install `rledger` if you are choosing: it wins when both are present, it is the only one that fills `diagnostics`, and BQL runs through it and nothing else.
The compatibility fixture exercises `rledger` 0.22.0 and 0.24.0, and Python Beancount 3.2.3. Other versions may work, but are not part of that tested contract. Install `rledger` if you are choosing: it wins when both are present, it supplies validation diagnostics, and BQL runs through it and nothing else. To control Python ledger plugins, choose the Python backend and read [Ledger plugins](#ledger-plugins).

<CodeGroup>
```bash rledger
Expand All @@ -38,9 +38,22 @@ After connecting, TablePro's database version display names the backend it selec

## Ledger plugins

A `plugin "..."` directive names Python code that Beancount imports and runs while it loads the ledger. The plugins that ship with Beancount itself, everything under `beancount.`, always run. Anything else is third-party code, so the Python backend skips it unless you turn on **Run Ledger Plugins** on the connection's Options tab.
With the Python backend, **Run Ledger Plugins** on the connection's **Options** tab starts off.
Only modules named `beancount.plugins.<name>` with no configuration argument run in that state.
Other Python modules and configured plugins are skipped. A ledger's `insert_pythonpath` option
does not add its folders to Python's import path while plugins run.

The setting belongs to one connection, so trusting your own ledger does not trust the next ledger someone sends you. Skipped plugin names appear in `diagnostics` with phase `security`. If the ledger needs one of them to load at all, the connection fails and the error names the plugin it skipped.
Turn on **Run Ledger Plugins** to run those directives for this connection. Their code runs on your
Mac. Skipped names appear in `diagnostics` with phase `security`. If a skipped plugin is required
to load the ledger, the connection fails and names it.

`rledger` runs the ledger plugins it supports. **Run Ledger Plugins** does not control that backend,
and BQL always uses it. Choose `TABLEPRO_BEANCOUNT_BACKEND=python` and keep the toggle off to read
without third-party or configured Python ledger plugins. Use SQL against the projected tables.

Both backends start in `/` with a limited environment. `PYTHONPATH` and `PYTHONHOME` are not passed
through, and the ledger folder is not their working directory. The selected executable and its
installed packages still run.

## Quick setup

Expand Down
4 changes: 3 additions & 1 deletion databases/postgresql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ Connect by hostname, never an IP address: the ticket is for the service principa

### Password file

Turn on **Use ~/.pgpass** to read the password from `~/.pgpass` instead of typing one. Entries are `hostname:port:database:username:password`, `*` matches any field, first match wins. An empty **Username** matches your macOS login name, and a socket connection matches as described in [Unix socket](#unix-socket). The form reports what it found, including `~/.pgpass has incorrect permissions (needs chmod 0600)`, the one that silently blocks a connect.
**Use ~/.pgpass** starts off. Turn it on to read the password from `~/.pgpass` instead of typing one. With it off, an empty password does not trigger a file lookup, including during backup and restore. Entries are `hostname:port:database:username:password`, `*` matches any field, first match wins. An empty **Username** matches your macOS login name, and a socket connection matches as described in [Unix socket](#unix-socket). The form reports what it found, including `~/.pgpass has incorrect permissions (needs chmod 0600)`, the one that silently blocks a connect.

Imported or synced password-file settings need [approval on this Mac](/connections/connection-form#sign-ins-from-this-mac) before use.

## Databases and schemas

Expand Down
38 changes: 15 additions & 23 deletions developers/connection-import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ title: Connection Import
description: Every query parameter a tablepro://import link accepts, and which ones the app refuses
---

Passwords are never accepted in an import link, and neither is anything else that decides where a
credential comes from. What is left is the rest of the connection form, one query parameter per
field, and a review sheet the user reads before anything is saved.
An import link opens a review sheet before saving a connection. It carries connection settings,
not passwords or approval to use local credentials.

```text
tablepro://import?name=Staging&host=db.example.com&port=5432&type=postgresql&username=admin
Expand All @@ -24,7 +23,7 @@ values that contain spaces: `sslMode=Verify%20CA`.
| Parameter | Description |
|-----------|-------------|
| `port` | Server port. Defaults to the type's standard port. |
| `socket` | MySQL and MariaDB. Socket file path, in place of `host` and `port`. See [Socket](#socket). |
| `socket` | MySQL and MariaDB take a socket file path. PostgreSQL takes its folder and keeps `port`. See [Socket](#socket). |
| `username` | Database username. |
| `database` | Default database name. |
| `color` | Sidebar color: `Red`, `Orange`, `Yellow`, `Green`, `Blue`, `Purple`, `Pink`, `Gray` or `None`. Falls back to `None`. |
Expand Down Expand Up @@ -76,25 +75,18 @@ Set `ssh=1`, or none of these are read.

## Driver fields

An `af_` prefix passes a driver-specific field straight to the plugin: `af_replicaSet=myrs` sets
`replicaSet` on a [MongoDB](/databases/mongodb) connection. Field names come from the plugin, so the
engine's own page is where they are listed.

These are dropped on import, because each decides where a credential comes from or runs code on
the Mac. The part after `af_` matches without case.

- Any name starting with `af_aws` or `af_entra`.
- `af_beancountRunLedgerPlugins`, `af_postDisconnectScript`, `af_preConnectScript`,
`af_preTunnelHost`, `af_preTunnelPort`, `af_promptForPassword`, `af_snowflakeConnectionName`,
`af_spEndpoint`, `af_sslClientKeyPassphrase` and `af_usePgpass`.
- `af_mongoAuthMechanism=MONGODB-AWS`.
- MongoDB secrets: `af_mongoAWSSessionToken`, `af_mongoParam_tlsCertificateKeyFilePassword`,
`af_mongoParam_sslClientCertificateKeyPassword`, `af_mongoParam_proxyPassword`, and
`af_mongoParam_authMechanismProperties` when it carries an `AWS_SESSION_TOKEN`.

The same rule covers `.tablepro` files, described in
[Connection Sharing](/features/connection-sharing#import). Add any of these by hand in the connection
editor afterwards.
Use `af_<fieldId>` for a known driver field. For example, `af_mongoReplicaSet=myrs` sets
`mongoReplicaSet` on a MongoDB connection. Match the field ID's case exactly.

Known settings, destination fields and local file paths are accepted. Unknown fields are dropped.
A sign-in selector that uses local credentials is also dropped: examples include
`af_mssqlAuthMethod=windows`, `af_awsAuth=profile`, and `af_usePgpass=true`.
A plain selector such as `af_mssqlAuthMethod=sql` is kept.

Secrets, internal fields and local script settings are refused under the same
[field rules as a file import](/features/connection-sharing#import). Configure those settings in
the connection editor. Imported connections do not carry
[sign-in approval](/connections/connection-form#sign-ins-from-this-mac).

## What the user sees

Expand Down
47 changes: 27 additions & 20 deletions developers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ the directory [refuses](/developers/listing-policy#rules) an integration that re

## Security model

The MCP server binds `127.0.0.1`, and on stock settings every request carries a token. A call is
The MCP server accepts local HTTP and Unix socket clients. Every request carries a token. A call is
allowed only where the token's scope, the token's connection allowlist, and the connection's own
**External Clients** level all permit it; the effective permission is the lowest of them.
[Tokens](/developers/tokens) has the full model. On top of that, an AI policy of **Never** refuses
Expand All @@ -99,30 +99,37 @@ 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."
A statement from MCP, AppleScript or Shortcuts is
[classified as a read or write](/features/safe-mode#what-counts-as-a-read) before it reaches the driver.
**Read Only** refuses anything classified as a write. A token or connection with write access does
not turn an approved read into a write: the driver receives it as read-only.

These fail at every level, **Read & Write** included:
The driver uses the engine's read-only mode where supported. Some engines still allow session or
temporary-object changes; others depend on command checks and the database login's privileges.
The note under **External Clients** in the connection form describes that engine's limit. Use a
login that can only read when that limit matters. Update the driver in **Settings > Extensions**
to get its current enforcement.

- 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.
A read that needs its own read-only transaction fails while the session has a transaction open.
Commit or roll it back in the app, then retry.

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
These requests fail at every external access level, including **Read & Write**:

- More than one statement, or an unfinished block. Send one statement per call. SQL Server scripts
are the exception: they run in batches at `GO` lines.
- File access or server-side code, including `COPY`, `ATTACH`, `LOAD`, `VACUUM INTO`, DuckDB's
`read_csv`, ClickHouse's `file`, `url` and `s3`, and T-SQL string execution.
- Non-built-in function calls on a connection that loads SQLite extensions. Run those in the app.
- Private requests built for the app's grid. Send the database statement instead.

The AI assistant follows the same statement rules. Its access comes from the connection's AI
policy and Safe Mode, rather than **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**.
A write goes through Safe Mode with its connection, database, schema and parameter values fixed.
Only the approved statements run in that scope. Destructive statements always need approval,
including `DROP`, `TRUNCATE`, `ALTER … DROP`, and `DELETE` without `WHERE`.
MCP and the assistant route them through `confirm_destructive_operation`.

## Publish it

Expand Down
23 changes: 21 additions & 2 deletions developers/mcp-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ The `Host` header must name a loopback host (`localhost`, `127.0.0.1`, `::1`) an

The port is `23508` by default. Make it a setting: when that port is busy the server binds another one, and the **Status** row in **Settings > MCP** shows the port in use. The handshake file is [not under the contract](/developers/versioning#what-is-not-under-the-contract).

## Local socket and handshake

The bridge and the `tablepro` CLI send HTTP over a Unix socket, normally
`~/Library/Application Support/TablePro/mcp.sock`. The socket has mode `0600` in a `0700` folder.
Only processes running as the same macOS user may connect. A bearer token is still required.

If the support path is too long for a Unix socket, or its folder cannot be made private, the socket
uses the `TablePro-MCP` folder in the user's macOS temporary directory. The handshake records the
actual path. Use the bundled bridge instead of hard-coding a path.

`~/Library/Application Support/TablePro/mcp-handshake.json` uses format version `3`. It holds
`socketPath`, `token`, `pid`, `instanceId`, `protocolVersion`, and `expiresAt`. It has no `port` field.
This file is [outside the stable contract](/developers/versioning#what-is-not-under-the-contract).

Before sending credentials, the bridge checks the file's owner and permissions, expiry, host process,
and socket ownership and location. It then calls `server/discover` over that socket and checks
`app.tablepro/instanceId` against the handshake. A mismatch refuses the connection. A restart or
socket failure requires a fresh trusted handshake; the bridge does not fall back to TCP.

## Protocol versions

| Version | Era | Notes |
Expand Down Expand Up @@ -255,7 +274,7 @@ Without `elicitation`, TablePro shows its own confirmation dialog on the user's
}
```

The `description` carries the statement verbatim, line breaks and all, up to 2,000 characters. Render it as a block rather than a one-line label: it is the only thing the person sees before they approve, and a `WHERE` clause that falls off the end changes what they agreed to.
The `description` carries the statements as they will run, with parameter values filled in and line breaks kept. The prompt names the connection, database and schema in use. Render the text as a block. If it exceeds 2,000 UTF-16 code units, the full confirmation opens on the Mac instead of sending a shortened form.

Present the form, then repeat the **same** call with the answer and the state echoed back:

Expand Down Expand Up @@ -327,7 +346,7 @@ Protocol errors are reserved for the envelope: a malformed request, a missing sc
|-------|-------|
| Request body | 10 MB |
| Request headers | 16 KB |
| Concurrent connections | 64 |
| Concurrent connections | 64 per listener: TCP and Unix socket |
| Idle connection timeout | 30 seconds |
| Handler deadline | 330 seconds |

Expand Down
Loading
Loading