11import { useState , useEffect , useCallback } from 'react'
22import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription } from '@/components/ui/dialog'
33import { Switch } from '@/components/ui/switch'
4+ import { Button } from '@/components/ui/button'
45import { Badge } from '@/components/ui/badge'
5- import { Loader2 , XCircle , AlertCircle , Plug } from 'lucide-react'
6- import { mcpApi , type McpStatus } from '@/api/mcp'
6+ import { DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
7+ import { DeleteDialog } from '@/components/ui/delete-dialog'
8+ import { DropdownMenuSeparator } from '@/components/ui/dropdown-menu'
9+ import { Loader2 , XCircle , AlertCircle , Plug , Shield , MoreVertical , Key , RefreshCw } from 'lucide-react'
10+ import { McpOAuthDialog } from '@/components/settings/McpOAuthDialog'
11+ import { mcpApi , type McpStatus , type McpServerConfig , type McpAuthStartResponse } from '@/api/mcp'
712import { useMutation } from '@tanstack/react-query'
813import { showToast } from '@/lib/toast'
914
10- interface McpServerConfig {
11- type : 'local' | 'remote'
12- enabled ?: boolean
13- command ?: string [ ]
14- url ?: string
15- environment ?: Record < string , string >
16- timeout ?: number
17- }
18-
1915interface RepoMcpDialogProps {
2016 open : boolean
2117 onOpenChange : ( open : boolean ) => void
@@ -28,6 +24,8 @@ interface RepoMcpDialogProps {
2824export function RepoMcpDialog ( { open, onOpenChange, config, directory } : RepoMcpDialogProps ) {
2925 const [ localStatus , setLocalStatus ] = useState < Record < string , McpStatus > > ( { } )
3026 const [ isLoadingStatus , setIsLoadingStatus ] = useState ( false )
27+ const [ removeAuthConfirmServer , setRemoveAuthConfirmServer ] = useState < string | null > ( null )
28+ const [ authDialogServerId , setAuthDialogServerId ] = useState < string | null > ( null )
3129
3230 const mcpServers = config ?. content ?. mcp as Record < string , McpServerConfig > | undefined || { }
3331 const serverIds = Object . keys ( mcpServers )
@@ -68,6 +66,40 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
6866 showToast . error ( error instanceof Error ? error . message : 'Failed to update MCP server' )
6967 } ,
7068 } )
69+
70+ const removeAuthMutation = useMutation ( {
71+ mutationFn : async ( serverId : string ) => {
72+ if ( ! directory ) throw new Error ( 'No directory provided' )
73+ await mcpApi . removeAuthDirectory ( serverId , directory )
74+ } ,
75+ onSuccess : async ( ) => {
76+ showToast . success ( 'Authentication removed for this location' )
77+ setRemoveAuthConfirmServer ( null )
78+ await fetchStatus ( )
79+ } ,
80+ onError : ( error ) => {
81+ showToast . error ( error instanceof Error ? error . message : 'Failed to remove authentication' )
82+ } ,
83+ } )
84+
85+ const handleOAuthAutoAuth = async ( ) => {
86+ if ( ! authDialogServerId || ! directory ) return
87+ await mcpApi . authenticateDirectory ( authDialogServerId , directory )
88+ await fetchStatus ( )
89+ setAuthDialogServerId ( null )
90+ }
91+
92+ const handleOAuthStartAuth = async ( ) : Promise < McpAuthStartResponse > => {
93+ if ( ! authDialogServerId ) throw new Error ( 'No server ID' )
94+ return await mcpApi . startAuth ( authDialogServerId )
95+ }
96+
97+ const handleOAuthCompleteAuth = async ( code : string ) => {
98+ if ( ! authDialogServerId ) return
99+ await mcpApi . completeAuth ( authDialogServerId , code )
100+ await fetchStatus ( )
101+ setAuthDialogServerId ( null )
102+ }
71103
72104 useEffect ( ( ) => {
73105 if ( open && directory ) {
@@ -156,7 +188,14 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
156188 const serverConfig = mcpServers [ serverId ]
157189 const status = localStatus [ serverId ]
158190 const isConnected = status ?. status === 'connected'
191+ const needsAuth = status ?. status === 'needs_auth'
159192 const failed = status ?. status === 'failed'
193+ const isRemote = serverConfig . type === 'remote'
194+ const hasOAuthConfig = isRemote && ! ! serverConfig . oauth
195+ const hasOAuthError = failed && isRemote && / o a u t h | a u t h .* s t a t e / i. test ( status . error )
196+ const isOAuthServer = hasOAuthConfig || hasOAuthError || ( needsAuth && isRemote )
197+ const connectedWithOAuth = isOAuthServer && isConnected
198+ const showAuthButton = needsAuth || ( isOAuthServer && failed )
160199
161200 return (
162201 < div
@@ -168,6 +207,11 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
168207 < p className = "text-sm font-medium truncate" >
169208 { getDisplayName ( serverId ) }
170209 </ p >
210+ { connectedWithOAuth && (
211+ < span title = "OAuth authenticated" >
212+ < Shield className = "h-3 w-3 text-muted-foreground" />
213+ </ span >
214+ ) }
171215 { getStatusBadge ( status ) }
172216 </ div >
173217 < p className = "text-xs text-muted-foreground truncate" >
@@ -181,20 +225,94 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
181225 ) }
182226 </ div >
183227
184- < Switch
185- checked = { isConnected }
186- disabled = { toggleMutation . isPending }
187- onCheckedChange = { ( enabled ) => {
188- toggleMutation . mutate ( { serverId, enable : enabled } )
189- } }
190- onClick = { ( e ) => e . stopPropagation ( ) }
191- />
228+ < div className = "flex items-center gap-2" >
229+ { showAuthButton ? (
230+ < Button
231+ onClick = { ( ) => setAuthDialogServerId ( serverId ) }
232+ disabled = { toggleMutation . isPending }
233+ variant = "default"
234+ size = "sm"
235+ >
236+ < Key className = "h-3 w-3 mr-1" />
237+ Auth
238+ </ Button >
239+ ) : (
240+ < Switch
241+ checked = { isConnected }
242+ disabled = { toggleMutation . isPending || removeAuthMutation . isPending }
243+ onCheckedChange = { ( enabled ) => {
244+ toggleMutation . mutate ( { serverId, enable : enabled } )
245+ } }
246+ onClick = { ( e ) => e . stopPropagation ( ) }
247+ />
248+ ) }
249+ { ( isOAuthServer || needsAuth ) && (
250+ < DropdownMenu >
251+ < DropdownMenuTrigger asChild >
252+ < Button variant = "ghost" size = "sm" className = "h-8 w-8 p-0" >
253+ < MoreVertical className = "h-4 w-4" />
254+ </ Button >
255+ </ DropdownMenuTrigger >
256+ < DropdownMenuContent align = "end" >
257+ { showAuthButton && (
258+ < DropdownMenuItem onClick = { ( ) => setAuthDialogServerId ( serverId ) } >
259+ < Key className = "h-4 w-4 mr-2" />
260+ Authenticate
261+ </ DropdownMenuItem >
262+ ) }
263+ { connectedWithOAuth && (
264+ < DropdownMenuItem onClick = { ( ) => setAuthDialogServerId ( serverId ) } >
265+ < RefreshCw className = "h-4 w-4 mr-2" />
266+ Re-authenticate
267+ </ DropdownMenuItem >
268+ ) }
269+ { connectedWithOAuth && (
270+ < >
271+ < DropdownMenuSeparator />
272+ < DropdownMenuItem
273+ onClick = { ( ) => setRemoveAuthConfirmServer ( serverId ) }
274+ disabled = { removeAuthMutation . isPending }
275+ >
276+ < Shield className = "h-4 w-4 mr-2" />
277+ { removeAuthMutation . isPending ? 'Removing...' : 'Remove Auth' }
278+ </ DropdownMenuItem >
279+ </ >
280+ ) }
281+ </ DropdownMenuContent >
282+ </ DropdownMenu >
283+ ) }
284+ </ div >
192285 </ div >
193286 )
194287 } ) }
195288 </ div >
196289 ) }
197290 </ div >
291+
292+ < DeleteDialog
293+ open = { ! ! removeAuthConfirmServer }
294+ onOpenChange = { ( ) => setRemoveAuthConfirmServer ( null ) }
295+ onConfirm = { ( ) => {
296+ if ( removeAuthConfirmServer ) {
297+ removeAuthMutation . mutate ( removeAuthConfirmServer )
298+ }
299+ } }
300+ onCancel = { ( ) => setRemoveAuthConfirmServer ( null ) }
301+ title = "Remove Authentication"
302+ description = "This will remove the OAuth credentials for this MCP server at this location. You will need to re-authenticate to use this server here again."
303+ itemName = { removeAuthConfirmServer ? getDisplayName ( removeAuthConfirmServer ) : '' }
304+ isDeleting = { removeAuthMutation . isPending }
305+ />
306+
307+ < McpOAuthDialog
308+ open = { ! ! authDialogServerId }
309+ onOpenChange = { ( o ) => ! o && setAuthDialogServerId ( null ) }
310+ serverName = { authDialogServerId || '' }
311+ onAutoAuth = { handleOAuthAutoAuth }
312+ onStartAuth = { handleOAuthStartAuth }
313+ onCompleteAuth = { handleOAuthCompleteAuth }
314+ directory = { directory }
315+ />
198316 </ DialogContent >
199317 </ Dialog >
200318 )
0 commit comments