Skip to main content
Glama
client-resolver.tsβ€’6.45 kB
/** * Type-safe client resolver for Attio API client factory methods * Updated to use unified createAttioClient interface with backward compatibility */ import { AxiosInstance } from 'axios'; import * as AttioClientModule from '../api/attio-client.js'; import { getContextApiKey } from '../api/client-context.js'; import { ClientConfig } from '../api/client-config.js'; /** * Supported factory method signatures exposed by attio-client module */ interface AttioClientFactories { getAttioClient?(): AxiosInstance | unknown; createAttioClient?(apiKey: string): AxiosInstance | unknown; buildAttioClient?(config: { apiKey: string }): AxiosInstance | unknown; [key: string]: unknown; } /** * Guards that the resolved value behaves like an Axios instance. We keep the * checks intentionally simple so tests can provide lightweight mocks. */ function assertAxiosInstance( value: unknown, source: string ): asserts value is AxiosInstance { const candidate = value as Record<string, unknown> | null | undefined; const hasGetMethod = !!candidate && typeof candidate === 'object' && typeof candidate.get === 'function'; const hasDefaults = hasGetMethod && 'defaults' in candidate && typeof candidate.defaults === 'object'; if (!hasGetMethod || !hasDefaults) { // Enhanced diagnostic information const diagnostics = { valueType: typeof value, isNull: value === null, isUndefined: value === undefined, isObject: typeof value === 'object' && value !== null, hasGetMethod, hasDefaults, hasDefaultsProperty: candidate && typeof candidate === 'object' && 'defaults' in candidate, defaultsType: candidate && typeof candidate === 'object' && 'defaults' in candidate ? typeof candidate.defaults : 'N/A', availableProperties: candidate && typeof candidate === 'object' ? Object.keys(candidate).slice(0, 10) : [], }; if (process.env.MCP_LOG_LEVEL === 'DEBUG') { console.error( `[client-resolver:assertAxiosInstance] Validation failed for ${source}:`, diagnostics ); } throw new Error( `${source} returned invalid Axios client instance (hasGetMethod=${hasGetMethod}, hasDefaults=${hasDefaults})` ); } } /** * Resolves an Attio client instance using the unified interface. * Uses getAttioClient() which handles caching, environment detection, and strategy pattern. * * This simplification fixes Issue #904 client initialization validation failures by using * the proven getAttioClient() code path instead of attempting multiple factory methods. */ export function resolveAttioClient(): AxiosInstance { const mod = AttioClientModule as AttioClientFactories; // Debug logging for API key resolution (informational only) if (process.env.MCP_LOG_LEVEL === 'DEBUG') { const contextApiKey = getContextApiKey(); const envApiKey = process.env.ATTIO_API_KEY; const resolvedApiKey = envApiKey || contextApiKey; console.error('[client-resolver:resolve] API key resolution:', { hasEnvApiKey: Boolean(envApiKey), envKeyLength: envApiKey?.length || 0, hasContextApiKey: Boolean(contextApiKey), contextKeyLength: contextApiKey?.length || 0, resolved: Boolean(resolvedApiKey), resolvedKeyLength: resolvedApiKey?.length || 0, source: resolvedApiKey === envApiKey ? 'env' : resolvedApiKey === contextApiKey ? 'context' : 'none', timestamp: new Date().toISOString(), }); } // Use getAttioClient() - it handles all the complexity: // - Caching (ClientCache and legacy apiInstance) // - Environment detection (E2E, test, production modes) // - Strategy pattern (ProductionClientStrategy, E2EClientStrategy, etc.) // - API key resolution (env, context, config) // This is the proven code path used throughout the codebase if (typeof mod.getAttioClient === 'function') { if (process.env.MCP_LOG_LEVEL === 'DEBUG') { console.error('[client-resolver:resolve] Using getAttioClient()'); } const client = mod.getAttioClient(); assertAxiosInstance(client, 'getAttioClient()'); return client; } // Fallback to createAttioClient with config object if (typeof mod.createAttioClient === 'function') { if (process.env.MCP_LOG_LEVEL === 'DEBUG') { console.error( '[client-resolver:resolve] Fallback to createAttioClient(config)' ); } try { const config: ClientConfig = {}; const client = ( mod.createAttioClient as (config: ClientConfig) => AxiosInstance )(config); assertAxiosInstance(client, 'createAttioClient(config)'); return client; } catch (error) { if (process.env.MCP_LOG_LEVEL === 'DEBUG') { console.error( '[client-resolver:resolve] createAttioClient failed:', error ); } // Continue to last resort } } // Last resort: buildAttioClient if (typeof mod.buildAttioClient === 'function') { const contextApiKey = getContextApiKey(); const envApiKey = process.env.ATTIO_API_KEY; const resolvedApiKey = envApiKey || contextApiKey; if (!resolvedApiKey) { throw new Error( 'Attio API key is required for client initialization. Please set ATTIO_API_KEY environment variable.' ); } if (process.env.MCP_LOG_LEVEL === 'DEBUG') { console.error('[client-resolver:resolve] Last resort: buildAttioClient'); } const client = mod.buildAttioClient({ apiKey: resolvedApiKey }); assertAxiosInstance(client, 'buildAttioClient()'); return client; } throw new Error( 'Failed to initialize Attio client. Please check your API configuration and ensure the client module is properly installed.' ); } /** * Checks whether an arbitrary value looks like an Attio Axios client. */ export function isAttioClient(client: unknown): client is AxiosInstance { try { assertAxiosInstance(client, 'isAttioClient check'); return true; } catch { return false; } } /** * Resolves and validates the client, guaranteeing callers receive a proper * Axios instance or a descriptive error. */ export function getValidatedAttioClient(): AxiosInstance { const client = resolveAttioClient(); assertAxiosInstance(client, 'getValidatedAttioClient()'); return client; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server