diff --git a/README.md b/README.md index f69ebcc..2e8af28 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Strategies Tests License - MCP + MCP

@@ -481,9 +481,9 @@ hl mcp serve # stdio transport (default) hl mcp serve --transport sse # SSE transport ``` -**16 tools exposed:** `account`, `status`, `trade`, `run_strategy`, `strategies`, `radar_run`, `apex_status`, `apex_run`, `reflect_run`, `setup_check`, `builder_status`, `wallet_list`, `wallet_auto`, `agent_memory`, `trade_journal`, `judge_report` +**20 tools exposed:** `account`, `status`, `trade`, `run_strategy`, `strategies`, `radar_run`, `apex_status`, `apex_run`, `reflect_run`, `setup_check`, `builder_status`, `wallet_list`, `wallet_auto`, `agent_memory`, `trade_journal`, `judge_report`, `obsidian_context`, `treadfi_spec_status`, `treadfi_capabilities`, `treadfi_market_params` -Fast tools (strategies, builder, wallet, setup, memory, journal, judge) call Python directly — zero subprocess overhead. +Fast tools (strategies, builder, wallet, setup, memory, journal, judge, TreadFi status) call Python directly — zero subprocess overhead. ### HTTP API & SSE @@ -533,7 +533,7 @@ One-click deploy of a full OpenClaw agent that uses our CLI as the tool backend. **What you get:** - OpenClaw gateway with web UI at `/openclaw` - Telegram integration — chat with your bot to start/stop trading, run scans, check status -- Our 13 MCP trading tools as the agent's primary capabilities +- Our MCP trading tool catalog as the agent's primary capabilities - Persistent state across redeploys via `/data` volume - Auto-onboard: bot sends "Agent ready" to Telegram on first deploy - REFLECT self-improvement: the agent analyzes its own trades and adjusts strategy parameters @@ -572,7 +572,7 @@ hl run engine_mm -i BTCSWP-USDYP --tick 10 ``` cli/ CLI commands and trading engine commands/ Subcommand modules (run, apex, radar, pulse, guard, reflect, house, ...) - mcp_server.py MCP server (16 tools via FastMCP) + mcp_server.py MCP server (20 tools via FastMCP) hl_adapter.py Direct HL API adapter (live + mock) builder_fee.py Builder fee config (HL native BuilderInfo) keystore.py Encrypted keystore (geth-compatible) diff --git a/cli/commands/treadfi.py b/cli/commands/treadfi.py new file mode 100644 index 0000000..cb53ca3 --- /dev/null +++ b/cli/commands/treadfi.py @@ -0,0 +1,35 @@ +"""hl treadfi — read-only TreadFi integration status.""" +from __future__ import annotations + +import json +from typing import Any + +import typer + +from modules import treadfi_contract + +treadfi_app = typer.Typer(no_args_is_help=True) + + +def _echo_json(payload: dict[str, Any]) -> None: + typer.echo(json.dumps(payload, indent=2)) + + +@treadfi_app.command("spec-status") +def treadfi_spec_status(): + """Report whether the TreadFi endpoint/MCP contract is present.""" + _echo_json(treadfi_contract.spec_status()) + + +@treadfi_app.command("capabilities") +def treadfi_capabilities(): + """List local TreadFi placeholders and blocked live capabilities.""" + _echo_json(treadfi_contract.capabilities()) + + +@treadfi_app.command("market-params") +def treadfi_market_params( + instrument: str = typer.Option("BTCSWP-USDYP", "--instrument", "-i"), +): + """Show locally known BTCSWP identifiers and missing live params.""" + _echo_json(treadfi_contract.market_params(instrument=instrument)) diff --git a/cli/main.py b/cli/main.py index 6253f07..6794006 100644 --- a/cli/main.py +++ b/cli/main.py @@ -35,6 +35,7 @@ from cli.commands.skills import skills_app from cli.commands.journal import journal_app from cli.commands.keys import keys_app +from cli.commands.treadfi import treadfi_app app.command("run", help="Start autonomous trading with a strategy")(run_cmd) app.command("status", help="Show positions, PnL, and risk state")(status_cmd) @@ -53,6 +54,7 @@ app.add_typer(skills_app, name="skills", help="Skill discovery and registry") app.add_typer(journal_app, name="journal", help="Trade journal — structured position records with reasoning") app.add_typer(keys_app, name="keys", help="Unified key management across backends") +app.add_typer(treadfi_app, name="treadfi", help="TreadFi integration status and read-only capability placeholders") def main(): diff --git a/cli/mcp_server.py b/cli/mcp_server.py index 1544778..887b09e 100644 --- a/cli/mcp_server.py +++ b/cli/mcp_server.py @@ -149,6 +149,31 @@ def setup_check() -> str: "passed": len(issues) == 0, }, indent=2) + @mcp.tool() + def treadfi_spec_status() -> str: + """Report whether the TreadFi endpoint/MCP contract is present.""" + from modules import treadfi_contract + + return json.dumps(treadfi_contract.spec_status(), indent=2) + + @mcp.tool() + def treadfi_capabilities() -> str: + """List local TreadFi placeholders and blocked live capabilities.""" + from modules import treadfi_contract + + return json.dumps(treadfi_contract.capabilities(), indent=2) + + @mcp.tool() + def treadfi_market_params(instrument: str = "BTCSWP-USDYP") -> str: + """Show locally known BTCSWP identifiers and missing live params. + + Args: + instrument: Requested market identifier (default: BTCSWP-USDYP) + """ + from modules import treadfi_contract + + return json.dumps(treadfi_contract.market_params(instrument=instrument), indent=2) + @mcp.tool() def account(mainnet: bool = False) -> str: """Get Hyperliquid account state (balances, positions).""" diff --git a/docs/api-reference.md b/docs/api-reference.md index a746466..e5ad1d7 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -592,7 +592,7 @@ python leaderboard.py serve --port 8090 ## MCP Server -The MCP server exposes 16 tools for AI agent orchestration via the [Model Context Protocol](https://modelcontextprotocol.io). This is the access path for Claude Code, OpenClaw, or any MCP-compatible client. +The MCP server exposes 20 tools for AI agent orchestration via the [Model Context Protocol](https://modelcontextprotocol.io). This is the access path for Claude Code, OpenClaw, or any MCP-compatible client. ### Starting the Server @@ -712,6 +712,43 @@ Returns: } ``` +#### `treadfi_spec_status()` + +Report whether the TreadFi endpoint/MCP contract is present. This is read-only +and does not call TreadFi. + +``` +Tool: treadfi_spec_status +Args: (none) +``` + +Returns JSON with `status`, missing contract fields, and known local `agent-cli` +context. + +#### `treadfi_capabilities()` + +List local TreadFi placeholders and blocked live capabilities. + +``` +Tool: treadfi_capabilities +Args: (none) +``` + +Returns JSON that marks live TreadFi discovery unavailable until Eng provides the +real contract. + +#### `treadfi_market_params(instrument="BTCSWP-USDYP")` + +Show locally known BTCSWP identifiers and missing live market parameters. + +``` +Tool: treadfi_market_params +Args: { "instrument": "BTCSWP-USDYP" } +``` + +Returns JSON with the local `BTCSWP-USDYP` / `yex:BTCSWP` identifiers and the +TreadFi fields still missing from the live contract. + ### Action Tools (Subprocess, seconds to minutes) These shell out to the CLI and may take significant time. @@ -1052,6 +1089,9 @@ else: | `wallet_list` | Fast | <100ms | None | | `wallet_auto` | Fast | <500ms | Creates keystore file | | `setup_check` | Fast | <100ms | None | +| `treadfi_spec_status` | Fast | <100ms | None | +| `treadfi_capabilities` | Fast | <100ms | None | +| `treadfi_market_params` | Fast | <100ms | None | | `account` | Subprocess | 1-5s | None | | `status` | Subprocess | <1s | None | | `trade` | Subprocess | 1-5s | Places order on HL | diff --git a/modules/treadfi_contract.py b/modules/treadfi_contract.py new file mode 100644 index 0000000..160f748 --- /dev/null +++ b/modules/treadfi_contract.py @@ -0,0 +1,93 @@ +"""Read-only TreadFi contract status helpers. + +This module deliberately avoids network calls. It gives CLI and MCP surfaces a +shared, machine-readable view of what is locally known and what remains blocked +until TreadFi/Eng provides a real endpoint contract. +""" +from __future__ import annotations + +from typing import Any + +from common.models import asset_to_coin, asset_to_instrument + + +REQUIRED_CONTRACT_FIELDS = ( + "transport", + "environments", + "auth", + "discovery", + "market_data", + "campaign_reporting", + "execution", + "operations", + "fixtures", +) + + +def spec_status() -> dict[str, Any]: + """Return the current TreadFi integration contract status.""" + return { + "status": "blocked", + "reason": "TreadFi endpoint and MCP tool specs are not present in agent-cli.", + "required_contract_fields": list(REQUIRED_CONTRACT_FIELDS), + "missing_contract_fields": list(REQUIRED_CONTRACT_FIELDS), + "known_local_context": { + "agent_cli_command": "hl", + "mcp_command": "hl mcp serve", + "mcp_server": "cli/mcp_server.py", + "btcswp_instrument": asset_to_instrument("BTCSWP"), + "btcswp_coin": asset_to_coin("BTCSWP"), + }, + "next_step": "Provide the TreadFi contract with fixtures before adding live client or execution code.", + } + + +def capabilities() -> dict[str, Any]: + """Return local placeholder capabilities without claiming live support.""" + return { + "live_discovery_available": False, + "status": "contract_missing", + "read_only_placeholders": [ + { + "name": "spec_status", + "description": "Report which TreadFi endpoint contract fields are still missing.", + }, + { + "name": "market_params", + "description": "Return locally known BTCSWP identifiers and mark live params unavailable.", + }, + ], + "blocked_until_contract": [ + "list_treadfi_tools", + "read_treadfi_depth", + "read_treadfi_leaderboard", + "place_treadfi_quote", + "cancel_treadfi_quote", + ], + } + + +def market_params(instrument: str = "BTCSWP-USDYP") -> dict[str, Any]: + """Return locally known BTCSWP identifiers and missing live fields.""" + requested = instrument.upper() + local_instrument = asset_to_instrument("BTCSWP") + local_coin = asset_to_coin("BTCSWP") + known = requested in {"BTCSWP", local_instrument, local_coin.upper()} + return { + "requested": instrument, + "known_locally": known, + "instrument": local_instrument, + "coin": local_coin, + "source": "common.models HIP-3 instrument registry", + "live_params_available": False, + "missing_live_fields": [ + "canonical_treadfi_market_id", + "oracle_reference_price", + "depth_bands", + "tick_size", + "size_decimals", + "collateral", + "campaign_window", + "attribution_rule", + ], + } diff --git a/tests/test_treadfi_contract.py b/tests/test_treadfi_contract.py new file mode 100644 index 0000000..e826955 --- /dev/null +++ b/tests/test_treadfi_contract.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import json + +from typer.testing import CliRunner + +from cli.main import app +from modules import treadfi_contract + + +runner = CliRunner() + + +def test_spec_status_is_blocked_until_contract_exists(): + status = treadfi_contract.spec_status() + + assert status["status"] == "blocked" + assert "transport" in status["missing_contract_fields"] + assert status["known_local_context"]["mcp_command"] == "hl mcp serve" + + +def test_market_params_use_local_btcswp_registry(): + params = treadfi_contract.market_params() + + assert params["known_locally"] is True + assert params["instrument"] == "BTCSWP-USDYP" + assert params["coin"] == "yex:BTCSWP" + assert params["live_params_available"] is False + + +def test_treadfi_spec_status_cli_outputs_json(): + result = runner.invoke(app, ["treadfi", "spec-status"]) + + assert result.exit_code == 0 + payload = json.loads(result.stdout) + assert payload["status"] == "blocked" + + +def test_treadfi_market_params_cli_accepts_instrument(): + result = runner.invoke(app, ["treadfi", "market-params", "--instrument", "BTCSWP"]) + + assert result.exit_code == 0 + payload = json.loads(result.stdout) + assert payload["requested"] == "BTCSWP" + assert payload["known_locally"] is True diff --git a/tests/test_treadfi_mcp.py b/tests/test_treadfi_mcp.py new file mode 100644 index 0000000..676de8e --- /dev/null +++ b/tests/test_treadfi_mcp.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import json +import sys +import types +from typing import Callable + + +class FakeFastMCP: + def __init__(self, *_args, **_kwargs): + self.tools: dict[str, Callable] = {} + + def tool(self, name: str | None = None, **_kwargs): + def decorator(func: Callable): + self.tools[name or func.__name__] = func + return func + + return decorator + + +def test_treadfi_tools_register_with_fastmcp(monkeypatch): + fastmcp_module = types.ModuleType("mcp.server.fastmcp") + fastmcp_module.FastMCP = FakeFastMCP + server_module = types.ModuleType("mcp.server") + mcp_module = types.ModuleType("mcp") + + monkeypatch.setitem(sys.modules, "mcp", mcp_module) + monkeypatch.setitem(sys.modules, "mcp.server", server_module) + monkeypatch.setitem(sys.modules, "mcp.server.fastmcp", fastmcp_module) + + from cli.mcp_server import create_mcp_server + + server = create_mcp_server() + + assert "treadfi_spec_status" in server.tools + assert "treadfi_capabilities" in server.tools + assert "treadfi_market_params" in server.tools + + payload = json.loads(server.tools["treadfi_spec_status"]()) + assert payload["status"] == "blocked"