DEV Community

Danilo Jamaal
Danilo Jamaal

Posted on • Edited on

Build an AI Trading Analyst with LunarCrush MCP + Remix + Gemini in 20 Minutes

Build an AI Trading Terminal with LunarCrush MCP + Remix + Gemini in 20 Minutes

Transform social intelligence into trading insights using Model Context Protocol (MCP) and AI-powered analysis

LunarCrush MCP Trading Terminal

Why MCP Changes Everything for AI Development

Traditional API integrations require developers to manually orchestrate multiple endpoints, handle complex error scenarios, and build custom data formatting logic. This creates significant overhead and maintenance burden.

Model Context Protocol (MCP) revolutionizes how AI systems access real-time data. Instead of manual API integration, MCP creates secure, standardized connections between AI models and data sources.

This means your AI can intelligently orchestrate multiple data tools, make complex decisions, and generate insights that would take hours to code manually.

What You'll Build

Trading Terminal Dashboard

In this tutorial, you'll create a production-ready AI Trading Terminal that:

β€’ βœ… MCP Integration - Direct connection between Google Gemini AI and LunarCrush tools
β€’ βœ… AI Orchestration - Gemini intelligently selects and combines multiple data sources

β€’ βœ… Real-time Analysis - Live progress tracking through a 6-step AI pipeline
β€’ βœ… Interactive Charts - Visual price history with responsive design
β€’ βœ… Smart Caching - Instant retrieval for previously analyzed cryptocurrencies

Time Investment: 20 minutes

Skill Level: Beginner to Intermediate

What You'll Learn: Remix, TypeScript, MCP integration, AI orchestration, real-time data processing

πŸ’‘ Pro Tip: By the end, you'll have a portfolio-worthy project that demonstrates modern AI development patterns with MCP!

Live Example: View the deployed version β†’


Before We Start

You'll Need:

β€’ Node.js 18+ installed

β€’ Basic knowledge of React/TypeScript

β€’ A code editor (VS Code recommended)

β€’ 2 API keys from different services (we'll walk through signup below)

Two Ways to Experience This Tutorial:

  1. πŸ‘¨β€πŸ’» Build It Yourself - Follow along step-by-step with your own API keys
  2. πŸš€ Try the Live Demo - View the deployed version and explore the code

Quick Project Setup:

# We'll build this step-by-step, but here's the final structure:
npx create-remix@latest lunarcrush-mcp --template remix-run/remix/templates/remix --typescript
cd lunarcrush-mcp
npm install @google/generative-ai @modelcontextprotocol/sdk @heroui/react recharts
Enter fullscreen mode Exit fullscreen mode

🚨 Common Issue: Make sure you have Node.js 18+ installed. Check with node --version


Account Setup Guide

We need 2 services for this project. Both have generous free tiers!

Sign Up For LunarCrush API

LunarCrush provides social sentiment data that most traders don't have access to.

Use my discount referral code JAMAALBUILDS to receive 15% off your plan.

  1. Visit LunarCrush Signup
  2. Enter your email address and click "Continue"
  3. Check your email for verification code and enter it
  4. Complete the onboarding steps: β€’ Select your favorite categories (or keep defaults) β€’ Create your profile (add photo and nickname if desired) β€’ Important: Select a subscription plan (you'll need it to generate an API key)

LunarCrush Signup

Generate Your API Key:

Once you've subscribed, navigate to the API authentication page and generate an API key.

Save this API key - you'll add it to your environment variables later.

Set Up Google Gemini AI

Google's Gemini AI will orchestrate the LunarCrush tools and generate trading recommendations.

  1. Sign up: Visit aistudio.google.com and click "Get API key"
  2. Choose authentication: Sign in with your Google account
  3. Create API key: β€’ Click "Create API key" β€’ Choose "Create API key in new project" or select existing project β€’ Copy your API key (starts with AIza...)

Project Setup

Create Remix Project

# Create new Remix project with TypeScript
npx create-remix@latest lunarcrush-mcp --template remix-run/remix/templates/remix --typescript
cd lunarcrush-mcp

# Install required dependencies
npm install @google/generative-ai @modelcontextprotocol/sdk @heroui/react recharts clsx tailwind-merge

# Install development dependencies
npm install @types/node --save-dev

# Create environment file
touch .env.local
Enter fullscreen mode Exit fullscreen mode

Set Up Environment Variables

Add your API keys to .env.local:

# .env.local
LUNARCRUSH_API_KEY=lc_your_key_here
GOOGLE_GEMINI_API_KEY=your_gemini_key_here
Enter fullscreen mode Exit fullscreen mode

Create Project Structure (Copy/Paste Terminal Commands)

# Create directory structure (some may already exist)
mkdir -p components hooks config types

# Create TypeScript interfaces
cat > types/index.ts << 'EOF'
import { SVGProps } from "react";

export type IconSvgProps = SVGProps<SVGSVGElement> & {
  size?: number;
};

// Gemini AI Types
export interface GeminiResponse {
  candidates?: Array<{
    content?: {
      parts?: Array<{
        text?: string;
      }>;
    };
  }>;
}

// Trading Analysis Types
export interface TradingAnalysis {
  symbol: string;
  recommendation: 'BUY' | 'SELL' | 'HOLD';
  confidence: number;
  reasoning: string;
  social_sentiment: 'bullish' | 'bearish' | 'neutral';
  key_metrics: Record<string, string | number | unknown>;
  ai_analysis: {
    summary?: string;
    pros?: string[];
    cons?: string[];
    key_factors?: string[];
  } | string;
  timestamp: string;
  chart_data: Array<{ date: string; price: number }>;
  success: boolean;
  error?: string;
  processingTime?: number;
}

// Tool Call Types
export interface ToolCall {
  tool: string;
  args: Record<string, unknown>;
  reason: string;
}

// MCP Tool Types
export interface McpTool {
  name: string;
  inputSchema: {
    type: string;
    properties?: Record<string, unknown>;
    required?: string[];
  };
  title?: string;
  description?: string;
  outputSchema?: Record<string, unknown>;
  annotations?: Record<string, unknown>;

}

// Tool Result Types
export interface ToolResult {
  tool: string;
  args: Record<string, unknown>;
  reason: string;
  result?: unknown;
  error?: string;
}
EOF
Enter fullscreen mode Exit fullscreen mode

Core Implementation (Copy/Paste Terminal Commands)

Create the MCP Client Hook

# Create MCP server hook
cat > hooks/useMcpServer.js << 'EOF'
// src/hooks/useGeminiMcp.js
import { useMcp } from 'use-mcp/react';

const useMcpServer = ({ url, clientName, autoReconnect = true, config }) => {
    // MCP connection via use-mcp
    const {
        state, // Connection state: 'discovering' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'
        tools, // Available tools from MCP server
        callTool, // Function to call tools on the MCP server
    } = useMcp({
        url,
        clientName,
        autoReconnect,
        ...config,
    });

    return {
        tools,
        callTool,
        state,
    };
};

export default useMcpServer;
EOF
Enter fullscreen mode Exit fullscreen mode

Create the Advanced Crypto Chart Component

# Create advanced crypto chart component
cat > app/components/CryptoChart.tsx << 'EOF'
import {
    LineChart,
    Line,
    XAxis,
    YAxis,
    CartesianGrid,
    Tooltip,
    ResponsiveContainer,
} from 'recharts';

interface ChartData {
    date: string;
    close: number;
}

interface CryptoChartProps {
    data: ChartData[];
    symbol: string;
}

export default function CryptoChart({ data, symbol }: CryptoChartProps) {
    if (!data || data.length === 0) {
        return (
            <div className='flex items-center justify-center h-64 bg-gray-50 dark:bg-gray-800 rounded-lg'>
                <p className='text-gray-500 dark:text-gray-400'>
                    No chart data available
                </p>
            </div>
        );
    }

    // Format the date for display
    const formatDate = (dateStr: string) => {
        const date = new Date(dateStr);
        return date.toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric',
        });
    };

    // Format price for display
    const formatPrice = (price: number) => {
        if (price < 0.01) {
            return `$${price.toFixed(6)}`;
        } else if (price < 1) {
            return `$${price.toFixed(4)}`;
        } else {
            return `$${price.toLocaleString()}`;
        }
    };

    return (
        <div className='w-full'>
            <div className='mb-4'>
                <h3 className='text-lg font-semibold text-gray-800 dark:text-white'>
                    {symbol} Price History (Last Week)
                </h3>
                <p className='text-sm text-gray-600 dark:text-gray-400'>
                    Historical price data from LunarCrush Topic_Time_Series
                </p>
            </div>

            <div className='h-64 w-full'>
                <ResponsiveContainer width='100%' height='100%'>
                    <LineChart
                        data={data}
                        margin={{
                            top: 5,
                            right: 30,
                            left: 20,
                            bottom: 5,
                        }}>
                        <CartesianGrid
                            strokeDasharray='3 3'
                            stroke='#374151'
                            opacity={0.3}
                        />
                        <XAxis
                            dataKey='date'
                            tickFormatter={formatDate}
                            stroke='#6B7280'
                            fontSize={12}
                        />
                        <YAxis tickFormatter={formatPrice} stroke='#6B7280' fontSize={12} />
                        <Tooltip
                            labelFormatter={(label) => `Date: ${formatDate(label)}`}
                            formatter={(value: number) => [formatPrice(value), 'Price']}
                            contentStyle={{
                                backgroundColor: '#1F2937',
                                border: '1px solid #374151',
                                borderRadius: '6px',
                                color: '#F9FAFB',
                            }}
                        />
                        <Line
                            type='monotone'
                            dataKey='close'
                            stroke='#3B82F6'
                            strokeWidth={2}
                            dot={{ fill: '#3B82F6', strokeWidth: 2, r: 4 }}
                            activeDot={{ r: 6, stroke: '#3B82F6', strokeWidth: 2 }}
                        />
                    </LineChart>
                </ResponsiveContainer>
            </div>

            <div className='mt-2 text-xs text-gray-500 dark:text-gray-400'>
                Data points: {data.length} | Range:{' '}
                {formatPrice(Math.min(...data.map((d) => d.close)))} -{' '}
                {formatPrice(Math.max(...data.map((d) => d.close)))}
            </div>
        </div>
    );
}
EOF
Enter fullscreen mode Exit fullscreen mode

Create the Main Trading Interface

# Create main trading page
cat > app/routes/_index.tsx << 'EOF'
import { useState, useEffect, lazy } from 'react';
import { useLoaderData } from '@remix-run/react';
import { json, type MetaFunction } from '@remix-run/node';
import { Input } from '@heroui/input';
import { Button } from '@heroui/button';
import { Card, CardBody } from '@heroui/card';
import { Chip } from '@heroui/chip';
import type { TradingAnalysis } from '../../types';

// Client-side cache using sessionStorage for persistence across page reloads
const getCache = () => {
  if (typeof window === 'undefined') return new Map(); // Server-side fallback

  try {
    const cached = sessionStorage.getItem('trading-cache');
    return cached ? new Map(JSON.parse(cached)) : new Map();
  } catch {
    return new Map();
  }
};

const setCache = (key: string, value: TradingAnalysis) => {
  if (typeof window === 'undefined') return; // Server-side fallback

  try {
    const cache = getCache();
    cache.set(key, value);
    sessionStorage.setItem('trading-cache', JSON.stringify([...cache]));
  } catch {
    // Ignore storage errors
  }
};

const ChartComponent = lazy(() => import('../../components/PriceChart'));

export const meta: MetaFunction = () => {
    return [
        { title: 'LunarCrush AI Trading Terminal | MCP Powered' },
        {
            name: 'description',
            content:
                'Professional AI-powered trading terminal with real-time social intelligence',
        },
    ];
};

export async function loader() {
    return json({
        message: 'Trading terminal loaded successfully',
        env: {
            hasGeminiKey: !!process.env.GOOGLE_GEMINI_API_KEY,
            hasLunarCrushKey: !!process.env.LUNARCRUSH_API_KEY,
        },
    });
}

export default function TradingIndex() {
    const { env } = useLoaderData<typeof loader>();
    const [isClient, setIsClient] = useState(false);

    const [searchTerm, setSearchTerm] = useState('');
    const [progressStep, setProgressStep] = useState(0);
    const [progressPercent, setProgressPercent] = useState(0);
    const [subStepMessage, setSubStepMessage] = useState('');

    // State for analysis results
    const [analysis, setAnalysis] = useState<TradingAnalysis | null>(null);
    const [isAnalyzing, setIsAnalyzing] = useState(false);

    // Ensure we're on the client side to prevent hydration mismatch
    useEffect(() => {
        setIsClient(true);
    }, []);

    // Check if we have the required environment indicators
    const hasRequiredKeys = env.hasGeminiKey && env.hasLunarCrushKey;

    const progressSteps = [
        {
            label: 'Connecting to LunarCrush MCP',
            description: 'Establishing secure connection to LunarCrush API...',
        },
        {
            label: 'Fetching social & market data',
            description: 'Retrieving real-time social sentiment and market data...',
        },
        {
            label: 'Processing social metrics',
            description: 'Analyzing social metrics and engagement patterns...',
        },
        {
            label: 'Running Gemini AI analysis',
            description: 'Google Gemini AI processing complex data patterns...',
        },
        {
            label: 'Analyzing market patterns',
            description: 'Deep learning analysis of price movements and trends...',
        },
        {
            label: 'Generating insights',
            description: 'Creating personalized trading recommendations...',
        },
        {
            label: 'Finalizing recommendations',
            description: 'Compiling comprehensive analysis report...',
        },
        {
            label: 'Preparing results',
            description: 'Formatting and validating final output...',
        },
    ];
    async function analyzeSymbol(symbol = 'BTC'): Promise<TradingAnalysis> {
        console.log(`οΏ½ Starting server-side analysis for ${symbol}`);

        const formData = new FormData();
        formData.append('symbol', symbol);

        const response = await fetch('/api/analyze', {
            method: 'POST',
            body: formData,
        });
        // Check if the response is ok

        console.log(
            `οΏ½ Received response for ${symbol}. Response OK: ${response.ok}${response.status}`
        );

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || 'Analysis failed');
        }

        const data = await response.json();
        if (!data.success) {
            throw new Error(data.error || 'Analysis failed');
        }

        console.log(
            `βœ… Analysis completed for ${symbol} ${data.success} ${data.data}`
        );
        return data.data;
    }

    // Handle progress animation when loading starts
    useEffect(() => {
        let stepInterval: NodeJS.Timeout;
        let subStepInterval: NodeJS.Timeout;

        // Sub-step messages for the final phases (steps 3-7)
        const aiAnalysisMessages = [
            'Analyzing sentiment correlations...',
            'Processing market volatility patterns...',
            'Evaluating social momentum indicators...',
            'Cross-referencing technical signals...',
            'Calculating risk-adjusted probabilities...',
            'Generating confidence intervals...',
            'Optimizing recommendation logic...',
            'Validating analysis accuracy...',
            'Processing social engagement metrics...',
            'Analyzing price-volume relationships...',
            'Evaluating market maker behavior...',
            'Scanning for whale activity patterns...',
            'Computing social sentiment scores...',
            'Analyzing influencer impact metrics...',
            'Processing fear & greed indicators...',
            'Evaluating community growth trends...',
            'Calculating momentum divergences...',
            'Analyzing support & resistance levels...',
            'Processing order book dynamics...',
            'Evaluating liquidity pool data...',
            'Computing volatility projections...',
            'Analyzing institutional flow patterns...',
            'Processing news sentiment impact...',
            'Evaluating correlation matrices...',
            'Computing risk-reward ratios...',
            'Analyzing market cycle positioning...',
            'Processing fundamental indicators...',
            'Evaluating adoption metrics...',
            'Computing probability distributions...',
            'Finalizing recommendation synthesis...',
            'Preparing comprehensive report...',
            'Validating output consistency...',
            'Optimizing confidence scoring...',
            'Formatting analysis results...',
        ];

        if (isAnalyzing) {
            // Reset states immediately
            setProgressStep(0);
            setProgressPercent(0);
            setSubStepMessage('');

            // Step progression - advance every 2.5 seconds and update progress
            stepInterval = setInterval(() => {
                setProgressStep((prev) => {
                    const next = prev + 1;
                    const finalStep =
                        next >= progressSteps.length - 1 ? progressSteps.length - 1 : next;

                    // Update progress based on step completion
                    // Each step represents equal progress up to 90%
                    const progressByStep = Math.round(
                        (finalStep + 1) * (90 / progressSteps.length)
                    );
                    setProgressPercent(progressByStep);

                    return finalStep;
                });
            }, 3000); // Change step every 3 seconds

            // Sub-step messaging for AI analysis phases (starts after step 3)
            subStepInterval = setInterval(() => {
                setProgressStep((currentStep) => {
                    // Only show sub-messages during AI analysis phases (steps 3-7)
                    if (currentStep >= 4) {
                        const randomMessage =
                            aiAnalysisMessages[
                                Math.floor(Math.random() * aiAnalysisMessages.length)
                            ];
                        setSubStepMessage(randomMessage);
                    } else {
                        setSubStepMessage('');
                    }
                    return currentStep;
                });
            }, 4000); // Rotate sub-messages every 1.5 seconds
        } else {
            // Loading finished - complete the progress
            setProgressPercent(100);
            setProgressStep(progressSteps.length - 1);
            setSubStepMessage('Analysis complete!');

            // Reset after showing completion
            const resetTimeout = setTimeout(() => {
                setProgressPercent(0);
                setProgressStep(0);
                setSubStepMessage('');
            }, 2000);

            return () => clearTimeout(resetTimeout);
        }

        return () => {
            if (stepInterval) clearInterval(stepInterval);
            if (subStepInterval) clearInterval(subStepInterval);
        };
    }, [isAnalyzing, progressSteps.length]); // Only depend on analyzing state

    const handleSearch = async () => {
        if (!searchTerm.trim()) return;

        setIsAnalyzing(true);
        setAnalysis(null);
        setProgressStep(0);
        setProgressPercent(0);
        setSubStepMessage('');

        try {
            // Step 1: Initialize server-side analysis
            setProgressStep(1);
            setProgressPercent(12.5);
            setSubStepMessage('πŸš€ Initializing server-side analysis...');
            await new Promise((resolve) => setTimeout(resolve, 500));

            // Step 2: Connecting to APIs
            setProgressStep(2);
            setProgressPercent(25);
            setSubStepMessage('πŸ”— Connecting to LunarCrush and Gemini APIs...');
            await new Promise((resolve) => setTimeout(resolve, 300));

            // Step 3: Start the analysis
            setProgressStep(3);
            setProgressPercent(50);
            setSubStepMessage('⚑ Gemini orchestrating analysis...');

            let analysisResponse;

            // Only use cache on client side to prevent hydration mismatch
            if (isClient) {
                const cache = getCache();
                if (cache.has(searchTerm.toUpperCase())) {
                    console.log(`πŸ“ Cache hit for ${searchTerm.toUpperCase()}`);
                    analysisResponse = cache.get(searchTerm.toUpperCase());
                } else {
                    analysisResponse = await analyzeSymbol(searchTerm.toUpperCase());
                    setCache(searchTerm.toUpperCase(), analysisResponse);
                }
            } else {
                analysisResponse = await analyzeSymbol(searchTerm.toUpperCase());
            }

            // Step 4: Finalize results
            setProgressStep(4);
            setProgressPercent(100);
            setSubStepMessage('βœ… Analysis complete!');

            // Set the final analysis
            setAnalysis({
                ...analysisResponse,
                timestamp: new Date().toISOString(),
            });
        } catch (error) {
            console.error('❌ Analysis failed:', error);
            setSubStepMessage(
                `❌ Error: ${
                    error instanceof Error ? error.message : 'Analysis failed'
                }`
            );

            // Set error analysis
            setAnalysis({
                success: false,
                error: error instanceof Error ? error.message : 'Analysis failed',
                symbol: searchTerm.toUpperCase(),
                recommendation: 'HOLD',
                confidence: 0,
                reasoning: 'Analysis could not be completed due to technical issues.',
                social_sentiment: 'neutral',
                key_metrics: {},
                ai_analysis: {
                    summary: 'Analysis failed due to technical issues.',
                    pros: [],
                    cons: ['Technical error occurred'],
                    key_factors: [],
                },
                timestamp: new Date().toISOString(),
                chart_data: [],
            });
        } finally {
            // Always stop analyzing state
            setTimeout(() => {
                setIsAnalyzing(false);
            }, 1000);
        }
    };

    return (
        <div className='min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-indigo-900'>
            {/* Header */}
            <header className='border-b border-slate-700/50 bg-slate-900/50 backdrop-blur-xl'>
                <div className='max-w-7xl mx-auto px-6 py-4'>
                    <div className='flex items-center justify-between'>
                        <div className='flex items-center gap-3'>
                            <div className='w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center'>
                                <span className='text-white font-bold text-sm'>LC</span>
                            </div>
                            <div>
                                <h1 className='text-xl font-bold text-white'>
                                    AI Trading Agent
                                </h1>
                                <p className='text-xs text-slate-400'>
                                    Powered by the LunarCrush MCP & Google Gemini
                                </p>
                            </div>
                        </div>
                        <div className='flex items-center gap-2'>
                            <div
                                className={`w-2 h-2 rounded-full ${
                                    hasRequiredKeys ? 'bg-green-500 animate-pulse' : 'bg-red-500'
                                }`}></div>
                            <span className='text-sm text-slate-300'>
                                {hasRequiredKeys ? 'API Ready' : 'Missing API Keys'}
                            </span>
                            {hasRequiredKeys && (
                                <Chip
                                    size='sm'
                                    variant='flat'
                                    className='bg-green-500/20 text-green-300 text-xs'>
                                    Server-Side Analysis
                                </Chip>
                            )}
                        </div>
                    </div>
                </div>
            </header>

            <div className='max-w-7xl mx-auto px-6 py-8'>
                {/* Search Section */}
                <div className='mb-8'>
                    <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl'>
                        <CardBody className='p-6'>
                            <div className='flex items-center gap-4 mb-4'>
                                <div className='flex-1'>
                                    <Input
                                        placeholder='Enter symbol (BTC, ETH, DOGE, etc.)'
                                        value={searchTerm}
                                        onChange={(e) => setSearchTerm(e.target.value)}
                                        onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
                                        size='lg'
                                        variant='bordered'
                                        classNames={{
                                            input: 'text-white placeholder:text-slate-400',
                                            inputWrapper:
                                                'border-slate-600 bg-slate-700/50 hover:border-slate-500',
                                        }}
                                    />
                                </div>
                                <Button
                                    color='primary'
                                    size='lg'
                                    onPress={() => handleSearch()}
                                    isLoading={isAnalyzing}
                                    className='px-8 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700'>
                                    {isAnalyzing ? 'Analyzing...' : 'Analyze'}
                                </Button>
                            </div>

                            {/* Quick Select Coins */}
                            <div className='flex items-center gap-3'>
                                <span className='text-sm text-slate-400'>Quick select:</span>
                                <div className='flex gap-2'>
                                    {['BTC', 'ETH', 'SOL', 'ADA', 'DOGE'].map((coin) => (
                                        <Chip
                                            key={coin}
                                            variant='flat'
                                            className='cursor-pointer bg-slate-700/50 text-slate-300 hover:bg-slate-600/50 border-slate-600'
                                            onClick={() => setSearchTerm(coin)}>
                                            {coin}
                                        </Chip>
                                    ))}
                                </div>
                            </div>
                        </CardBody>
                    </Card>
                </div>

                {/* Enhanced Loading State with Progress Bar */}
                {isAnalyzing && (
                    <Card className='mb-10 bg-slate-800/40 border border-slate-700/30 backdrop-blur-2xl shadow-2xl overflow-hidden'>
                        <CardBody className='p-10'>
                            <div className='text-center'>
                                <div className='flex justify-center mb-8'>
                                    <div className='relative'>
                                        {/* Outer rotating ring */}
                                        <div className='w-20 h-20 border-4 border-slate-700/50 rounded-full'></div>
                                        {/* Main spinning ring */}
                                        <div className='absolute top-0 left-0 w-20 h-20 border-4 border-transparent border-t-blue-500 border-r-purple-500 rounded-full animate-spin'></div>
                                        {/* Inner pulsing dot */}
                                        <div className='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-3 h-3 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full animate-pulse'></div>
                                    </div>
                                </div>

                                <h3 className='text-2xl font-bold bg-gradient-to-r from-white to-slate-300 bg-clip-text text-transparent mb-3'>
                                    Analyzing {searchTerm}
                                </h3>
                                <p className='text-slate-400 mb-8'>
                                    Gathering comprehensive market intelligence...
                                </p>

                                {/* Progress Bar */}
                                <div className='max-w-lg mx-auto mb-8'>
                                    <div className='flex items-center justify-between mb-3'>
                                        <span className='text-sm text-slate-400'>Progress</span>
                                        <span className='text-sm font-medium text-blue-400'>
                                            {Math.round(progressPercent)}%
                                        </span>
                                    </div>
                                    <div className='w-full bg-slate-700/50 rounded-full h-3 overflow-hidden'>
                                        <div
                                            className='h-full bg-gradient-to-r from-blue-500 via-purple-500 to-cyan-500 rounded-full transition-all duration-300 ease-out relative'
                                            style={{ width: `${progressPercent}%` }}>
                                            <div className='absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-pulse'></div>
                                        </div>
                                    </div>
                                </div>

                                {/* Current Step Indicator */}
                                <div className='bg-slate-700/30 rounded-xl p-6 mb-6'>
                                    <div className='flex items-center justify-center gap-3 mb-4'>
                                        <div className='w-3 h-3 bg-blue-500 rounded-full animate-bounce'></div>
                                        <h4 className='text-blue-400 font-bold'>
                                            {progressStep < progressSteps.length
                                                ? progressSteps[progressStep].label
                                                : 'Finalizing results'}
                                        </h4>
                                    </div>
                                    <p className='text-slate-400 text-sm mb-3'>
                                        {progressStep < progressSteps.length
                                            ? progressSteps[progressStep].description
                                            : 'Preparing comprehensive analysis report...'}
                                    </p>
                                    {/* Sub-step messaging for AI analysis phases */}
                                    {subStepMessage && (
                                        <div className='flex items-center justify-center gap-2 mt-4 p-3 bg-slate-600/20 rounded-lg border border-slate-600/30'>
                                            <div className='w-2 h-2 bg-cyan-400 rounded-full animate-pulse'></div>
                                            <span className='text-cyan-300 text-sm font-medium'>
                                                {subStepMessage}
                                            </span>
                                        </div>
                                    )}
                                </div>

                                {/* Step Progress Indicators */}
                                <div className='grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-3 max-w-6xl mx-auto'>
                                    {[
                                        { icon: 'πŸ”—', label: 'Connect', step: 0 },
                                        { icon: 'πŸ“Š', label: 'Fetch Data', step: 1 },
                                        { icon: '⚑', label: 'Process', step: 2 },
                                        { icon: '🧠', label: 'AI Analysis', step: 3 },
                                        { icon: 'οΏ½', label: 'Patterns', step: 4 },
                                        { icon: 'πŸ’‘', label: 'Insights', step: 5 },
                                        { icon: '🎯', label: 'Recommend', step: 6 },
                                        { icon: 'βœ…', label: 'Complete', step: 7 },
                                    ].map((item, index) => (
                                        <div
                                            key={index}
                                            className={`flex flex-col items-center gap-2 p-3 rounded-xl border transition-all duration-300 ${
                                                progressStep >= item.step
                                                    ? 'bg-blue-500/20 border-blue-500/30 text-blue-400'
                                                    : 'bg-slate-700/20 border-slate-600/20 text-slate-500'
                                            }`}>
                                            <div
                                                className={`text-xl ${
                                                    progressStep >= item.step ? 'animate-bounce' : ''
                                                }`}>
                                                {item.icon}
                                            </div>
                                            <div className='text-xs font-medium text-center'>
                                                {item.label}
                                            </div>
                                            {progressStep === item.step && (
                                                <div className='w-1 h-1 bg-blue-400 rounded-full animate-ping'></div>
                                            )}
                                        </div>
                                    ))}
                                </div>
                            </div>
                        </CardBody>
                    </Card>
                )}

                {/* Results Display */}
                {analysis && analysis.success && (
                    <div className='grid lg:grid-cols-3 gap-6'>
                        {/* Left Column - Main Analysis */}
                        <div className='lg:col-span-2 space-y-6'>
                            {/* Signal Card */}
                            <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl'>
                                <CardBody className='p-6'>
                                    <div className='flex items-center justify-between mb-6'>
                                        <div>
                                            <h2 className='text-2xl font-bold text-white mb-1'>
                                                {analysis.symbol}
                                            </h2>
                                            <p className='text-slate-400 text-sm'>
                                                {new Date().toLocaleDateString('en-US', {
                                                    weekday: 'long',
                                                    year: 'numeric',
                                                    month: 'long',
                                                    day: 'numeric',
                                                })}
                                            </p>
                                        </div>
                                        <div className='text-right'>
                                            <Chip
                                                size='lg'
                                                className={`font-bold text-white ${
                                                    analysis.recommendation === 'BUY'
                                                        ? 'bg-gradient-to-r from-green-500 to-emerald-600'
                                                        : analysis.recommendation === 'SELL'
                                                        ? 'bg-gradient-to-r from-red-500 to-rose-600'
                                                        : 'bg-gradient-to-r from-yellow-500 to-orange-600'
                                                }`}>
                                                {analysis.recommendation}
                                            </Chip>
                                            <div className='text-slate-300 text-sm mt-2'>
                                                {analysis.confidence}% Confidence
                                            </div>
                                        </div>
                                    </div>

                                    {/* Reasoning */}
                                    <div className='bg-slate-700/30 rounded-lg p-4 mb-6'>
                                        <h4 className='text-white font-semibold mb-2'>
                                            Analysis Reasoning
                                        </h4>
                                        <p className='text-slate-300 text-sm leading-relaxed'>
                                            {analysis.reasoning}
                                        </p>
                                    </div>

                                    {/* Social Sentiment */}
                                    <div className='flex items-center gap-3'>
                                        <span className='text-slate-400 text-sm'>
                                            Social Sentiment:
                                        </span>
                                        <Chip
                                            variant='flat'
                                            className={`${
                                                analysis.social_sentiment === 'bullish'
                                                    ? 'bg-green-500/20 text-green-400 border-green-500/30'
                                                    : analysis.social_sentiment === 'bearish'
                                                    ? 'bg-red-500/20 text-red-400 border-red-500/30'
                                                    : 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'
                                            }`}>
                                            {analysis?.social_sentiment?.toUpperCase()}
                                        </Chip>
                                    </div>
                                </CardBody>
                            </Card>

                            {/* Key Metrics - Show on mobile above chart */}
                            {analysis.key_metrics && (
                                <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl lg:hidden'>
                                    <CardBody className='p-6'>
                                        <h4 className='text-white font-semibold mb-4'>
                                            Market Metrics
                                        </h4>
                                        <div className='space-y-3'>
                                            {Object.entries(analysis.key_metrics)
                                                .filter(
                                                    ([, value]) => value !== null && value !== undefined
                                                )
                                                .map(([key, value]) => {
                                                    const formatValue = (
                                                        key: string,
                                                        value: string | number
                                                    ) => {
                                                        if (typeof value !== 'number') return value;

                                                        // Price formatting
                                                        if (key.includes('price')) {
                                                            return `$${value.toLocaleString(undefined, {
                                                                minimumFractionDigits: 2,
                                                                maximumFractionDigits: 2,
                                                            })}`;
                                                        }

                                                        // Market cap and volume formatting
                                                        if (key.includes('cap') || key.includes('volume')) {
                                                            if (value >= 1e12)
                                                                return `$${(value / 1e12).toFixed(2)}T`;
                                                            if (value >= 1e9)
                                                                return `$${(value / 1e9).toFixed(2)}B`;
                                                            if (value >= 1e6)
                                                                return `$${(value / 1e6).toFixed(2)}M`;
                                                            if (value >= 1e3)
                                                                return `$${(value / 1e3).toFixed(2)}K`;
                                                            return `$${value.toLocaleString()}`;
                                                        }

                                                        // Score/rank formatting (no decimals)
                                                        if (
                                                            key.includes('score') ||
                                                            key.includes('rank') ||
                                                            key.includes('dominance')
                                                        ) {
                                                            return value.toFixed(0);
                                                        }

                                                        // Social metrics formatting
                                                        if (
                                                            key.includes('mentions') ||
                                                            key.includes('engagements') ||
                                                            key.includes('creators')
                                                        ) {
                                                            if (value >= 1e6)
                                                                return `${(value / 1e6).toFixed(1)}M`;
                                                            if (value >= 1e3)
                                                                return `${(value / 1e3).toFixed(1)}K`;
                                                            return value.toLocaleString();
                                                        }

                                                        return value.toLocaleString();
                                                    };

                                                    return (
                                                        <div
                                                            key={key}
                                                            className='flex items-center justify-between py-2'>
                                                            <span className='text-slate-400 text-sm capitalize'>
                                                                {key.replace(/_/g, ' ')}
                                                            </span>
                                                            <span className='text-white font-semibold text-sm'>
                                                                {formatValue(key, value as string | number)}
                                                            </span>
                                                        </div>
                                                    );
                                                })}
                                        </div>
                                    </CardBody>
                                </Card>
                            )}

                            {/* Price Chart */}
                            {analysis.chart_data && analysis.chart_data.length > 0 && (
                                <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl'>
                                    <CardBody className='p-6'>
                                        <ChartComponent
                                            data={analysis.chart_data}
                                            symbol={analysis.symbol}
                                        />
                                    </CardBody>
                                </Card>
                            )}

                            {/* AI Analysis */}
                            {analysis.ai_analysis &&
                                typeof analysis.ai_analysis === 'object' && (
                                    <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl'>
                                        <CardBody className='p-6'>
                                            <h4 className='text-white font-semibold mb-4'>
                                                AI Deep Analysis
                                            </h4>

                                            {/* Summary */}
                                            {analysis.ai_analysis.summary && (
                                                <div className='mb-6 p-4 bg-blue-500/10 border border-blue-500/20 rounded-lg'>
                                                    <h5 className='text-blue-400 font-medium mb-2'>
                                                        Executive Summary
                                                    </h5>
                                                    <p className='text-slate-300 text-sm leading-relaxed'>
                                                        {analysis.ai_analysis.summary}
                                                    </p>
                                                </div>
                                            )}

                                            {/* Pros and Cons */}
                                            <div className='grid md:grid-cols-2 gap-4'>
                                                {/* Pros */}
                                                {analysis.ai_analysis.pros && (
                                                    <div className='p-4 bg-green-500/10 border border-green-500/20 rounded-lg'>
                                                        <h5 className='text-green-400 font-medium mb-3 flex items-center gap-2'>
                                                            <span className='w-2 h-2 bg-green-400 rounded-full'></span>
                                                            Bullish Factors
                                                        </h5>
                                                        <ul className='space-y-2'>
                                                            {analysis.ai_analysis.pros.map(
                                                                (pro: string, index: number) => (
                                                                    <li
                                                                        key={index}
                                                                        className='text-slate-300 text-sm flex items-start gap-2'>
                                                                        <span className='text-green-400 mt-1'>
                                                                            β€’
                                                                        </span>
                                                                        <span>{pro}</span>
                                                                    </li>
                                                                )
                                                            )}
                                                        </ul>
                                                    </div>
                                                )}

                                                {/* Cons */}
                                                {analysis.ai_analysis.cons && (
                                                    <div className='p-4 bg-red-500/10 border border-red-500/20 rounded-lg'>
                                                        <h5 className='text-red-400 font-medium mb-3 flex items-center gap-2'>
                                                            <span className='w-2 h-2 bg-red-400 rounded-full'></span>
                                                            Risk Factors
                                                        </h5>
                                                        <ul className='space-y-2'>
                                                            {analysis.ai_analysis.cons.map(
                                                                (con: string, index: number) => (
                                                                    <li
                                                                        key={index}
                                                                        className='text-slate-300 text-sm flex items-start gap-2'>
                                                                        <span className='text-red-400 mt-1'>β€’</span>
                                                                        <span>{con}</span>
                                                                    </li>
                                                                )
                                                            )}
                                                        </ul>
                                                    </div>
                                                )}
                                            </div>

                                            {/* Key Factors */}
                                            {analysis.ai_analysis.key_factors && (
                                                <div className='mt-4 p-4 bg-purple-500/10 border border-purple-500/20 rounded-lg'>
                                                    <h5 className='text-purple-400 font-medium mb-3'>
                                                        Key Factors to Monitor
                                                    </h5>
                                                    <div className='flex flex-wrap gap-2'>
                                                        {analysis.ai_analysis.key_factors.map(
                                                            (factor: string, index: number) => (
                                                                <Chip
                                                                    key={index}
                                                                    variant='flat'
                                                                    className='bg-purple-500/20 text-purple-300 border-purple-500/30 text-xs text-wrap h-12'>
                                                                    {factor}
                                                                </Chip>
                                                            )
                                                        )}
                                                    </div>
                                                </div>
                                            )}
                                        </CardBody>
                                    </Card>
                                )}
                        </div>

                        {/* Right Column - Metrics */}
                        <div className='space-y-6'>
                            {/* Key Metrics - Hidden on mobile, shown on desktop */}
                            {analysis.key_metrics && (
                                <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl hidden lg:block'>
                                    <CardBody className='p-6'>
                                        <h4 className='text-white font-semibold mb-4'>
                                            Market Metrics
                                        </h4>
                                        <div className='space-y-3'>
                                            {Object.entries(analysis.key_metrics)
                                                .filter(
                                                    ([, value]) => value !== null && value !== undefined
                                                )
                                                .map(([key, value]) => {
                                                    const formatValue = (
                                                        key: string,
                                                        value: string | number
                                                    ) => {
                                                        if (typeof value !== 'number') return value;

                                                        // Price formatting
                                                        if (key.includes('price')) {
                                                            return `$${value.toLocaleString(undefined, {
                                                                minimumFractionDigits: 2,
                                                                maximumFractionDigits: 2,
                                                            })}`;
                                                        }

                                                        // Market cap and volume formatting
                                                        if (key.includes('cap') || key.includes('volume')) {
                                                            if (value >= 1e12)
                                                                return `$${(value / 1e12).toFixed(2)}T`;
                                                            if (value >= 1e9)
                                                                return `$${(value / 1e9).toFixed(2)}B`;
                                                            if (value >= 1e6)
                                                                return `$${(value / 1e6).toFixed(2)}M`;
                                                            if (value >= 1e3)
                                                                return `$${(value / 1e3).toFixed(2)}K`;
                                                            return `$${value.toLocaleString()}`;
                                                        }

                                                        // Score/rank formatting (no decimals)
                                                        if (
                                                            key.includes('score') ||
                                                            key.includes('rank') ||
                                                            key.includes('dominance')
                                                        ) {
                                                            return value.toFixed(0);
                                                        }

                                                        // Social metrics formatting
                                                        if (
                                                            key.includes('mentions') ||
                                                            key.includes('engagements') ||
                                                            key.includes('creators')
                                                        ) {
                                                            if (value >= 1e6)
                                                                return `${(value / 1e6).toFixed(1)}M`;
                                                            if (value >= 1e3)
                                                                return `${(value / 1e3).toFixed(1)}K`;
                                                            return value.toLocaleString();
                                                        }

                                                        return value.toLocaleString();
                                                    };

                                                    return (
                                                        <div
                                                            key={key}
                                                            className='flex items-center justify-between py-2'>
                                                            <span className='text-slate-400 text-sm capitalize'>
                                                                {key.replace(/_/g, ' ')}
                                                            </span>
                                                            <span className='text-white font-semibold text-sm'>
                                                                {formatValue(key, value as string | number)}
                                                            </span>
                                                        </div>
                                                    );
                                                })}
                                        </div>
                                    </CardBody>
                                </Card>
                            )}

                            {/* MCP Status */}
                            <Card className='bg-slate-800/50 border-slate-700/50 backdrop-blur-xl'>
                                <CardBody className='p-6'>
                                    <h4 className='text-white font-semibold mb-4'>
                                        Data Sources
                                    </h4>
                                    <div className='space-y-3'>
                                        <div className='flex items-center gap-3'>
                                            <div className='w-2 h-2 bg-green-500 rounded-full'></div>
                                            <span className='text-slate-300 text-sm'>
                                                LunarCrush MCP
                                            </span>
                                        </div>
                                        <div className='flex items-center gap-3'>
                                            <div className='w-2 h-2 bg-blue-500 rounded-full'></div>
                                            <span className='text-slate-300 text-sm'>
                                                Google Gemini AI
                                            </span>
                                        </div>
                                        <div className='flex items-center gap-3'>
                                            <div className='w-2 h-2 bg-purple-500 rounded-full'></div>
                                            <span className='text-slate-300 text-sm'>
                                                Real-time Analysis
                                            </span>
                                        </div>
                                    </div>
                                </CardBody>
                            </Card>

                            {/* Disclaimer */}
                            <Card className='bg-amber-500/10 border-amber-500/20 backdrop-blur-xl'>
                                <CardBody className='p-4'>
                                    <h5 className='text-amber-400 font-medium text-sm mb-2'>
                                        ⚠️ Disclaimer
                                    </h5>
                                    <p className='text-amber-300/80 text-xs leading-relaxed'>
                                        This analysis is for informational purposes only and should
                                        not be considered financial advice. Always do your own
                                        research before making investment decisions.
                                    </p>
                                </CardBody>
                            </Card>
                        </div>
                    </div>
                )}

                {/* Error State */}
                {analysis && !analysis.success && (
                    <Card className='bg-red-500/10 border-red-500/20 backdrop-blur-xl'>
                        <CardBody className='p-6'>
                            <h3 className='text-red-400 font-semibold mb-2'>
                                Analysis Error
                            </h3>
                            <p className='text-red-300 text-sm'>
                                {analysis.error ||
                                    'An error occurred during analysis. Please try again.'}
                            </p>
                        </CardBody>
                    </Card>
                )}
            </div>

            {/* Footer */}
            <footer className='relative mt-20 border-t border-slate-700/30 bg-slate-900/80 backdrop-blur-2xl'>
                <div className='max-w-7xl mx-auto px-6 py-12'>
                    <div className='grid md:grid-cols-4 gap-8 mb-8'>
                        {/* Main Info */}
                        <div className='md:col-span-1'>
                            <div className='flex items-center gap-3 mb-4'>
                                <div className='w-10 h-10 bg-gradient-to-br from-blue-500 via-purple-500 to-cyan-500 rounded-xl flex items-center justify-center'>
                                    <span className='text-white font-bold text-sm'>LC</span>
                                </div>
                                <div>
                                    <h3 className='text-lg font-bold text-white'>
                                        LunarCrush AI
                                    </h3>
                                    <p className='text-xs text-slate-400'>Trading Terminal</p>
                                </div>
                            </div>
                            <p className='text-slate-400 text-sm leading-relaxed mb-4'>
                                Intelligent trading signals powered by social sentiment analysis
                                and Google Gemini AI.
                            </p>
                            <a
                                href='https://github.com/danilobatson/lunarcrush_mcp'
                                target='_blank'
                                rel='noopener noreferrer'
                                className='inline-flex items-center gap-2 text-blue-400 hover:text-blue-300 text-sm font-medium transition-colors'>
                                <span>πŸ”—</span>
                                View Source Code
                            </a>
                        </div>

                        {/* Built With */}
                        <div>
                            <h4 className='text-white font-bold mb-4'>Built With</h4>
                            <div className='space-y-3'>
                                <a
                                    href='https://remix.run/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-blue-400 rounded-full'></span>
                                    Remix - Full Stack Framework
                                </a>
                                <a
                                    href='https://www.typescriptlang.org/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-blue-400 rounded-full'></span>
                                    TypeScript - Type Safety
                                </a>
                                <a
                                    href='https://tailwindcss.com/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-cyan-400 rounded-full'></span>
                                    Tailwind CSS - Styling
                                </a>
                                <a
                                    href='https://heroui.com/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-purple-400 rounded-full'></span>
                                    HeroUI - Components
                                </a>
                            </div>
                        </div>

                        {/* Powered By */}
                        <div>
                            <h4 className='text-white font-bold mb-4'>Powered By</h4>
                            <div className='space-y-3'>
                                <a
                                    href='https://lunarcrush.com/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-green-400 rounded-full'></span>
                                    LunarCrush - Social Analytics
                                </a>
                                <a
                                    href='https://ai.google.dev/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-yellow-400 rounded-full'></span>
                                    Google Gemini - AI Analysis
                                </a>
                                <a
                                    href='https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#writing-mcp-clients'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-purple-400 rounded-full'></span>
                                    MCP TypeScript SDK
                                </a>
                                <a
                                    href='https://vite.dev/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-orange-400 rounded-full'></span>
                                    Vite - Build Tool
                                </a>
                            </div>
                        </div>

                        {/* Resources */}
                        <div>
                            <h4 className='text-white font-bold mb-4'>Resources</h4>
                            <div className='space-y-3'>
                                <a
                                    href='https://lunarcrush.com/about/api'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-blue-400 rounded-full'></span>
                                    LunarCrush API Docs
                                </a>
                                <a
                                    href='https://ai.google.dev/docs'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-yellow-400 rounded-full'></span>
                                    Gemini AI Documentation
                                </a>
                                <a
                                    href='https://vercel.com/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-purple-400 rounded-full'></span>
                                    Vercel - Deployment
                                </a>
                                <a
                                    href='https://remix.run/docs'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors'>
                                    <span className='w-2 h-2 bg-cyan-400 rounded-full'></span>
                                    Remix Documentation
                                </a>
                            </div>
                        </div>
                    </div>

                    {/* Bottom Section */}
                    <div className='pt-8 border-t border-slate-700/30'>
                        <div className='flex flex-col md:flex-row items-center justify-between gap-4'>
                            <div className='text-slate-400 text-sm'>
                                Β© 2025 LunarCrush MCP Powered AI Trading Agent. Built for
                                demonstration purposes.
                            </div>
                            <div className='flex items-center gap-1 text-sm text-slate-400'>
                                <span>Powered by:</span>
                                <a
                                    href='https://lunarcrush.com/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='text-blue-400 hover:text-blue-300 transition-colors'>
                                    LunarCrush
                                </a>
                                <span>β€’</span>
                                <a
                                    href='https://ai.google.dev/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='text-yellow-400 hover:text-yellow-300 transition-colors'>
                                    Gemini AI
                                </a>
                                <span>β€’</span>
                                <a
                                    href='https://danilobatson.github.io/'
                                    target='_blank'
                                    rel='noopener noreferrer'
                                    className='text-purple-400 hover:text-purple-300 transition-colors'>
                                    Developer Portfolio
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            </footer>
        </div>
    );
}
EOF
Enter fullscreen mode Exit fullscreen mode

Create the Analysis API Endpoint

Server-Side MCP Integration

# Create the analysis API route
cat > app/routes/api.analyze.ts << 'EOF'
import { json, type ActionFunctionArgs } from '@remix-run/node';
import { GoogleGenAI } from '@google/genai';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import type { 
  TradingAnalysis, 
  GeminiResponse, 
  McpTool, 
  ToolCall, 
  ToolResult 
} from '../../types';

async function geminiFlashResponse(
    ai: GoogleGenAI,
    contents: string
): Promise<GeminiResponse> {
    return await ai.models.generateContent({
        model: 'gemini-2.0-flash-lite',
        contents: contents,
    });
}

function createOrchestrationPrompt(
    symbol: string,
    availableTools: Record<string, McpTool>
): string {
    return `
You are a cryptocurrency analyst. I need you to analyze ${symbol.toUpperCase()} using the available LunarCrush MCP tools. Use a MAX of four tools.

AVAILABLE MCP TOOLS:
${JSON.stringify(availableTools, null, 2)}

TASK: Create a plan to gather comprehensive data for ${symbol.toUpperCase()} trading analysis.

Based on the available tools, decide which tools to call and with what parameters to get:
1. Current price and market data
2. Social sentiment metrics
3. Historical performance data
4. Ranking and positioning data
5. Get one week price historical time series data for charting purposes. Look only for the price metrics.

Prioritize getting data for the one week price chart. The price chart is important. If you don't get data back in the response try a few different solutions to get the data (e.g. try the name of the coin FIRST then try the symbol). When using the Cryptocurrencies tool it should have no limit but sorted by market cap.

Respond with a JSON array of tool calls in this exact format:
[
{
  "tool": "tool_name",
  "args": {"param": "value"},
  "reason": "Short reason why this tool call is needed"
}
]

Be specific with parameters. For example, if you need to find ${symbol} in a list first, plan that step.
`;
}

async function executeGeminiToolChoices(
    symbol: string,
    orchestrationResponse: GeminiResponse,
    client: Client
): Promise<Record<string, unknown>> {
    console.time(`ToolExecution for ${symbol}`);
    try {
        const responseText =
            orchestrationResponse.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
        console.log('πŸ€– Gemini orchestration response:', responseText);

        // Extract JSON array from response
        const jsonMatch = responseText?.match(/\[[\s\S]*\]/);
        if (!jsonMatch) {
            console.log('⚠️ No JSON array found, using fallback');
            console.timeEnd(`ToolExecution for ${symbol}`);
            return {
                symbol: symbol.toUpperCase(),
                toolResults: [],
                error: 'No tool calls found in response',
            };
        }

        const toolCalls: ToolCall[] = JSON.parse(jsonMatch[0]);
        const gatheredData: {
            symbol: string;
            toolResults: Array<{
                tool: string;
                args: Record<string, unknown>;
                reason: string;
                result?: unknown;
                error?: string;
            }>;
        } = {
            symbol: symbol.toUpperCase(),
            toolResults: [],
        };

        // Execute tool calls concurrently with Promise.all
        const toolPromises = toolCalls.map(async (toolCall: ToolCall) => {
            try {
                // Check if tool is Cryptocurrencies and cached

                console.log(`πŸ› οΈ Executing: ${toolCall.tool} - ${toolCall.reason}`);
                const result = await client.callTool({
                    name: toolCall.tool,
                    arguments: toolCall.args,
                });

                return {
                    tool: toolCall.tool,
                    args: toolCall.args,
                    reason: toolCall.reason,
                    result,
                };
            } catch (error) {
                console.error(`❌ Tool ${toolCall.tool} failed:`, error);
                return {
                    tool: toolCall.tool,
                    args: toolCall.args,
                    reason: toolCall.reason,
                    error: error instanceof Error ? error.message : 'Unknown error',
                };
            }
        });

        gatheredData.toolResults = (await Promise.all(
            toolPromises
        )) as ToolResult[];
        console.timeEnd(`ToolExecution for ${symbol}`);
        return gatheredData;
    } catch (error) {
        console.error('❌ Error executing tool choices:', error);
        console.timeEnd(`ToolExecution for ${symbol}`);
        return {
            symbol: symbol.toUpperCase(),
            toolResults: [],
            error: error instanceof Error ? error.message : 'Unknown error',
        };
    }
}

function createAnalysisPrompt(
    symbol: string,
    gatheredData: Record<string, unknown>
): string {
    return `
You are an expert cryptocurrency analyst. Analyze the following data for ${symbol.toUpperCase()} gathered from LunarCrush MCP tools and provide a comprehensive trading recommendation. Keep it short for faster response times.

GATHERED DATA FROM MCP TOOLS:
${JSON.stringify(gatheredData, null, 2)}

ANALYSIS REQUIREMENTS:
Based on the above data from the MCP tools, provide a detailed trading analysis. Look for:

1. CURRENT MARKET DATA:
 - Real current price (not demo data)
 - Market cap and volume
 - Recent performance metrics

2. SOCIAL SENTIMENT:
 - Social mentions and engagement
 - Galaxy Score and health indicators
 - Community sentiment trends

3. POSITIONING DATA:
 - AltRank and market positioning
 - Relative performance vs other cryptocurrencies

4. CHART DATA:
 - Price trends over the last week
 - Could be used to create a chart
 - Could be under close instead of price
 - If you find price/time series data, include ONLY 12AM and 12PM time data points
 - Format as: [{"time": "2025-06-10 07:00", "close": 2675.51}, ...]
 - Keep chart_data small to prevent response truncation

 Respond with a JSON array of tool calls in this exact format:
{
"recommendation": "BUY|SELL|HOLD",
"confidence": 0-100,
"reasoning": "Brief explanation of the recommendation",
"social_sentiment": "bullish|bearish|neutral",
"key_metrics": {
  "price": "actual price from MCP data",
  "galaxy_score": "score from data",
  "alt_rank": "rank from data",
  "social_dominance": "dominance from data",
  "market_cap": "cap from data",
  "volume_24h": "volume from data",
  "mentions": "mentions from data",
  "engagements": "engagements/interactions from data",
  "creators": "creators from data"
},
"ai_analysis": {
  "summary": "1-2 sentence overview of the analysis",
  "pros": ["Positive factor 1", "Positive factor 2", "etc"],
  "cons": ["Risk factor 1", "Risk factor 2", "etc"],
  "key_factors": ["Important factor to monitor 1", "Important factor 2", "etc"]
},
"chart_data": [{"time": "2025-06-10 07:00", "close": 2675.51}],
"miscellaneous": "Any other relevant insights"
}

IMPORTANT:
- Use ONLY actual data from the MCP tools, not placeholder values
- Make the analysis beginner-friendly, concise, and educational
- Focus on explaining WHY the recommendation is made
- Extract real metrics from the gathered data
- Do not break JSON response format instructed above
`;
}

function parseAnalysisResponse(
    response: GeminiResponse,
    symbol: string,
    cryptoData: Record<string, unknown>
): TradingAnalysis {
    try {
        const text = response.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
        if (!text) {
            throw new Error('No response from Gemini');
        }

        console.log('πŸ€– Gemini raw response:', text);

        // Extract JSON from response with better handling
        const jsonMatch = text.match(/\{[\s\S]*\}/);
        if (!jsonMatch) {
            throw new Error('No JSON found in Gemini response');
        }

        let jsonText = jsonMatch[0];

        // Handle truncated JSON by trying to fix common issues
        if (!jsonText.endsWith('}')) {
            // Find the last complete field before truncation
            const lastCompleteField = jsonText.lastIndexOf('"}');
            if (lastCompleteField > 0) {
                jsonText = jsonText.substring(0, lastCompleteField + 2) + '}';
            }
        }

        const analysis = JSON.parse(jsonText);

        // Validate and format response
        return {
            symbol: symbol.toUpperCase(),
            recommendation: analysis.recommendation || 'HOLD',
            confidence: analysis.confidence || 50,
            reasoning: analysis.reasoning || 'Analysis completed',
            social_sentiment: analysis.social_sentiment || 'neutral',
            key_metrics: analysis.key_metrics,
            ai_analysis:
                analysis.ai_analysis || analysis.reasoning || 'AI analysis completed',
            timestamp: new Date().toISOString(),
            chart_data: transformChartData(analysis.chart_data || []),
            success: true, // Add success property
        };
    } catch (error) {
        console.error('❌ Error parsing Gemini response:', error);

        // Fallback response
        return {
            symbol: symbol.toUpperCase(),
            recommendation: 'HOLD',
            confidence: 50,
            reasoning: 'Analysis completed with limited data',
            social_sentiment: 'neutral',
            key_metrics: cryptoData || {}, // Fix: remove 'this.' and use fallback
            ai_analysis: {
                summary: 'Unable to complete full AI analysis. Please try again.',
                pros: [],
                cons: ['Analysis parsing failed'],
                key_factors: [],
            },
            timestamp: new Date().toISOString(),
            chart_data: [],
            success: true, // Add success property even for fallback
        };
    }
}

function transformChartData(
    chartData: Array<{
        time?: string;
        date?: string;
        close?: number;
        price?: number;
    }>
): Array<{ date: string; price: number }> {
    if (!Array.isArray(chartData) || chartData.length === 0) {
        return [];
    }

    // Transform and filter valid data points
    const transformedData = chartData
        .map((item) => ({
            date: item.time || item.date || '',
            price: item.close || item.price || 0,
        }))
        .filter((item) => item.date && item.price > 0)
        .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());

    return transformedData;
}

// Initialize Gemini AI with server-side API key
const getGeminiAI = () => {
    const apiKey = process.env.GOOGLE_GEMINI_API_KEY;
    if (!apiKey) {
        throw new Error('GOOGLE_GEMINI_API_KEY environment variable is required');
    }
    return new GoogleGenAI({ apiKey });
};

// Create a server-side MCP client using official SDK
async function createMCPClient(apiKey: string): Promise<Client> {
    console.log('πŸ”„ Initializing MCP client with official SDK...');

    // Create SSE transport for LunarCrush MCP server
    const transport = new SSEClientTransport(
        new URL(`https://lunarcrush.ai/sse?key=${apiKey}`)
    );

    // Create MCP client
    const client = new Client(
        {
            name: 'lunarcrush-mcp-trading',
            version: '1.0.0',
        },
        {
            capabilities: {
                tools: {},
            },
        }
    );

    // Connect to the server
    await client.connect(transport);
    console.log('βœ… MCP client connected successfully');

    return client;
}

export async function action({ request }: ActionFunctionArgs) {
    if (request.method !== 'POST') {
        return json({ error: 'Method not allowed' }, { status: 405 });
    }

    let client: Client | null = null;

    try {
        const formData = await request.formData();
        const symbol = formData.get('symbol') as string;

        if (!symbol) {
            return json({ error: 'Symbol is required' }, { status: 400 });
        }

        console.log(`πŸš€ Starting server-side MCP analysis for ${symbol}`);

        // Step 1: Create and connect MCP client
        const apiKey = process.env.LUNARCRUSH_API_KEY;
        if (!apiKey) {
            throw new Error('LUNARCRUSH_API_KEY environment variable is required');
        }

        client = await createMCPClient(apiKey);
        const ai = getGeminiAI();

        console.log('πŸ”„ MCP client initialized successfully');
        console.log(`πŸ”„ Fetching available MCP tools...`);

        const { tools } = await client.listTools();

        console.log(
            `πŸ“‹ Available MCP tools: ${tools.map((t) => t.name).join(', ')}`
        );

        const chooseToolsPrompt = createOrchestrationPrompt(symbol, tools as unknown as Record<string, McpTool>);

        console.log(`πŸ€– Letting Gemini choose tools for ${symbol} analysis...`);

        const chooseToolsResponse = await geminiFlashResponse(
            ai,
            chooseToolsPrompt
        );

        // Step 2: Execute Gemini's tool choices to gather data

        console.log('πŸ€– Gemini orchestrator response:', chooseToolsResponse);

        const gatheredData = await executeGeminiToolChoices(
            symbol,
            chooseToolsResponse,
            client
        );

        const analysisPrompt = createAnalysisPrompt(symbol, gatheredData);

        console.log('🧠 Generating final analysis...');

        // Step 3: Let Gemini analyze the collected data

        const analysisResponse = await geminiFlashResponse(ai, analysisPrompt);

        console.log('πŸ€– Gemini final analysis response:', analysisResponse);

        return json({
            data: parseAnalysisResponse(analysisResponse, symbol, gatheredData),
            success: true,
        });
    } catch (error) {
        console.error('Server-side MCP analysis error:', error);
        return json(
            {
                success: false,
                error: error instanceof Error ? error.message : 'Analysis failed',
            },
            { status: 500 }
        );
    } finally {
        // Clean up MCP client connection
        if (client) {
            try {
                await client.close();
                console.log('🧹 MCP client connection closed');
            } catch (cleanupError) {
                console.warn('Warning: MCP client cleanup failed:', cleanupError);
            }
        }
    }
}
EOF
Enter fullscreen mode Exit fullscreen mode

Testing & Deployment

Local Testing

# Start your development server
npm run dev  # Remix app (localhost:5173)
Enter fullscreen mode Exit fullscreen mode

Verify Everything Works:

  1. πŸ” Dashboard loads: Visit localhost:5173
  2. πŸš€ MCP Analysis triggers: Enter a symbol like "BTC" and click "Analyze"
  3. πŸ“Š Progress tracking works: Watch the 6-step progress complete
  4. πŸ’Ύ Quick select works: Try the BTC, ETH, SOL buttons
  5. πŸ“ˆ Charts render: Toggle between Simple and Advanced chart views
  6. πŸ”„ Cache works: Re-analyze the same symbol for instant results

πŸ” Pro Debugging Tip: Use browser dev tools Network tab to monitor API calls and check the MCP connection status indicator in the header.

Deploy to Production

Deploy on Vercel (Recommended):

Deploy with Vercel

  1. Push to GitHub:
git init
git add .
git commit -m "LunarCrush MCP AI Trading Terminal with Remix"
git branch -M main
git remote add origin https://github.com/danilobatson/lunarcrush_mcp.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode
  1. Connect to Vercel:
    β€’ Visit vercel.com and sign up
    β€’ Import your GitHub repository
    β€’ Configure project settings for Remix

  2. Add Environment Variables in Vercel:
    β€’ Go to Settings β†’ Environment Variables
    β€’ Add all variables from your .env.local:

LUNARCRUSH_API_KEY=lc_your_api_key_here
GOOGLE_GEMINI_API_KEY=your_gemini_key_here
Enter fullscreen mode Exit fullscreen mode
  1. Deploy: β€’ Click "Deploy" in Vercel β€’ Wait for deployment to complete β€’ Test your live application

Live Example: You can see a deployed version at https://lunarcrush-mcp.vercel.app/


Troubleshooting Common Issues

Here are solutions to the most common problems:

Issue Symptoms Solution
MCP Connection Status Red Red indicator in header, analysis fails Check environment variables and API key validity. Restart dev server.
Charts Not Loading "Loading chart..." never completes Verify recharts dependency installed: npm install recharts
Quick Select Not Working Buttons don't trigger analysis Check browser console for JavaScript errors, ensure React state updates
Environment Variables Missing "API key not configured" errors Ensure both LUNARCRUSH_API_KEY and GOOGLE_GEMINI_API_KEY in .env.local
Gemini API Errors "AI analysis failed" messages Check Google AI quota at aistudio.google.com, verify API key format
Cache Issues Stale analysis results Clear browser sessionStorage: sessionStorage.clear() in dev tools console
Build Errors TypeScript or import errors Run npm install and check all imports match the actual file structure

Level Up: Adding Advanced Features

Want to make this even more impressive? Here are some powerful extensions:

AI Enhancement Prompts

Real-time WebSocket Integration:

"Add WebSocket integration to this Remix trading terminal for real-time price updates. Connect to the existing MCP infrastructure and add live price streaming with connection status indicators and automatic reconnection logic."
Enter fullscreen mode Exit fullscreen mode

Portfolio Management Dashboard:

"Extend this MCP trading terminal with a portfolio tracking feature. Allow users to save multiple cryptocurrency positions, track profit/loss calculations, and get AI-powered portfolio recommendations using the existing analysis structure."
Enter fullscreen mode Exit fullscreen mode

Multi-Asset Analysis:

"Add batch analysis capabilities to analyze multiple cryptocurrencies simultaneously using MCP tool orchestration. Include comparison views, correlation analysis, and portfolio optimization recommendations."
Enter fullscreen mode Exit fullscreen mode

Ready to Scale?

LunarCrush MCP Server Benefits:
β€’ 11+ Specialized Tools for comprehensive social intelligence

β€’ Real-time Data Streaming with protocol-level optimization
β€’ Enterprise-Grade Rate Limiting built into the MCP layer
β€’ AI-Native Integration designed for intelligent orchestration

MCP Ecosystem Expansion:
β€’ Explore other MCP servers for different data sources (news, social media, market data)
β€’ Build custom MCP tools for proprietary data sources
β€’ Integrate multiple MCP servers for comprehensive multi-source analysis
β€’ Create MCP tool chains for complex multi-step analysis workflows


Conclusion

Congratulations! You've successfully built a production-ready AI Trading Terminal that demonstrates cutting-edge development patterns with Model Context Protocol.

What You've Accomplished

β€’ βœ… MCP Integration - Secure AI-to-data connections with standardized protocols
β€’ βœ… AI Orchestration - Gemini intelligently selecting and combining data tools
β€’ βœ… Real-time Processing - Live progress tracking through complex workflows
β€’ βœ… Modern Architecture - Remix SSR with advanced React patterns and hooks
β€’ βœ… Professional UI/UX - Beautiful, responsive interface with dual chart modes
β€’ βœ… Production Ready - Comprehensive error handling, caching, and deployment configuration
β€’ βœ… Performance Optimized - Smart caching, lazy loading, and efficient state management

What's Next?

Extend Your Trading Terminal:
β€’ Add multi-cryptocurrency comparison and correlation analysis
β€’ Implement advanced portfolio management and position tracking
β€’ Create custom alert systems with email/SMS notifications via MCP tools
β€’ Build sophisticated technical analysis with multiple chart indicators

Production Optimizations:
β€’ Set up comprehensive monitoring with Sentry or LogRocket
β€’ Implement advanced rate limiting and request validation
β€’ Add Redis for distributed server-side caching

Advanced MCP Features:
β€’ Multi-server MCP orchestration with different data providers
β€’ Custom MCP tool development for proprietary data sources
β€’ Real-time data streaming with enhanced MCP protocol features
β€’ Complex AI agent workflows with multiple AI providers and decision trees

Key Technical Insights

MCP Protocol Advantages Demonstrated:

  • Tool Discovery: Dynamic detection of available data sources
  • Intelligent Orchestration: AI-driven selection of optimal tools
  • Protocol-Level Error Handling: Robust connection management
  • Standardized Responses: Consistent data formats across tools
  • Performance Optimization: Efficient data retrieval and caching

React/Remix Patterns Showcased:

  • Lazy Loading: Charts loaded on-demand for better performance
  • Smart Caching: SessionStorage integration for instant re-analysis
  • Real-time State Management: Progress tracking with useEffect hooks
  • Error Boundaries: Graceful degradation without page crashes
  • Responsive Design: Mobile-first approach with HeroUI components

πŸš€ Take Action

Get Started Now:

  1. Subscribe To LunarCrush API - Get access to unique social metrics and MCP tools
  2. Fork the Repository - Start building your own enhanced version
  3. Join the Developer Community - Share your enhancements and improvements

Share Your Success:
β€’ Tweet your deployed app with #LunarCrush #MCP #AI #Remix
β€’ Write about your MCP development experience on LinkedIn
β€’ Submit to developer showcases and tech community platforms
β€’ Create video walkthroughs demonstrating the MCP integration

Learn More:
β€’ Dive deeper into MCP protocol documentation and best practices
β€’ Explore advanced Remix patterns for SSR and performance optimization
β€’ Experiment with other AI providers (Claude, OpenAI) for comparison
β€’ Build custom MCP servers for your own data sources


Resources & Documentation

β€’ LunarCrush API Documentation - Complete API reference
β€’ Google Gemini AI Documentation - AI model capabilities and limits
β€’ Remix Framework Documentation - Full-stack web development
β€’ HeroUI Component Library - React component system

πŸš€ Complete GitHub Repository

Built with ❀️ using LunarCrush β€’ Google Gemini β€’ Remix β€’ HeroUI


Questions or Issues? Drop them below! I respond to every comment and love helping fellow developers build amazing applications with MCP and AI integration. πŸš€

Ready to revolutionize how AI accesses real-time data? Start building your MCP-powered AI trading terminal today and join the next generation of intelligent application development!

Get LunarCrush MCP Access β†’

Use my discount referral code JAMAALBUILDS to receive 15% off your plan.

Top comments (0)