"""Configuration management for Opinion.trade MCP server."""
import os
from typing import Optional
from pydantic import BaseModel, Field, field_validator, model_validator
class OpinionConfig(BaseModel):
"""Opinion.trade configuration from environment variables.
Supports dual-mode operation:
- Read-only mode: API key only, market data tools available
- Trading mode: API key + private key, full trading access with EIP712 signing
"""
# Required Configuration
api_key: str = Field(
...,
min_length=1,
description="Opinion.trade API key for authentication"
)
# Trading Mode Configuration (Optional)
private_key: Optional[str] = Field(
default=None,
description="Ethereum private key for EIP712 signing (enables trading mode)"
)
# Network Settings
chain_id: int = Field(
default=56,
gt=0,
description="Chain ID (56=BNB mainnet, 97=BNB testnet)"
)
# API Endpoints
api_host: str = Field(
default="https://proxy.opinion.trade:8443",
description="Opinion.trade API endpoint"
)
# General Settings
api_timeout: int = Field(
default=30,
gt=0,
description="API request timeout in seconds"
)
enable_trading: bool = Field(
default=False,
description="Enable trading mode (auto-set from private_key presence)"
)
class Config:
case_sensitive = False
@model_validator(mode='after')
def set_enable_trading(self):
"""Automatically set enable_trading based on private_key presence."""
if self.private_key:
self.enable_trading = True
return self
@classmethod
def from_env(cls) -> "OpinionConfig":
"""Load configuration from environment variables.
Environment variables:
OPINION_API_KEY: Required - API key for authentication
OPINION_PRIVATE_KEY: Optional - Private key for trading
OPINION_CHAIN_ID: Optional - Chain ID (default: 56)
OPINION_API_HOST: Optional - API host (default: https://proxy.opinion.trade:8443)
OPINION_API_TIMEOUT: Optional - Request timeout (default: 30)
Returns:
OpinionConfig: Configuration object
Raises:
ValueError: If API key is missing or chain_id is invalid
"""
from dotenv import load_dotenv
load_dotenv()
# Load required configuration
api_key = os.getenv("OPINION_API_KEY")
if not api_key:
raise ValueError(
"OPINION_API_KEY environment variable is required. "
"Get your API key from https://docs.opinion.trade/"
)
# Load optional configuration
private_key = os.getenv("OPINION_PRIVATE_KEY") or None
chain_id_str = os.getenv("OPINION_CHAIN_ID", "56")
api_host = os.getenv("OPINION_API_HOST", "https://proxy.opinion.trade:8443")
api_timeout_str = os.getenv("OPINION_API_TIMEOUT", "30")
# Parse numeric values
try:
chain_id = int(chain_id_str)
except ValueError:
raise ValueError(
f"OPINION_CHAIN_ID must be an integer, got '{chain_id_str}'"
)
try:
api_timeout = int(api_timeout_str)
except ValueError:
raise ValueError(
f"OPINION_API_TIMEOUT must be an integer, got '{api_timeout_str}'"
)
# Validate chain_id
if chain_id not in (56, 97):
print(
f"Warning: OPINION_CHAIN_ID {chain_id} is not standard. "
f"Expected 56 (BNB mainnet) or 97 (BNB testnet)"
)
# Validate private key format if provided
if private_key:
if not private_key.startswith("0x"):
print(
"Warning: OPINION_PRIVATE_KEY should start with '0x'. "
"Attempting to use as-is."
)
# Determine if trading mode should be enabled
enable_trading = bool(private_key)
return cls(
api_key=api_key,
private_key=private_key,
chain_id=chain_id,
api_host=api_host,
api_timeout=api_timeout,
enable_trading=enable_trading
)
def get_mode_description(self) -> str:
"""Get human-readable description of current mode.
Returns:
str: Mode description (e.g., "Trading Mode - BNB Chain" or "Read-Only Mode")
"""
if self.enable_trading:
chain_name = {
56: "BNB Mainnet",
97: "BNB Testnet"
}.get(self.chain_id, f"Chain {self.chain_id}")
return f"Trading Mode - {chain_name}"
else:
return "Read-Only Mode - Market data only"