Skip to main content
Glama
mk965

Asset Price MCP Server

coingecko-api.ts5.07 kB
import { z } from "zod"; import { PriceService, AssetSymbol, AssetPrice } from "../types.js"; import { fetchJson } from "../utils.js"; import { apiCache } from "../cache.js"; const BASE_URL = "https://api.coingecko.com/api/v3"; const CoinGeckoMarketSchema = z.object({ id: z.string(), symbol: z.string(), name: z.string(), current_price: z.number(), market_cap: z.number().optional(), total_volume: z.number().optional(), price_change_percentage_24h: z.number().optional().nullable(), last_updated: z.string().optional() }); const CoinGeckoMarketsSchema = z.array(CoinGeckoMarketSchema); export class CoinGeckoService implements PriceService { private symbolMap: Map<string, string> = new Map(); // symbol (btc) -> id (bitcoin) getName(): string { return "CoinGecko"; } async getSupportedAssets(): Promise<AssetSymbol[]> { // Check cache first const cacheKey = `${BASE_URL}/coins/markets`; const cached = apiCache.get<AssetSymbol[]>(cacheKey); if (cached) { // Rebuild map from cache if needed if (this.symbolMap.size === 0) { // We can't easily rebuild the full map from just AssetSymbol[] if we don't store the ID // So we might need to store the raw response or fetch again if map is empty. // For simplicity, let's just refetch if map is empty or trust the flow. // Actually, let's store the full market data in a separate cache key to rebuild map. } return cached; } // Fetch Top 100 coins const url = `${BASE_URL}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`; const markets = await fetchJson(url, CoinGeckoMarketsSchema); if (!markets) return []; const symbols: AssetSymbol[] = markets.map(m => { this.symbolMap.set(m.symbol.toUpperCase(), m.id); return { name: m.name, symbol: m.symbol.toUpperCase(), // Normalize to uppercase }; }); // Cache symbols apiCache.set(cacheKey, symbols); // Also cache individual prices from this batch to save requests markets.forEach(m => { const price: AssetPrice = { name: m.name, symbol: m.symbol.toUpperCase(), price: m.current_price, currency: 'USD', updatedAt: m.last_updated || new Date().toISOString(), marketCap: m.market_cap, volume24h: m.total_volume, change24h: m.price_change_percentage_24h || 0 }; // Cache both by ID and Symbol if possible, but our interface uses Symbol // We will cache by symbol for getPrice to use apiCache.set(`CG_PRICE_${m.symbol.toUpperCase()}_USD`, price); }); return symbols; } async getPrice(symbol: string, currency: string = 'USD'): Promise<AssetPrice | null> { const upperSymbol = symbol.toUpperCase(); // Check cache first (populated by getSupportedAssets or previous calls) const cacheKey = `CG_PRICE_${upperSymbol}_${currency}`; const cached = apiCache.get<AssetPrice>(cacheKey); if (cached) return cached; // If not in cache, we need to find the ID. // If we haven't fetched markets yet, do it now to populate map. if (this.symbolMap.size === 0) { await this.getSupportedAssets(); } let id = this.symbolMap.get(upperSymbol); // If still not found, maybe the user passed the ID directly? // Or we could try to search, but for now let's assume if it's not in top 100, we might need to search endpoint (omitted for simplicity) if (!id) { // Fallback: assume symbol is id (lowercase) id = symbol.toLowerCase(); } const url = `${BASE_URL}/simple/price?ids=${id}&vs_currencies=${currency.toLowerCase()}&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&include_last_updated_at=true`; // Result format: { "bitcoin": { "usd": 50000, ... } } // We don't have a strict schema for dynamic keys easily, so we parse as Record const data = await fetchJson<Record<string, any>>(url); if (!data || !data[id]) return null; const item = data[id]; const cur = currency.toLowerCase(); if (item[cur] === undefined) return null; const price: AssetPrice = { name: id, // We might not have the pretty name here, use ID symbol: upperSymbol, price: item[cur], currency: currency, updatedAt: item.last_updated_at ? new Date(item.last_updated_at * 1000).toISOString() : new Date().toISOString(), marketCap: item[`${cur}_market_cap`], volume24h: item[`${cur}_24h_vol`], change24h: item[`${cur}_24h_change`] }; apiCache.set(cacheKey, price); return price; } async getPrices(symbols: string[], currency: string = 'USD'): Promise<AssetPrice[]> { // Optimization: fetch multiple IDs at once if we have the map // For now, simple implementation const promises = symbols.map(s => this.getPrice(s, currency)); const results = await Promise.all(promises); return results.filter((p): p is AssetPrice => p !== null); } }

Latest Blog Posts

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/mk965/asset-price-mcp'

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