608 lines
27 KiB
Python
608 lines
27 KiB
Python
"""
|
|
Gridbot MCP Server - Wraps eToroGridbot REST API as MCP tools.
|
|
|
|
Requires SSH tunnel: ssh -L 8000:localhost:8000 services
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from typing import Any
|
|
|
|
import httpx
|
|
from mcp.server import Server
|
|
from mcp.server.stdio import stdio_server
|
|
from mcp.types import Tool, TextContent
|
|
|
|
BASE_URL = os.getenv("GRIDBOT_API_URL", "http://localhost:8000")
|
|
TIMEOUT = 30.0
|
|
|
|
server = Server("gridbot-mcp")
|
|
|
|
|
|
async def api_get(endpoint: str) -> dict[str, Any]:
|
|
"""Make GET request to gridbot API."""
|
|
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
|
try:
|
|
resp = await client.get(f"{BASE_URL}{endpoint}")
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
except httpx.ConnectError:
|
|
return {"error": "Connection failed - is SSH tunnel running?"}
|
|
except httpx.HTTPStatusError as e:
|
|
return {"error": f"HTTP {e.response.status_code}: {e.response.text}"}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
async def api_post(endpoint: str, data: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
"""Make POST request to gridbot API."""
|
|
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
|
try:
|
|
resp = await client.post(f"{BASE_URL}{endpoint}", json=data or {})
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
except httpx.ConnectError:
|
|
return {"error": "Connection failed - is SSH tunnel running?"}
|
|
except httpx.HTTPStatusError as e:
|
|
return {"error": f"HTTP {e.response.status_code}: {e.response.text}"}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
async def api_delete(endpoint: str) -> dict[str, Any]:
|
|
"""Make DELETE request to gridbot API."""
|
|
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
|
try:
|
|
resp = await client.delete(f"{BASE_URL}{endpoint}")
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
except httpx.ConnectError:
|
|
return {"error": "Connection failed - is SSH tunnel running?"}
|
|
except httpx.HTTPStatusError as e:
|
|
return {"error": f"HTTP {e.response.status_code}: {e.response.text}"}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@server.list_tools()
|
|
async def list_tools() -> list[Tool]:
|
|
"""List available gridbot tools."""
|
|
return [
|
|
Tool(
|
|
name="health",
|
|
description="Check gridbot conductor health status, queue size, and capacity",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="emergency_status",
|
|
description="Get emergency stop status, circuit breaker state, drawdown, and daily loss limits",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="market_hours",
|
|
description="Get market hours summary for all exchanges (NYSE, NASDAQ, CRYPTO, FOREX, etc)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="positions",
|
|
description="Get all portfolio positions with real-time P/L (uses BotPriceProvider, works on weekends)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="pending_orders",
|
|
description="Get all pending limit orders with amounts, rates, TP/SL, and headroom",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="risk_score",
|
|
description="Get current portfolio risk score and components (volatility, drawdown, concentration, leverage)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="vix",
|
|
description="Get VIX (volatility index) value and interpretation (LOW/NORMAL/ELEVATED/HIGH)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="crypto_fear_greed",
|
|
description="Get Crypto Fear & Greed index with classification and trading guidance",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="dashboard",
|
|
description="Get full dashboard summary including positions, orders, and analytics",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="analytics",
|
|
description="Get trading analytics summary (win rate, Sharpe ratio, P/L)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="validate_trade",
|
|
description="Validate a trade before execution (checks risk limits, concentration, etc)",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Ticker symbol (e.g., BTC, AAPL)"},
|
|
"side": {"type": "string", "enum": ["BUY", "SELL"], "description": "Trade direction"},
|
|
"amount": {"type": "number", "description": "Trade amount in USD"},
|
|
},
|
|
"required": ["symbol", "side", "amount"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="queue_status",
|
|
description="Get signal processing queue status",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="emergency_stop",
|
|
description="HALT all trading (emergency use only)",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"reason": {"type": "string", "description": "Reason for emergency stop"},
|
|
"confirm": {"type": "boolean", "description": "Must be true to execute"},
|
|
},
|
|
"required": ["reason", "confirm"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="emergency_reset",
|
|
description="Resume trading after emergency stop",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"confirm": {"type": "boolean", "description": "Must be true to execute"},
|
|
},
|
|
"required": ["confirm"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="cleanup_stale_orders",
|
|
description="Cleanup stale/expired pending orders",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"confirm": {"type": "boolean", "description": "Must be true to execute"},
|
|
},
|
|
"required": ["confirm"],
|
|
},
|
|
),
|
|
# === NEW TOOLS ===
|
|
Tool(
|
|
name="bot_health",
|
|
description="Get health status of all trading bots (Freqtrade, Hummingbot, etc)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="stale_bots",
|
|
description="Get list of bots that haven't sent signals recently",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="correlations",
|
|
description="Get top correlated asset pairs in portfolio",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="trades",
|
|
description="Get recent trades with P/L details",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="expectancy",
|
|
description="Get trading expectancy metrics for all symbols",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="profitable_symbols",
|
|
description="Get list of profitable symbols ranked by expectancy",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="monitoring_stats",
|
|
description="Get system monitoring stats (signal counts, rejection reasons, latency)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="statistics",
|
|
description="Get trading statistics summary (win rate, avg P/L, trade counts)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="support_resistance",
|
|
description="Get support/resistance levels for a symbol",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Ticker symbol (e.g., BTC, AAPL)"},
|
|
},
|
|
"required": ["symbol"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="signal_quality",
|
|
description="Get signal quality report (acceptance rate, rejection reasons)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="trend_alerts",
|
|
description="Get active trend change alerts for monitored positions",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="env_health",
|
|
description="Get environment health (API keys, credentials status)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="candle_freshness",
|
|
description="Get candle data freshness (staleness check for each instrument)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="rate_limiter",
|
|
description="Get rate limiter statistics (requests per minute, throttled count)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="portfolio_allocation",
|
|
description="Get portfolio allocation breakdown (crypto %, stocks %, metals %)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="create_order",
|
|
description="Create a new order (BUY/SELL) with full validation pipeline. Goes through all risk checks.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Trading symbol (e.g., GLD, NVDA, BTC)"},
|
|
"action": {"type": "string", "enum": ["BUY", "SELL"], "description": "Trade direction"},
|
|
"amount": {"type": "number", "description": "Position size in USD (max 10000)"},
|
|
"order_type": {"type": "string", "enum": ["MARKET", "LIMIT"], "default": "LIMIT", "description": "Order type"},
|
|
"limit_price": {"type": "number", "description": "Limit price (required for LIMIT orders, omit for MARKET)"},
|
|
"take_profit_pct": {"type": "number", "description": "Take profit percentage (e.g., 10 for 10%)"},
|
|
"stop_loss_pct": {"type": "number", "description": "Stop loss percentage (e.g., 5 for 5%)"},
|
|
"dry_run": {"type": "boolean", "default": False, "description": "Validate only, do not execute"},
|
|
},
|
|
"required": ["symbol", "action", "amount"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="close_position",
|
|
description="Close an existing position (full or partial). Use position_id from positions tool.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"position_id": {"type": "integer", "description": "eToro position ID to close"},
|
|
"close_percentage": {"type": "number", "description": "Percentage to close (1-100). Omit for full close."},
|
|
},
|
|
"required": ["position_id"],
|
|
},
|
|
),
|
|
# === SIGNAL INTENTS ===
|
|
Tool(
|
|
name="intents",
|
|
description="List signal intents (pending orders awaiting trigger price). Filter by status or symbol.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"status": {"type": "string", "enum": ["pending", "executed", "expired", "rejected"], "description": "Filter by status"},
|
|
"symbol": {"type": "string", "description": "Filter by symbol (e.g., BTC, AAPL)"},
|
|
"limit": {"type": "number", "default": 50, "description": "Max results (1-200)"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="intents_pending",
|
|
description="List pending signal intents awaiting execution (shortcut for intents with status=pending)",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Filter by symbol (e.g., BTC, AAPL)"},
|
|
"limit": {"type": "number", "default": 50, "description": "Max results (1-200)"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="intents_executed",
|
|
description="List executed signal intents (orders that were triggered and filled)",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Filter by symbol (e.g., BTC, AAPL)"},
|
|
"limit": {"type": "number", "default": 50, "description": "Max results (1-200)"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="intents_stats",
|
|
description="Get signal intent statistics (pending/executed/expired counts, execution rate, amounts)",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
Tool(
|
|
name="create_intent",
|
|
description="Create a signal intent (limit order that executes when price reaches trigger). Use for weekend/after-hours trading.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {"type": "string", "description": "Trading symbol (e.g., BTC, NVDA, COIN)"},
|
|
"is_buy": {"type": "boolean", "default": True, "description": "True for BUY, False for SELL"},
|
|
"amount": {"type": "number", "description": "Position size in USD (max 10000)"},
|
|
"trigger_price": {"type": "number", "description": "Price at which to trigger execution"},
|
|
"take_profit_pct": {"type": "number", "description": "Take profit % (e.g., 10 for 10%). Default: 15% crypto, 9% stocks"},
|
|
"stop_loss_pct": {"type": "number", "description": "Stop loss % (e.g., 5 for 5%). Default: 10% crypto, 6% stocks"},
|
|
"expires_in_hours": {"type": "number", "description": "Hours until expiration (default: 168 stocks, 72 crypto)"},
|
|
"source_type": {"type": "string", "default": "manual", "description": "Signal source: manual, freqtrade, hummingbot, pi_post, newsletter"},
|
|
},
|
|
"required": ["symbol", "amount", "trigger_price"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="cancel_intent",
|
|
description="Cancel a pending signal intent by ID",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"intent_id": {"type": "string", "description": "Intent ID to cancel"},
|
|
},
|
|
"required": ["intent_id"],
|
|
},
|
|
),
|
|
# === ORDER MANAGEMENT ===
|
|
Tool(
|
|
name="cancel_order",
|
|
description="Cancel a single pending limit order by order_id",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"order_id": {"type": "integer", "description": "Order ID to cancel (from pending_orders)"},
|
|
},
|
|
"required": ["order_id"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="cancel_orders_bulk",
|
|
description="Cancel multiple pending orders at once. Use for cleaning up duplicates.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"order_ids": {
|
|
"type": "array",
|
|
"items": {"type": "integer"},
|
|
"description": "List of order IDs to cancel",
|
|
},
|
|
"confirm": {"type": "boolean", "description": "Must be true to execute"},
|
|
},
|
|
"required": ["order_ids", "confirm"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="find_duplicate_orders",
|
|
description="Find duplicate pending orders (same instrument + rate + direction). Returns order IDs to cancel.",
|
|
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
),
|
|
# === CONTAINER OPERATIONS ===
|
|
Tool(
|
|
name="container_logs",
|
|
description="Get container logs with optional grep filter. Allowed: conductor, freqtrade, hummingbot, dashboard, postgres, redis.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"container": {"type": "string", "enum": ["conductor", "freqtrade", "hummingbot", "dashboard", "postgres", "redis"], "description": "Container name"},
|
|
"tail": {"type": "number", "default": 100, "description": "Number of lines (1-1000)"},
|
|
"grep": {"type": "string", "description": "Grep pattern to filter logs"},
|
|
"since": {"type": "string", "description": "Show logs since (e.g., '1h', '30m')"},
|
|
},
|
|
"required": ["container"],
|
|
},
|
|
),
|
|
Tool(
|
|
name="container_restart",
|
|
description="Restart a container (requires confirm=true). Allowed: conductor, freqtrade, hummingbot, dashboard, postgres, redis.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"container": {"type": "string", "enum": ["conductor", "freqtrade", "hummingbot", "dashboard", "postgres", "redis"], "description": "Container to restart"},
|
|
"confirm": {"type": "boolean", "description": "Must be true to execute"},
|
|
},
|
|
"required": ["container", "confirm"],
|
|
},
|
|
),
|
|
]
|
|
|
|
|
|
@server.call_tool()
|
|
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
"""Execute a gridbot tool."""
|
|
result: dict[str, Any]
|
|
|
|
match name:
|
|
case "health":
|
|
result = await api_get("/health")
|
|
case "emergency_status":
|
|
result = await api_get("/emergency/status")
|
|
case "market_hours":
|
|
result = await api_get("/api/market-hours/summary")
|
|
case "positions":
|
|
result = await api_get("/portfolio/positions/detailed")
|
|
case "pending_orders":
|
|
result = await api_get("/portfolio/pending-orders")
|
|
case "risk_score":
|
|
result = await api_get("/api/risk-score/latest")
|
|
case "vix":
|
|
result = await api_get("/api/market-intel/vix")
|
|
case "crypto_fear_greed":
|
|
result = await api_get("/api/market-intel/crypto-fear-greed")
|
|
case "dashboard":
|
|
result = await api_get("/api/dashboard/summary")
|
|
case "analytics":
|
|
result = await api_get("/api/analytics/summary")
|
|
case "queue_status":
|
|
result = await api_get("/queue/status")
|
|
case "validate_trade":
|
|
result = await api_post("/portfolio/validate-trade", {
|
|
"symbol": arguments["symbol"],
|
|
"side": arguments["side"],
|
|
"amount": arguments["amount"],
|
|
})
|
|
case "emergency_stop":
|
|
if not arguments.get("confirm"):
|
|
result = {"error": "Must set confirm=true to execute emergency stop"}
|
|
else:
|
|
result = await api_post("/emergency/stop", {
|
|
"reason": arguments.get("reason", "Manual halt"),
|
|
"confirmation": True,
|
|
})
|
|
case "emergency_reset":
|
|
if not arguments.get("confirm"):
|
|
result = {"error": "Must set confirm=true to reset emergency stop"}
|
|
else:
|
|
result = await api_post("/emergency/reset", {"confirmation": True})
|
|
case "cleanup_stale_orders":
|
|
if not arguments.get("confirm"):
|
|
result = {"error": "Must set confirm=true to cleanup orders"}
|
|
else:
|
|
result = await api_post("/api/order-ttl/cleanup", {"confirmation": True})
|
|
# === NEW TOOL HANDLERS ===
|
|
case "bot_health":
|
|
result = await api_get("/api/bots/health")
|
|
case "stale_bots":
|
|
result = await api_get("/api/bots/stale")
|
|
case "correlations":
|
|
result = await api_get("/api/correlations/top")
|
|
case "trades":
|
|
result = await api_get("/api/dashboard/trades")
|
|
case "expectancy":
|
|
result = await api_get("/api/expectancy/")
|
|
case "profitable_symbols":
|
|
result = await api_get("/api/expectancy/profitable/list")
|
|
case "monitoring_stats":
|
|
result = await api_get("/api/monitoring/stats")
|
|
case "statistics":
|
|
result = await api_get("/api/statistics/summary")
|
|
case "support_resistance":
|
|
symbol = arguments.get("symbol", "BTC")
|
|
result = await api_get(f"/api/support-resistance/levels?symbol={symbol}")
|
|
case "signal_quality":
|
|
result = await api_get("/api/signal-quality/report")
|
|
case "trend_alerts":
|
|
result = await api_get("/api/trend-monitoring/alerts")
|
|
case "env_health":
|
|
result = await api_get("/api/health/env")
|
|
case "candle_freshness":
|
|
result = await api_get("/api/monitoring/candle-cache/stats")
|
|
case "rate_limiter":
|
|
result = await api_get("/api/monitoring/rate_limiter/stats")
|
|
case "portfolio_allocation":
|
|
result = await api_get("/api/portfolio/status")
|
|
case "create_order":
|
|
payload = {
|
|
"symbol": arguments["symbol"],
|
|
"action": arguments["action"],
|
|
"amount": arguments["amount"],
|
|
"order_type": arguments.get("order_type", "LIMIT"),
|
|
"take_profit_pct": arguments.get("take_profit_pct", 10),
|
|
"stop_loss_pct": arguments.get("stop_loss_pct", 5),
|
|
"dry_run": arguments.get("dry_run", False),
|
|
}
|
|
if arguments.get("limit_price"):
|
|
payload["limit_price"] = arguments["limit_price"]
|
|
result = await api_post("/api/trade/order", payload)
|
|
case "close_position":
|
|
payload = {"position_id": arguments["position_id"]}
|
|
if arguments.get("close_percentage"):
|
|
payload["close_percentage"] = arguments["close_percentage"]
|
|
result = await api_post("/api/trade/close", payload)
|
|
# === SIGNAL INTENTS ===
|
|
case "intents":
|
|
params = []
|
|
if arguments.get("status"):
|
|
params.append(f"status={arguments['status']}")
|
|
if arguments.get("symbol"):
|
|
params.append(f"symbol={arguments['symbol']}")
|
|
if arguments.get("limit"):
|
|
params.append(f"limit={arguments['limit']}")
|
|
query = f"?{'&'.join(params)}" if params else ""
|
|
result = await api_get(f"/intents{query}")
|
|
case "intents_pending":
|
|
params = []
|
|
if arguments.get("symbol"):
|
|
params.append(f"symbol={arguments['symbol']}")
|
|
if arguments.get("limit"):
|
|
params.append(f"limit={arguments['limit']}")
|
|
query = f"?{'&'.join(params)}" if params else ""
|
|
result = await api_get(f"/intents/pending{query}")
|
|
case "intents_executed":
|
|
params = []
|
|
if arguments.get("symbol"):
|
|
params.append(f"symbol={arguments['symbol']}")
|
|
if arguments.get("limit"):
|
|
params.append(f"limit={arguments['limit']}")
|
|
query = f"?{'&'.join(params)}" if params else ""
|
|
result = await api_get(f"/intents/executed{query}")
|
|
case "intents_stats":
|
|
result = await api_get("/intents/stats")
|
|
case "create_intent":
|
|
payload = {
|
|
"symbol": arguments["symbol"],
|
|
"is_buy": arguments.get("is_buy", True),
|
|
"amount": arguments["amount"],
|
|
"trigger_price": arguments["trigger_price"],
|
|
}
|
|
if arguments.get("take_profit_pct"):
|
|
payload["take_profit_pct"] = arguments["take_profit_pct"]
|
|
if arguments.get("stop_loss_pct"):
|
|
payload["stop_loss_pct"] = arguments["stop_loss_pct"]
|
|
if arguments.get("expires_in_hours"):
|
|
payload["expires_in_hours"] = arguments["expires_in_hours"]
|
|
if arguments.get("source_type"):
|
|
payload["source_type"] = arguments["source_type"]
|
|
result = await api_post("/intents", payload)
|
|
case "cancel_intent":
|
|
intent_id = arguments["intent_id"]
|
|
result = await api_delete(f"/intents/{intent_id}")
|
|
# === ORDER MANAGEMENT ===
|
|
case "cancel_order":
|
|
order_id = arguments["order_id"]
|
|
result = await api_delete(f"/api/trade/order/{order_id}")
|
|
case "cancel_orders_bulk":
|
|
if not arguments.get("confirm"):
|
|
result = {"error": "Must set confirm=true to cancel orders"}
|
|
else:
|
|
order_ids = arguments["order_ids"]
|
|
result = await api_post("/api/trade/orders/cancel-bulk", {"order_ids": order_ids})
|
|
case "find_duplicate_orders":
|
|
result = await api_get("/api/trade/orders/duplicates")
|
|
# === CONTAINER OPERATIONS ===
|
|
case "container_logs":
|
|
params = [f"container={arguments['container']}"]
|
|
if arguments.get("tail"):
|
|
params.append(f"tail={arguments['tail']}")
|
|
if arguments.get("grep"):
|
|
params.append(f"grep={arguments['grep']}")
|
|
if arguments.get("since"):
|
|
params.append(f"since={arguments['since']}")
|
|
result = await api_get(f"/containers/logs?{'&'.join(params)}")
|
|
case "container_restart":
|
|
result = await api_post(
|
|
f"/containers/restart?container={arguments['container']}&confirm={str(arguments.get('confirm', False)).lower()}"
|
|
)
|
|
case _:
|
|
result = {"error": f"Unknown tool: {name}"}
|
|
|
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
|
|
|
|
async def main():
|
|
"""Run the MCP server."""
|
|
async with stdio_server() as (read_stream, write_stream):
|
|
await server.run(read_stream, write_stream, server.create_initialization_options())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
asyncio.run(main())
|