Skip to main content
Glama

Bybit MCP Server

by sammcj
citationStore.ts7.55 kB
/** * Citation store for managing tool response data and references */ import type { CitationData, ExtractedMetric } from '@/types/citation'; export class CitationStore { private citations: Map<string, CitationData> = new Map(); private maxCitations = 100; // Limit to prevent memory issues private cleanupThreshold = 120; // Clean up citations older than 2 hours /** * Store tool response data with citation metadata */ storeCitation(data: CitationData): void { this.citations.set(data.referenceId, data); // Clean up old citations if we exceed the limit if (this.citations.size > this.maxCitations) { this.cleanupOldCitations(); } } /** * Retrieve citation data by reference ID */ getCitation(referenceId: string): CitationData | undefined { return this.citations.get(referenceId); } /** * Get all citations sorted by timestamp (newest first) */ getAllCitations(): CitationData[] { return Array.from(this.citations.values()) .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); } /** * Get recent citations (last N citations) */ getRecentCitations(limit: number = 10): CitationData[] { return this.getAllCitations().slice(0, limit); } /** * Extract key metrics from tool response data */ extractMetrics(toolName: string, rawData: any): ExtractedMetric[] { const metrics: ExtractedMetric[] = []; try { switch (toolName) { case 'get_ticker': if (rawData.lastPrice) { metrics.push({ type: 'price', label: 'Last Price', value: rawData.lastPrice, unit: 'USD', significance: 'high' }); } if (rawData.price24hPcnt) { metrics.push({ type: 'percentage', label: '24h Change', value: rawData.price24hPcnt, unit: '%', significance: 'high' }); } if (rawData.volume24h) { metrics.push({ type: 'volume', label: '24h Volume', value: rawData.volume24h, significance: 'medium' }); } break; case 'get_kline': if (rawData.data && Array.isArray(rawData.data) && rawData.data.length > 0) { const latestCandle = rawData.data[0]; if (latestCandle.close) { metrics.push({ type: 'price', label: 'Close Price', value: latestCandle.close, unit: 'USD', significance: 'high' }); } } break; case 'get_ml_rsi': if (rawData.data && Array.isArray(rawData.data) && rawData.data.length > 0) { const latestRsi = rawData.data[0]; if (latestRsi.mlRsi !== undefined) { metrics.push({ type: 'indicator', label: 'ML RSI', value: latestRsi.mlRsi.toFixed(2), significance: 'high' }); } if (latestRsi.trend) { metrics.push({ type: 'other', label: 'Trend', value: latestRsi.trend, significance: 'medium' }); } } break; case 'get_orderbook': if (rawData.bids && rawData.bids.length > 0) { metrics.push({ type: 'price', label: 'Best Bid', value: rawData.bids[0][0], unit: 'USD', significance: 'high' }); } if (rawData.asks && rawData.asks.length > 0) { metrics.push({ type: 'price', label: 'Best Ask', value: rawData.asks[0][0], unit: 'USD', significance: 'high' }); } break; default: // Generic extraction for unknown tools if (typeof rawData === 'object' && rawData !== null) { Object.entries(rawData).forEach(([key, value]) => { if (typeof value === 'string' || typeof value === 'number') { metrics.push({ type: 'other', label: key, value: value, significance: 'low' }); } }); } break; } } catch (error) { console.warn('Error extracting metrics:', error); } return metrics.slice(0, 5); // Limit to 5 key metrics } /** * Process tool response and store citation if it has reference metadata */ processToolResponse(toolResponse: any): void { console.log('🔍 Processing tool response for citations:', toolResponse); if (!toolResponse || typeof toolResponse !== 'object') { console.log('❌ Invalid tool response format'); return; } // MCP responses are wrapped in a content array, so we need to extract the actual data let actualData = toolResponse; // Check if response has content array (MCP format) if (toolResponse.content && Array.isArray(toolResponse.content) && toolResponse.content.length > 0) { console.log('🔍 Found MCP content array, extracting data...'); const contentItem = toolResponse.content[0]; if (contentItem.type === 'text' && contentItem.text) { try { actualData = JSON.parse(contentItem.text); console.log('🔍 Parsed content data:', actualData); } catch (e) { console.log('❌ Failed to parse content text as JSON'); return; } } } // Check if response has reference metadata if (actualData._referenceId && actualData._timestamp && actualData._toolName) { console.log('✅ Found reference metadata:', { referenceId: actualData._referenceId, toolName: actualData._toolName, timestamp: actualData._timestamp }); const extractedMetrics = this.extractMetrics(actualData._toolName, actualData); const citationData: CitationData = { referenceId: actualData._referenceId, timestamp: actualData._timestamp, toolName: actualData._toolName, endpoint: actualData._endpoint, rawData: actualData, extractedMetrics }; this.storeCitation(citationData); console.log('📋 Stored citation data for', actualData._referenceId); } else { console.log('❌ No reference metadata found in tool response'); console.log('🔍 Available keys in actualData:', Object.keys(actualData)); } } /** * Clean up citations older than the threshold */ private cleanupOldCitations(): void { const now = Date.now(); const thresholdMs = this.cleanupThreshold * 60 * 1000; // Convert minutes to milliseconds for (const [referenceId, citation] of this.citations.entries()) { const citationAge = now - new Date(citation.timestamp).getTime(); if (citationAge > thresholdMs) { this.citations.delete(referenceId); } } // Cleanup completed silently } /** * Clear all citations */ clear(): void { this.citations.clear(); } /** * Get citation count */ getCount(): number { return this.citations.size; } } // Singleton instance export const citationStore = new CitationStore();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sammcj/bybit-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server