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:
| Field | Type | Description |
|---|---|---|
policy_level | int 1–5 | 1=BLOCK Survival, 2=BLOCK Defensive, 3=CAUTIOUS, 4=GREEN Selective, 5=GREEN Expansion. Returned as integer, not string label. |
exposure_policy.max_size_fraction | float 0–1 | Maximum position size as fraction of portfolio (multiply by 100 for %). |
exposure_policy.max_leverage | string | DeFi 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_bias | string | LONG_PREFERRED, SHORT_PREFERRED, or NEUTRAL. |
exposure_policy.allowed_actions / blocked_actions | string[] | Enum tokens documented in the API action reference. |
risk_flags.structural_blockers | string[] | If non-empty, agent MUST pause new entries. |
confidence_score | float 0–1 | Signal agreement × data quality. NOT a probability of market prediction. |
data_quality_score | int 0–100 | % of data sources reporting LIVE data. |
binding_constraint.source | string | RULES, DEFI, MACRO, or CYCLE — which cap is limiting. |
policy_hash | string | SHA-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
| Variable | Required | Description |
|---|---|---|
RISKSTATE_API_KEY | Yes | API key from riskstate.ai (free during beta) |
RISKSTATE_API_URL | No | Custom 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
| Parameter | Type | Required | Description |
|---|---|---|---|
asset | "BTC" | "ETH" | Yes | Asset to analyze |
wallet_address | string | No | DeFi wallet for on-chain position data |
protocol | "spark" | "aave" | No | Lending protocol (default: spark) |
include_details | boolean | No | Include 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:
- If
risk_flags.structural_blockersis non-empty → do not open new positions - If
policy_level <= 2(BLOCK Survival or BLOCK Defensive) → do not open new positions - If
exposure_policy.blocked_actionscontainsNEW_TRADES→ do not open new positions - Cap position size:
portfolio_value * exposure_policy.max_size_fraction * conviction - Respect
exposure_policy.max_leverage(DeFi) anddirection_bias(all markets) - Filter your trade through
exposure_policy.allowed_actions/blocked_actions - If
data_quality_score < 70orstale_fieldsis non-empty → halve size or abstain - Re-query after
ttl_seconds(default 60s; cached responses indicated bycached: 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-statefor multi-position aggregation,/v2/stress-scenariofor 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
- Landing page: riskstate.ai
- API reference: /docs/api
- npm package: @riskstate/mcp-server
License
MIT