query_data
Retrieve raw cryptocurrency on-chain data from CryptoQuant API. Use endpoint paths discovered earlier, with optional parameters like window, limit, date range, exchange, and symbol.
Instructions
Query raw data from CryptoQuant API. Workflow: initialize() → discover_endpoints(asset, category) → query_data(endpoint, params). Use endpoint paths and parameter values from discover_endpoints response.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| endpoint | Yes | API endpoint path (e.g., /v1/btc/market-data/mvrv) | |
| params | No | Query parameters |
Implementation Reference
- src/tools/core.ts:297-433 (handler)The main handler function for the query_data tool. Validates auth, endpoint, params, and plan limits, then fetches data from the CryptoQuant API and returns the response.
server.tool( "query_data", "Query raw data from CryptoQuant API. Workflow: initialize() → discover_endpoints(asset, category) → query_data(endpoint, params). Use endpoint paths and parameter values from discover_endpoints response.", queryDataSchema, async (params: QueryDataParams) => { logger.debug("[query_data] called with endpoint:", params.endpoint); logger.debug("[query_data] params:", params.params); const authError = requireAuth(); if (authError) return authError; const state = getPermissionState(); const endpoint = getEndpointByPath(params.endpoint); if (!endpoint) { return errorResponse(`Unknown endpoint: ${params.endpoint}`, { action: "Use discover_endpoints() to find valid endpoints", }); } const queryParams = params.params || {}; const validation = validateEndpointParams(endpoint, queryParams); if (!validation.valid) { const paramOptions = getParameterOptions(params.endpoint); return errorResponse("Invalid parameters", { details: validation.errors, endpoint: params.endpoint, available_parameters: paramOptions?.parameters, required_parameters: paramOptions?.required, }); } const planState = getPlanLimitsState(); if (planState.loaded) { if (!hasEndpointAccess(params.endpoint)) { const requiredPlan = getRequiredPlan(params.endpoint); return errorResponse("Endpoint not accessible on your plan", { your_plan: planState.plan, required_plan: requiredPlan, upgrade_url: "https://cryptoquant.com/pricing", suggestion: `Upgrade to ${capitalizeFirst(requiredPlan)} plan for access to this endpoint`, }); } const fromDate = queryParams.from as string | undefined; const windowParam = queryParams.window as string | undefined; if (fromDate) { const dateValidation = validateDateRange( params.endpoint, fromDate, windowParam, ); if (!dateValidation.valid) { return errorResponse( dateValidation.error || "Date range exceeds plan limit", { your_plan: planState.plan, ...(dateValidation.limit && { limit: dateValidation.limit }), ...(dateValidation.earliest_allowed && { earliest_allowed: dateValidation.earliest_allowed, }), upgrade_url: "https://cryptoquant.com/pricing", suggestion: "Upgrade to Premium for unlimited historical data access", }, ); } } } const apiKey = state.api_key; if (!apiKey) { return errorResponse("API key not available", { action: "Re-initialize with your API key", }); } try { const urlParams = new URLSearchParams(); for (const [key, value] of Object.entries(queryParams)) { if (value !== undefined && value !== null) { urlParams.set(key, String(value)); } } if (!urlParams.has("limit")) { urlParams.set("limit", "100"); } urlParams.set("source", "mcp"); const apiUrl = getApiBaseUrl(); const fullUrl = `${apiUrl}${params.endpoint}?${urlParams.toString()}`; logger.debug("[query_data] API request:", fullUrl); const response = await fetch(fullUrl, { method: "GET", headers: { Authorization: `Bearer ${apiKey}` }, }); logger.debug("[query_data] API response status:", response.status, response.statusText); if (!response.ok) { const errorBody = await response.text(); return errorResponse( `API request failed: ${response.status} ${response.statusText}`, { details: errorBody, endpoint: params.endpoint, }, ); } let data: Record<string, unknown>; try { data = (await response.json()) as Record<string, unknown>; } catch { return errorResponse("Failed to parse API response", { endpoint: params.endpoint, }); } const rateLimitInfo = buildRateLimitInfo(response); return jsonResponse({ success: true, endpoint: params.endpoint, params: queryParams, ...(rateLimitInfo && { rate_limit: rateLimitInfo }), ...data, }); } catch (error) { return errorResponse(`Network error: ${error}`, { endpoint: params.endpoint, }); } }, ); - src/tools/core.ts:73-110 (schema)Zod schema defining input parameters for query_data: endpoint (required string) and optional params object with window, limit, from, to, exchange, symbol, market, token, miner, type.
const queryDataSchema = { endpoint: z .string() .describe("API endpoint path (e.g., /v1/btc/market-data/mvrv)"), params: z .object({ window: z .string() .optional() .describe("Time window granularity (hour, day, block, etc.)"), limit: z .number() .optional() .describe("Number of data points to return (max 1000)"), from: z.string().optional().describe("Start date (ISO 8601 format)"), to: z.string().optional().describe("End date (ISO 8601 format)"), exchange: z .string() .optional() .describe("Exchange filter (for exchange-specific data)"), symbol: z .string() .optional() .describe("Trading symbol (e.g., btc_usd, btc_usdt)"), market: z.string().optional().describe("Market type (spot, perpetual)"), token: z .string() .optional() .describe("Token filter (for alt/erc20 data)"), miner: z.string().optional().describe("Miner filter (for miner data)"), type: z .string() .optional() .describe("Entity type filter (e.g., exchange, entity, miner, bank)"), }) .optional() .describe("Query parameters"), }; - src/tools/core.ts:297-433 (registration)Registration of the query_data tool on the MCP server via server.tool() within registerCoreTools().
server.tool( "query_data", "Query raw data from CryptoQuant API. Workflow: initialize() → discover_endpoints(asset, category) → query_data(endpoint, params). Use endpoint paths and parameter values from discover_endpoints response.", queryDataSchema, async (params: QueryDataParams) => { logger.debug("[query_data] called with endpoint:", params.endpoint); logger.debug("[query_data] params:", params.params); const authError = requireAuth(); if (authError) return authError; const state = getPermissionState(); const endpoint = getEndpointByPath(params.endpoint); if (!endpoint) { return errorResponse(`Unknown endpoint: ${params.endpoint}`, { action: "Use discover_endpoints() to find valid endpoints", }); } const queryParams = params.params || {}; const validation = validateEndpointParams(endpoint, queryParams); if (!validation.valid) { const paramOptions = getParameterOptions(params.endpoint); return errorResponse("Invalid parameters", { details: validation.errors, endpoint: params.endpoint, available_parameters: paramOptions?.parameters, required_parameters: paramOptions?.required, }); } const planState = getPlanLimitsState(); if (planState.loaded) { if (!hasEndpointAccess(params.endpoint)) { const requiredPlan = getRequiredPlan(params.endpoint); return errorResponse("Endpoint not accessible on your plan", { your_plan: planState.plan, required_plan: requiredPlan, upgrade_url: "https://cryptoquant.com/pricing", suggestion: `Upgrade to ${capitalizeFirst(requiredPlan)} plan for access to this endpoint`, }); } const fromDate = queryParams.from as string | undefined; const windowParam = queryParams.window as string | undefined; if (fromDate) { const dateValidation = validateDateRange( params.endpoint, fromDate, windowParam, ); if (!dateValidation.valid) { return errorResponse( dateValidation.error || "Date range exceeds plan limit", { your_plan: planState.plan, ...(dateValidation.limit && { limit: dateValidation.limit }), ...(dateValidation.earliest_allowed && { earliest_allowed: dateValidation.earliest_allowed, }), upgrade_url: "https://cryptoquant.com/pricing", suggestion: "Upgrade to Premium for unlimited historical data access", }, ); } } } const apiKey = state.api_key; if (!apiKey) { return errorResponse("API key not available", { action: "Re-initialize with your API key", }); } try { const urlParams = new URLSearchParams(); for (const [key, value] of Object.entries(queryParams)) { if (value !== undefined && value !== null) { urlParams.set(key, String(value)); } } if (!urlParams.has("limit")) { urlParams.set("limit", "100"); } urlParams.set("source", "mcp"); const apiUrl = getApiBaseUrl(); const fullUrl = `${apiUrl}${params.endpoint}?${urlParams.toString()}`; logger.debug("[query_data] API request:", fullUrl); const response = await fetch(fullUrl, { method: "GET", headers: { Authorization: `Bearer ${apiKey}` }, }); logger.debug("[query_data] API response status:", response.status, response.statusText); if (!response.ok) { const errorBody = await response.text(); return errorResponse( `API request failed: ${response.status} ${response.statusText}`, { details: errorBody, endpoint: params.endpoint, }, ); } let data: Record<string, unknown>; try { data = (await response.json()) as Record<string, unknown>; } catch { return errorResponse("Failed to parse API response", { endpoint: params.endpoint, }); } const rateLimitInfo = buildRateLimitInfo(response); return jsonResponse({ success: true, endpoint: params.endpoint, params: queryParams, ...(rateLimitInfo && { rate_limit: rateLimitInfo }), ...data, }); } catch (error) { return errorResponse(`Network error: ${error}`, { endpoint: params.endpoint, }); } }, ); - src/tools/core.ts:684-711 (helper)Helper function that builds an example query_data() call string for a given endpoint, used to display usage examples.
function buildExampleQuery(endpoint: ParsedEndpoint): string { const params: string[] = []; for (const required of endpoint.required_parameters) { const values = endpoint.parameters[required]; if (values && values.length > 0) { params.push(`${required}=${values[0]}`); } } if ( endpoint.parameters.window && !endpoint.required_parameters.includes("window") ) { params.push(`window=${endpoint.parameters.window[0]}`); } params.push("limit=100"); const paramStr = params .map((p) => { const [key, val] = p.split("="); return `"${key}": "${val}"`; }) .join(", "); return `query_data(endpoint="${endpoint.path}", params={${paramStr}})`; } - src/index.ts:157-157 (registration)Top-level registration call in main() that registers all core tools including query_data on the MCP server.
registerCoreTools(server);