list_assets
Access the directory of canonical assets grouped by economic exposure, showing synonyms, venues, aggregated open interest, and cross-market status.
Instructions
Directory of every canonical asset that trades on Hyperliquid or any builder dex, grouped by economic exposure (not by venue ticker). Each asset entry lists its synonyms (e.g. PAXG is a synonym of GOLD), which venues it trades on, aggregated open interest, and a cross-market flag (listed on 2+ venues). Prefer this over list_markets when the user asks 'what assets are available?', 'which venues is GOLD on?', or 'show me cross-market assets'.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| useToonFormat | No | Return data in compact toon format (default: true). Set to false for standard JSON. | |
| crossMarketOnly | No | If true, return only assets listed on 2+ venues. Default: false (return all). |
Implementation Reference
- src/index.ts:291-305 (registration)Registration of the 'list_assets' tool on the MCP server, with schema definition (useToonFormat, crossMarketOnly) and handler that calls /assets API endpoint.
if (shouldRegister("list_assets")) server.registerTool( "list_assets", { description: "Directory of every canonical asset that trades on Hyperliquid or any builder dex, grouped by economic exposure (not by venue ticker). Each asset entry lists its synonyms (e.g. PAXG is a synonym of GOLD), which venues it trades on, aggregated open interest, and a cross-market flag (listed on 2+ venues). Prefer this over list_markets when the user asks 'what assets are available?', 'which venues is GOLD on?', or 'show me cross-market assets'.", inputSchema: { useToonFormat: useToonFormatSchema, crossMarketOnly: z.boolean().optional().describe("If true, return only assets listed on 2+ venues. Default: false (return all)."), }, }, async ({ useToonFormat, crossMarketOnly }) => { const params: Record<string, string> = {}; if (crossMarketOnly) params.crossMarketOnly = 'true'; return toolResult(await callAPI(useToonFormat, "/assets", params)); } ); - src/index.ts:300-304 (handler)Handler function for list_assets: accepts useToonFormat and crossMarketOnly, builds params, and calls the /assets API endpoint via callAPI helper.
async ({ useToonFormat, crossMarketOnly }) => { const params: Record<string, string> = {}; if (crossMarketOnly) params.crossMarketOnly = 'true'; return toolResult(await callAPI(useToonFormat, "/assets", params)); } - src/index.ts:295-298 (schema)Input schema for list_assets: accepts optional useToonFormat (default true) and optional crossMarketOnly boolean.
inputSchema: { useToonFormat: useToonFormatSchema, crossMarketOnly: z.boolean().optional().describe("If true, return only assets listed on 2+ venues. Default: false (return all)."), }, - src/index.ts:94-166 (helper)The callAPI helper used by list_assets handler to make HTTP requests with timeout, retries, and error handling.
async function callAPI(useToon: boolean, path: string, params?: Record<string, string>): Promise<any> { const url = new URL(`${BASE}${path}`); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== "") { url.searchParams.set(key, value); } }); } let lastError: Error | null = null; for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const headers: Record<string, string> = {}; if (API_KEY) headers["X-API-Key"] = API_KEY; const response = await fetch(url.toString(), { headers, signal: controller.signal, }); clearTimeout(timeout); if (response.status === 429) { // Rate limited — retry after delay if (attempt < MAX_RETRIES) { await new Promise((r) => setTimeout(r, RETRY_DELAY_MS * (attempt + 1))); continue; } throw new Error("Rate limit exceeded. Please wait a moment and try again."); } if (response.status === 404) { throw new Error("Not found. The requested resource does not exist — check the address or symbol."); } if (response.status === 401) { throw new Error("Invalid API key. Check your COINVERSAA_API_KEY environment variable."); } if (!response.ok) { const body = await response.json().catch(() => null); const msg = body?.error || response.statusText; throw new Error(`Request failed (${response.status}): ${msg}`); } const data = await response.json(); return useToon ? toonEncode(data) : data; } catch (err: any) { if (err.name === "AbortError") { lastError = new Error("Request timed out after 30 seconds. The server may be under heavy load — try again."); } else if (err.cause?.code === "ECONNREFUSED" || err.cause?.code === "ENOTFOUND") { lastError = new Error("Cannot connect to the Coinversa API. Check your COINVERSAA_API_URL setting and network connection."); } else { lastError = err; } // Retry on transient network errors if (attempt < MAX_RETRIES && (err.name === "AbortError" || err.cause?.code === "ECONNRESET")) { await new Promise((r) => setTimeout(r, RETRY_DELAY_MS * (attempt + 1))); continue; } throw lastError; } } throw lastError || new Error("Request failed after retries"); } - src/index.ts:172-174 (helper)The toolResult helper used by list_assets to format the API response into MCP content.
function toolResult(data: any) { return { content: [{ type: "text" as const, text: formatJSON(data) }] }; }