rmi-backend/app/_archive/legacy_2026_07/mcp_router.py
cryptorugmunch 628c1d2a10
Some checks failed
CI / build (push) Failing after 3s
refactor(rmi-backend,audit): mount Wave 3 + archive 136 dead-code files (P2.3)
PHASE 2.3 (AUDIT-2026-Q3.md):

Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
  - app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
    POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
    Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
    with the v1 /api/v1/scanner/ stub.
  - app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
    library module consumed by unified_scanner_router via get_wallet_scanner()).
  - app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
    /api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).

Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
  - 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
  - 63 flat app/*.py (domain modules never imported by live code).
  - 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
    tests/unit/test_bridge_health.py which directly imports it; reach graph
    considered tests/ only as transitive reach — to be patched in next cycle).

Forced-LIVE (NOT archived per user directive):
  - app/ai_pipeline_v3.py  (3 importers in audit window, importers themselves DEAD)
  - app/splade_bm25.py       (LIVE via app.rag_service)
  - app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
  - app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)

Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
  - imports = 348 app.* modules
  - reached = 194 files reachable from roots
  - archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
  - Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)

pyproject.toml updates:
  - setuptools.packages.find: added exclude for app._archive*
  - ruff.extend-exclude: added "app/_archive/"
  - mypy.exclude: added "app/_archive/"

Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).

Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.

Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
2026-07-06 20:52:31 +02:00

397 lines
13 KiB
Python

"""
MCP Router - Receives tool execution requests from Cloudflare X402 Workers.
Exposes /mcp/tools (catalog) and /mcp/execute (tool execution).
This is what the worker calls when x402 payment is verified.
"""
import logging
import os
from datetime import UTC, datetime
import httpx
from fastapi import APIRouter, HTTPException, Request
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/mcp", tags=["mcp-router"])
# ═══════════════════════════════════════════════════════════
# TOOL CATALOG - What the worker fetches on startup
# ═══════════════════════════════════════════════════════════
TOOLS = {
# Security
"honeypot_check": {
"name": "Honeypot Detector",
"endpoint": "/api/v1/contract/audit",
"method": "POST",
"category": "security",
"price": "$0.05",
},
"rug_pull_predictor": {
"name": "Rug Pull Predictor",
"endpoint": "/api/v1/contract/audit",
"method": "POST",
"category": "security",
"price": "$0.10",
},
"rugshield": {
"name": "Rug Shield",
"endpoint": "/api/v1/security/scan",
"method": "POST",
"category": "security",
"price": "$0.02",
},
"audit": {
"name": "Smart Contract Audit",
"endpoint": "/api/v1/contract/audit",
"method": "POST",
"category": "security",
"price": "$0.05",
},
"mev_protection": {
"name": "MEV Protection Check",
"endpoint": "/api/v1/tools/cast/contract-info",
"method": "GET",
"category": "security",
"price": "$0.08",
},
"bridge_security": {
"name": "Bridge Security Monitor",
"endpoint": "/api/v1/defillama/chains",
"method": "GET",
"category": "security",
"price": "$0.08",
},
"profile_flip": {
"name": "Profile Flip Detector",
"endpoint": "/api/v1/token/scan",
"method": "POST",
"category": "security",
"price": "$0.03",
},
"fresh_pair": {
"name": "Fresh Pair Scanner",
"endpoint": "/api/v1/tokens/new",
"method": "GET",
"category": "security",
"price": "$0.03",
},
"clone_detect": {
"name": "Clone Detector",
"endpoint": "/api/v1/token/scan",
"method": "POST",
"category": "security",
"price": "$0.02",
},
"urlcheck": {
"name": "URL Safety Check",
"endpoint": "/api/v1/security/scan",
"method": "POST",
"category": "security",
"price": "$0.01",
},
# Intelligence
"whale": {
"name": "Whale Wallet Decoder",
"endpoint": "/api/v1/helius/whale-profile",
"method": "POST",
"category": "intelligence",
"price": "$0.15",
},
"whale_scan": {
"name": "Whale Scanner",
"endpoint": "/api/v1/helius/whale-scan",
"method": "POST",
"category": "intelligence",
"price": "$0.03",
},
"whale_profile": {
"name": "Whale Profile",
"endpoint": "/api/v1/helius/whale-profile",
"method": "POST",
"category": "intelligence",
"price": "$0.05",
},
"smartmoney": {
"name": "Smart Money Tracker",
"endpoint": "/api/v1/smart-money",
"method": "GET",
"category": "intelligence",
"price": "$0.05",
},
"cluster": {
"name": "Wallet Cluster Analysis",
"endpoint": "/api/v1/entity/clusters",
"method": "GET",
"category": "intelligence",
"price": "$0.05",
},
"insider": {
"name": "Insider Trading Detection",
"endpoint": "/api/v1/threat/reputation/0x0",
"method": "GET",
"category": "intelligence",
"price": "$0.10",
},
"sniper_detect": {
"name": "Sniper Detector",
"endpoint": "/api/v1/helius/sniper-detect",
"method": "POST",
"category": "intelligence",
"price": "$0.08",
},
"syndicate_scan": {
"name": "Syndicate Scanner",
"endpoint": "/api/v1/helius/syndicate/scan",
"method": "GET",
"category": "intelligence",
"price": "$0.08",
},
"copy_trade_finder": {
"name": "Copy Trade Finder",
"endpoint": "/api/v1/whales/top",
"method": "GET",
"category": "intelligence",
"price": "$0.10",
},
"liquidity_flow": {
"name": "Liquidity Flow Tracker",
"endpoint": "/api/v1/exchange/whales",
"method": "GET",
"category": "intelligence",
"price": "$0.08",
},
# Market
"pulse": {
"name": "Market Pulse",
"endpoint": "/api/v1/intelligence/dashboard",
"method": "GET",
"category": "market",
"price": "$0.01",
},
"market_overview": {
"name": "Market Overview",
"endpoint": "/api/v1/markets/ccxt",
"method": "GET",
"category": "market",
"price": "$0.05",
},
"chain_health": {
"name": "Chain Health",
"endpoint": "/api/v1/defillama/chains",
"method": "GET",
"category": "market",
"price": "$0.05",
},
"defi_yield_scanner": {
"name": "DeFi Yield Scanner",
"endpoint": "/api/v1/defillama/protocols",
"method": "GET",
"category": "market",
"price": "$0.08",
},
"gas_forecast": {
"name": "Gas Forecaster",
"endpoint": "/api/v1/mempool/status",
"method": "GET",
"category": "market",
"price": "$0.05",
},
# Analysis
"wallet": {
"name": "Wallet Analysis",
"endpoint": "/api/v1/wallet/multichain/{address}",
"method": "GET",
"category": "analysis",
"price": "$0.05",
},
"forensics": {
"name": "Token Forensics",
"endpoint": "/api/v1/wallet/{address}/analysis",
"method": "GET",
"category": "analysis",
"price": "$0.10",
},
"token_deep_dive": {
"name": "Token Deep Dive",
"endpoint": "/api/v1/birdeye/token/{address}",
"method": "GET",
"category": "analysis",
"price": "$0.10",
},
"portfolio_tracker": {
"name": "Portfolio Tracker",
"endpoint": "/api/v1/wallet/pnl/{address}",
"method": "GET",
"category": "analysis",
"price": "$0.10",
},
"token_comparison": {
"name": "Token Comparison",
"endpoint": "/api/v1/token/scan",
"method": "POST",
"category": "analysis",
"price": "$0.08",
},
"nft_wash_detector": {
"name": "NFT Wash Detector",
"endpoint": "/api/v1/threat/reputation/0x0",
"method": "GET",
"category": "analysis",
"price": "$0.10",
},
# Launch
"launch": {
"name": "Token Launch Analysis",
"endpoint": "/api/v1/tokens/new",
"method": "GET",
"category": "launchpad",
"price": "$0.03",
},
"launch_intel": {
"name": "Launch Intelligence",
"endpoint": "/api/v1/tokens/trending",
"method": "GET",
"category": "launchpad",
"price": "$0.05",
},
"sniper_alert": {
"name": "Sniper Alert",
"endpoint": "/api/v1/tokens/new",
"method": "GET",
"category": "launchpad",
"price": "$0.05",
},
"airdrop_finder": {
"name": "Airdrop Finder",
"endpoint": "/api/v1/wallet/pnl/{address}",
"method": "GET",
"category": "intelligence",
"price": "$0.05",
},
# Social
"sentiment": {
"name": "Sentiment Analysis",
"endpoint": "/api/v1/sentiment/market",
"method": "GET",
"category": "social",
"price": "$0.03",
},
"social_signal": {
"name": "Social Signal Analyzer",
"endpoint": "/api/v1/sentiment/token/{address}",
"method": "GET",
"category": "social",
"price": "$0.10",
},
}
def _build_tools_catalog() -> dict:
"""Merge hardcoded TOOLS with dynamic TOOL_PRICES, preferring dynamic data."""
merged = dict(TOOLS) # Start with hardcoded
try:
from app.routers.x402_enforcement import TOOL_PRICES
# Tool ID → internal endpoint mapping
_ENDPOINT_MAP = {
"forensic_valuation": "/api/v1/x402-tools/forensic_valuation",
"osint_identity_hunt": "/api/v1/x402-tools/osint_identity_hunt",
"investigation_report": "/api/v1/x402-tools/investigation_report",
"forensic_pack": "/api/v1/x402-tools/forensic_pack",
"catalog": "/api/v1/x402-tools/catalog",
"smart_money_alpha": "/api/v1/x402-tools/smart_money_alpha",
"meme_vibe_score": "/api/v1/x402-tools/meme_vibe_score",
"mcp-proxy": "/api/v1/x402-tools/mcp-proxy",
"human-execute": "/api/v1/x402-tools/human-execute",
}
for tool_id, pricing in TOOL_PRICES.items():
if tool_id not in merged:
endpoint = _ENDPOINT_MAP.get(tool_id, f"/api/v1/x402-tools/{tool_id}")
merged[tool_id] = {
"name": pricing.get("description", tool_id.replace("_", " ").title()),
"endpoint": endpoint,
"method": "POST",
"category": pricing.get("category", "analysis"),
"price": f"${pricing.get('price_usd', 0.01):.2f}",
}
else:
# Update existing entries with latest pricing/category from TOOL_PRICES
if tool_id in TOOL_PRICES:
merged[tool_id]["category"] = TOOL_PRICES[tool_id].get(
"category", merged[tool_id].get("category", "analysis")
)
merged[tool_id]["price"] = f"${TOOL_PRICES[tool_id].get('price_usd', 0.01):.2f}"
except Exception as e:
logger.warning(f"mcp_router: could not merge TOOL_PRICES: {e}")
return merged
@router.get("/tools")
async def mcp_tools():
"""Return tool catalog for Cloudflare Worker to cache."""
catalog = _build_tools_catalog()
return {
"tools": catalog,
"total": len(catalog),
"categories": list({t["category"] for t in catalog.values()}),
"updated_at": datetime.now(UTC).isoformat(),
}
@router.post("/execute/{tool_id}")
async def mcp_execute(tool_id: str, request: Request):
"""Execute a tool - called by Cloudflare Worker after x402 payment verified."""
tool = TOOLS.get(tool_id)
if not tool:
raise HTTPException(status_code=404, detail=f"Tool {tool_id} not found")
try:
body = await request.json() if request.headers.get("content-type") == "application/json" else {}
except Exception:
body = {}
# Forward to the actual backend endpoint
endpoint = tool["endpoint"]
method = tool["method"]
# Replace path parameters from body
for key in ["address", "token", "chain"]:
if f"{{{key}}}" in endpoint and key in body:
endpoint = endpoint.replace(f"{{{key}}}", body[key])
# Internal calls bypass auth - add internal API key header
internal_headers = {}
auth_token = os.getenv("RMI_AUTH_TOKEN", "")
if auth_token:
internal_headers["X-API-Key"] = auth_token
try:
async with httpx.AsyncClient(timeout=30) as c:
if method == "GET":
# Build query params from body
params = {k: v for k, v in body.items() if k not in ["address", "token"] and f"{{{k}}}" not in endpoint}
r = await c.get(f"http://127.0.0.1:8000{endpoint}", params=params, headers=internal_headers)
else:
r = await c.post(f"http://127.0.0.1:8000{endpoint}", json=body, headers=internal_headers)
if r.status_code == 200:
return {
"tool": tool_id,
"status": "success",
"data": r.json(),
"executed_at": datetime.now(UTC).isoformat(),
}
return {
"tool": tool_id,
"status": "error",
"error": f"Backend returned {r.status_code}",
"executed_at": datetime.now(UTC).isoformat(),
}
except Exception as e:
return {"tool": tool_id, "status": "error", "error": str(e)}
@router.get("/health")
async def mcp_health():
catalog = _build_tools_catalog()
return {"status": "healthy", "tools": len(catalog), "timestamp": datetime.now(UTC).isoformat()}