diff --git a/connections/connection-form.mdx b/connections/connection-form.mdx index a5d0268..c2fa3f8 100644 --- a/connections/connection-form.mdx +++ b/connections/connection-form.mdx @@ -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. @@ -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 + + + ## 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. diff --git a/connections/ssh-tunneling.mdx b/connections/ssh-tunneling.mdx index 02b2a9e..970c20d 100644 --- a/connections/ssh-tunneling.mdx +++ b/connections/ssh-tunneling.mdx @@ -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`. diff --git a/databases/beancount.mdx b/databases/beancount.mdx index 0f35535..844cc48 100644 --- a/databases/beancount.mdx +++ b/databases/beancount.mdx @@ -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. ## 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). ```bash rledger @@ -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.` 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 diff --git a/databases/postgresql.mdx b/databases/postgresql.mdx index f917db1..ec8dd38 100644 --- a/databases/postgresql.mdx +++ b/databases/postgresql.mdx @@ -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 diff --git a/developers/connection-import.mdx b/developers/connection-import.mdx index 707be83..3e9e11e 100644 --- a/developers/connection-import.mdx +++ b/developers/connection-import.mdx @@ -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 @@ -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`. | @@ -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_` 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 diff --git a/developers/index.mdx b/developers/index.mdx index 017891e..85bba8d 100644 --- a/developers/index.mdx +++ b/developers/index.mdx @@ -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 @@ -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 diff --git a/developers/mcp-protocol.mdx b/developers/mcp-protocol.mdx index 6159931..f7c34ff 100644 --- a/developers/mcp-protocol.mdx +++ b/developers/mcp-protocol.mdx @@ -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 | @@ -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: @@ -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 | diff --git a/developers/mcp-tools.mdx b/developers/mcp-tools.mdx index 260185e..f86d44a 100644 --- a/developers/mcp-tools.mdx +++ b/developers/mcp-tools.mdx @@ -41,11 +41,11 @@ Past the scope, a call clears three more gates: 2. **Connection policy.** **External Clients** set to **Blocked** hides the connection: `list_connections` omits it and any tool that names it is refused. An AI policy of **Never** does the same, except that [`purpose: "display"`](#listing-for-display) still lists it. Otherwise the first call that touches a connection asks the user, as often as **Settings > MCP > Approval** says. That question is always a dialog on the user's Mac, never an elicitation: a client that could answer it could also grant itself the connection. It has no deadline, so the client owns the timeout, and a refusal stands for 60 seconds before the question can be put again. 3. **Safe Mode.** A write goes through the connection's Safe Mode, which may ask the user to confirm it or to authenticate. See [Approvals](/developers/mcp-protocol#approvals-and-input-required). -A call that has to open a connection with a pre-connect script also waits for the user to run the script, in a dialog on their Mac. **Cancel** returns `denied`. With no answer in 60 seconds the dialog closes and the call returns `timeout`. +A call that opens a connection may also need approval on the Mac for a pre-connect script, environment variables, or [local sign-in credentials](/connections/connection-form#sign-ins-from-this-mac). A refusal returns `denied`; an unanswered prompt returns `timeout`. `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. -**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). +**Read Only** refuses a statement classified as a write with `denied`. Reads also use the driver's read-only mode where supported. [What Read Only allows](/developers#what-read-only-allows) lists engine limits and the statements refused at every access level. Multiple statements return `invalid_argument`, except for SQL Server scripts sent to [`execute_query`](#execute_query) or [`confirm_destructive_operation`](#writing-data). ## Connections @@ -157,9 +157,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 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). +One statement, 100 KB at most. Destructive statements are refused here; use [`confirm_destructive_operation`](#writing-data). 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` 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. +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 any destructive statement 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. @@ -178,12 +178,12 @@ Formats are `csv`, `json` and `sql`; pass exactly one of `query` or `tables[]`. | Tool | Arguments | Returns | |------|-----------|---------| | `insert_rows` | `connection_id`, `table`, `columns`, `rows` (`database`, `schema`) | `table`, `rows_submitted`, `rows_affected` | -| `confirm_destructive_operation` | `connection_id`, `query` (`timeout_seconds`, `database`, `schema`) | A result set | +| `confirm_destructive_operation` | `connection_id`, `query` (`timeout_seconds`, `database`, `schema`) | A result set, plus `result_sets[]` for a SQL Server script | | `transaction_control` | `connection_id`, `action` (`database`, `schema`) | `status`, the action that completed, and `connection_id` | -`insert_rows` binds parameters, so values are never spliced into SQL text. Each row is an array with one value per column, and a call carries at most 1,000 rows. +`insert_rows` binds parameters. Its confirmation shows the values that will be written, and the approved values run in the named database and schema. Each row is an array with one value per column, and a call carries at most 1,000 rows. -`confirm_destructive_operation` runs one `DROP`, `TRUNCATE` or `ALTER … DROP`. Anything that is not destructive is rejected; use `execute_query` for those. The user approves it first, through your elicitation prompt if your client supports elicitation, otherwise through TablePro's own dialog on their Mac. +`confirm_destructive_operation` runs one destructive statement, or a SQL Server script containing a destructive statement. This includes `DROP`, `TRUNCATE`, `ALTER … DROP`, and `DELETE` without `WHERE`. Other statements are rejected; use `execute_query` for those. The user approves it first, through your elicitation prompt if your client supports elicitation, otherwise through TablePro's own dialog on their Mac. `transaction_control` takes `action` as `begin`, `commit` or `rollback`. The transaction stays open across calls until it is committed or rolled back, and it runs on the same session as the user's own tabs, so their editor runs and their grid saves land inside it while it is open. TablePro shows it above the connection's tabs and on its connections strip entry, with **Roll Back** and **Commit**, until it ends. Leave nothing open. diff --git a/developers/tokens.mdx b/developers/tokens.mdx index ebb356f..37b907c 100644 --- a/developers/tokens.mdx +++ b/developers/tokens.mdx @@ -3,7 +3,7 @@ title: Tokens description: Token model, scopes, connection allowlists, expiry, revocation, and the activity log --- -**Read Only** is where the **Generate Token** sheet starts, and it is the right level for a client that only reads. Move to **Read & Write** when an agent has to change data, and to **Full Access** only for `DROP` and `TRUNCATE`, which nothing below it can reach. The plaintext is shown once at creation and never again; what persists is a salted SHA-256 hash in the login keychain. +**Read Only** is where the **Generate Token** sheet starts, and it is the right level for a client that only reads. Move to **Read & Write** when an agent has to change data, and to **Full Access** for destructive statements through [`confirm_destructive_operation`](/developers/mcp-tools#writing-data). The plaintext is shown once at creation and never again; what persists is a salted SHA-256 hash in the login keychain. MCP tokens @@ -15,8 +15,8 @@ description: Token model, scopes, connection allowlists, expiry, revocation, and | Level | Scopes | What it can do | |-------|--------|----------------| | **Read Only** | `tools:read`, `resources:read` | Read schema and run `SELECT`. | -| **Read & Write** | plus `tools:write` | `INSERT`, `UPDATE`, `DELETE`, connect and disconnect, switch database or schema, open and focus tabs. | -| **Full Access** | plus `admin` | Also `confirm_destructive_operation`, the only tool that runs `DROP` or `TRUNCATE`. | +| **Read & Write** | plus `tools:write` | Non-destructive writes, connect and disconnect, switch database or schema, open and focus tabs. | +| **Full Access** | plus `admin` | Also `confirm_destructive_operation`, the tool for destructive statements. | `connections:display` sits outside the levels. Only [pairing](/developers/pairing#scopes) grants it, and only while **Allow apps to list connections hidden from AI** is on under **Settings > MCP > Connection Access**. The **Generate Token** sheet never does. It does one thing: lets [`list_connections`](/developers/mcp-tools#listing-for-display) take `purpose: "display"`. A token holding it reads **Lists connections hidden from AI** in the token list. @@ -67,7 +67,7 @@ Revoking cancels every request that token has in flight and clears the connectio ## The stdio bridge token -Every server start mints a token named `__stdio_bridge__` and writes it into the handshake file for the bundled bridge. It is **Read & Write**, so an agent going through the bridge can read and write but cannot call `confirm_destructive_operation`. +Every server start mints a token named `__stdio_bridge__` and writes it into the [handshake file](/developers/mcp-protocol#local-socket-and-handshake) for the bundled bridge. The bridge sends it over the private socket after checking the socket and host process. It is **Read & Write**, so an agent going through the bridge can read and write but cannot call `confirm_destructive_operation`. It expires after an hour and is rotated 15 minutes before that, deleted when the server stops, and any stale copy is removed the next time the store loads. It is hidden from the Settings token list, though its name can appear in the activity log. For a narrower scope, skip the bridge and connect over [HTTP with a token you mint](/integrations/mcp-clients#http-transport). @@ -124,9 +124,9 @@ Request limits are keyed by token id, and `subscriptions/listen` is exempt. Fail | Read SSH keys | no | | Read license data | no | | Read or change app settings | no | -| Read files outside TablePro's own support directory | no | +| Read arbitrary local files | no | | Change Safe Mode rules | no | | 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, 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. +`export_data` can write a new file in Downloads under its [path rules](/developers/mcp-tools#export_data). SQL 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. diff --git a/developers/versioning.mdx b/developers/versioning.mdx index 0525062..ce2efb2 100644 --- a/developers/versioning.mdx +++ b/developers/versioning.mdx @@ -110,7 +110,7 @@ These can change in any release, without notice: - Transport framing, internal message routing, and any HTTP path other than `/mcp` and `/v1/integrations/exchange`. -- The handshake file at `~/Library/Application Support/TablePro/mcp-handshake.json`. Use the bundled +- The [handshake file and private socket](/developers/mcp-protocol#local-socket-and-handshake). Use the bundled `tablepro-mcp` bridge, which finds the server on its own, or connect over HTTP to the port on the **Status** row in **Settings > MCP**, `23508` by default. - The connections file at `~/Library/Application Support/TablePro/connections.json`. List diff --git a/development/plugin-development.mdx b/development/plugin-development.mdx index 6258e15..17a1c57 100644 --- a/development/plugin-development.mdx +++ b/development/plugin-development.mdx @@ -13,7 +13,7 @@ A plugin is a macOS loadable bundle target with `WRAPPER_EXTENSION = tableplugin | Key | Type | Required | Purpose | |-----|------|----------|---------| -| `TableProPluginKitVersion` | integer | Yes | The PluginKit ABI the plugin was built against. Current value: 41 | +| `TableProPluginKitVersion` | integer | Yes | The PluginKit ABI the plugin was built against. Current value: 42 | | `TableProProvidesDatabaseTypeIds` | array of strings | Recommended | Database type IDs the plugin serves, which is what makes lazy loading possible | | `CFBundleShortVersionString` | string | Yes | Plugin version shown in **Settings > Extensions** | | `CFBundleVersion` | string | Yes | Build number. Of two copies of a plugin the higher build loads, and a registry update needs a higher build | diff --git a/features/ai-assistant.mdx b/features/ai-assistant.mdx index 913d6c4..194af07 100644 --- a/features/ai-assistant.mdx +++ b/features/ai-assistant.mdx @@ -84,8 +84,8 @@ The mode picker in the composer footer controls which tools the AI can call. It | Mode | Tools available | When to use | |------|----------------|-------------| | **Ask** | Read-only schema lookups: `list_connections`, `get_connection_status`, `list_databases`, `list_schemas`, `list_tables`, `describe_table`, `get_table_ddl`. | Questions, exploration, drafting queries you run yourself. | -| **Edit** | All Ask tools, plus `execute_query` for `SELECT`, `INSERT`, `UPDATE`, `DELETE`. Destructive DDL (`DROP`, `TRUNCATE`, `ALTER…DROP`) stays blocked. | Letting the AI run the queries it proposes. | -| **Agent** | All tools, plus `confirm_destructive_operation` for destructive DDL. Runs tools in a loop. | Multi-step migrations and schema changes. | +| **Edit** | All Ask tools, plus `execute_query` for `SELECT`, `INSERT`, `UPDATE`, `DELETE`. Destructive statements stay blocked. See [external query rules](/developers#what-read-only-allows). | Letting the AI run the queries it proposes. | +| **Agent** | All tools, plus `confirm_destructive_operation` for destructive statements. Runs tools in a loop. | Multi-step migrations and schema changes. | Mode and [Safe Mode](/features/safe-mode) are independent gates. Agent mode does not bypass Safe Mode. @@ -100,7 +100,7 @@ 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. +`execute_query` follows the [external query rules](/developers#what-read-only-allows), including their SQL Server script exception. 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. diff --git a/features/backup-restore.mdx b/features/backup-restore.mdx index f5ba46f..aec068a 100644 --- a/features/backup-restore.mdx +++ b/features/backup-restore.mdx @@ -16,7 +16,7 @@ Most engines here need a command line tool that does not ship inside TablePro. I MariaDB 11.0 renamed its clients, so `mariadb-dump` and `mariadb` are accepted in place of `mysqldump` and `mysql`. -The lookup takes the first match from `/usr/bin/which`, then `/opt/homebrew/bin`, `/usr/local/bin`, and Postgres.app's latest version. That path decides which client version does the work. +The lookup takes the first executable in an absolute `PATH` folder, then checks standard tool folders such as `/usr/local/bin` and `/opt/homebrew/bin`. It also checks `/usr/local/mysql/bin` and Postgres.app's latest version. Empty and relative `PATH` entries are skipped. That path decides which client version does the work. On PostgreSQL that match is checked against the server first, for both directions. `pg_dump` and `pg_restore` refuse a server newer than themselves, 15 and later refuse servers older than 9.2, and TablePro names the database with `-d`, which arrived in 9.3. When the first match cannot reach the server, the newest one that can is taken from the Homebrew `postgresql@…` and `libpq` installs, every Postgres.app version, and `/Library/PostgreSQL`. When none can, the run stops before it starts and names the version to install: PostgreSQL 9.1 needs `pg_dump` 9.3 to 14, so `brew install postgresql@14`. A binary that does not answer `--version` is used anyway. @@ -172,6 +172,8 @@ Your password never reaches the tool's argument list, which every process on the SqlPackage is the exception. It accepts a password only inside its connection string, which means the command line, where other processes on the Mac can read it while the tool runs. A dump and a restore both ask before starting. Windows or Entra authentication avoids it entirely: leave the username empty and the tool uses integrated security. +The tools run directly, with connection values passed as arguments. They inherit a limited environment plus the connection's password and SSL settings. Shell startup files are not loaded. PostgreSQL password-file lookup follows [Use ~/.pgpass](/databases/postgresql#password-file). + ## Failures A non-zero exit shows the last 64 KB of the tool's stderr in a scrollable monospaced view, and DuckDB's own error text in the same place. Most come down to one of these. diff --git a/features/connection-sharing.mdx b/features/connection-sharing.mdx index cdc372e..ab8d613 100644 --- a/features/connection-sharing.mdx +++ b/features/connection-sharing.mdx @@ -3,6 +3,8 @@ title: Connection Sharing description: Export connections and their saved queries to a file, share links, import from other apps, and resolve passwords from secret managers --- +import LocalCredentialConsent from "/snippets/local-credential-consent.mdx"; + Passwords stay in your Keychain, so every route here hands over a connection definition and leaves the other person to supply their own credential. An encrypted export is the one exception. | Route | Use it when | Passwords | Needs | @@ -18,7 +20,7 @@ Passwords stay in your Keychain, so every route here hands over a connection def Right-click a connection > **Share > Export to File…**, selecting several first to export them together, or **File > Export > Export Connections…** for all of them. -The file carries what the connection form holds, from host and port through SSH and SSL configuration, color, icon, tags, the full group path, Safe Mode level, startup commands, AI policy, the Local only flag, and any driver field the plugin does not mark secret. Passwords, key passphrases, TOTP secrets, and secret driver fields stay behind, and so do the settings [Import](#import) strips. +The file carries host and port, SSH and SSL configuration, color, icon, tags, the group path, Safe Mode level, startup commands, AI policy, the Local only flag, and known shareable driver fields. Sign-in selectors can travel; approval to use this Mac's credentials does not. Passwords and other secrets need an [encrypted export](#encrypted-export). [Import](#import) applies its own field rules when saving a connection. Export connections @@ -83,21 +85,26 @@ Group and folder paths match by name from the top down: an existing group or fol Import preview -Anything that decides where a credential comes from, or runs code on your Mac, is stripped on the way in, from a file and from a link alike: +A file or import link keeps known settings, server addresses and local file paths. It drops +unknown driver fields, secrets, and settings that run local code. Check the affected settings after import: -- AWS options including IAM authentication, the region, the profile and the RDS endpoint -- MongoDB's **AWS IAM** auth mechanism, which imports as **Default**, and the secrets a pasted MongoDB connection string carried -- **Application (Client) ID**, **Directory (Tenant) ID** and the signed-in account of a Microsoft Entra ID connection -- Snowflake's **CLI Connection Name** and Spanner's **REST Endpoint** -- Beancount's **Run Ledger Plugins** -- **Use ~/.pgpass** and **Prompt for password** -- The SSL client key passphrase, and the pre-tunnel host and port -- **Pre-Connect Script** and **Post-Disconnect Script** +- Cloud credential selectors, cached sign-ins and profiles, Windows or Kerberos authentication + selectors, and **Use ~/.pgpass**. Plain password, service-account and emulator choices remain + where the driver supports them. +- **Run Ledger Plugins**, **Prompt for password**, **Pre-Connect Script** and **Post-Disconnect Script**. +- Internal tunnel fields and secret driver fields. Encrypted files carry supported secrets in their + credentials section. A service-account key must contain the JSON key, not a path to a key on this Mac. -Set them again on the imported connection. A credential profile new to this Mac arrives set to ask for its password; see [Credential Profiles](/connections/credential-profiles#sharing-and-sync). +A new credential profile arrives set to ask for its password. Replacing a connection keeps its local +credential settings; a changed destination still needs approval. See +[Credential Profiles](/connections/credential-profiles#sharing-and-sync). Startup SQL and tunnel commands run on every connect, so a file brings them only if you agree: importing lists each one and asks, and **Import Without Commands** is the default. A link's sheet shows its startup SQL in full before you add it. +## Sign-ins from this Mac + + + ## Share via link **Share > Copy TablePro Link** produces a `tablepro://import?…` URL with the name, host, port, type, username, database, and any SSH or SSL settings. The recipient opens it, reviews the prefilled form, adds a password, and saves. @@ -142,7 +149,7 @@ Groups and saved queries come across the way they do on the Mac. The review list Needs a [Pro license](/features/licensing). -Press `Cmd+,`, then **Settings > General > Linked Folders > Add Folder…** and point it at a directory of `.tablepro` files: a Git repo, a Dropbox folder, a network drive. Those connections appear read-only in the sidebar, and each person enters their own password. +Press `Cmd+,`, then **Settings > General > Linked Folders > Add Folder…** and point it at a directory of `.tablepro` files: a Git repo, a Dropbox folder, a network drive. Those connections appear read-only in the sidebar. Each person supplies their credentials. Linked definitions keep sign-in selectors, subject to [approval on this Mac](#sign-ins-from-this-mac). Each folder's row says what the last scan found: how many connections, or why it found none. @@ -169,7 +176,9 @@ Each folder's row says what the last scan found: how many connections, or why it Needs a [Pro license](/features/licensing). -Write `$VAR` or `${VAR}` in a `.tablepro` file and it resolves from TablePro's process environment at connect time. An app launched from the Dock inherits no shell exports, so set the variable with `launchctl setenv NAME value` or launch TablePro from a terminal. +Write `$VAR` or `${VAR}` in a `.tablepro` file to read TablePro's process environment at connect time. **Send Environment Variables?** shows the variable names and destination before reading their values. Click **Send Values** to approve. Approval stays on this Mac; a new destination or variable asks again. Background reconnects stop until you approve through a connection you open yourself. + +An app launched from the Dock inherits no shell exports. Set the variable with `launchctl setenv NAME value` or launch TablePro from a terminal. ```json { "host": "${DB_HOST}", "username": "$DB_USER" } diff --git a/features/icloud-sync.mdx b/features/icloud-sync.mdx index 62d33f1..898e225 100644 --- a/features/icloud-sync.mdx +++ b/features/icloud-sync.mdx @@ -3,6 +3,8 @@ title: iCloud Sync description: Sync connections, groups, tags, and saved queries to your Macs, iPhone, and iPad, and settings, table favorites, and SSH profiles between Macs --- +import LocalCredentialConsent from "/snippets/local-credential-consent.mdx"; + Sync needs a Pro or Team [license](/features/licensing) and starts off. Press `Cmd+,`, open **Settings > Sync**, turn on **Sync this Mac with iCloud**, pick the categories, and click **Sync Now**. The iPhone and iPad app shares the same CloudKit data but carries connections, groups, tags, and saved queries only. @@ -35,6 +37,12 @@ A sync runs at launch, when you switch back to the app, 2 seconds after you chan A custom theme travels as its file. Moving one to the Trash on one Mac moves it to the Trash on the others. A theme file copied into the Themes folder in Finder goes up the next time you edit it in TablePro or turn sync on. A Mac on an earlier version of TablePro switches its **Appearance** to **System** when a theme choice arrives from an updated Mac. +## Sign-ins on another Mac + + + +Connections that use environment variables also need [approval on each Mac](/features/connection-sharing#environment-variables). + ## Keeping a connection off iCloud A localhost or throwaway database is rarely worth a round trip. Mark it **Local only** under **iCloud Sync** on the connection form's **Options** tab, or right-click it and choose **Exclude from iCloud Sync**. It shows a struck-through cloud icon in the connection list, and the flag survives duplicating and exporting. diff --git a/features/mcp.mdx b/features/mcp.mdx index e4f8e83..b1b2ae9 100644 --- a/features/mcp.mdx +++ b/features/mcp.mdx @@ -3,7 +3,7 @@ title: MCP Server description: Built-in Model Context Protocol server that lets AI clients query your databases through TablePro --- -The [MCP](https://modelcontextprotocol.io) server binds `127.0.0.1` and nothing else: no remote mode, no TLS certificate, no setting that opens it to your network. Claude Desktop, Claude Code, Cursor, and Zed reach your databases through the connections you already saved, and never see a password. +Use the bundled `tablepro-mcp` bridge to connect an AI client to your saved connections. The bridge and the `tablepro` command use a private Unix socket. Direct HTTP clients use `127.0.0.1`. Neither listener opens to your network. This page covers the **Settings > MCP** pane. The protocol itself, the tool catalog, the prompts and the token model live under [Developers](/developers). @@ -20,7 +20,7 @@ With the toggle off, nothing starts the server. A client that launches the bundl While it is only answering a client, TablePro stays out of the Dock and the app switcher. It appears in both, with its menu bar, once it has a window on screen, including one a client asked it to open. A TablePro the bridge launched while the toggle is off quits again. -The default port is **23508**. If it is taken, TablePro asks the kernel for a free one instead, and the **Status** row says the port you set was in use. Either way the real port goes to `~/Library/Application Support/TablePro/mcp-handshake.json`, which the bridge reads, so a client config never carries a port. +The default HTTP port is **23508**. If it is taken, the server binds a free port, shown in **Status**. The bridge uses the socket recorded in the [handshake file](/developers/mcp-protocol#local-socket-and-handshake), so its config needs no port. ## Server configuration @@ -82,7 +82,7 @@ Tokens that hold this grant read **Lists connections hidden from AI** in the tok ## Authentication -Every request needs a token, including one from your own Mac. The bundled bridge mints its own, so a client set up through **Connect a Client…** needs nothing from you; an HTTP client or a launcher needs a token you generate or pair. Create, scope, allowlist, expire and revoke tokens in this section, described in [Tokens](/developers/tokens). +Every request needs a token, including one from your own Mac. The server supplies a token to the bundled bridge, so a client set up through **Connect a Client…** needs nothing from you; an HTTP client or a launcher needs a token you generate or pair. Create, scope, allowlist, expire and revoke tokens in this section, described in [Tokens](/developers/tokens). ## Activity and connected clients @@ -96,8 +96,8 @@ 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. 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. [What Read Only allows](/developers#what-read-only-allows) lists the rest. +- Writes go through the connection's [Safe Mode](/features/safe-mode#external-clients). Destructive statements need a full-access token and approval every time. See [the confirmation tool](/developers/mcp-tools#writing-data). +- Reads use the driver's read-only mode where supported. Statements that access files or run server-side code are refused. [What Read Only allows](/developers#what-read-only-allows) covers engine limits and other refusals. - 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. diff --git a/features/safe-mode.mdx b/features/safe-mode.mdx index 0ef94e6..f16f6ee 100644 --- a/features/safe-mode.mdx +++ b/features/safe-mode.mdx @@ -122,10 +122,10 @@ 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. The database login still decides what a statement that passes may do; see [What Read Only allows](/developers#what-read-only-allows). +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. Reads also use the driver's read-only mode where supported; 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. -`DROP` and `TRUNCATE` always go through the `confirm_destructive_operation` tool, which needs a `fullAccess` token and the user's approval every time. No token scope skips it. See the [integration security model](/developers#security-model). +Destructive statements go through [`confirm_destructive_operation`](/developers/mcp-tools#writing-data), which needs a `fullAccess` token and the user's approval every time. No token scope skips it. See the [integration security model](/developers#security-model). A client that answers its own approval prompt has only answered its own. Only a `fullAccess` token stands in for TablePro's dialog. On every other token the dialog still appears on the Mac, so a `readWrite` client asks twice. diff --git a/features/team.mdx b/features/team.mdx index eaf7cee..6e40e2c 100644 --- a/features/team.mdx +++ b/features/team.mdx @@ -56,4 +56,6 @@ Right-click a connection and choose **Share > Publish to Team Library…**, or p Teammates get the connections in their list the way a Linked Folder delivers them, and shared queries in their Favorites sidebar labeled with whoever published each one. Their app pulls the library every 7 days, and **Settings > License > Team** shows when it last did, with **Refresh** to pull now. A pull that fails keeps what was there and says **Couldn't refresh the team library** with the reason. A pulled connection arrives without its startup SQL, because it lands with no confirmation step and nothing that would run by itself travels with it. +Shared sign-in settings need [approval on each Mac](/connections/connection-form#sign-ins-from-this-mac) before using local cloud credentials or Kerberos tickets. + You review and remove what you published from your account at [tablepro.app/account](https://tablepro.app/account). diff --git a/integrations/mcp-clients.mdx b/integrations/mcp-clients.mdx index da13940..1d59eaf 100644 --- a/integrations/mcp-clients.mdx +++ b/integrations/mcp-clients.mdx @@ -3,7 +3,7 @@ title: MCP Clients description: Connect Claude Desktop, Claude Code, Cursor, VS Code, Cline, Continue, Zed, Windsurf, Antigravity, Goose, and custom clients to TablePro --- -Paste one absolute path into your client's MCP config and stop there. `tablepro-mcp` ships inside the app bundle; it starts TablePro when nothing is running, reads the port, and carries the token, so none of those end up in the file. +Paste one absolute path into your client's MCP config and stop there. `tablepro-mcp` ships inside the app bundle; it starts TablePro when nothing is running, finds the private socket, and carries the token, so none of those end up in the file. ```json { @@ -108,7 +108,7 @@ TablePro does not have to be open first, but **Enable MCP Server** has to be on. The token is not yours to manage either. Each server start mints one named `__stdio_bridge__`, writes it into the handshake file, and rotates it 15 minutes before its one-hour expiry. It is **Read & Write**, so an agent on the bridge can read and write but cannot call `confirm_destructive_operation`. -The port is absent for the same reason: TablePro writes the port it actually bound into the handshake file, so a conflict resolves itself and your config never changes. +The bridge reads the socket path from the [handshake file](/developers/mcp-protocol#local-socket-and-handshake). It checks the socket before sending a token, then checks the server instance before forwarding client requests. The HTTP port is only for clients using the HTTP transport. Two installs share that one handshake file, so the bridge reaches whichever TablePro is running rather than the bundle whose path you configured. Quit the one you do not want. diff --git a/security/privacy.mdx b/security/privacy.mdx index daf1327..f01ad26 100644 --- a/security/privacy.mdx +++ b/security/privacy.mdx @@ -12,7 +12,7 @@ Three requests leave a stock install without you asking: one usage heartbeat a d | [Plugin catalog](#the-plugin-catalog) | `updates.tablepro.app` | Yes | None | | [AI provider](#ai-providers) | whichever provider you add | No provider is configured on a fresh install | Delete the provider, or turn off **Enable AI Features** | | [iCloud sync](#icloud-sync) | your own iCloud account | No | **Settings > Sync** | -| [MCP clients](#the-mcp-server) | nowhere: it listens on `127.0.0.1` | No | **Settings > MCP** | +| [MCP clients](#the-mcp-server) | Local HTTP and a private Unix socket | No | **Settings > MCP** | | [License and team library](#license-checks-and-the-team-library) | `api.tablepro.app` | Only after you activate a license | Do not activate one | | [Your databases](#your-database-connections) | the hosts you type into the connection form | Only what you connect to | Do not connect | | Crash reports | Nowhere. The app has no crash reporter. | - | - | @@ -95,7 +95,7 @@ With password sync off, secrets are written `kSecAttrAccessibleAfterFirstUnlockT ## The MCP server -The server listens on `127.0.0.1` and nothing else. Its listener sets `requiredLocalEndpoint` to the IPv4 loopback address, so there is no remote mode and no setting that opens it to your network. Default port 23508. +Direct HTTP clients connect through IPv4 loopback. The bundled bridge and CLI use a private Unix socket that accepts only the same macOS user. Both listeners require authentication for data access. Neither has a remote mode. See [MCP transport and handshake](/developers/mcp-protocol#local-socket-and-handshake) for paths and permissions. It is off by default, and nothing starts it while **Enable MCP Server** is off: not the bundled bridge, not the pairing flow, not a `tablepro://integrations/start-mcp` link. What a paired client can reach is real data, so four gates stack: @@ -106,6 +106,8 @@ It is off by default, and nothing starts it while **Enable MCP Server** is off: Loopback-only describes where the server binds, not everything that can reach it. The server returns CORS headers for two remote origins, `claude.ai` and `app.cursor.com`, so a page you have open there can call your local server from your own browser. The four gates above are what stands between such a call and your data, and the first one means a page without a token gets nothing. +Reads use the driver's read-only mode where supported. Writes run only as approved, in the named database and schema. Statements that access files or run server-side code are refused. [External query rules](/developers#what-read-only-allows) describe the limits. + Every call is written to the local activity log with the token behind it, statements as SHA-256 digests. See [Tokens](/developers/tokens) for the whole model. ## Update checks @@ -172,7 +174,8 @@ Every path here is under `~/Library/Application Support/TablePro/` unless it say | `ExecutionAudit.json` | The execution log described in this section. | | `TableLoadHistory.jsonl` | The table load history described in this section, 7 days. Mode `0600`. | | `mcp-audit.db` | The MCP activity log, 90 days, statements as digests. Mode `0600` inside a `0700` directory. | -| `mcp-handshake.json` | The bound port and the bridge token, mode `0600`. | +| `mcp-handshake.json` | The socket path, bridge token, host process, server instance, protocol version and expiry. Mode `0600`. | +| `mcp.sock` | The bridge and CLI socket. See [transport paths](/developers/mcp-protocol#local-socket-and-handshake) for its fallback location. | | `TabState/`, `RecentlyClosedTabs/` | Open and recently closed tabs, including their query text. | | `FilterState/`, `ColumnLayout/` | Per-table filters and column widths. | | `LastSession.json`, `LastOpenConnections.json` | What to reopen for **Reopen Last Session**, and which of those connections dial on their own. | @@ -184,6 +187,8 @@ Every path here is under `~/Library/Application Support/TablePro/` unless it say ## Passwords and secrets +Imported, synced and shared connections need [local sign-in approval](/connections/connection-form#sign-ins-from-this-mac) before using this Mac's cloud credentials or Kerberos tickets. Environment variable references have a [separate prompt](/features/connection-sharing#environment-variables). These approvals stay on the Mac that granted them. + Secrets live in the macOS keychain, under the `com.TablePro` service and in the data protection keychain: connection passwords, SSH passwords, key passphrases, SSL client key passphrases, TOTP secrets, plugin secure fields, AI provider keys, and the license key. Google sign-in refresh tokens for BigQuery and Spanner are kept in the same keychain under `com.TablePro.GoogleOAuth`, one per OAuth client ID. MCP tokens are the exception, stored as a salted SHA-256 hash; the plaintext is shown once at creation and never written down. A connection can name a [password source](/features/connection-sharing#password-sources) instead of using the keychain: a file, an environment variable, a shell command, 1Password, HashiCorp Vault, AWS Secrets Manager, or a Vault database role. Those resolve at connect time, on your machine, and are never synced, since a path or a command belongs to one Mac. A Vault database role sends your Vault token to the address it names, and the database password it gets back stays in memory. diff --git a/snippets/local-credential-consent.mdx b/snippets/local-credential-consent.mdx new file mode 100644 index 0000000..88643e5 --- /dev/null +++ b/snippets/local-credential-consent.mdx @@ -0,0 +1,8 @@ +Before an imported or synced connection uses this Mac's cloud credentials or Kerberos tickets, +review **Sign In with Credentials from This Mac?**. The dialog names the destination and sign-in +method. Click **Sign In** to approve that use. Links, Linked Folders and Team Library connections +follow the same rule. + +Approval stays on this Mac and covers the connection, destination and sign-in settings shown. +A new destination or added credential use asks again. A background reconnect cannot grant approval: +open the connection yourself to review it.