""" Free MCP Server Integrations - Boar Blockchain, Solana Token Analysis, autonsol Adds free, keyless MCP data sources to the caching shield fallback engine. All three require NO API keys - pure free data. Boar Blockchain (50 tools): EVM data - balances, txs, blocks, ENS, ERC-20 Solana Token Analysis (6 tools): Risk scoring 0-100, pump.fun signals, momentum autonsol/sol-mcp: Real-time token risk + rug flags Usage: from app.caching_shield.mcp_sources import MCPDataSources mcp = MCPDataSources() score = await mcp.solana_risk_score("EPjFWd...") balance = await mcp.evm_balance("0x...", chain_id=1) """ import json import logging import time import httpx logger = logging.getLogger("mcp_sources") # Smithery MCP endpoints (HTTP transport) BOAR_URL = "https://server.smithery.ai/@boar-network/blockchain-advanced/mcp" SOLANA_TOKEN_URL = "https://server.smithery.ai/@insomniactools/solana-agentkit-mcp/mcp" AUTONSOL_URL = "https://server.smithery.ai/..." # placeholder - need exact URL # Cache _l1: dict[str, tuple] = {} _cache_hits = 0 _cache_misses = 0 class MCPDataSources: """Wraps free MCP servers as cached data sources.""" def __init__(self): self._http: httpx.AsyncClient | None = None async def _get_http(self) -> httpx.AsyncClient: if self._http is None: self._http = httpx.AsyncClient( timeout=15.0, headers={ "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "rmi-caching-shield/1.0", }, ) return self._http async def _mcp_call(self, url: str, tool: str, args: dict, ttl: int = 60) -> dict | None: """Make a cached MCP tool call.""" global _cache_hits, _cache_misses cache_key = f"mcp:{tool}:{json.dumps(args, sort_keys=True)}" # L1 cache entry = _l1.get(cache_key) if entry: expiry, data = entry if time.monotonic() < expiry: _cache_hits += 1 return data del _l1[cache_key] _cache_misses += 1 http = await self._get_http() try: # MCP JSON-RPC call r = await http.post( url, json={ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": tool, "arguments": args}, }, ) if r.status_code == 200: data = r.json() result = data.get("result", {}).get("content", [{}])[0].get("text", "") if result: try: parsed = json.loads(result) except Exception: parsed = {"value": str(result)[:500]} _l1[cache_key] = (time.monotonic() + ttl, parsed) return parsed except Exception as e: logger.debug(f"MCP {tool} error: {e}") return None # ═══════════════════════════════════════════════════════════════════════ # BOAR BLOCKCHAIN - EVM Data (50 tools, FREE, keyless) # ═══════════════════════════════════════════════════════════════════════ async def evm_balance(self, address: str, chain: str = "ethereum") -> dict | None: """Get ETH/native balance. Free, no key.""" return await self._mcp_call( BOAR_URL, "get_balance", { "address": address, "chain": chain, }, ttl=15, ) async def evm_transactions(self, address: str, chain: str = "ethereum", limit: int = 10) -> dict | None: """Get recent transactions.""" return await self._mcp_call( BOAR_URL, "get_transactions", { "address": address, "chain": chain, "limit": limit, }, ttl=30, ) async def evm_contract_info(self, address: str, chain: str = "ethereum") -> dict | None: """Get contract/ERC-20 info.""" return await self._mcp_call( BOAR_URL, "get_contract_info", { "address": address, "chain": chain, }, ttl=300, ) async def ens_resolve(self, name: str) -> dict | None: """Resolve ENS name to address.""" return await self._mcp_call( BOAR_URL, "resolve_ens", { "name": name, }, ttl=3600, ) async def evm_block_info(self, block_number: int | None = None, chain: str = "ethereum") -> dict | None: """Get block info.""" args = {"chain": chain} if block_number: args["block_number"] = block_number return await self._mcp_call(BOAR_URL, "get_block", args, ttl=60) # ═══════════════════════════════════════════════════════════════════════ # SOLANA TOKEN ANALYSIS - Risk Scoring (6 tools, NO AUTH) # ═══════════════════════════════════════════════════════════════════════ async def solana_risk_score(self, mint: str) -> dict | None: """Get token risk score 0-100. Free, no auth.""" return await self._mcp_call( SOLANA_TOKEN_URL, "analyze_token", { "token_address": mint, }, ttl=60, ) async def solana_momentum(self, mint: str) -> dict | None: """Get BUY/SELL momentum signals.""" return await self._mcp_call( SOLANA_TOKEN_URL, "get_momentum", { "token_address": mint, }, ttl=15, ) async def solana_batch_screen(self, mints: list[str]) -> dict | None: """Batch screen multiple tokens for risk.""" return await self._mcp_call( SOLANA_TOKEN_URL, "batch_screen", { "token_addresses": mints[:10], }, ttl=45, ) # ═══════════════════════════════════════════════════════════════════════ # AUTONSOL/SOL-MCP - Rug Detection (FREE) # ═══════════════════════════════════════════════════════════════════════ async def rug_check(self, mint: str) -> dict | None: """Real-time rug pull detection with flags.""" return await self._mcp_call( AUTONSOL_URL, "check_token", { "mint": mint, }, ttl=60, ) # ═══════════════════════════════════════════════════════════════════════ # STATS # ═══════════════════════════════════════════════════════════════════════ def stats(self) -> dict: return { "cache_hits": _cache_hits, "cache_misses": _cache_misses, "sources": { "boar_blockchain": "50 tools, EVM, FREE keyless", "solana_token_analysis": "6 tools, risk scoring, NO AUTH", "autonsol": "rug detection, FREE", }, } # Singleton _mcp: MCPDataSources | None = None def get_mcp_sources() -> MCPDataSources: global _mcp if _mcp is None: _mcp = MCPDataSources() return _mcp