diff --git a/ccw/frontend/src/components/codexlens/CcwToolsCard.tsx b/ccw/frontend/src/components/codexlens/CcwToolsCard.tsx
new file mode 100644
index 00000000..037d238e
--- /dev/null
+++ b/ccw/frontend/src/components/codexlens/CcwToolsCard.tsx
@@ -0,0 +1,153 @@
+// ========================================
+// CCW Tools Card Component
+// ========================================
+// Displays all registered CCW tools, highlighting codex-lens related tools
+
+import { useIntl } from 'react-intl';
+import { Wrench, AlertCircle, Loader2 } from 'lucide-react';
+import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
+import { Badge } from '@/components/ui/Badge';
+import { useCcwToolsList } from '@/hooks';
+import type { CcwToolInfo } from '@/lib/api';
+
+const CODEX_LENS_PREFIX = 'codex_lens';
+
+function isCodexLensTool(tool: CcwToolInfo): boolean {
+ return tool.name.startsWith(CODEX_LENS_PREFIX);
+}
+
+export function CcwToolsCard() {
+ const { formatMessage } = useIntl();
+ const { tools, isLoading, error } = useCcwToolsList();
+
+ const codexLensTools = tools.filter(isCodexLensTool);
+ const otherTools = tools.filter((t) => !isCodexLensTool(t));
+
+ if (isLoading) {
+ return (
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.title' })}
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.loading' })}
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.title' })}
+
+
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.error' })}
+
+
+ {formatMessage({ id: 'codexlens.mcp.errorDesc' })}
+
+
+
+
+
+ );
+ }
+
+ if (tools.length === 0) {
+ return (
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.title' })}
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.emptyDesc' })}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.mcp.title' })}
+
+
+ {formatMessage({ id: 'codexlens.mcp.totalCount' }, { count: tools.length })}
+
+
+
+
+ {/* CodexLens Tools Section */}
+ {codexLensTools.length > 0 && (
+
+
+ {formatMessage({ id: 'codexlens.mcp.codexLensSection' })}
+
+
+ {codexLensTools.map((tool) => (
+
+ ))}
+
+
+ )}
+
+ {/* Other Tools Section */}
+ {otherTools.length > 0 && (
+
+
+ {formatMessage({ id: 'codexlens.mcp.otherSection' })}
+
+
+ {otherTools.map((tool) => (
+
+ ))}
+
+
+ )}
+
+
+ );
+}
+
+interface ToolRowProps {
+ tool: CcwToolInfo;
+ variant: 'default' | 'secondary';
+}
+
+function ToolRow({ tool, variant }: ToolRowProps) {
+ return (
+
+
+ {tool.name}
+
+
+ {tool.description}
+
+
+ );
+}
+
+export default CcwToolsCard;
diff --git a/ccw/frontend/src/components/codexlens/LspServerCard.tsx b/ccw/frontend/src/components/codexlens/LspServerCard.tsx
new file mode 100644
index 00000000..de7fb917
--- /dev/null
+++ b/ccw/frontend/src/components/codexlens/LspServerCard.tsx
@@ -0,0 +1,157 @@
+// ========================================
+// CodexLens LSP Server Card
+// ========================================
+// Displays LSP server status, stats, and start/stop/restart controls
+
+import { useIntl } from 'react-intl';
+import {
+ Server,
+ Power,
+ PowerOff,
+ RotateCw,
+ FolderOpen,
+ Layers,
+ Cpu,
+} from 'lucide-react';
+import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
+import { Badge } from '@/components/ui/Badge';
+import { Button } from '@/components/ui/Button';
+import { cn } from '@/lib/utils';
+import { useCodexLensLspStatus, useCodexLensLspMutations } from '@/hooks';
+import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
+
+interface LspServerCardProps {
+ disabled?: boolean;
+}
+
+export function LspServerCard({ disabled = false }: LspServerCardProps) {
+ const { formatMessage } = useIntl();
+ const projectPath = useWorkflowStore(selectProjectPath);
+ const {
+ available,
+ semanticAvailable,
+ projectCount,
+ modes,
+ isLoading,
+ } = useCodexLensLspStatus();
+ const { startLsp, stopLsp, restartLsp, isStarting, isStopping, isRestarting } = useCodexLensLspMutations();
+
+ const isMutating = isStarting || isStopping || isRestarting;
+
+ const handleToggle = async () => {
+ if (available) {
+ await stopLsp(projectPath);
+ } else {
+ await startLsp(projectPath);
+ }
+ };
+
+ const handleRestart = async () => {
+ await restartLsp(projectPath);
+ };
+
+ return (
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.lsp.title' })}
+
+
+ {available
+ ? formatMessage({ id: 'codexlens.lsp.status.running' })
+ : formatMessage({ id: 'codexlens.lsp.status.stopped' })
+ }
+
+
+
+
+ {/* Stats Grid */}
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.lsp.projects' })}
+
+
+ {available ? projectCount : '--'}
+
+
+
+
+
+
+
+ {formatMessage({ id: 'codexlens.lsp.semanticAvailable' })}
+
+
+ {semanticAvailable
+ ? formatMessage({ id: 'codexlens.lsp.available' })
+ : formatMessage({ id: 'codexlens.lsp.unavailable' })
+ }
+
+
+
+
+
0 ? 'text-accent' : 'text-muted-foreground')} />
+
+
+ {formatMessage({ id: 'codexlens.lsp.modes' })}
+
+
+ {available ? modes.length : '--'}
+
+
+
+
+
+ {/* Action Buttons */}
+
+
+ {available && (
+
+ )}
+
+
+
+ );
+}
+
+export default LspServerCard;
diff --git a/ccw/frontend/src/components/codexlens/OverviewTab.tsx b/ccw/frontend/src/components/codexlens/OverviewTab.tsx
index 7014ad84..070fc489 100644
--- a/ccw/frontend/src/components/codexlens/OverviewTab.tsx
+++ b/ccw/frontend/src/components/codexlens/OverviewTab.tsx
@@ -16,6 +16,7 @@ import { cn } from '@/lib/utils';
import type { CodexLensVenvStatus, CodexLensConfig } from '@/lib/api';
import { IndexOperations } from './IndexOperations';
import { FileWatcherCard } from './FileWatcherCard';
+import { LspServerCard } from './LspServerCard';
interface OverviewTabProps {
installed: boolean;
@@ -143,8 +144,11 @@ export function OverviewTab({ installed, status, config, isLoading, onRefresh }:
- {/* File Watcher */}
-