Skip to main content

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

// 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();

Advanced React Patterns

// 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 };
};

Vue.js Integration

// 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 };
}

Next.js Integration

// 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' });
  }
}

Mobile Integration

React Native

// 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();

Error Handling & Loading States

// 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;

Performance Optimization

Caching Strategy

// 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;
          });
        });
      })
    );
  }
});

WebSocket Integration

// 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();

Testing

// __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');
  });
});