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();
// 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;
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 };
};
// 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;
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 };
}
<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>
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' });
}
}
// 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' } };
}
};
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();
// 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;
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;
// 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;
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;
});
});
})
);
}
});
// 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
}
);
};
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();
// 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;
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');
});
});
// __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();
});
});
});
Related Resources
API Reference
Complete API documentation
Authentication
API key setup and security
Rate Limiting
Handle rate limits and errors
WebSocket Guide
Real-time data streaming