Skip to content

Commit 465fc4f

Browse files
committed
Fix MCP status indicator to use new mcp_endpoints table
- Updated McpServerStatus component to query mcp_endpoints instead of old mcp_server_config/mcp_server_health tables - Status now shows: active (green), inactive (yellow), not_configured (gray) - Shows endpoint count and last used time in tooltip - Clicking the status indicator navigates to /admin/mcp-setup - Gracefully handles table not existing yet (before migration runs)
1 parent 2640f11 commit 465fc4f

File tree

1 file changed

+82
-163
lines changed

1 file changed

+82
-163
lines changed

src/components/admin/McpServerStatus.tsx

Lines changed: 82 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -2,107 +2,70 @@ import { useEffect, useState } from "react";
22
import { useAuth } from "@/contexts/AuthContext";
33
import { supabase } from "@/integrations/supabase/client";
44
import { CheckCircle2, XCircle, AlertCircle } from "lucide-react";
5-
import { toast } from "sonner";
65
import {
76
Tooltip,
87
TooltipContent,
98
TooltipProvider,
109
TooltipTrigger,
1110
} from "@/components/ui/tooltip";
1211
import { 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

2121
export 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

Comments
 (0)