initialize
Initialize a CryptoQuant session to obtain available assets, metric categories, and plan. Required before using any other tools.
Instructions
Initialize CryptoQuant session. MUST be called first before any other CryptoQuant tools. Returns available assets (btc, eth, etc.), metric categories per asset (e.g., market-indicator, network-indicator), and your plan. Use the returned asset_categories to know which discover_endpoints() calls are valid.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| api_key | No | API key (optional if CRYPTOQUANT_API_KEY env var is set) |
Implementation Reference
- src/tools/auth.ts:53-196 (registration)Registration of the 'initialize' tool on the MCP server via server.tool(). This is where the tool name, description, schema, and handler are bound together.
export function registerAuthTools(server: McpServer): void { server.tool( "initialize", "Initialize CryptoQuant session. MUST be called first before any other CryptoQuant tools. Returns available assets (btc, eth, etc.), metric categories per asset (e.g., market-indicator, network-indicator), and your plan. Use the returned asset_categories to know which discover_endpoints() calls are valid.", initializeSchema, async (params: InitializeParams) => { logger.debug("[initialize] starting initialization"); const apiKey = resolveApiKey(params.api_key); if (!apiKey) { logger.debug("[initialize] no API key available"); return jsonResponse({ status: "api_key_required", message: "API key not found. Please configure using one of these methods:", setup_options: [ { method: "Environment Variable (Recommended)", instruction: "Add to your MCP config (~/.claude/mcp.json):", example: { mcpServers: { cryptoquant: { command: "npx", args: ["-y", "@cryptoquant_official/mcp"], env: { CRYPTOQUANT_API_KEY: "your-api-key" }, }, }, }, }, { method: "Direct Parameter", instruction: "Call initialize with api_key parameter:", example: "initialize(api_key='your-api-key')", }, ], get_api_key: "https://cryptoquant.com/settings/api", }); } const apiUrl = getApiUrl(); logger.debug("[initialize] using API URL:", apiUrl); const result = await initializePermissions(apiKey, apiUrl); logger.debug("[initialize] permissions result:", { success: result.success, from_cache: result.from_cache, cache_status: result.cache_status, error: result.error, }); if (!result.success) { return jsonResponse({ success: false, error: result.error, help: { check_key: "Check your API key at https://cryptoquant.com/settings/api", retry: "Or call initialize(api_key='your-api-key') with a valid key", }, }); } // Save or update credentials based on key source if (params.api_key) { // Key from parameter: save it (enables key replacement) saveApiKey(params.api_key); } else if (!process.env.CRYPTOQUANT_API_KEY) { // Key from stored credentials: just update validated_at updateValidatedAt(); } const discovery = getDiscoverySummary(); const assetCategories = getAssetCategoryMap(); const permState = getPermissionState(); const planState = getPlanLimitsState(); const cachedDiscovery = getCachedDiscovery(); const totalEndpoints = discovery?.total_endpoints || cachedDiscovery?.summary.total_endpoints || 0; const accessibleSummary = getAccessibleEndpointsSummary(totalEndpoints); // Build session info with cache status const sessionInfo = { plan: permState.plan, cache_status: result.cache_status, ...(planState.apiRateLimit && { rate_limit: `${planState.apiRateLimit.token}/${planState.apiRateLimit.window}`, }), }; // Build scope info (compact summary) const scopeInfo: Record<string, unknown> = { total_endpoints: discovery?.total_endpoints || cachedDiscovery?.summary.total_endpoints || 0, accessible: accessibleSummary.count, note: "Use discover_endpoints(asset, category) for details", }; // Add asset breakdown from cache summary if available if (cachedDiscovery?.summary.assets) { const assets: Record<string, { endpoints: number; categories: number }> = {}; for (const assetInfo of cachedDiscovery.summary.assets) { assets[assetInfo.name] = { endpoints: assetInfo.endpoint_count, categories: assetInfo.categories.length, }; } scopeInfo.assets = assets; } else if (discovery) { // Fallback to discovery data const assets: Record<string, { endpoints: number; categories: number }> = {}; for (const assetInfo of discovery.assets) { assets[assetInfo.name] = { endpoints: assetInfo.count, categories: assetCategories?.[assetInfo.name]?.length || 0, }; } scopeInfo.assets = assets; } const planInfo = { plan: permState.plan, plan_limits_loaded: planState.loaded, accessible_endpoints: accessibleSummary.count, // Only include endpoint details for basic/advanced plans (limited access) ...(accessibleSummary.endpoints && { accessible_list: accessibleSummary.endpoints }), fetched_at: planState.fetched_at ? new Date(planState.fetched_at).toISOString() : null, note: getPlanNote(permState.plan), }; return jsonResponse({ success: true, session: sessionInfo, scope: scopeInfo, discovery: discovery ? { total_endpoints: discovery.total_endpoints, assets: discovery.assets.map((a) => a.name), categories: discovery.categories.map((c) => c.name), asset_categories: assetCategories, fetched_at: discovery.fetched_at, } : null, plan_info: planInfo, ...(result.discovery_error && { warning: `Discovery partial: ${result.discovery_error}` }), }); } ); - src/tools/auth.ts:58-195 (handler)The handler function for the 'initialize' tool. It resolves the API key, calls initializePermissions(), and returns a structured response with session info, discovery scope, and plan details.
async (params: InitializeParams) => { logger.debug("[initialize] starting initialization"); const apiKey = resolveApiKey(params.api_key); if (!apiKey) { logger.debug("[initialize] no API key available"); return jsonResponse({ status: "api_key_required", message: "API key not found. Please configure using one of these methods:", setup_options: [ { method: "Environment Variable (Recommended)", instruction: "Add to your MCP config (~/.claude/mcp.json):", example: { mcpServers: { cryptoquant: { command: "npx", args: ["-y", "@cryptoquant_official/mcp"], env: { CRYPTOQUANT_API_KEY: "your-api-key" }, }, }, }, }, { method: "Direct Parameter", instruction: "Call initialize with api_key parameter:", example: "initialize(api_key='your-api-key')", }, ], get_api_key: "https://cryptoquant.com/settings/api", }); } const apiUrl = getApiUrl(); logger.debug("[initialize] using API URL:", apiUrl); const result = await initializePermissions(apiKey, apiUrl); logger.debug("[initialize] permissions result:", { success: result.success, from_cache: result.from_cache, cache_status: result.cache_status, error: result.error, }); if (!result.success) { return jsonResponse({ success: false, error: result.error, help: { check_key: "Check your API key at https://cryptoquant.com/settings/api", retry: "Or call initialize(api_key='your-api-key') with a valid key", }, }); } // Save or update credentials based on key source if (params.api_key) { // Key from parameter: save it (enables key replacement) saveApiKey(params.api_key); } else if (!process.env.CRYPTOQUANT_API_KEY) { // Key from stored credentials: just update validated_at updateValidatedAt(); } const discovery = getDiscoverySummary(); const assetCategories = getAssetCategoryMap(); const permState = getPermissionState(); const planState = getPlanLimitsState(); const cachedDiscovery = getCachedDiscovery(); const totalEndpoints = discovery?.total_endpoints || cachedDiscovery?.summary.total_endpoints || 0; const accessibleSummary = getAccessibleEndpointsSummary(totalEndpoints); // Build session info with cache status const sessionInfo = { plan: permState.plan, cache_status: result.cache_status, ...(planState.apiRateLimit && { rate_limit: `${planState.apiRateLimit.token}/${planState.apiRateLimit.window}`, }), }; // Build scope info (compact summary) const scopeInfo: Record<string, unknown> = { total_endpoints: discovery?.total_endpoints || cachedDiscovery?.summary.total_endpoints || 0, accessible: accessibleSummary.count, note: "Use discover_endpoints(asset, category) for details", }; // Add asset breakdown from cache summary if available if (cachedDiscovery?.summary.assets) { const assets: Record<string, { endpoints: number; categories: number }> = {}; for (const assetInfo of cachedDiscovery.summary.assets) { assets[assetInfo.name] = { endpoints: assetInfo.endpoint_count, categories: assetInfo.categories.length, }; } scopeInfo.assets = assets; } else if (discovery) { // Fallback to discovery data const assets: Record<string, { endpoints: number; categories: number }> = {}; for (const assetInfo of discovery.assets) { assets[assetInfo.name] = { endpoints: assetInfo.count, categories: assetCategories?.[assetInfo.name]?.length || 0, }; } scopeInfo.assets = assets; } const planInfo = { plan: permState.plan, plan_limits_loaded: planState.loaded, accessible_endpoints: accessibleSummary.count, // Only include endpoint details for basic/advanced plans (limited access) ...(accessibleSummary.endpoints && { accessible_list: accessibleSummary.endpoints }), fetched_at: planState.fetched_at ? new Date(planState.fetched_at).toISOString() : null, note: getPlanNote(permState.plan), }; return jsonResponse({ success: true, session: sessionInfo, scope: scopeInfo, discovery: discovery ? { total_endpoints: discovery.total_endpoints, assets: discovery.assets.map((a) => a.name), categories: discovery.categories.map((c) => c.name), asset_categories: assetCategories, fetched_at: discovery.fetched_at, } : null, plan_info: planInfo, ...(result.discovery_error && { warning: `Discovery partial: ${result.discovery_error}` }), }); } - src/tools/auth.ts:24-26 (schema)Zod schema for the 'initialize' tool. Defines the single optional parameter 'api_key'.
const initializeSchema = { api_key: z.string().optional().describe("API key (optional if CRYPTOQUANT_API_KEY env var is set)"), }; - src/tools/auth.ts:30-51 (helper)Helper function that resolves the API key from parameter, environment variable CRYPTOQUANT_API_KEY, or stored credentials, in priority order.
function resolveApiKey(paramKey?: string): string | undefined { if (paramKey) { logger.debug("[resolveApiKey] using key from parameter"); return paramKey; } const envKey = process.env.CRYPTOQUANT_API_KEY; const isEnvKeyValid = envKey && envKey.trim() && !envKey.startsWith("${"); if (isEnvKeyValid) { logger.debug("[resolveApiKey] using key from environment variable"); return envKey; } const storedKey = getStoredApiKey(); if (storedKey) { logger.debug("[resolveApiKey] using key from stored credentials"); return storedKey; } logger.debug("[resolveApiKey] no API key found"); return undefined; } - src/permissions.ts:57-191 (helper)Core initialization logic: validates cache, fetches discovery endpoints and plan limits from CryptoQuant API, writes cache, and returns the initialization result.
export async function initializePermissions( apiKey: string, apiUrl: string, ): Promise<InitializeResult> { logger.debug("[initializePermissions] starting with API URL:", apiUrl); const apiKeyPrefix = apiKey.slice(0, 8); // 1. Try cache first for /my/discovery data const cache = readCache(apiUrl); logger.debug("[initializePermissions] cache status:", cache ? "found" : "not found"); if (cache && isCacheValid(cache, apiUrl, apiKeyPrefix)) { logger.debug("[initializePermissions] using valid cache, plan:", cache.metadata.plan); // Load plan limits from cache loadPlanLimitsFromCache( cache.parsed.limits, cache.parsed.statics, cache.parsed.apiRateLimit, cache.metadata.plan, ); // Still need to fetch /discovery/endpoints for parameter options const discoveryResult = await fetchDiscoveryEndpoints(apiKey, apiUrl); if (!discoveryResult.success) { // Discovery failed but we have cache - continue with warning logger.warn(`Discovery fetch failed, using cache: ${discoveryResult.error}`); } const cacheStatus = getCacheStatus(cache, true); cachedDiscovery = cache; permissionState = { authenticated: true, api_key: apiKey, cached_at: Date.now(), plan: cache.metadata.plan, plan_limits_loaded: true, from_cache: true, }; return { success: true, from_cache: true, cache_status: cacheStatus, summary: cache.summary, }; } // 2. Cache miss or invalid - fetch fresh data logger.debug("[initializePermissions] cache miss or invalid, fetching fresh data"); try { // Fetch /discovery/endpoints (for parameter options) logger.debug("[initializePermissions] fetching /discovery/endpoints"); const discoveryResult = await fetchDiscoveryEndpoints(apiKey, apiUrl); if (!discoveryResult.success) { logger.debug("[initializePermissions] discovery fetch failed:", discoveryResult.error); return { success: false, error: discoveryResult.error, from_cache: false, cache_status: "none", }; } logger.debug("[initializePermissions] discovery fetch succeeded"); // Fetch /my/discovery/endpoints (for plan limits) logger.debug("[initializePermissions] fetching /my/discovery/endpoints"); const planResult = await fetchPlanLimits(apiKey, apiUrl); logger.debug("[initializePermissions] plan limits fetch:", planResult.success ? "success" : "failed"); const planState = getPlanLimitsState(); if (!planResult.success) { logger.warn(`Plan limits fetch failed: ${planResult.error}`); } // 3. Generate summary and write cache if (planResult.success && planResult.rawResponse && planResult.parsed) { const rawResponse = extractRawResponse(planResult.rawResponse); if (rawResponse) { const summary = generateSummary( planResult.parsed.limits, planResult.parsed.statics, ); writeCache( apiUrl, apiKey, rawResponse, planResult.parsed, summary, planResult.plan || "unknown", ); // Store for later use const newCache = readCache(apiUrl); cachedDiscovery = newCache; permissionState = { authenticated: true, api_key: apiKey, cached_at: Date.now(), plan: planState.plan, plan_limits_loaded: planState.loaded, from_cache: false, }; return { success: true, from_cache: false, cache_status: "fresh", summary, }; } } // Cache write failed but auth succeeded permissionState = { authenticated: true, api_key: apiKey, cached_at: Date.now(), plan: planState.plan, plan_limits_loaded: planState.loaded, from_cache: false, }; return { success: true, from_cache: false, cache_status: "none" }; } catch (error) { return { success: false, error: `Network error: ${error}`, from_cache: false, cache_status: "none", }; } }