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`.
-
-
+
+
-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.