Version: 1.0 Last Updated: 2025-10-27 Status: Production Ready
- Overview
- Why Hybrid MCP Architecture?
- CoinGecko MCP Server
- Custom MCP Servers
- Configuration
- Integration Steps
- Usage Examples
- Best Practices
- Troubleshooting
CryptoFunk uses a hybrid MCP architecture that combines:
- External MCP Servers - Third-party servers (like CoinGecko) for market data
- Custom MCP Servers - Internal servers for execution, risk, and indicators
This approach provides the best of both worlds:
- Rapid development - Leverage existing MCP servers
- Full control - Custom logic where needed
- Reduced maintenance - External servers maintained by providers
| Task | Build from Scratch | Use CoinGecko MCP | Savings |
|---|---|---|---|
| Market data API integration | 12 hours | 1 hour | 11 hours |
| Multi-exchange aggregation | 8 hours | 0 hours | 8 hours |
| Historical data fetching | 6 hours | 0 hours | 6 hours |
| WebSocket streaming | 4 hours | 0 hours | 4 hours |
| Error handling & retries | 3 hours | 0 hours | 3 hours |
| Total | 33 hours | 1 hour | 32 hours |
| Feature | CoinGecko MCP | Custom Build |
|---|---|---|
| Market Data Tools | 76+ pre-built | Need to build all |
| Multi-Exchange | Built-in | Manual integration |
| Historical Data | Instant access | Need to collect |
| Maintenance | CoinGecko handles | Our responsibility |
| Cost | Free tier + Pro | Infrastructure only |
| Reliability | CoinGecko SLA | Our ops |
| Setup Time | <1 hour | Days/weeks |
CoinGecko MCP is an external MCP server provided by CoinGecko that gives access to comprehensive cryptocurrency market data through 76+ tools.
Server URL: https://mcp.api.coingecko.com/mcp
Transport: HTTP Streaming
Authentication: None required for free tier
Rate Limits: 100 requests/minute (free tier), higher with Pro
get_price- Current cryptocurrency pricesget_price_by_id- Price for specific coinget_simple_price- Simple price query with multiple vs currenciesget_token_price- Token price by contract address
get_market_chart- Historical market data (price, volume, market cap)get_market_chart_range- Market data for specific date rangeget_ohlc- OHLCV candlestick dataget_coin_market_data- Comprehensive market statistics
get_coin_info- Detailed coin informationget_coin_by_id- Coin data by IDget_coin_tickers- Trading pair tickersget_coin_history- Historical snapshots
get_trending- Trending coinsget_top_gainers- Top gaining coinsget_top_losers- Top losing coinsget_recently_added- Recently listed coins
search_coins- Search for cryptocurrencieslist_coins- List all supported coinsget_categories- Market categoriesget_category_coins- Coins in specific category
get_nft_collection- NFT collection dataget_nft_market- NFT marketplace statisticslist_nft_collections- All NFT collections
get_defi_pools- DeFi liquidity poolsget_exchanges- Exchange informationget_exchange_tickers- Exchange trading pairs
Full list: See CoinGecko MCP documentation at https://mcp.api.coingecko.com
✅ 76+ pre-built tools - No need to implement ✅ Multi-exchange aggregation - Prices from multiple sources ✅ Historical data - Years of historical OHLCV data ✅ No API key required - Free tier available ✅ Maintained by CoinGecko - Always up-to-date ✅ Reliable - Enterprise-grade SLA ✅ Global CDN - Low latency worldwide
Solution: Use Redis caching layer to mitigate rate limits and latency.
These are internal servers we build for functionality not available externally.
Purpose: Execute trades on exchanges
Why Custom: Needs direct exchange API for actual trading
Binary: ./bin/order-executor-server
Transport: stdio
Tools:
place_market_order(symbol, side, quantity)→ order_idplace_limit_order(symbol, side, quantity, price)→ order_idcancel_order(order_id)→ statusget_order_status(order_id)→ orderget_positions()→ positions[]
Technology: CCXT for unified exchange API
Purpose: Portfolio risk management
Why Custom: Custom risk rules and calculations
Binary: ./bin/risk-analyzer-server
Transport: stdio
Tools:
calculate_position_size(win_rate, capital, kelly_fraction)→ sizecalculate_var(returns[], confidence)→ var_valuecheck_portfolio_limits(positions, new_trade, limits)→ approvedcalculate_sharpe(returns[], risk_free_rate)→ sharpecalculate_drawdown(equity_curve[])→ drawdown
Technology: Custom Go implementation
Purpose: Technical analysis calculations
Why Custom: Specialized indicator calculations
Binary: ./bin/technical-indicators-server
Transport: stdio
Tools:
calculate_rsi(prices[], period)→ rsi_valuecalculate_macd(prices[], fast, slow, signal)→ macd_resultcalculate_bollinger_bands(prices[], period, std_devs)→ bandscalculate_ema(prices[], period)→ ema_valuecalculate_adx(high[], low[], close[], period)→ adx_valuedetect_patterns(candlesticks[])→ patterns[]
Technology: cinar/indicator library (60+ indicators)
Purpose: Binance-specific features
Why Optional: CoinGecko MCP covers 90% of use cases
Binary: ./bin/market-data-server
Transport: stdio
Use Case: Only enable if you need:
- Exchange-specific order book depth
- Real-time WebSocket from specific exchange
- Binance-specific endpoints
Default: Disabled (CoinGecko MCP is primary data source)
File: configs/config.yaml
mcp:
# External MCP Servers
external:
coingecko:
enabled: true
name: "CoinGecko MCP"
url: "https://mcp.api.coingecko.com/mcp"
transport: "http_streaming"
description: "76+ market data tools"
cache_ttl: 60 # seconds
rate_limit:
enabled: true
requests_per_minute: 100
# Internal (Custom) MCP Servers
internal:
order_executor:
enabled: true
name: "Order Executor"
command: "./bin/order-executor-server"
transport: "stdio"
description: "Execute orders on exchanges"
env:
EXCHANGE: "binance"
MODE: "paper" # paper or live
risk_analyzer:
enabled: true
name: "Risk Analyzer"
command: "./bin/risk-analyzer-server"
transport: "stdio"
description: "Portfolio risk management"
technical_indicators:
enabled: true
name: "Technical Indicators"
command: "./bin/technical-indicators-server"
transport: "stdio"
description: "Technical analysis indicators"
market_data:
enabled: false # Disabled by default
name: "Market Data (Binance)"
command: "./bin/market-data-server"
transport: "stdio"
note: "CoinGecko MCP is primary data source"File: internal/config/config.go
type MCPConfig struct {
External MCPExternalServers
Internal MCPInternalServers
}
type MCPExternalServerConfig struct {
Enabled bool
Name string
URL string
Transport string // "http_streaming"
CacheTTL int // seconds
RateLimit MCPRateLimitConfig
Tools []string
}
type MCPInternalServerConfig struct {
Enabled bool
Name string
Command string // path to binary
Transport string // "stdio"
Args []string
Env map[string]string
Tools []string
}Edit configs/config.yaml:
mcp:
external:
coingecko:
enabled: true
url: "https://mcp.api.coingecko.com/mcp"
cache_ttl: 60
rate_limit:
enabled: true
requests_per_minute: 100 # Adjust for your tier// internal/market/coingecko.go
package market
import (
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type CoinGeckoClient struct {
client *mcp.Client
}
func NewCoinGeckoClient(url string) (*CoinGeckoClient, error) {
client, err := mcp.NewClient(mcp.ClientConfig{
ServerURL: url,
Transport: mcp.TransportHTTPStreaming,
})
if err != nil {
return nil, err
}
return &CoinGeckoClient{client: client}, nil
}
func (c *CoinGeckoClient) GetPrice(symbol string, vsCurrency string) (float64, error) {
result, err := c.client.CallTool("get_price", map[string]any{
"ids": symbol,
"vs_currencies": vsCurrency,
})
if err != nil {
return 0, err
}
// Parse result
price := result.(map[string]any)[symbol].(map[string]any)[vsCurrency].(float64)
return price, nil
}
func (c *CoinGeckoClient) GetMarketChart(symbol string, days int) (*MarketChart, error) {
result, err := c.client.CallTool("get_market_chart", map[string]any{
"id": symbol,
"vs_currency": "usd",
"days": days,
})
if err != nil {
return nil, err
}
// Parse and return
return parseMarketChart(result), nil
}// internal/market/cache.go
package market
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type CachedCoinGeckoClient struct {
client *CoinGeckoClient
redis *redis.Client
cacheTTL time.Duration
}
func (c *CachedCoinGeckoClient) GetPrice(ctx context.Context, symbol string) (float64, error) {
// Check cache first
cacheKey := fmt.Sprintf("price:%s", symbol)
cached, err := c.redis.Get(ctx, cacheKey).Result()
if err == nil {
var price float64
json.Unmarshal([]byte(cached), &price)
return price, nil
}
// Cache miss - fetch from CoinGecko MCP
price, err := c.client.GetPrice(symbol, "usd")
if err != nil {
return 0, err
}
// Store in cache
data, _ := json.Marshal(price)
c.redis.Set(ctx, cacheKey, data, c.cacheTTL)
return price, nil
}// cmd/agents/technical-agent/main.go
package main
import (
"github.com/ajitpratap0/cryptofunk/internal/market"
)
func main() {
// Connect to CoinGecko MCP
coinGecko, err := market.NewCoinGeckoClient(
"https://mcp.api.coingecko.com/mcp",
)
if err != nil {
log.Fatal(err)
}
// Connect to Technical Indicators MCP Server
indicators, err := mcp.NewClient(mcp.ClientConfig{
Command: "./bin/technical-indicators-server",
Transport: mcp.TransportStdio,
})
if err != nil {
log.Fatal(err)
}
// Use both servers
prices, _ := coinGecko.GetMarketChart("bitcoin", 30)
rsi, _ := indicators.CallTool("calculate_rsi", map[string]any{
"prices": prices.ClosePrices,
"period": 14,
})
// Generate signal based on analysis...
}// Using CoinGecko MCP
price, err := coinGecko.GetPrice("bitcoin", "usd")
fmt.Printf("BTC Price: $%.2f\n", price)// Get 30 days of historical data
chart, err := coinGecko.GetMarketChart("ethereum", 30)
// Extract OHLCV
for _, candle := range chart.Prices {
fmt.Printf("Time: %v, Price: %.2f\n", candle.Time, candle.Price)
}
// Store in TimescaleDB for backtesting
db.StoreCandlesticks("ETHUSD", chart.ToCandlesticks())// CoinGecko aggregates prices from multiple exchanges
tickers, err := coinGecko.CallTool("get_coin_tickers", map[string]any{
"id": "bitcoin",
})
for _, ticker := range tickers {
fmt.Printf("Exchange: %s, Price: $%.2f, Volume: $%.2f\n",
ticker.Exchange, ticker.Last, ticker.Volume)
}// 1. Get market data from CoinGecko MCP
prices, err := coinGecko.GetMarketChart("bitcoin", 100)
// 2. Calculate indicators using Technical Indicators MCP Server
rsi, _ := indicators.CallTool("calculate_rsi", map[string]any{
"prices": prices.ClosePrices,
"period": 14,
})
macd, _ := indicators.CallTool("calculate_macd", map[string]any{
"prices": prices.ClosePrices,
"fast": 12,
"slow": 26,
"signal": 9,
})
// 3. Use LLM for analysis (via Bifrost)
decision := llm.Analyze(prices, rsi, macd)
// 4. Execute if approved by Risk Agent
if riskAgent.Approve(decision) {
orderExecutor.CallTool("place_market_order", decision)
}Always cache CoinGecko responses:
// Cache with appropriate TTL based on data type
cacheTTL := map[string]time.Duration{
"price": 60 * time.Second, // Prices change frequently
"market_chart": 5 * time.Minute, // Historical data less frequent
"coin_info": 30 * time.Minute, // Metadata rarely changes
}Benefits:
- Reduce API calls (stay within rate limits)
- Lower latency (Redis is faster than external API)
- Cost savings (if using Pro tier)
type RateLimiter struct {
limiter *rate.Limiter
}
func (r *RateLimiter) WaitIfNeeded(ctx context.Context) error {
return r.limiter.Wait(ctx)
}
// Before each CoinGecko MCP call
rateLimiter.WaitIfNeeded(ctx)
price, err := coinGecko.GetPrice("bitcoin", "usd")func (c *CoinGeckoClient) GetPriceWithRetry(symbol string, maxRetries int) (float64, error) {
var price float64
var err error
for i := 0; i < maxRetries; i++ {
price, err = c.GetPrice(symbol, "usd")
if err == nil {
return price, nil
}
// Exponential backoff
time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
}
return 0, fmt.Errorf("failed after %d retries: %w", maxRetries, err)
}// Try CoinGecko first, fallback to custom market data server if needed
price, err := coinGecko.GetPrice("bitcoin", "usd")
if err != nil {
// Fallback to Binance-specific server
price, err = marketDataServer.CallTool("get_current_price", map[string]any{
"symbol": "BTCUSDT",
})
}// Sync historical data to TimescaleDB periodically
func syncHistoricalData(coinGecko *CoinGeckoClient, db *Database) {
for _, symbol := range []string{"bitcoin", "ethereum", "binancecoin"} {
// Get 365 days of data
chart, err := coinGecko.GetMarketChart(symbol, 365)
if err != nil {
log.Error("Failed to fetch data", "symbol", symbol, "error", err)
continue
}
// Store in TimescaleDB
err = db.StoreCandlesticks(symbol, chart.ToCandlesticks())
if err != nil {
log.Error("Failed to store data", "symbol", symbol, "error", err)
}
}
}
// Run daily
ticker := time.NewTicker(24 * time.Hour)
go func() {
for range ticker.C {
syncHistoricalData(coinGecko, db)
}
}()Error: failed to connect to https://mcp.api.coingecko.com/mcp
Solutions:
# 1. Check internet connectivity
ping mcp.api.coingecko.com
# 2. Test endpoint directly
curl https://mcp.api.coingecko.com/mcp
# 3. Check firewall rules
# 4. Verify proxy settings if behind corporate firewallError: 429 Too Many Requests
Solutions:
# 1. Enable caching in config.yaml
mcp:
external:
coingecko:
cache_ttl: 120 # Increase to 2 minutes
# 2. Reduce request frequency
rate_limit:
requests_per_minute: 50 # Lower than limit
# 3. Upgrade to CoinGecko Pro (if budget allows)
# 4. Batch requests where possibleProblem: CoinGecko MCP calls taking >2 seconds
Solutions:
// 1. Add timeout to client
client := mcp.NewClient(mcp.ClientConfig{
ServerURL: coinGeckoURL,
Timeout: 5 * time.Second, // Set reasonable timeout
})
// 2. Use aggressive caching
cacheTTL := 5 * time.Minute // Cache longer
// 3. Parallel requests for multiple symbols
var wg sync.WaitGroup
prices := make(map[string]float64)
mu := sync.Mutex{}
for _, symbol := range symbols {
wg.Add(1)
go func(s string) {
defer wg.Done()
price, _ := coinGecko.GetPrice(s, "usd")
mu.Lock()
prices[s] = price
mu.Unlock()
}(symbol)
}
wg.Wait()Error: failed to start order-executor-server
Solutions:
# 1. Check binary exists and is executable
ls -la ./bin/order-executor-server
chmod +x ./bin/order-executor-server
# 2. Test binary directly
./bin/order-executor-server
# 3. Check logs
tail -f /var/log/cryptofunk/order-executor-server.log
# 4. Verify environment variables
echo $BINANCE_API_KEYProblem: CoinGecko prices differ from exchange prices
Explanation: CoinGecko aggregates prices from multiple exchanges, so there may be slight differences.
Solutions:
// 1. Use specific exchange ticker
tickers, _ := coinGecko.CallTool("get_coin_tickers", map[string]any{
"id": "bitcoin",
"exchange_ids": "binance", // Filter by exchange
})
// 2. For trading, use exchange-specific API
// CoinGecko for analysis, Binance API for execution
analysisPrice := coinGecko.GetPrice("bitcoin", "usd")
executionPrice := binanceAPI.GetPrice("BTCUSDT")-
Review Architecture: See ARCHITECTURE.md for hybrid MCP design
-
Implementation: Follow TASKS.md Phase 2.1 for integration steps
-
Testing: Test CoinGecko MCP connection:
go run cmd/test-mcp-client/main.go
-
Build Agents: Integrate CoinGecko MCP into analysis agents (Phase 3)
- CoinGecko MCP Docs: https://mcp.api.coingecko.com
- Official MCP Go SDK: https://github.com/modelcontextprotocol/go-sdk
- CoinGecko API Docs: https://www.coingecko.com/en/api/documentation
- CryptoFunk Architecture: ARCHITECTURE.md
- CryptoFunk Tasks: TASKS.md
Conclusion: By using CoinGecko MCP, we save 32+ hours of development time while gaining access to comprehensive market data from a reliable, maintained external service. This lets us focus on building the unique value of CryptoFunk: intelligent trading logic, risk management, and LLM-powered decision-making.