From 2d7e905dddc17b9530723ac8367e7ea246e51f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Sat, 18 Jul 2026 11:49:32 +0200 Subject: [PATCH] feat(mcp): lead with Claude Desktop/ChatGPT connect steps, collapse tokens (#695) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What & why The **AI Connector** settings page was built token-first, but tokens are only needed for **Claude Code**. Most people connect via **Claude Desktop** or **ChatGPT**, which sign in with OAuth (no token). This reorders the page around that reality. ## Changes - **Lead with the connection instructions.** A **Claude Desktop / ChatGPT** tab selector (`ToggleGroup`) is now the first card. Each tab renders the exact ordered steps for that app, with the copyable connector URL inline. - **ChatGPT developer mode.** The ChatGPT tab's first step tells the user to turn on developer mode, which is currently required to add a custom connector. - **Collapse the token stuff.** Token management (Create a token, Your tokens, rotate/revoke) and the Claude Code CLI command moved into a **"Connect with Claude Code"** section that is **collapsed by default**. It auto-opens right after a token is minted so the one-time secret and the list stay visible. - **Copy tweaks.** The one-time-secret alert stays pinned at the top; the non-Pro alert copy no longer references "create a token" as the primary action. ## Review notes (findings applied) - **Bug fix:** rotate/revoke used `router` visits without `preserveState`, which remounted the page and snapped the collapsed section shut (and reset the selected tab). Both now pass `preserveState: true`. - Trigger markup made valid (no block elements inside ` + + ); + + const connectors: Record< + ConnectorApp, + { label: string; steps: ReactNode[] } + > = { + claude: { + label: __('Claude Desktop'), + steps: [ + __('Open Settings → Connectors in Claude Desktop.'), + __('Click "Add custom connector".'), + <> + {__('Give it a name and paste this URL:')} + {oauthUrlBlock} + , + __( + 'Approve the connection on the Whisper Money screen that opens.', + ), + ], + }, + chatgpt: { + label: __('ChatGPT'), + steps: [ + __( + 'Turn on developer mode: Settings → Connectors → Advanced → Developer mode. Custom connectors are only available with it on.', + ), + __('In Connectors, click "Create".'), + <> + {__('Give it a name and paste this URL:')} + {oauthUrlBlock} + , + __( + 'Approve the connection on the Whisper Money screen that opens.', + ), + ], + }, + }; + return ( @@ -118,7 +194,7 @@ export default function Mcp() { {__( - 'You can create a token now, but it only works on a paid plan.', + 'You can set it up now, but it only works on a paid plan.', )}{' '} )} - {/* Create token */} - - - {__('Create a token')} - - {__( - 'Read-only tokens can analyse your data. Read & write tokens can also create, edit and delete transactions, categories, labels and automation rules.', - )} - - - -
-
- - - form.setData('name', e.target.value) - } - placeholder={__('e.g. Claude Desktop')} - required - /> -
-
- - {/* Radix Select renders a hidden native - form.setData( - 'scope', - value as - | 'read' - | 'read_write', - ) - } - > - - - - - - {__('Read only')} - - - {__('Read & write')} - - - -
- - -
- {form.errors.name && ( -

- {form.errors.name} -

- )} -
-
- - {/* Token list */} - - - {__('Your tokens')} - - {__( - 'Rotate a token if it leaks, or revoke it to cut off access.', - )} - - - - {tokens.length === 0 ? ( -

- {__('You have no tokens yet.')} -

- ) : ( -
    - {tokens.map((token) => ( -
  • -
    -
    - - {token.name} - - - {token.scope === - 'read_write' - ? __('Read & write') - : __('Read only')} - -
    -

    - {__('Created')}:{' '} - {formatDate( - token.created_at, - )}{' '} - · {__('Last used')}:{' '} - {formatDate( - token.last_used_at, - )} -

    -
    -
    - - - - - - - - {__( - 'Rotate this token?', - )} - - - {__( - 'Rotating gives you a new secret and cancels the old one. Anything using the current token stops working until you reconnect it with the new secret.', - )} - - - - - {__('Cancel')} - - - router.post( - rotate( - token.id, - ).url, - {}, - { - preserveScroll: true, - }, - ) - } - > - {__('Rotate')} - - - - - - - - - - - - {__( - 'Revoke this token?', - )} - - - {__( - 'Anything using this token loses access right away, and you cannot undo it.', - )} - - - - - {__('Cancel')} - - - router.delete( - destroy( - token.id, - ).url, - { - preserveScroll: true, - }, - ) - } - > - {__('Revoke')} - - - - -
    -
  • - ))} -
- )} -
-
- - {/* Connection instructions */} + {/* Connect Claude Desktop / ChatGPT — the primary flow */} {__('How to connect')} {__( - 'Connect your AI assistant using the details for it below.', + 'Pick your app, then follow the steps. You sign in and approve the connection — no token needed.', )} - -
-

- {__('Claude Desktop & ChatGPT')} -

-

- {__( - 'These apps sign in instead of using a token. Add a custom connector pointing at the URL below, then approve the connection on the Whisper Money screen that opens. No token needed.', - )} -

-
- - {oauthUrl} - - -
-

- {__( - 'Connected apps can read, analyse and make changes to your data (bank-connected accounts stay read-only). You approve the connection on the Whisper Money screen.', - )} -

-
+ {connectors[key].label} + + ))} + -
-

- {__('Claude Code')} -

-

- {__( - 'Run this, using one of your tokens in place of .', - )} -

- - {`claude mcp add --transport http whisper-money ${serverUrl} --header "Authorization: Bearer "`} - -
+
    + {connectors[connector].steps.map( + (step, index) => ( +
  1. {step}
  2. + ), + )} +
+ +

+ {__( + 'Connected apps can read, analyse and make changes to your data (bank-connected accounts stay read-only).', + )} +

+ + {/* Developer flow: Claude Code + token management, hidden by default */} + + + + + + + +
+

+ {__( + 'Run this, using one of your tokens in place of .', + )} +

+ + {`claude mcp add --transport http whisper-money ${serverUrl} --header "Authorization: Bearer "`} + +
+ + {/* Create token */} +
+
+

+ {__('Create a token')} +

+

+ {__( + 'Read-only tokens can analyse your data. Read & write tokens can also create, edit and delete transactions, categories, labels and automation rules.', + )} +

+
+
+
+ + + form.setData( + 'name', + e.target.value, + ) + } + placeholder={__( + 'e.g. Claude Desktop', + )} + required + /> +
+
+ + {/* Radix Select renders a hidden native + form.setData( + 'scope', + value as + | 'read' + | 'read_write', + ) + } + > + + + + + + {__( + 'Read only', + )} + + + {__( + 'Read & write', + )} + + + +
+
+ + + {form.errors.name && ( +

+ {form.errors.name} +

+ )} + + + {/* Token list */} +
+
+

+ {__('Your tokens')} +

+

+ {__( + 'Rotate a token if it leaks, or revoke it to cut off access.', + )} +

+
+ {tokens.length === 0 ? ( +

+ {__('You have no tokens yet.')} +

+ ) : ( +
    + {tokens.map((token) => ( +
  • +
    +
    + + {token.name} + + + {token.scope === + 'read_write' + ? __( + 'Read & write', + ) + : __( + 'Read only', + )} + +
    +

    + {__('Created')}:{' '} + {formatDate( + token.created_at, + )}{' '} + ·{' '} + {__( + 'Last used', + )} + :{' '} + {formatDate( + token.last_used_at, + )} +

    +
    +
    + + + + + + + + {__( + 'Rotate this token?', + )} + + + {__( + 'Rotating gives you a new secret and cancels the old one. Anything using the current token stops working until you reconnect it with the new secret.', + )} + + + + + {__( + 'Cancel', + )} + + + router.post( + rotate( + token.id, + ) + .url, + {}, + { + preserveScroll: true, + preserveState: true, + }, + ) + } + > + {__( + 'Rotate', + )} + + + + + + + + + + + + {__( + 'Revoke this token?', + )} + + + {__( + 'Anything using this token loses access right away, and you cannot undo it.', + )} + + + + + {__( + 'Cancel', + )} + + + router.delete( + destroy( + token.id, + ) + .url, + { + preserveScroll: true, + preserveState: true, + }, + ) + } + > + {__( + 'Revoke', + )} + + + + +
    +
  • + ))} +
+ )} +
+
+
+
+