| import os |
| import shutil |
| import pathlib |
| import subprocess |
| from datetime import datetime |
| import gradio as gr |
|
|
| from agents import Agent, Runner |
| from agents.mcp import MCPServerStdio |
|
|
| DEFAULT_EXCHANGE = os.getenv("EXCHANGE_NAME", "binance") |
|
|
| REPO_DIR = pathlib.Path("crypto-indicators-mcp") |
| REPO_URL = "https://github.com/kukapay/crypto-indicators-mcp.git" |
| REPO_ENTRY = REPO_DIR / "index.js" |
| REPO_NODE_MODULES = REPO_DIR / "node_modules" |
|
|
| def _cmd_exists(name: str) -> bool: |
| return shutil.which(name) is not None |
|
|
| def _run(cmd, **kwargs): |
| |
| print(f"[boot] $ {' '.join(cmd)}") |
| subprocess.run(cmd, check=True, **kwargs) |
|
|
| def ensure_repo_ready(): |
| print("[boot] ensuring prerequisites...") |
| if not _cmd_exists("git"): |
| raise RuntimeError("git not found. Add `git` to packages.txt.") |
| if not _cmd_exists("node"): |
| raise RuntimeError("node not found. Add `nodejs` to packages.txt.") |
| if not _cmd_exists("npm"): |
| raise RuntimeError("npm not found. Add `npm` to packages.txt.") |
|
|
| print("[boot] syncing repo...") |
| if not REPO_DIR.exists(): |
| _run(["git", "clone", "--depth=1", REPO_URL, str(REPO_DIR)]) |
| else: |
| _run(["git", "-C", str(REPO_DIR), "fetch", "--depth=1", "origin"]) |
| _run(["git", "-C", str(REPO_DIR), "reset", "--hard", "origin/HEAD"]) |
|
|
| |
| if not REPO_NODE_MODULES.exists(): |
| print("[boot] installing npm deps...") |
| lock = REPO_DIR / "package-lock.json" |
| cmd = ["npm", "ci"] if lock.exists() else ["npm", "install", "--omit=dev"] |
| _run(cmd, cwd=str(REPO_DIR)) |
| else: |
| print("[boot] node_modules present; skipping npm install.") |
|
|
| if not REPO_ENTRY.exists(): |
| raise FileNotFoundError(f"Cannot find {REPO_ENTRY}; check repository structure.") |
|
|
| |
| ensure_repo_ready() |
|
|
| async def handle_request(request: str): |
| instructions = f"""You are a cryptocurrency perpetuals trading researcher. You have the tools for trend/momentum/volatility/volume technical indicators and strategies. |
| Default analysis interval is 1h, and default lookback period is 36. |
| Default indicators/strategies you have are: |
| - EMA (20, 200) |
| - MACD (12, 26, 9) |
| - stochastic RSI (14, 14, 3, 3) |
| - Accumulation/Distribution (ADL) |
| - Volume |
| Based on the indicators, look for possible long/short opportunities, and propose a strategy. |
| The current datetime is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}""" |
|
|
| params = { |
| "command": "node", |
| "args": [str(REPO_ENTRY)], |
| "env": { |
| "EXCHANGE_NAME": DEFAULT_EXCHANGE |
| } |
| } |
|
|
| |
| async with MCPServerStdio(params=params, client_session_timeout_seconds=180) as mcp_server: |
| agent = Agent( |
| name="crypto_technical_researcher", |
| instructions=instructions, |
| model="gpt-4.1-mini", |
| mcp_servers=[mcp_server], |
| ) |
| result = await Runner.run(agent, request) |
| return result.final_output |
|
|
| iface = gr.Interface( |
| fn=handle_request, |
| inputs=gr.Textbox(label="Request", placeholder="e.g., Get the latest indicators on BTC"), |
| outputs=gr.Textbox(label="Response", lines=12), |
| title="Crypto Technical Researcher", |
| description="Ask for technical analysis via an MCP-powered toolchain." |
| ) |
|
|
| if __name__ == "__main__": |
| |
| iface.launch() |
|
|