({ manager, onChanged, onCancel, onDeleted }: McpPickerProps)
| 75 | * (disabled), Disable (connected/failed), Reconnect (always), Delete (always). |
| 76 | */ |
| 77 | export function McpPicker({ manager, onChanged, onCancel, onDeleted }: McpPickerProps) { |
| 78 | const [snapshot, setSnapshot] = useState(() => manager.snapshot()) |
| 79 | const [selected, setSelected] = useState(0) |
| 80 | const [busy, setBusy] = useState(false) |
| 81 | const [busyMessage, setBusyMessage] = useState("") |
| 82 | const [authingServer, setAuthingServer] = useState<string | null>(null) |
| 83 | const [authUrl, setAuthUrl] = useState("") |
| 84 | const [actionMode, setActionMode] = useState(false) |
| 85 | const [actionSelected, setActionSelected] = useState(0) |
| 86 | // Pending destructive confirmation. The resolve ref unblocks the awaiting |
| 87 | // `deleteSelected` call when the user picks y/n at the prompt. |
| 88 | const [pendingConfirm, setPendingConfirm] = useState<{ |
| 89 | title: string |
| 90 | body: string |
| 91 | resolve: (ok: boolean) => void |
| 92 | } | null>(null) |
| 93 | const codeResolveRef = useRef<((code: string) => void) | null>(null) |
| 94 | const codeRejectRef = useRef<((reason: Error) => void) | null>(null) |
| 95 | |
| 96 | const servers = snapshot.servers |
| 97 | const count = servers.length |
| 98 | const current = servers[selected] |
| 99 | const actions = current ? buildActions(current) : [] |
| 100 | |
| 101 | /** Show a yes/no confirmation and resolve when the user picks one. Used |
| 102 | * for destructive actions like deleting a server. */ |
| 103 | function confirmDangerousAction(title: string, body: string): Promise<boolean> { |
| 104 | return new Promise<boolean>((resolve) => { |
| 105 | setPendingConfirm({ title, body, resolve }) |
| 106 | }) |
| 107 | } |
| 108 | |
| 109 | useInput((input, key) => { |
| 110 | if (authingServer || busy) return |
| 111 | |
| 112 | // A destructive confirmation is in flight. Eat every key except the |
| 113 | // explicit y/n + enter / esc — matches the rest of the picker's |
| 114 | // "no shorthand" rule. |
| 115 | if (pendingConfirm) { |
| 116 | if (key.escape) { |
| 117 | pendingConfirm.resolve(false) |
| 118 | setPendingConfirm(null) |
| 119 | return |
| 120 | } |
| 121 | if (key.return) { |
| 122 | pendingConfirm.resolve(true) |
| 123 | setPendingConfirm(null) |
| 124 | return |
| 125 | } |
| 126 | if (input === "y" || input === "Y") { |
| 127 | pendingConfirm.resolve(true) |
| 128 | setPendingConfirm(null) |
| 129 | return |
| 130 | } |
| 131 | if (input === "n" || input === "N") { |
| 132 | pendingConfirm.resolve(false) |
| 133 | setPendingConfirm(null) |
| 134 | return |
nothing calls this directly
no test coverage detected