> ## Documentation Index
> Fetch the complete documentation index at: https://finance.chiefpriest.design/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Integration

> Integrate the Financial MCP Server with web applications and mobile apps

## Overview

The Financial MCP Server provides a REST API that can be easily integrated into any frontend application. This guide covers integration patterns for popular frameworks and platforms.

## React Integration

### Basic Setup

<CodeGroup>
  ```javascript API Client theme={null}
  // api/financialClient.js
  const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8001/api/v1';

  class FinancialClient {
    async getStockQuote(symbol) {
      const response = await fetch(`${API_BASE_URL}/stock/${symbol}`);
      if (!response.ok) throw new Error('Failed to fetch stock quote');
      return response.json();
    }

    async getMarketGainers() {
      const response = await fetch(`${API_BASE_URL}/market/gainers`);
      if (!response.ok) throw new Error('Failed to fetch market gainers');
      return response.json();
    }

    async getCryptoPrices(limit = 20) {
      const response = await fetch(`${API_BASE_URL}/crypto?limit=${limit}`);
      if (!response.ok) throw new Error('Failed to fetch crypto prices');
      return response.json();
    }
  }

  export default new FinancialClient();
  ```

  ```jsx Stock Quote Component theme={null}
  // components/StockQuote.jsx
  import React, { useState, useEffect } from 'react';
  import financialClient from '../api/financialClient';

  const StockQuote = ({ symbol }) => {
    const [quote, setQuote] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
      const fetchQuote = async () => {
        try {
          setLoading(true);
          const data = await financialClient.getStockQuote(symbol);
          setQuote(data);
        } catch (err) {
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };

      if (symbol) {
        fetchQuote();
      }
    }, [symbol]);

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error}</div>;
    if (!quote) return null;

    return (
      <div className="stock-quote">
        <h3>{quote.symbol}</h3>
        <p className="price">${quote.price}</p>
        <p className={`change ${quote.change >= 0 ? 'positive' : 'negative'}`}>
          {quote.change >= 0 ? '+' : ''}{quote.change} ({quote.changesPercentage}%)
        </p>
      </div>
    );
  };

  export default StockQuote;
  ```
</CodeGroup>

### Advanced React Patterns

<CodeGroup>
  ```javascript Custom Hook theme={null}
  // hooks/useFinancialData.js
  import { useState, useEffect } from 'react';
  import financialClient from '../api/financialClient';

  export const useStockQuote = (symbol) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    useEffect(() => {
      if (!symbol) return;

      const fetchData = async () => {
        setLoading(true);
        setError(null);
        
        try {
          const quote = await financialClient.getStockQuote(symbol);
          setData(quote);
        } catch (err) {
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };

      fetchData();
      
      // Auto-refresh every 30 seconds
      const interval = setInterval(fetchData, 30000);
      return () => clearInterval(interval);
    }, [symbol]);

    return { data, loading, error };
  };

  export const useMarketData = () => {
    const [gainers, setGainers] = useState([]);
    const [losers, setLosers] = useState([]);
    const [loading, setLoading] = useState(false);

    const fetchMarketData = async () => {
      setLoading(true);
      try {
        const [gainersData, losersData] = await Promise.all([
          financialClient.getMarketGainers(),
          financialClient.getMarketLosers()
        ]);
        setGainers(gainersData.gainers || []);
        setLosers(losersData.losers || []);
      } catch (error) {
        console.error('Failed to fetch market data:', error);
      } finally {
        setLoading(false);
      }
    };

    useEffect(() => {
      fetchMarketData();
    }, []);

    return { gainers, losers, loading, refresh: fetchMarketData };
  };
  ```

  ```jsx Dashboard Component theme={null}
  // components/Dashboard.jsx
  import React from 'react';
  import { useMarketData } from '../hooks/useFinancialData';
  import StockQuote from './StockQuote';

  const Dashboard = () => {
    const { gainers, losers, loading } = useMarketData();

    return (
      <div className="dashboard">
        <div className="watchlist">
          <h2>Watchlist</h2>
          <StockQuote symbol="AAPL" />
          <StockQuote symbol="MSFT" />
          <StockQuote symbol="GOOGL" />
        </div>

        <div className="market-movers">
          <div className="gainers">
            <h3>Top Gainers</h3>
            {loading ? (
              <div>Loading...</div>
            ) : (
              <ul>
                {gainers.slice(0, 5).map(stock => (
                  <li key={stock.symbol}>
                    {stock.symbol}: +{stock.changesPercentage}%
                  </li>
                ))}
              </ul>
            )}
          </div>

          <div className="losers">
            <h3>Top Losers</h3>
            {loading ? (
              <div>Loading...</div>
            ) : (
              <ul>
                {losers.slice(0, 5).map(stock => (
                  <li key={stock.symbol}>
                    {stock.symbol}: {stock.changesPercentage}%
                  </li>
                ))}
              </ul>
            )}
          </div>
        </div>
      </div>
    );
  };

  export default Dashboard;
  ```
</CodeGroup>

## Vue.js Integration

<CodeGroup>
  ```javascript Vue Composable theme={null}
  // composables/useFinancialData.js
  import { ref, onMounted, onUnmounted } from 'vue';

  export function useStockQuote(symbol) {
    const quote = ref(null);
    const loading = ref(false);
    const error = ref(null);
    let interval = null;

    const fetchQuote = async () => {
      if (!symbol.value) return;
      
      loading.value = true;
      error.value = null;
      
      try {
        const response = await fetch(`/api/v1/stock/${symbol.value}`);
        if (!response.ok) throw new Error('Failed to fetch quote');
        quote.value = await response.json();
      } catch (err) {
        error.value = err.message;
      } finally {
        loading.value = false;
      }
    };

    onMounted(() => {
      fetchQuote();
      interval = setInterval(fetchQuote, 30000);
    });

    onUnmounted(() => {
      if (interval) clearInterval(interval);
    });

    return { quote, loading, error, refresh: fetchQuote };
  }
  ```

  ```vue Vue Component theme={null}
  <template>
    <div class="stock-quote">
      <div v-if="loading">Loading...</div>
      <div v-else-if="error">Error: {{ error }}</div>
      <div v-else-if="quote" class="quote-data">
        <h3>{{ quote.symbol }}</h3>
        <p class="price">${{ quote.price }}</p>
        <p :class="['change', quote.change >= 0 ? 'positive' : 'negative']">
          {{ quote.change >= 0 ? '+' : '' }}{{ quote.change }} 
          ({{ quote.changesPercentage }}%)
        </p>
      </div>
    </div>
  </template>

  <script setup>
  import { ref } from 'vue';
  import { useStockQuote } from '../composables/useFinancialData';

  const props = defineProps(['symbol']);
  const symbol = ref(props.symbol);
  const { quote, loading, error } = useStockQuote(symbol);
  </script>

  <style scoped>
  .price { font-size: 1.5em; font-weight: bold; }
  .positive { color: green; }
  .negative { color: red; }
  </style>
  ```
</CodeGroup>

## Next.js Integration

<CodeGroup>
  ```javascript API Routes theme={null}
  // pages/api/stock/[symbol].js
  export default async function handler(req, res) {
    const { symbol } = req.query;
    
    try {
      const response = await fetch(`${process.env.FINANCIAL_MCP_URL}/api/v1/stock/${symbol}`);
      const data = await response.json();
      
      res.status(200).json(data);
    } catch (error) {
      res.status(500).json({ error: 'Failed to fetch stock data' });
    }
  }
  ```

  ```javascript Server-Side Rendering theme={null}
  // pages/stock/[symbol].js
  import { GetServerSideProps } from 'next';

  export default function StockPage({ quote, error }) {
    if (error) return <div>Error: {error}</div>;
    
    return (
      <div>
        <h1>{quote.symbol} - ${quote.price}</h1>
        <p>Change: {quote.change} ({quote.changesPercentage}%)</p>
      </div>
    );
  }

  export const getServerSideProps = async ({ params }) => {
    try {
      const response = await fetch(`${process.env.FINANCIAL_MCP_URL}/api/v1/stock/${params.symbol}`);
      const quote = await response.json();
      
      return { props: { quote } };
    } catch (error) {
      return { props: { error: 'Failed to fetch stock data' } };
    }
  };
  ```
</CodeGroup>

## Mobile Integration

### React Native

<CodeGroup>
  ```javascript React Native Service theme={null}
  // services/FinancialService.js
  import AsyncStorage from '@react-native-async-storage/async-storage';

  class FinancialService {
    constructor() {
      this.baseURL = 'https://stocks-dev.up.railway.app/api/v1';
      this.cache = new Map();
    }

    async getStockQuote(symbol) {
      const cacheKey = `quote_${symbol}`;
      const cached = this.cache.get(cacheKey);
      
      if (cached && Date.now() - cached.timestamp < 60000) {
        return cached.data;
      }

      try {
        const response = await fetch(`${this.baseURL}/stock/${symbol}`);
        const data = await response.json();
        
        this.cache.set(cacheKey, { data, timestamp: Date.now() });
        return data;
      } catch (error) {
        throw new Error(`Failed to fetch quote for ${symbol}`);
      }
    }

    async getWatchlist() {
      try {
        const watchlist = await AsyncStorage.getItem('watchlist');
        return watchlist ? JSON.parse(watchlist) : [];
      } catch (error) {
        return [];
      }
    }

    async addToWatchlist(symbol) {
      const watchlist = await this.getWatchlist();
      if (!watchlist.includes(symbol)) {
        watchlist.push(symbol);
        await AsyncStorage.setItem('watchlist', JSON.stringify(watchlist));
      }
    }
  }

  export default new FinancialService();
  ```

  ```jsx React Native Component theme={null}
  // components/StockList.jsx
  import React, { useState, useEffect } from 'react';
  import { View, Text, FlatList, StyleSheet } from 'react-native';
  import FinancialService from '../services/FinancialService';

  const StockList = () => {
    const [stocks, setStocks] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      const fetchWatchlist = async () => {
        try {
          const symbols = await FinancialService.getWatchlist();
          const quotes = await Promise.all(
            symbols.map(symbol => FinancialService.getStockQuote(symbol))
          );
          setStocks(quotes);
        } catch (error) {
          console.error('Failed to fetch watchlist:', error);
        } finally {
          setLoading(false);
        }
      };

      fetchWatchlist();
    }, []);

    const renderStock = ({ item }) => (
      <View style={styles.stockItem}>
        <Text style={styles.symbol}>{item.symbol}</Text>
        <Text style={styles.price}>${item.price}</Text>
        <Text style={[
          styles.change,
          item.change >= 0 ? styles.positive : styles.negative
        ]}>
          {item.change >= 0 ? '+' : ''}{item.change}
        </Text>
      </View>
    );

    return (
      <FlatList
        data={stocks}
        renderItem={renderStock}
        keyExtractor={item => item.symbol}
        refreshing={loading}
      />
    );
  };

  const styles = StyleSheet.create({
    stockItem: { flexDirection: 'row', padding: 16 },
    symbol: { flex: 1, fontWeight: 'bold' },
    price: { flex: 1, textAlign: 'right' },
    change: { flex: 1, textAlign: 'right' },
    positive: { color: 'green' },
    negative: { color: 'red' }
  });

  export default StockList;
  ```
</CodeGroup>

## Error Handling & Loading States

<CodeGroup>
  ```javascript Error Boundary theme={null}
  // components/ErrorBoundary.jsx
  import React from 'react';

  class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: null };
    }

    static getDerivedStateFromError(error) {
      return { hasError: true, error };
    }

    componentDidCatch(error, errorInfo) {
      console.error('Financial data error:', error, errorInfo);
    }

    render() {
      if (this.state.hasError) {
        return (
          <div className="error-fallback">
            <h2>Something went wrong with financial data</h2>
            <button onClick={() => this.setState({ hasError: false, error: null })}>
              Try again
            </button>
          </div>
        );
      }

      return this.props.children;
    }
  }

  export default ErrorBoundary;
  ```

  ```javascript Loading Component theme={null}
  // components/LoadingSpinner.jsx
  import React from 'react';

  const LoadingSpinner = ({ message = 'Loading financial data...' }) => (
    <div className="loading-container">
      <div className="spinner" />
      <p>{message}</p>
    </div>
  );

  export default LoadingSpinner;
  ```
</CodeGroup>

## Performance Optimization

### Caching Strategy

<CodeGroup>
  ```javascript Service Worker Caching theme={null}
  // sw.js
  const CACHE_NAME = 'financial-data-v1';
  const CACHE_DURATION = 60000; // 1 minute

  self.addEventListener('fetch', event => {
    if (event.request.url.includes('/api/v1/stock/')) {
      event.respondWith(
        caches.open(CACHE_NAME).then(cache => {
          return cache.match(event.request).then(cachedResponse => {
            if (cachedResponse) {
              const cachedTime = new Date(cachedResponse.headers.get('cached-time'));
              if (Date.now() - cachedTime.getTime() < CACHE_DURATION) {
                return cachedResponse;
              }
            }

            return fetch(event.request).then(response => {
              const responseClone = response.clone();
              responseClone.headers.set('cached-time', new Date().toISOString());
              cache.put(event.request, responseClone);
              return response;
            });
          });
        })
      );
    }
  });
  ```

  ```javascript React Query Integration theme={null}
  // hooks/useFinancialQuery.js
  import { useQuery, useQueryClient } from 'react-query';
  import financialClient from '../api/financialClient';

  export const useStockQuery = (symbol) => {
    return useQuery(
      ['stock', symbol],
      () => financialClient.getStockQuote(symbol),
      {
        staleTime: 30000, // 30 seconds
        cacheTime: 300000, // 5 minutes
        refetchInterval: 60000, // Refetch every minute
        enabled: !!symbol
      }
    );
  };

  export const useMarketGainersQuery = () => {
    return useQuery(
      ['market', 'gainers'],
      () => financialClient.getMarketGainers(),
      {
        staleTime: 60000, // 1 minute
        refetchInterval: 120000 // Refetch every 2 minutes
      }
    );
  };
  ```
</CodeGroup>

## WebSocket Integration

<CodeGroup>
  ```javascript WebSocket Client theme={null}
  // services/WebSocketService.js
  class WebSocketService {
    constructor() {
      this.ws = null;
      this.subscribers = new Map();
    }

    connect() {
      this.ws = new WebSocket('ws://localhost:8001/ws');
      
      this.ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        const subscribers = this.subscribers.get(data.symbol) || [];
        subscribers.forEach(callback => callback(data));
      };

      this.ws.onopen = () => console.log('WebSocket connected');
      this.ws.onclose = () => setTimeout(() => this.connect(), 5000);
    }

    subscribe(symbol, callback) {
      if (!this.subscribers.has(symbol)) {
        this.subscribers.set(symbol, []);
        this.ws.send(JSON.stringify({ action: 'subscribe', symbol }));
      }
      this.subscribers.get(symbol).push(callback);
    }

    unsubscribe(symbol, callback) {
      const subscribers = this.subscribers.get(symbol) || [];
      const index = subscribers.indexOf(callback);
      if (index > -1) {
        subscribers.splice(index, 1);
        if (subscribers.length === 0) {
          this.subscribers.delete(symbol);
          this.ws.send(JSON.stringify({ action: 'unsubscribe', symbol }));
        }
      }
    }
  }

  export default new WebSocketService();
  ```

  ```jsx Real-time Component theme={null}
  // components/RealTimeQuote.jsx
  import React, { useState, useEffect } from 'react';
  import WebSocketService from '../services/WebSocketService';

  const RealTimeQuote = ({ symbol }) => {
    const [quote, setQuote] = useState(null);

    useEffect(() => {
      const handleUpdate = (data) => setQuote(data);
      
      WebSocketService.subscribe(symbol, handleUpdate);
      return () => WebSocketService.unsubscribe(symbol, handleUpdate);
    }, [symbol]);

    return quote ? (
      <div className="real-time-quote">
        <span className="symbol">{quote.symbol}</span>
        <span className="price">${quote.price}</span>
        <span className={`change ${quote.change >= 0 ? 'up' : 'down'}`}>
          {quote.change >= 0 ? '↗' : '↘'} {quote.changesPercentage}%
        </span>
      </div>
    ) : null;
  };

  export default RealTimeQuote;
  ```
</CodeGroup>

## Testing

<CodeGroup>
  ```javascript Jest Tests theme={null}
  // __tests__/FinancialClient.test.js
  import FinancialClient from '../api/financialClient';

  // Mock fetch
  global.fetch = jest.fn();

  describe('FinancialClient', () => {
    beforeEach(() => {
      fetch.mockClear();
    });

    test('getStockQuote returns quote data', async () => {
      const mockQuote = { symbol: 'AAPL', price: 150.00, change: 2.50 };
      fetch.mockResolvedValueOnce({
        ok: true,
        json: async () => mockQuote
      });

      const result = await FinancialClient.getStockQuote('AAPL');
      
      expect(fetch).toHaveBeenCalledWith('http://localhost:8001/api/v1/stock/AAPL');
      expect(result).toEqual(mockQuote);
    });

    test('getStockQuote handles errors', async () => {
      fetch.mockResolvedValueOnce({
        ok: false,
        status: 404
      });

      await expect(FinancialClient.getStockQuote('INVALID'))
        .rejects.toThrow('Failed to fetch stock quote');
    });
  });
  ```

  ```javascript React Testing Library theme={null}
  // __tests__/StockQuote.test.jsx
  import React from 'react';
  import { render, screen, waitFor } from '@testing-library/react';
  import StockQuote from '../components/StockQuote';
  import FinancialClient from '../api/financialClient';

  jest.mock('../api/financialClient');

  describe('StockQuote', () => {
    test('displays stock quote data', async () => {
      const mockQuote = { symbol: 'AAPL', price: 150.00, change: 2.50, changesPercentage: 1.69 };
      FinancialClient.getStockQuote.mockResolvedValue(mockQuote);

      render(<StockQuote symbol="AAPL" />);

      expect(screen.getByText('Loading...')).toBeInTheDocument();

      await waitFor(() => {
        expect(screen.getByText('AAPL')).toBeInTheDocument();
        expect(screen.getByText('$150')).toBeInTheDocument();
        expect(screen.getByText('+2.5 (1.69%)')).toBeInTheDocument();
      });
    });
  });
  ```
</CodeGroup>

## Related Resources

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete API documentation
  </Card>

  <Card title="Authentication" icon="key" href="/api-reference/authentication">
    API key setup and security
  </Card>

  <Card title="Rate Limiting" icon="clock" href="/guides/troubleshooting">
    Handle rate limits and errors
  </Card>

  <Card title="WebSocket Guide" icon="bolt" href="/guides/configuration">
    Real-time data streaming
  </Card>
</CardGroup>
