Pairing
Pairing is how an extension gets a TablePro token without the user copying and pasting one. The user runs aPair with TablePro command in the extension, picks scopes and connections inside TablePro, and the extension receives a token over a Raycast deep link callback.
The flow is PKCE-flavored: the extension generates a verifier, hashes it into a challenge, and the token is only released after the verifier is presented. This prevents another app on the same machine from intercepting the redirect and stealing the token.
Sequence
Step by step
1. Extension generates a verifier and challenge
2. Extension opens the pair deep link
3. TablePro shows the approval sheet
The user sees:- The client name from the request.
- A scopes radio (defaults to the requested scope, downgradeable).
- A connections multi-select (defaults to all unless
connection-idswas provided). - An expiry picker (defaults to never).
4. TablePro generates a token and a one-time code
On approval, TablePro callsMCPTokenStore.generate(...) to mint a token, then stores a pending exchange:
5. TablePro redirects with the code
TablePro opens theredirect URL with NSWorkspace.shared.open(...). The encoding depends on the redirect scheme:
raycast://...: TablePro appends?context={"code":"<uuid>"}(URL-encoded JSON). Raycast parsescontextand passes it to the receiving command asLaunchProps.launchContext. This matches Raycast’s documented launch-context convention.- Anything else (
http://127.0.0.1:<port>/callback, custom schemes): TablePro appends?code=<uuid>as a flat query parameter. Standard OAuth-callback shape.
6. Extension exchanges the code
The extension reads the MCP port from~/Library/Application Support/TablePro/mcp-handshake.json, then:
7. TablePro validates and returns the token
Server-side check:403.
8. Extension stores the token
updateCommandMetadata or write to the password preference. Tokens stored in Raycast preferences live in the macOS Keychain.
Security properties
Errors
A failed exchange is recorded in the activity log under the
auth category with outcome denied.
Denied approvals
If the user clicks Deny on the approval sheet, TablePro opens theredirect URL with two extra parameters so the extension can show a clear error and stop spinning:
error=deniederror_description=user_denied
raycast://... redirects these are wrapped inside the standard context JSON payload ({"error":"denied","error_description":"user_denied"}); for any other scheme they are appended as flat query parameters.
Extensions should treat the presence of an error parameter on the callback as terminal and surface the description to the user.
Implementing pairing in another extension
The flow is not Raycast-specific. Cursor, Claude Desktop, or any custom client can use it. Requirements:- Generate a verifier and challenge.
- Open
tablepro://integrations/pair?...with a deep link callback URL the OS can route back to the extension. - Read the MCP port from the handshake file.
- POST
{ code, code_verifier }to/v1/integrations/exchange. - Store the returned token in OS Keychain.
http://127.0.0.1:<port>/callback as the redirect.