@@ -2,107 +2,70 @@ import { useEffect, useState } from "react";
22import { useAuth } from "@/contexts/AuthContext" ;
33import { supabase } from "@/integrations/supabase/client" ;
44import { CheckCircle2 , XCircle , AlertCircle } from "lucide-react" ;
5- import { toast } from "sonner" ;
65import {
76 Tooltip ,
87 TooltipContent ,
98 TooltipProvider ,
109 TooltipTrigger ,
1110} from "@/components/ui/tooltip" ;
1211import { cn } from "@/lib/utils" ;
12+ import { useNavigate } from "react-router-dom" ;
1313
14- interface McpServerHealth {
15- status : "online " | "offline" | "degraded" | "unknown " | "not_configured" ;
16- last_check ?: string ;
17- response_time_ms ? : number ;
18- error_message ?: string ;
14+ interface McpStatus {
15+ status : "active " | "inactive " | "not_configured" ;
16+ endpoint_count : number ;
17+ active_count : number ;
18+ last_used ?: string ;
1919}
2020
2121export function McpServerStatus ( ) {
2222 const { tenant } = useAuth ( ) ;
23- const [ health , setHealth ] = useState < McpServerHealth > ( { status : "unknown" } ) ;
23+ const navigate = useNavigate ( ) ;
24+ const [ status , setStatus ] = useState < McpStatus > ( {
25+ status : "not_configured" ,
26+ endpoint_count : 0 ,
27+ active_count : 0
28+ } ) ;
2429 const [ isLoading , setIsLoading ] = useState ( true ) ;
25- const [ mcpEnabled , setMcpEnabled ] = useState < boolean | null > ( null ) ;
2630
27- // Check if MCP is configured and enabled for this tenant
28- const checkMcpConfig = async ( ) : Promise < boolean > => {
29- if ( ! tenant ?. id ) return false ;
30-
31- try {
32- const { data, error } = await supabase
33- . from ( "mcp_server_config" )
34- . select ( "enabled" )
35- . eq ( "tenant_id" , tenant . id )
36- . maybeSingle ( ) ;
37-
38- if ( error || ! data ) {
39- // No config means MCP is not set up - this is not an error
40- setMcpEnabled ( false ) ;
41- return false ;
42- }
43-
44- setMcpEnabled ( data . enabled ) ;
45- return data . enabled ;
46- } catch {
47- setMcpEnabled ( false ) ;
48- return false ;
49- }
50- } ;
51-
52- const fetchHealthWithRetry = async ( attempt = 0 , maxRetries = 3 ) : Promise < boolean > => {
53- if ( ! tenant ?. id ) return false ;
31+ const fetchStatus = async ( ) => {
32+ if ( ! tenant ?. id ) return ;
5433
5534 try {
35+ // Check for MCP endpoints
5636 const { data, error } = await supabase
57- . from ( "mcp_server_health" )
58- . select ( "*" )
59- . eq ( "tenant_id" , tenant . id )
60- . order ( "last_check" , { ascending : false } )
61- . limit ( 1 )
62- . maybeSingle ( ) ;
37+ . from ( "mcp_endpoints" )
38+ . select ( "id, enabled, last_used_at" )
39+ . order ( "last_used_at" , { ascending : false , nullsFirst : false } ) ;
6340
6441 if ( error ) {
65- console . error ( "Error fetching MCP health:" , error ) ;
66-
67- // Retry with exponential backoff on error
68- if ( attempt < maxRetries ) {
69- const delay = Math . pow ( 2 , attempt ) * 1000 ; // 1s, 2s, 4s
70- console . log ( `Retrying health check in ${ delay } ms (attempt ${ attempt + 1 } /${ maxRetries } )` ) ;
71- await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
72- return fetchHealthWithRetry ( attempt + 1 , maxRetries ) ;
42+ // Table might not exist yet - that's okay
43+ if ( error . code === '42P01' ) {
44+ setStatus ( { status : "not_configured" , endpoint_count : 0 , active_count : 0 } ) ;
45+ return ;
7346 }
74-
75- // Don't show error toast - just silently set to unknown
76- setHealth ( { status : "unknown" } ) ;
77- return false ;
47+ console . error ( "Error fetching MCP status:" , error ) ;
48+ setStatus ( { status : "not_configured" , endpoint_count : 0 , active_count : 0 } ) ;
49+ return ;
7850 }
7951
80- if ( data ) {
81- setHealth ( {
82- status : data . status as "online" | "offline" | "degraded" ,
83- last_check : data . last_check ,
84- response_time_ms : data . response_time_ms ,
85- error_message : data . error_message ,
86- } ) ;
87- return true ;
88- } else {
89- setHealth ( { status : "unknown" } ) ;
90- return true ; // No error, just no data
91- }
92- } catch ( err : any ) {
93- console . error ( "Error fetching MCP health:" , err ) ;
94-
95- // Retry with exponential backoff on exception
96- if ( attempt < maxRetries ) {
97- const delay = Math . pow ( 2 , attempt ) * 1000 ;
98- console . log ( `Retrying health check in ${ delay } ms (attempt ${ attempt + 1 } /${ maxRetries } )` ) ;
99- await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
100- return fetchHealthWithRetry ( attempt + 1 , maxRetries ) ;
52+ if ( ! data || data . length === 0 ) {
53+ setStatus ( { status : "not_configured" , endpoint_count : 0 , active_count : 0 } ) ;
54+ return ;
10155 }
10256
103- // Don't show error toast - just silently set to unknown
104- setHealth ( { status : "unknown" } ) ;
105- return false ;
57+ const activeEndpoints = data . filter ( e => e . enabled ) ;
58+ const lastUsed = data . find ( e => e . last_used_at ) ?. last_used_at ;
59+
60+ setStatus ( {
61+ status : activeEndpoints . length > 0 ? "active" : "inactive" ,
62+ endpoint_count : data . length ,
63+ active_count : activeEndpoints . length ,
64+ last_used : lastUsed ,
65+ } ) ;
66+ } catch ( err ) {
67+ console . error ( "Error fetching MCP status:" , err ) ;
68+ setStatus ( { status : "not_configured" , endpoint_count : 0 , active_count : 0 } ) ;
10669 } finally {
10770 setIsLoading ( false ) ;
10871 }
@@ -111,98 +74,62 @@ export function McpServerStatus() {
11174 useEffect ( ( ) => {
11275 if ( ! tenant ?. id ) return ;
11376
114- let channel : ReturnType < typeof supabase . channel > | null = null ;
115-
116- const init = async ( ) => {
117- // First check if MCP is configured
118- const isEnabled = await checkMcpConfig ( ) ;
119-
120- if ( ! isEnabled ) {
121- // MCP not configured - set status and stop
122- setHealth ( { status : "not_configured" } ) ;
123- setIsLoading ( false ) ;
124- return ;
125- }
126-
127- // MCP is enabled, fetch health status
128- await fetchHealthWithRetry ( 0 , 3 ) ;
129-
130- // Subscribe to real-time updates for immediate health status changes
131- channel = supabase
132- . channel ( "mcp_health_changes" )
133- . on (
134- "postgres_changes" ,
135- {
136- event : "*" ,
137- schema : "public" ,
138- table : "mcp_server_health" ,
139- filter : `tenant_id=eq.${ tenant ?. id } ` ,
140- } ,
141- ( ) => {
142- // Fetch health without retries on real-time updates (subscription already handles reconnection)
143- fetchHealthWithRetry ( 0 , 0 ) ;
144- }
145- )
146- . subscribe ( ( status ) => {
147- if ( status === 'SUBSCRIBED' ) {
148- console . log ( 'MCP health subscription active' ) ;
149- } else if ( status === 'CHANNEL_ERROR' ) {
150- console . error ( 'MCP health channel error' ) ;
151- }
152- } ) ;
153- } ;
154-
155- init ( ) ;
77+ fetchStatus ( ) ;
78+
79+ // Subscribe to changes
80+ const channel = supabase
81+ . channel ( "mcp_endpoints_changes" )
82+ . on (
83+ "postgres_changes" ,
84+ {
85+ event : "*" ,
86+ schema : "public" ,
87+ table : "mcp_endpoints" ,
88+ } ,
89+ ( ) => fetchStatus ( )
90+ )
91+ . subscribe ( ) ;
15692
15793 return ( ) => {
158- if ( channel ) {
159- supabase . removeChannel ( channel ) ;
160- }
94+ supabase . removeChannel ( channel ) ;
16195 } ;
16296 } , [ tenant ?. id ] ) ;
16397
164- const getStatusIcon = ( ) => {
165- switch ( health . status ) {
166- case "online" :
167- return < CheckCircle2 className = "h-4 w-4 text-green-500" /> ;
168- case "offline" :
169- return < XCircle className = "h-4 w-4 text-red-500" /> ;
170- case "degraded" :
171- return < AlertCircle className = "h-4 w-4 text-yellow-500" /> ;
98+ const getStatusColor = ( ) => {
99+ switch ( status . status ) {
100+ case "active" :
101+ return "text-green-500" ;
102+ case "inactive" :
103+ return "text-yellow-500" ;
172104 default :
173- return < AlertCircle className = "h-4 w-4 text-gray-400" /> ;
105+ return " text-muted-foreground" ;
174106 }
175107 } ;
176108
177- const getStatusText = ( ) => {
178- switch ( health . status ) {
179- case "online" :
180- return "MCP Server Online" ;
181- case "offline" :
182- return "MCP Server Offline" ;
183- case "degraded" :
184- return "MCP Server Degraded" ;
185- case "not_configured" :
186- return "MCP Not Configured" ;
109+ const getDotColor = ( ) => {
110+ switch ( status . status ) {
111+ case "active" :
112+ return "bg-green-500" ;
113+ case "inactive" :
114+ return "bg-yellow-500" ;
187115 default :
188- return "MCP Server Status Unknown " ;
116+ return "bg-muted-foreground/50 " ;
189117 }
190118 } ;
191119
192120 const getTooltipContent = ( ) => {
193- const lines = [ getStatusText ( ) ] ;
194-
195- if ( health . last_check ) {
196- const lastCheck = new Date ( health . last_check ) ;
197- lines . push ( `Last Check: ${ lastCheck . toLocaleString ( ) } ` ) ;
121+ if ( status . status === "not_configured" ) {
122+ return "MCP not configured\nClick to set up" ;
198123 }
199124
200- if ( health . response_time_ms ) {
201- lines . push ( `Response Time: ${ health . response_time_ms } ms` ) ;
202- }
125+ const lines = [
126+ status . status === "active" ? "MCP Active" : "MCP Inactive" ,
127+ `${ status . active_count } /${ status . endpoint_count } endpoints active` ,
128+ ] ;
203129
204- if ( health . error_message ) {
205- lines . push ( `Error: ${ health . error_message } ` ) ;
130+ if ( status . last_used ) {
131+ const lastUsed = new Date ( status . last_used ) ;
132+ lines . push ( `Last used: ${ lastUsed . toLocaleString ( ) } ` ) ;
206133 }
207134
208135 return lines . join ( "\n" ) ;
@@ -222,21 +149,13 @@ export function McpServerStatus() {
222149 < Tooltip >
223150 < TooltipTrigger asChild >
224151 < button
152+ onClick = { ( ) => navigate ( "/admin/mcp-setup" ) }
225153 className = { cn (
226154 "flex items-center gap-1.5 text-xs transition-colors hover:text-foreground" ,
227- health . status === "online" && "text-green-500" ,
228- health . status === "offline" && "text-red-500" ,
229- health . status === "degraded" && "text-yellow-500" ,
230- ( health . status === "unknown" || health . status === "not_configured" ) && "text-muted-foreground"
155+ getStatusColor ( )
231156 ) }
232157 >
233- < div className = { cn (
234- "h-2 w-2 rounded-full" ,
235- health . status === "online" && "bg-green-500" ,
236- health . status === "offline" && "bg-red-500" ,
237- health . status === "degraded" && "bg-yellow-500" ,
238- ( health . status === "unknown" || health . status === "not_configured" ) && "bg-muted-foreground/50"
239- ) } />
158+ < div className = { cn ( "h-2 w-2 rounded-full" , getDotColor ( ) ) } />
240159 < span > MCP</ span >
241160 </ button >
242161 </ TooltipTrigger >
0 commit comments