From 1371a0eb45f71049e601f0d1351de2b0807b86ce Mon Sep 17 00:00:00 2001 From: abhigyanpatwari Date: Mon, 19 Jan 2026 06:43:02 +0530 Subject: [PATCH] refactor: simplify MCP popup with improved UI and config display --- gitnexus/src/components/MCPToggle.tsx | 390 ++++++++++---------------- 1 file changed, 154 insertions(+), 236 deletions(-) diff --git a/gitnexus/src/components/MCPToggle.tsx b/gitnexus/src/components/MCPToggle.tsx index 2e08085abb..46b765889d 100644 --- a/gitnexus/src/components/MCPToggle.tsx +++ b/gitnexus/src/components/MCPToggle.tsx @@ -1,37 +1,39 @@ /** * MCP Toggle Component * - * Toggle switch for enabling/disabling MCP exposure to external AI agents. - * Shows connection status and setup instructions if bridge not found. + * Toggle for enabling MCP exposure to external AI agents (Cursor, Claude, etc.) + * Shows MCP config for setup and connection status. */ import { useState, useEffect, useCallback, useRef } from 'react'; -import { Copy, Check, AlertCircle, X, Sparkles } from 'lucide-react'; +import { Copy, Check, X, Sparkles, Zap, ExternalLink } from 'lucide-react'; import { getMCPClient, type CodebaseContext } from '../core/mcp/mcp-client'; type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error'; interface MCPToggleProps { - /** Handler for search tool calls from external agents */ onSearch?: (query: string, limit?: number) => Promise; - /** Handler for cypher tool calls from external agents */ onCypher?: (query: string) => Promise; - /** Handler for blastRadius tool calls from external agents */ onBlastRadius?: (nodeId: string, hops?: number) => Promise; - /** Handler for highlight tool calls from external agents */ onHighlight?: (nodeIds: string[], color?: string) => void; - /** Handler for grep tool calls from external agents */ onGrep?: (pattern: string, caseSensitive?: boolean, maxResults?: number) => Promise; - /** Handler for read tool calls from external agents */ onRead?: (filePath: string, startLine?: number, endLine?: number) => Promise; - /** Whether to show the onboarding tip */ showOnboardingTip?: boolean; - /** Callback to get codebase context for external agents */ getContext?: () => Promise; } const MCP_TIP_DISMISSED_KEY = 'gitnexus-mcp-tip-dismissed'; +// MCP config that users copy to their AI agent +const MCP_CONFIG = `{ + "mcpServers": { + "gitnexus": { + "command": "npx", + "args": ["-y", "gitnexus-mcp"] + } + } +}`; + export function MCPToggle({ onSearch, onCypher, @@ -44,40 +46,35 @@ export function MCPToggle({ }: MCPToggleProps = {}) { const [status, setStatus] = useState('disconnected'); const [copied, setCopied] = useState(false); - const [showSetup, setShowSetup] = useState(false); - const [hasConnectionError, setHasConnectionError] = useState(false); - const setupPopupRef = useRef(null); + const [showPopup, setShowPopup] = useState(false); + const popupRef = useRef(null); const [showTip, setShowTip] = useState(false); const isConnected = status === 'connected'; const isConnecting = status === 'connecting'; - // Show tip when graph becomes ready (only once per session) + // Show tip when graph becomes ready useEffect(() => { if (showOnboardingTip) { const dismissed = localStorage.getItem(MCP_TIP_DISMISSED_KEY); if (!dismissed) { - // Small delay so it appears after graph loads const timer = setTimeout(() => setShowTip(true), 1500); return () => clearTimeout(timer); } } }, [showOnboardingTip]); - // Close setup popup when clicking outside + // Close popup when clicking outside useEffect(() => { - if (!showSetup) return; - + if (!showPopup) return; const handleClickOutside = (event: MouseEvent) => { - if (!setupPopupRef.current) return; - if (!setupPopupRef.current.contains(event.target as Node)) { - setShowSetup(false); + if (popupRef.current && !popupRef.current.contains(event.target as Node)) { + setShowPopup(false); } }; - document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); - }, [showSetup]); + }, [showPopup]); const dismissTip = () => { setShowTip(false); @@ -88,79 +85,34 @@ export function MCPToggle({ const client = getMCPClient(); setStatus('connecting'); setShowTip(false); - setHasConnectionError(false); try { await client.connect(); - // Register tool handlers if provided - if (onSearch) { - client.registerHandler('search', async (params) => { - return await onSearch(params.query, params.limit); - }); - } - - if (onCypher) { - client.registerHandler('cypher', async (params) => { - return await onCypher(params.query); - }); - } - - if (onBlastRadius) { - client.registerHandler('blastRadius', async (params) => { - return await onBlastRadius(params.nodeId, params.hops); - }); - } - - if (onHighlight) { - client.registerHandler('highlight', async (params) => { - onHighlight(params.nodeIds, params.color); - return { highlighted: params.nodeIds.length }; - }); - } - - // Register context tool handler - if (getContext) { - client.registerHandler('context', async () => { - const context = await getContext(); - return context; - }); - } - - // Register grep tool handler - if (onGrep) { - client.registerHandler('grep', async (params) => { - return await onGrep(params.pattern, params.caseSensitive, params.maxResults); - }); - } - - // Register read tool handler - if (onRead) { - client.registerHandler('read', async (params) => { - return await onRead(params.filePath, params.startLine, params.endLine); - }); - } + // Register tool handlers + if (onSearch) client.registerHandler('search', async (params) => onSearch(params.query, params.limit)); + if (onCypher) client.registerHandler('cypher', async (params) => onCypher(params.query)); + if (onBlastRadius) client.registerHandler('blastRadius', async (params) => onBlastRadius(params.nodeId, params.hops)); + if (onHighlight) client.registerHandler('highlight', async (params) => { onHighlight(params.nodeIds, params.color); return { highlighted: params.nodeIds.length }; }); + if (onGrep) client.registerHandler('grep', async (params) => onGrep(params.pattern, params.caseSensitive, params.maxResults)); + if (onRead) client.registerHandler('read', async (params) => onRead(params.filePath, params.startLine, params.endLine)); + if (getContext) client.registerHandler('context', async () => getContext()); setStatus('connected'); - setShowSetup(false); - setHasConnectionError(false); + setShowPopup(false); localStorage.setItem(MCP_TIP_DISMISSED_KEY, 'true'); - // Send codebase context after connecting + // Send context after connecting if (getContext) { try { const context = await getContext(); - if (context) { - client.sendContext(context); - } + if (context) client.sendContext(context); } catch (e) { console.error('[MCP] Failed to send context:', e); } } } catch { setStatus('error'); - setShowSetup(true); - setHasConnectionError(true); } }, [onSearch, onCypher, onBlastRadius, onHighlight, onGrep, onRead, getContext]); @@ -168,7 +120,6 @@ export function MCPToggle({ const client = getMCPClient(); client.disconnect(); setStatus('disconnected'); - setHasConnectionError(false); }, []); const toggle = useCallback(() => { @@ -179,8 +130,8 @@ export function MCPToggle({ } }, [isConnected, isConnecting, connect, disconnect]); - const copyCommand = (command: string) => { - navigator.clipboard.writeText(command); + const copyConfig = () => { + navigator.clipboard.writeText(MCP_CONFIG); setCopied(true); setTimeout(() => setCopied(false), 2000); }; @@ -189,189 +140,156 @@ export function MCPToggle({ useEffect(() => { const client = getMCPClient(); const unsubscribe = client.onConnectionChange((connected) => { - if (connected) { - setStatus('connected'); - setShowSetup(false); - setHasConnectionError(false); - } else { - setStatus('disconnected'); - } + setStatus(connected ? 'connected' : 'disconnected'); + if (connected) setShowPopup(false); }); return () => { unsubscribe(); }; }, []); - // Error state - show setup popup - if (hasConnectionError && showSetup) { - return ( -
- MCP - - -
-

- MCP daemon isn't running. Follow the steps below: -

- -
-

1) Install/configure your IDE:

-
- npx gitnexus-mcp setup - -
-
- -
-

2) Start the daemon (recommended):

-
- npx gitnexus-mcp daemon - -
-
- -
-

Dev mode (from `gitnexus-mcp` dir):

-
- npm run build && node dist/cli.js daemon - -
-
- - -
-
- ); - } - - // Toggle switch UI return (
- MCP + {/* MCP Button */} - {isConnected && ( - Connected + + {/* Popup */} + {showPopup && ( +
+ {/* Header */} +
+
+
+
+ +
+
+

Connect AI Agents

+

Cursor, Claude Code, Antigravity

+
+
+ +
+
+ + {/* Content */} +
+ {/* Step 1: Config */} +
+
+ 1 + Add to your AI agent's MCP config +
+
+
+                                    {MCP_CONFIG}
+                                
+ +
+
+ + {/* Step 2: Connect */} +
+
+ 2 + Connect browser to daemon +
+ +
+ + {/* Status message */} + {status === 'error' && ( +

+ Daemon not running. Make sure your AI agent has started gitnexus-mcp. +

+ )} + + {/* Help link */} + + Learn more + + +
+
)} - {/* Onboarding Tip Popup */} - {showTip && !isConnected && ( -
+ {/* Onboarding Tip */} + {showTip && !isConnected && !showPopup && ( +
- -
-
- +
+
+

- Connect your AI coding tool + Connect your AI tools

-

- Let Cursor, Claude Code, or Antigravity use GitNexus for code intelligence. +

+ Let Cursor or Claude access GitNexus code intelligence.

-
-
- -
-

Run this in your terminal:

-
- npx gitnexus-mcp setup
- -
)}