MCP Server

MCP server for RiskState — pre-trade risk permissions for BTC/USD and ETH/USD. Spot, perpetual futures (perps), and DeFi borrowing aware.

Your system asks: "How much can I risk right now?" RiskState answers with: policy level, max exposure, leverage limits, blocked actions — computed from 30+ real-time signals.

What it does

Wraps the RiskState /v1/risk-state API as an MCP tool. One tool: get_risk_policy. The wrapper returns a human-readable summary followed by the full v1.4.0 JSON response so agents can route either depending on how much detail they need.

Key fields agents typically read from the JSON:

FieldTypeDescription
policy_levelint 1–51=BLOCK Survival, 2=BLOCK Defensive, 3=CAUTIOUS, 4=GREEN Selective, 5=GREEN Expansion. Returned as integer, not string label.
exposure_policy.max_size_fractionfloat 0–1Maximum position size as fraction of portfolio (multiply by 100 for %).
exposure_policy.max_leveragestringDeFi borrowing cap as "0x", "1x", "1.5x", or "2x". For perps, use max_size_fraction as notional cap and choose your own leverage.
exposure_policy.direction_biasstringLONG_PREFERRED, SHORT_PREFERRED, or NEUTRAL.
exposure_policy.allowed_actions / blocked_actionsstring[]Enum tokens documented in the API action reference.
risk_flags.structural_blockersstring[]If non-empty, agent MUST pause new entries.
confidence_scorefloat 0–1Signal agreement × data quality. NOT a probability of market prediction.
data_quality_scoreint 0–100% of data sources reporting LIVE data.
binding_constraint.sourcestringRULES, DEFI, MACRO, or CYCLE — which cap is limiting.
policy_hashstringSHA-256 over scoring inputs for non-repudiation.

v1.4.0 (score_v3) also exposes structural_score, tactical_score, policy_permissions (with direction_layer) — the new semantic layer that will drive sizing from score_v4 onward (earliest 2026-10-22). See Methodology for the full layered model.

The API aggregates 15+ external APIs and ~30 real-time signals server-side (Binance + cascading OKX/Bybit/CoinGlass fallbacks, FRED, Yahoo Finance, Lido, DefiLlama, Ultrasound, Frankfurter). See the API reference for the full response schema.

What this wrapper does (and doesn't)

This is a thin wrapper — it translates MCP tool calls into REST API requests to POST /v1/risk-state and returns the response. All computation (scoring, policy engine, data ingestion) happens server-side.

This wrapper adds:

  • MCP protocol compliance (stdio transport for Claude Desktop / Claude Code)
  • Input validation via Zod schemas
  • Human-readable policy summary prepended to responses
  • Specific error messages (auth, rate limit, timeout) for agent recovery

This wrapper does NOT:

  • Cache responses (the API has 60s server-side cache)
  • Perform any scoring or computation locally
  • Guarantee response schema stability (follows API versioning)

Installation

npm install @riskstate/mcp-server

Configuration

Environment Variables

VariableRequiredDescription
RISKSTATE_API_KEYYesAPI key from riskstate.ai (free during beta)
RISKSTATE_API_URLNoCustom API base URL (default: https://api.riskstate.ai)

Claude Desktop

Add to ~/.config/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "riskstate": {
      "command": "npx",
      "args": ["-p", "@riskstate/mcp-server", "riskstate-mcp"],
      "env": {
        "RISKSTATE_API_KEY": "your-api-key"
      }
    }
  }
}

Claude Code

claude mcp add riskstate -- npx -p @riskstate/mcp-server riskstate-mcp

Set the API key in your environment:

export RISKSTATE_API_KEY=your-api-key

Global install (alternative)

npm install -g @riskstate/mcp-server
riskstate-mcp  # starts MCP server on stdio

Usage

The server exposes one tool: get_risk_policy.

Parameters

ParameterTypeRequiredDescription
asset"BTC" | "ETH"YesAsset to analyze
wallet_addressstringNoDeFi wallet for on-chain position data
protocol"spark" | "aave"NoLending protocol (default: spark)
include_detailsbooleanNoInclude full breakdown (subscores, macro, risk flags)

Example Response

The wrapper returns a text content with two parts: a one-screen human-readable summary, then a blank line, then the full JSON response from /v1/risk-state.

Summary block (synthesized by the wrapper):

POLICY: Level 3 | MID
MAX SIZE: 47.0%
LEVERAGE: 0x
BLOCKED: LEVERAGE, AGGRESSIVE_LONG, ALL_IN
REGIME: RANGE | VOLATILITY: NORMAL
CONFIDENCE: 0.68 | DATA QUALITY: 88%
BINDING: CYCLE (MID cycle + NEUTRAL macro)
TTL: 60s

Followed by the v1.4.0 JSON (illustrative — values not current market state):

{
  "exposure_policy": {
    "max_size_fraction": 0.47,
    "leverage_allowed": false,
    "max_leverage": "0x",
    "direction_bias": "NEUTRAL",
    "direction_layer": "tactical",
    "reduce_recommended": false,
    "allowed_actions": ["WAIT", "DCA", "RR_GT_2"],
    "blocked_actions": ["LEVERAGE", "AGGRESSIVE_LONG", "ALL_IN"]
  },
  "tactical_state": "NEUTRAL",
  "structural_state": "MID",
  "macro_state": "NEUTRAL",
  "market_regime": "RANGE",
  "volatility_regime": "NORMAL",
  "policy_level": 3,
  "confidence_score": 0.68,
  "data_quality_score": 88,
  "binding_constraint": {
    "source": "CYCLE",
    "reason": "MID cycle + NEUTRAL macro",
    "reason_codes": ["CYCLE_MID", "MACRO_NEUTRAL"],
    "cap_value": 0.55
  },
  "risk_flags": {
    "structural_blockers": [],
    "context_risks": ["HIGH_FUNDING"]
  },
  "structural_score": { "overall": 58, "label": "NEUTRAL", "dataQuality": 92 },
  "tactical_score":   { "overall": 52, "label": "NEUTRAL" },
  "policy_permissions": {
    "risk_permission_score": 54,
    "direction_bias": "NEUTRAL",
    "direction_layer": "tactical",
    "regime_context": "RANGE",
    "reason_codes": ["TACTICAL_NEUTRAL_52", "STRUCTURAL_NEUTRAL_58"]
  },
  "defi": null,
  "policy_hash": "a3f8c2e1d5b9f7a4c8e2b6d1f9a3c7e5b2d6f8a1c3e7b9d5f2a6c4e8b1d7f3a9",
  "scoring_version": "score_v3",
  "version": "1.4.0",
  "timestamp": "2026-04-23T10:34:12.847Z",
  "asset": "BTC",
  "cached": false,
  "ttl_seconds": 60,
  "key_type": "external",
  "stale_fields": []
}

The JSON shape mirrors the v1 API response exactly — no transformations beyond serialization.

How agents should use this

Call get_risk_policy before every trade:

  1. If risk_flags.structural_blockers is non-empty → do not open new positions
  2. If policy_level <= 2 (BLOCK Survival or BLOCK Defensive) → do not open new positions
  3. If exposure_policy.blocked_actions contains NEW_TRADES → do not open new positions
  4. Cap position size: portfolio_value * exposure_policy.max_size_fraction * conviction
  5. Respect exposure_policy.max_leverage (DeFi) and direction_bias (all markets)
  6. Filter your trade through exposure_policy.allowed_actions / blocked_actions
  7. If data_quality_score < 70 or stale_fields is non-empty → halve size or abstain
  8. Re-query after ttl_seconds (default 60s; cached responses indicated by cached: true)

Use policy_level for human/log readability; treat the exposure_policy fields as the binding contract.

Limitations

  • v1 scope: BTC/USD and ETH/USD only (USD-denominated assessment). More assets planned.
  • Markets: Spot, perpetual futures, and DeFi borrowing. Same response — interpretation differs by market (see the API reference).
  • Protocols: Spark and Aave V3 only for DeFi position data.
  • Rate limit: 60 requests/minute per API key.
  • Latency: ~5–12s on cold-cache fresh compute (15+ upstream API aggregation), <1s on cached requests within the 60s TTL.
  • MCP wraps v1 only. v2 endpoints (/v2/portfolio-risk-state for multi-position aggregation, /v2/stress-scenario for tail scenarios) are alpha and exposed as REST only — see API v2 alpha. MCP coverage of v2 will follow v2 stabilisation.
  • Tested with: Claude Desktop, Claude Code. Should work with any MCP-compatible client.

Links

License

MIT