diff --git a/customization/general-settings.mdx b/customization/general-settings.mdx index a4c7e263..728a2f98 100644 --- a/customization/general-settings.mdx +++ b/customization/general-settings.mdx @@ -54,7 +54,7 @@ Older servers have neither: MySQL before 5.7.8, MariaDB before 10.1.1. A stateme ## Command line tool -**Install** writes a `tablepro` command to `/usr/local/bin` that opens database URLs in the app. When it cannot write there, the section prints the command to run in Terminal yourself. See [Terminal](/integrations/terminal). +Shows whether `/usr/local/bin/tablepro` links to this copy of the app, with the Terminal line that links or removes it. The command opens database URLs in the app. See [Terminal](/integrations/terminal). ## Trusted links diff --git a/customization/settings.mdx b/customization/settings.mdx index d0c4a653..b872c378 100644 --- a/customization/settings.mdx +++ b/customization/settings.mdx @@ -100,7 +100,6 @@ What a fresh install ships with, so one glance down this column says what you ch | MCP | Maximum row limit | 10,000 | | MCP | Query timeout | 30 seconds | | MCP | Log MCP queries in history | On | -| MCP | Require authentication | On | | MCP | Approval | Ask Once for Each Connection | | MCP | Allow apps to list connections hidden from AI | Off | | Sync | Sync this Mac with iCloud | Off | diff --git a/developers/mcp-protocol.mdx b/developers/mcp-protocol.mdx index df17fa75..61599310 100644 --- a/developers/mcp-protocol.mdx +++ b/developers/mcp-protocol.mdx @@ -274,7 +274,7 @@ Present the form, then repeat the **same** call with the answer and the state ec A call that carries `inputResponses` or `requestState` is never cached, whatever the method's normal hint says. -An accepted form stands in for TablePro's own dialog only on a `fullAccess` token. On a `readOnly` or `readWrite` token, and on the anonymous loopback principal, Safe Mode still puts its dialog on the Mac afterwards, so a write is confirmed twice: once in your interface, once in TablePro's. Ask for `fullAccess` at [pairing](/developers/pairing) if the second prompt is not what you want. +An accepted form stands in for TablePro's own dialog only on a `fullAccess` token. On a `readOnly` or `readWrite` token, Safe Mode still puts its dialog on the Mac afterwards, so a write is confirmed twice: once in your interface, once in TablePro's. Ask for `fullAccess` at [pairing](/developers/pairing) if the second prompt is not what you want. ## Errors diff --git a/developers/pairing.mdx b/developers/pairing.mdx index f9d76d82..7247e335 100644 --- a/developers/pairing.mdx +++ b/developers/pairing.mdx @@ -22,7 +22,7 @@ sequenceDiagram E->>E: verifier = randomBytes(32).base64url E->>E: challenge = base64url(SHA-256(verifier)) E->>T: open tablepro://integrations/pair
?client=…&challenge=…&redirect=…&response_mode=query&state=… - T->>M: lazy-start server + T->>M: start server (refused while MCP is off) T->>U: Approval sheet (client, destination, scopes, connections, expiry) U->>T: Approve T->>T: mint token, hold pending exchange
{ code, plaintext, challenge }, 5 min @@ -157,7 +157,8 @@ The sheet names the client and counts down the five minutes the request is good the link asked for, movable in either direction), **Allowed Connections** (all, or a checked subset), and **Expiration** (never, 1, 7, 30 or 90 days). A link that asks for `connections:display` adds **List connections hidden from AI**, off until the user ticks it. The link's parameters are a -request, not a grant. +request, not a grant. **Deny** is the default button: Return and Escape both deny, and **Approve** +takes a click. The sheet does not take focus from another app; TablePro's Dock icon bounces instead. Approving mints the token, holds the plaintext against a one-time code for 5 minutes, and opens the redirect. Pairing again mints a second token rather than replacing the first, so an extension that @@ -175,6 +176,9 @@ re-pairs should stop using its old one. Revoke either under ## Errors +With **Enable MCP Server** off, the pair link stops before the approval sheet with an alert that +names **Settings > MCP**. No redirect is opened, so the extension sees no answer. + A failed verification burns the code, so retrying with a guessed verifier is not an option: start a new pair request. diff --git a/developers/tokens.mdx b/developers/tokens.mdx index cdb607db..f8c83283 100644 --- a/developers/tokens.mdx +++ b/developers/tokens.mdx @@ -14,22 +14,17 @@ description: Token model, scopes, connection allowlists, expiry, revocation, and | Level | Scopes | What it can do | |-------|--------|----------------| -| Anonymous loopback | `tools:read`, `resources:read` | Read schema and run `SELECT`. | -| **Read Only** | `tools:read`, `resources:read` | The same, under an issued token. | +| **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`. | -Anonymous loopback is the floor and cannot be raised: the scope check refuses `tools:write` and `admin` to a caller with no issued token, whatever it asks for. - -`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, and an anonymous caller never holds it. 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. +`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. `confirm_destructive_operation` also needs the connection's external access to be **Read & Write**, and no permission level skips the user's approval. [Scopes and gates](/developers/mcp-tools#scopes-and-gates) names which tools sit on which scope. -## Require authentication - -**Settings > MCP > Authentication > Require authentication** is on by default. Turning it on for the first time with no tokens yet mints a **Read & Write** token named "Default token" and reveals the plaintext once. +## No token, no access -With the toggle off, a request from your own machine with no `Authorization` header is accepted as the anonymous loopback caller in the table above. Presenting an `Authorization` header still goes through normal token validation, so an invalid token is rejected either way. The server is loopback only, with no remote mode. +A request with no `Authorization` header gets `401` with a `Bearer` challenge, from your own Mac as much as from anywhere else. There is no setting that admits a caller without a token. The server is loopback only, with no remote mode. ## Connection allowlist @@ -100,7 +95,7 @@ Every authentication, tool call, resource read, and query is recorded in `~/Libr | Timestamp | when the call happened | | Token | the token behind the call, by name and id | | Category | `auth`, `access`, `admin`, `query`, `tool`, `resource` | -| Action | `auth.success`, `auth.anonymousLoopback`, `auth.failure`, `auth.rateLimited`, `pairing.exchange`, `pairing.redirectRejected`, `token.created`, `token.revoked`, `server.started`, `server.stopped`, `query.executed`, `tool.`, `resource.read` | +| Action | `auth.success`, `auth.failure`, `auth.rateLimited`, `pairing.exchange`, `pairing.redirectRejected`, `token.created`, `token.revoked`, `server.started`, `server.stopped`, `query.executed`, `tool.`, `resource.read` | | Connection | the connection the call targeted, or `-` | | Outcome | `success`, `denied`, `error`, `rateLimited` | @@ -119,7 +114,7 @@ Entries are chained: each row's hash covers the previous one, so a row cannot be | Failed pairing exchanges | 5 per 5 minutes | | Pairing lockout | 15 minutes | -Request limits are keyed by token id, or by client address for an anonymous caller, and `subscriptions/listen` is exempt. Failed auth attempts are counted per client address, and every client on the Mac shares one, so a client that keeps sending a bad token locks the others out too. A malformed `Authorization` header, a scheme other than `Bearer`, and an unknown, expired or revoked token each count. A request with no `Authorization` header gets the `401` challenge and does not count. A successful auth clears the count. Anything over a limit returns `-33010` with HTTP `429` and a `Retry-After` header. Transport limits such as the body cap are on [MCP Protocol](/developers/mcp-protocol#transport-limits). +Request limits are keyed by token id, and `subscriptions/listen` is exempt. Failed auth attempts are counted per client address, and every client on the Mac shares one, so a client that keeps sending a bad token locks the others out too. A malformed `Authorization` header, a scheme other than `Bearer`, and an unknown, expired or revoked token each count. A request with no `Authorization` header gets the `401` challenge and does not count. A successful auth clears the count. Anything over a limit returns `-33010` with HTTP `429` and a `Retry-After` header. Transport limits such as the body cap are on [MCP Protocol](/developers/mcp-protocol#transport-limits). ## What a token cannot do diff --git a/developers/url-scheme.mdx b/developers/url-scheme.mdx index 2ae7e4a2..a8b1f9e6 100644 --- a/developers/url-scheme.mdx +++ b/developers/url-scheme.mdx @@ -17,7 +17,7 @@ Raycast extension with `open()` from `@raycast/api`. | [`connect//query?sql=…`](#run-a-query) | A query tab with the SQL prefilled, after a confirmation | | [`import?…`](#import-a-connection) | A review sheet that saves a new connection | | [`integrations/pair?…`](#start-pairing) | The pairing approval sheet | -| [`integrations/start-mcp`](#lazy-start-the-mcp-server) | Nothing. Starts the MCP server and returns | +| [`integrations/start-mcp`](#start-the-mcp-server) | Nothing. Starts the MCP server and returns | | [`settings/`](#open-a-settings-pane) | Settings on that pane, or on the last-used pane | ## Connection IDs are UUIDs @@ -85,7 +85,7 @@ tablepro://connect//query?sql= ``` A dialog titled **Open Query from Link** appears first, previewing the first 300 characters of the -SQL; **Open Query** opens the tab. Nothing executes, the user runs it from the editor. A tab already +SQL; **Open Query** opens the tab, and Return or Escape cancels. Nothing executes, the user runs it from the editor. A tab already holding exactly this SQL comes to front instead of a second one opening. `sql` is required and caps at 51,200 UTF-16 units. Longer than that and the link is dropped. @@ -126,14 +126,15 @@ open "tablepro://integrations/pair?client=My%20Launcher&challenge=E9Melhoa2OwvFr [Pairing](/developers/pairing) has the exchange step and the error codes. -## Lazy-start the MCP server +## Start the MCP server ```text tablepro://integrations/start-mcp ``` -Starts the MCP server if it is not running, then returns. MCP does not have to be enabled in Settings -first. The bundled `tablepro-mcp` CLI uses this to bootstrap on a cold launch. +Starts the MCP server if it is not running, then returns. The bundled `tablepro-mcp` CLI uses this +to bootstrap on a cold launch. With **Enable MCP Server** off in **Settings > MCP**, the start is +refused: no listener, no alert, and a launch made only for this URL quits again. The server takes the configured port (`23508` by default) and falls back to a kernel-assigned free port when that one is busy. The **Status** row in **Settings > MCP** shows the port it bound. diff --git a/features/mcp.mdx b/features/mcp.mdx index a42ce9b9..79f5f7e4 100644 --- a/features/mcp.mdx +++ b/features/mcp.mdx @@ -16,9 +16,9 @@ This page covers the **Settings > MCP** pane. The protocol itself, the tool cata Open **Settings > MCP** and turn on **Enable MCP Server**. The **Status** row underneath is how you check it before touching a client config: it reads **Running on port 23508**, or **Stopped**, or the reason it failed. -Leaving the toggle off does not keep the server down. It starts on demand when a client launches the bundled `tablepro-mcp` bridge and when you pair an extension, so turning the toggle off stops the listener but not those paths. +With the toggle off, nothing starts the server. A client that launches the bundled `tablepro-mcp` bridge gets "The TablePro MCP server is turned off" within a second instead of a timeout, and pairing an extension stops at the same message with **Open MCP Settings**. -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. +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. 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. @@ -64,6 +64,10 @@ The question always arrives as a dialog on your Mac, never through the client. A answer it in its own interface could also answer it without asking you, and this is the gate that decides whether it reaches the connection at all. +The dialog does not take focus from the app you are typing in. TablePro's Dock icon bounces until +you switch to it, **Deny** is the default button, and Return and Escape both deny. **Allow** takes a +click. + Nothing here times out. Whoever sent the request decides how long to wait for you, and an answer counts whenever you give it. @@ -78,9 +82,7 @@ Tokens that hold this grant read **Lists connections hidden from AI** in the tok ## Authentication -**Require authentication** is on by default. Turning it on for the first time with no tokens yet generates a full-access "Default token" and shows the plaintext once, so copy it then. Create, scope, allowlist, expire and revoke tokens in the same section, described in [Tokens](/developers/tokens). - -With the toggle off, a caller on your own machine is accepted without a token, but only as a read-only caller: schema reads and `SELECT`. Anything beyond reading needs a token you issued. +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). ## Activity and connected clients diff --git a/images/command-line-install-dark.png b/images/command-line-install-dark.png deleted file mode 100644 index c048ca68..00000000 Binary files a/images/command-line-install-dark.png and /dev/null differ diff --git a/images/command-line-install.png b/images/command-line-install.png deleted file mode 100644 index 29a84493..00000000 Binary files a/images/command-line-install.png and /dev/null differ diff --git a/images/command-line-link-dark.png b/images/command-line-link-dark.png new file mode 100644 index 00000000..767b0fa5 Binary files /dev/null and b/images/command-line-link-dark.png differ diff --git a/images/command-line-link.png b/images/command-line-link.png new file mode 100644 index 00000000..ed0272bf Binary files /dev/null and b/images/command-line-link.png differ diff --git a/integrations/mcp-clients.mdx b/integrations/mcp-clients.mdx index 63955742..da139405 100644 --- a/integrations/mcp-clients.mdx +++ b/integrations/mcp-clients.mdx @@ -104,7 +104,7 @@ Click **Connect a Client…** in **Settings > MCP**. The sheet covers Claude Des ## What the config leaves out -TablePro does not have to be open first. Finding no trusted handshake, the bridge runs `open -g tablepro://integrations/start-mcp` and polls for one every 200ms for up to 10 seconds. A TablePro started that way stays out of the Dock, the app switcher and the menu bar until it has a window to show; open the app yourself and it behaves normally for the rest of that run. +TablePro does not have to be open first, but **Enable MCP Server** has to be on. Finding no trusted handshake, the bridge runs `open -g tablepro://integrations/start-mcp` and polls for one every 200ms for up to 10 seconds. With the toggle off, TablePro refuses the start and the bridge exits with that error at once. A TablePro started that way stays out of the Dock, the app switcher and the menu bar until it has a window to show; open the app yourself and it behaves normally for the rest of that run. 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`. @@ -153,6 +153,10 @@ Clients see the full [tool catalog](/developers/mcp-tools), the [resources](/dev The bridge writes its failure to stderr and as a JSON-RPC error, so the message in the client names the layer that refused. +### The TablePro MCP server is turned off + +**Enable MCP Server** is off in **Settings > MCP**. Turn it on, then relaunch the client. + ### TablePro is not running The bridge could not launch the app, or got no trusted handshake within 10 seconds. Open TablePro and check that **Settings > MCP** shows the server running; if it does, toggle **Enable MCP Server** off and on, then relaunch the client. diff --git a/integrations/raycast.mdx b/integrations/raycast.mdx index bae66fd9..6b4e7726 100644 --- a/integrations/raycast.mdx +++ b/integrations/raycast.mdx @@ -20,7 +20,7 @@ It defaults to `/Applications/TablePro.app`, so change it only for a build that ## Pair -Run **Pair with TablePro**. An approval sheet opens in TablePro where you pick the scope (read-only +Turn on **Enable MCP Server** in **Settings > MCP** first; with it off, pairing stops at an error. Then run **Pair with TablePro**. An approval sheet opens in TablePro where you pick the scope (read-only to start), the connections the token may reach, and an optional expiry. Approving sends a one-time code back to Raycast, which exchanges it for a token over localhost. [Pairing](/developers/pairing) documents the protocol. @@ -74,6 +74,6 @@ under Raycast's [privacy policy](https://www.raycast.com/privacy). | Symptom | What to do | |---|---| | Paired, but the connection list is empty | TablePro omits every connection the token cannot reach. Either the allowlist you approved left it out, or its **External Clients** level is **Blocked**. Re-pair with the connection selected, or raise the level in the [connection form's Options tab](/connections/connection-form#options). | -| Commands hang on a running TablePro | The MCP server is stopped. The extension opens `tablepro://integrations/start-mcp` and retries on its own; give it a moment. | +| Commands hang on a running TablePro | The MCP server is stopped. Turn on **Enable MCP Server** in **Settings > MCP**; the extension retries on its own. | | `401` | The token was revoked or has expired. Run **Pair with TablePro** again. | | `403` and other MCP errors | See [MCP client troubleshooting](/integrations/mcp-clients#when-it-does-not-work). | diff --git a/integrations/terminal.mdx b/integrations/terminal.mdx index 4308c5d1..c282ff7e 100644 --- a/integrations/terminal.mdx +++ b/integrations/terminal.mdx @@ -16,22 +16,27 @@ Any [connection URL](/connections/urls) works, including its query parameters. T connection when one matches on type, host, port, database and user; otherwise the session is temporary and never written to disk. Running `tablepro` with no argument opens the app and nothing else. -## Install the tablepro command +## Add the tablepro command to your PATH -Open **Settings > General**, find **Command Line**, and click **Install**. It writes a small script at -`/usr/local/bin/tablepro` whose whole body is `exec open -b com.TablePro "$@"`. +The command ships inside the app at `TablePro.app/Contents/SharedSupport/bin/tablepro`, a script +whose whole body is `exec open -b com.TablePro "$@"`. Link it into `/usr/local/bin` once: + +```bash +sudo mkdir -p /usr/local/bin && sudo rm -f /usr/local/bin/tablepro && sudo ln -s /Applications/TablePro.app/Contents/SharedSupport/bin/tablepro /usr/local/bin/tablepro +``` + +`sudo` asks for your password in Terminal. TablePro itself never writes outside its own folders and +never asks for an administrator password. **Settings > General > Command Line** prints the same line +with the path of the copy you are running, and reads **Linked** once the link is in place. Any other +directory on your `PATH` works too, and one you own needs no `sudo`. - Command Line install section in TablePro settings - Command Line install section in TablePro settings + Command Line section in TablePro settings with the Terminal command to link tablepro + Command Line section in TablePro settings with the Terminal command to link tablepro -That directory is root-owned on a stock macOS install, so expect an administrator password prompt. -Cancel it and nothing is written. If the write fails anyway, Settings prints the exact `sudo` command -to paste into Terminal; run it, switch back, and the status updates itself. - -**Uninstall** removes the script again. When something else already occupies that path, **Install** -is dimmed and Settings says *"A different file already exists at …"*: remove that file first. +To remove it, run `sudo rm /usr/local/bin/tablepro`. The link follows the app, so updating TablePro +updates the command; move the app and the link needs making again. ## Open a file diff --git a/security/privacy.mdx b/security/privacy.mdx index bcf47493..3df18f94 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) | `raw.githubusercontent.com` | 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` | Off, but starts on demand | **Settings > MCP** | +| [MCP clients](#the-mcp-server) | nowhere: it listens on `127.0.0.1` | 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,14 +95,14 @@ With password sync off, secrets are written `kSecAttrAccessibleAfterFirstUnlockT 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. -It is off by default, but it starts on demand: the bundled bridge and the pairing flow both start it. What a paired client can reach is real data, so four gates stack: +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: -1. **Require authentication**, on by default. With it on, a request that arrives without an `Authorization` header is refused. Turn it off and a loopback caller with no header is allowed instead, capped at `tools:read` and `resources:read` however much it asks for. +1. A token on every request. One that arrives without an `Authorization` header is refused, from your own Mac too, and no setting changes that. 2. The token's scope and its connection allowlist. 3. The connection's **External Clients** level: **Blocked**, **Read Only** (the default for a new connection), or **Read & Write**. The effective permission is the lower of that and the token's scope. 4. The connection's **AI Policy** of **Never**, its [Safe Mode](/features/safe-mode) level, and the **Approval** setting under **Settings > MCP**, which decides how often a client is asked before it reaches a connection. -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, which is the reason to leave **Require authentication** on. +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. 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.