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