Skip to content

Commit 03aaae7

Browse files
committed
feat(clients): Phase 3 — regenerate choice + remove redundant Cursor bridge card
Autonomous decisions: - Two secondary buttons (Rotate key / New client) — clearer than a single Regenerate that opens a sub-choice; both paths stay one click from the Cursor result view. - Kept cursorBridge.* keys still referenced by the modal; removed only card-only keys (title, description, gatewayStopped, regenerate). - E2E TC-CL-003 switches to Generic tab first — modal defaults to Cursor tab since Phase 2, so raw-key assertion requires the tab switch. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
1 parent 3fcf6ca commit 03aaae7

5 files changed

Lines changed: 65 additions & 185 deletions

File tree

apps/desktop/src/features/clients/ClientsPage.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import {
4242
} from '@/lib/api/gateway';
4343
import { RegisterApiKeyClientModal } from './RegisterApiKeyClientModal';
4444
import { ClientApiKeysSection } from './ClientApiKeysSection';
45-
import { CursorBridgeSection } from './CursorBridgeSection';
4645
import {
4746
isStarterFeatureSet,
4847
listFeatureSetsBySpace,
@@ -349,12 +348,6 @@ export default function ClientsPage() {
349348

350349
<div className="flex-1 overflow-auto px-8 py-8">
351350
<div className="mx-auto max-w-[2000px] space-y-8">
352-
<CursorBridgeSection
353-
gatewayUrl={gatewayStatus.url || 'http://localhost:45818'}
354-
gatewayRunning={gatewayStatus.running}
355-
onRegistered={() => void refreshClients()}
356-
/>
357-
358351
{isLoading ? (
359352
<div className="flex h-64 items-center justify-center">
360353
<Loader2 className="text-primary-500 h-8 w-8 animate-spin" />

apps/desktop/src/features/clients/CursorBridgeSection.tsx

Lines changed: 0 additions & 166 deletions
This file was deleted.

apps/desktop/src/features/clients/RegisterApiKeyClientModal.tsx

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import {
2020
X,
2121
} from 'lucide-react';
2222
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, SearchableSelect } from '@mcpmux/ui';
23-
import { registerApiKeyClient, type RegisteredApiKeyClient } from '@/lib/api/gateway';
23+
import {
24+
createClientApiKey,
25+
registerApiKeyClient,
26+
type RegisteredApiKeyClient,
27+
} from '@/lib/api/gateway';
2428
import {
2529
createMachine,
2630
getHostname,
@@ -192,9 +196,28 @@ export function RegisterApiKeyClientModal({
192196
};
193197

194198
/**
195-
* Regenerate Cursor snippet by minting a new client (same as legacy CursorBridgeSection).
199+
* Rotate the existing session client's API key and rebuild the Cursor snippet.
200+
*/
201+
const handleRotateKey = async () => {
202+
if (!result) return;
203+
204+
setIsSubmitting(true);
205+
setError(null);
206+
try {
207+
const rotated = await createClientApiKey(result.clientId);
208+
setResult(rotated);
209+
setCursorSnippet(buildCursorBridgeMcpJson(rotated.apiKey, gatewayUrl));
210+
} catch (e) {
211+
setError(e instanceof Error ? e.message : String(e));
212+
} finally {
213+
setIsSubmitting(false);
214+
}
215+
};
216+
217+
/**
218+
* Mint a fresh client for the Cursor preset, leaving the prior session client intact.
196219
*/
197-
const handleRegenerateCursor = async () => {
220+
const handleNewClient = async () => {
198221
const client = await mintClient();
199222
if (!client) return;
200223

@@ -339,20 +362,47 @@ export function RegisterApiKeyClientModal({
339362
</p>
340363
</div>
341364

365+
{error && (
366+
<p
367+
className="text-sm text-red-600 dark:text-red-400"
368+
data-testid="cursor-bridge-error"
369+
>
370+
{error}
371+
</p>
372+
)}
373+
374+
<p className="text-xs text-[rgb(var(--muted))]">
375+
{t('registerModal.cursor.regenerateHint')}
376+
</p>
377+
342378
<div className="flex flex-wrap justify-end gap-2">
343379
<Button
344380
variant="secondary"
345381
size="md"
346-
onClick={() => void handleRegenerateCursor()}
382+
onClick={() => void handleRotateKey()}
383+
disabled={isSubmitting}
384+
data-testid="register-api-key-rotate-key"
385+
>
386+
{isSubmitting ? (
387+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
388+
) : (
389+
<KeyRound className="mr-2 h-4 w-4" />
390+
)}
391+
{t('registerModal.cursor.rotateKey')}
392+
</Button>
393+
<Button
394+
variant="secondary"
395+
size="md"
396+
onClick={() => void handleNewClient()}
347397
disabled={isSubmitting}
348-
data-testid="cursor-bridge-regenerate"
398+
data-testid="register-api-key-new-client"
349399
>
350400
{isSubmitting ? (
351401
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
352402
) : (
353403
<KeyRound className="mr-2 h-4 w-4" />
354404
)}
355-
{t('cursorBridge.regenerate')}
405+
{t('registerModal.cursor.newClient')}
356406
</Button>
357407
<Button
358408
variant="primary"

apps/desktop/src/locales/en/clients.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,21 +178,20 @@
178178
}
179179
},
180180
"cursor": {
181-
"resultTitle": "Cursor config ready"
181+
"resultTitle": "Cursor config ready",
182+
"regenerateHint": "Need a new key? Rotate keeps the same client; New client creates a separate connection you can revoke later.",
183+
"rotateKey": "Rotate key",
184+
"newClient": "New client"
182185
}
183186
},
184187
"cursorBridge": {
185-
"title": "Global Cursor setup (no per-repo files)",
186-
"description": "Paste one config into ~/.cursor/mcp.json. Each Cursor window sends its own workspace folder via mcp-remote — no per-repo .cursor/mcp.json files needed.",
187-
"gatewayStopped": "Start the gateway from the Dashboard before generating a config.",
188188
"generateHint": "Creates a dedicated API-key client and builds the full ~/.cursor/mcp.json snippet with the key embedded.",
189189
"generate": "Generate global config",
190190
"generating": "Generating…",
191-
"regenerate": "Generate new key",
192191
"pasteInto": "Paste into ~/.cursor/mcp.json",
193192
"copy": "Copy config",
194193
"copied": "Copied",
195-
"keyOnceWarning": "The API key is shown only in this snippet. Store it in ~/.cursor/mcp.json now — McpMux cannot display it again. Regenerating creates a new key.",
194+
"keyOnceWarning": "The API key is shown only in this snippet. Store it in ~/.cursor/mcp.json now — McpMux cannot display it again.",
196195
"fallbackNote": "Per-repo install via Workspaces remains available if you prefer not to use npx/mcp-remote."
197196
}
198197
}

tests/e2e/specs/clients.wdio.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ describe('Connections - Page shell', () => {
7070
await registerBtn.click();
7171
await browser.pause(800);
7272

73+
const genericTab = await byTestId('register-api-key-tab-generic');
74+
await genericTab.click();
75+
await browser.pause(300);
76+
7377
const nameInput = await byTestId('register-api-key-name');
7478
await nameInput.setValue('e2e-headless-bot');
7579

0 commit comments

Comments
 (0)