refactor(V2): get_instruments common-only on /mcp (enriched with Deribit filters)
Move get_instruments off the per-exchange Deribit router and onto the
unified interface, without losing the advanced Deribit filtering.
- /mcp/tools/get_instruments now accepts the Deribit-specific filters
(currency, kind, expiry_from/to, strike_min/max, min_open_interest) and
pagination (offset/limit). Venues that list everything (Hyperliquid)
ignore them. Response adds `meta: {deribit: {total, offset, limit,
has_more}}` surfacing per-source pagination.
- Remove /mcp-deribit/tools/get_instruments (endpoint + tool wrapper +
GetInstrumentsReq schema). The DeribitClient.get_instruments METHOD
stays — it's what the unified normalizer calls.
Tests: cover the enriched filters + meta passthrough; app-boot asserts
/mcp-deribit/tools/get_instruments is gone. Docs (API_REFERENCE/README/
CLAUDE) and smoke updated; also fixed the stale Hyperliquid tool count
(14 → 15) found during the doc check.
325 passed, ruff clean. Verified live: kind=option limit=5 → 5/940
has_more=true; /mcp-deribit/tools/get_instruments → 404.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,8 @@ def test_app_boots_and_health_responds(monkeypatch):
|
||||
assert "/mcp/tools/get_instruments" in paths
|
||||
assert "/mcp/tools/get_historical" in paths
|
||||
assert "/mcp/tools/get_indicators" in paths
|
||||
# get_historical / get_indicators are common-only, not per-exchange
|
||||
# get_instruments / get_historical / get_indicators are common-only
|
||||
assert "/mcp-deribit/tools/get_instruments" not in paths
|
||||
assert "/mcp-deribit/tools/get_historical" not in paths
|
||||
assert "/mcp-deribit/tools/get_technical_indicators" not in paths
|
||||
assert "/mcp-hyperliquid/tools/get_historical" not in paths
|
||||
|
||||
@@ -38,14 +38,21 @@ post() {
|
||||
echo " OK ${path} → HTTP ${status}"
|
||||
}
|
||||
|
||||
echo "==> get_instruments exchange=deribit (fees + history_start live)"
|
||||
echo "==> get_instruments exchange=deribit (fees + history_start live + meta)"
|
||||
post "/mcp/tools/get_instruments" '{"exchange":"deribit","currency":"BTC","kind":"future"}' 200 \
|
||||
"assert d['instruments']; i=d['instruments'][0]; assert i['exchange']=='deribit'; assert 'fees' in i and 'history_start' in i; print(' ', i['symbol'], i['fees'], i['history_start'])"
|
||||
"assert d['instruments']; i=d['instruments'][0]; assert i['exchange']=='deribit'; assert 'fees' in i and 'history_start' in i; assert 'has_more' in d['meta']['deribit']; print(' ', i['symbol'], i['fees'], i['history_start'], '| meta:', d['meta']['deribit'])"
|
||||
|
||||
echo "==> get_instruments deribit option chain paginato (kind=option, limit=5)"
|
||||
post "/mcp/tools/get_instruments" '{"exchange":"deribit","currency":"BTC","kind":"option","limit":5,"offset":0}' 200 \
|
||||
"assert len(d['instruments'])<=5; assert d['meta']['deribit']['limit']==5; print(' got:', len(d['instruments']), '| total:', d['meta']['deribit'].get('total'), '| has_more:', d['meta']['deribit'].get('has_more'))"
|
||||
|
||||
echo "==> get_instruments fan-out (deribit + hyperliquid)"
|
||||
post "/mcp/tools/get_instruments" '{}' 200 \
|
||||
"exs={i['exchange'] for i in d['instruments']}; assert {'deribit','hyperliquid'} <= exs, exs; print(' venues:', sorted(exs))"
|
||||
|
||||
echo "==> get_instruments NON più su /mcp-deribit → 404"
|
||||
post "/mcp-deribit/tools/get_instruments" '{"currency":"BTC"}' 404
|
||||
|
||||
echo "==> get_historical SINGLE deribit BTC-PERPETUAL 1h"
|
||||
post "/mcp/tools/get_historical" "{\"exchange\":\"deribit\",\"instrument\":\"BTC-PERPETUAL\",\"interval\":\"1h\",\"start_date\":\"${start}\",\"end_date\":\"${end}\"}" 200 \
|
||||
"assert d['exchange']=='deribit'; assert d['candles']; c=d['candles'][0]; assert {'timestamp','open','high','low','close','volume'} <= set(c); print(' candles:', len(d['candles']))"
|
||||
|
||||
@@ -68,11 +68,18 @@ class _FakeDeribit:
|
||||
self._raises = raises
|
||||
|
||||
async def get_instruments(self, **kwargs: Any) -> dict[str, Any]:
|
||||
return {"instruments": [{
|
||||
"name": "BTC-PERPETUAL", "kind": "future", "tick_size": 0.5,
|
||||
"maker_commission": 0.0, "taker_commission": 0.0005,
|
||||
"creation_timestamp": 1534377600000,
|
||||
}]}
|
||||
self.inst_call = kwargs
|
||||
return {
|
||||
"instruments": [{
|
||||
"name": "BTC-PERPETUAL", "kind": "future", "tick_size": 0.5,
|
||||
"maker_commission": 0.0, "taker_commission": 0.0005,
|
||||
"creation_timestamp": 1534377600000,
|
||||
}],
|
||||
"total": 1,
|
||||
"offset": kwargs.get("offset", 0),
|
||||
"limit": kwargs.get("limit", 100),
|
||||
"has_more": False,
|
||||
}
|
||||
|
||||
async def get_historical(self, **kwargs: Any) -> dict[str, Any]:
|
||||
if self._raises:
|
||||
@@ -136,6 +143,27 @@ async def test_get_instruments_unsupported_exchange_raises_400():
|
||||
assert exc.value.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_instruments_deribit_filters_and_meta():
|
||||
fake = _FakeDeribit()
|
||||
uc = UnifiedClient(_FakeRegistry({"deribit": fake}), env="mainnet")
|
||||
out = await uc.get_instruments(
|
||||
exchange="deribit", currency="ETH", kind="option",
|
||||
strike_min=1000.0, expiry_to="2026-12-31", min_open_interest=5.0,
|
||||
offset=5, limit=10,
|
||||
)
|
||||
# advanced Deribit filters reach the client
|
||||
assert fake.inst_call["currency"] == "ETH"
|
||||
assert fake.inst_call["kind"] == "option"
|
||||
assert fake.inst_call["strike_min"] == 1000.0
|
||||
assert fake.inst_call["expiry_to"] == "2026-12-31"
|
||||
assert fake.inst_call["min_open_interest"] == 5.0
|
||||
assert fake.inst_call["offset"] == 5 and fake.inst_call["limit"] == 10
|
||||
# pagination metadata surfaced per source
|
||||
assert out["meta"]["deribit"]["limit"] == 10
|
||||
assert out["meta"]["deribit"]["has_more"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_instruments_partial_failure_reports_failed():
|
||||
uc = UnifiedClient(_FakeRegistry({"deribit": _FakeDeribit()}), env="mainnet")
|
||||
|
||||
Reference in New Issue
Block a user