UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

235 lines (234 loc) 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.detectCacheType = detectCacheType; exports.createNonceCache = createNonceCache; exports.createNonceCacheWithConfig = createNonceCacheWithConfig; const memory_nonce_cache_1 = require("./memory-nonce-cache"); const redis_nonce_cache_1 = require("./redis-nonce-cache"); const dynamodb_nonce_cache_1 = require("./dynamodb-nonce-cache"); const cloudflare_kv_nonce_cache_1 = require("./cloudflare-kv-nonce-cache"); /** * Get environment variable with fallback support for old naming * Supports migration from XMCPI_ to MCPI_ prefixes */ function getEnvWithFallback(newKey, oldKey) { const newValue = process.env[newKey]; if (newValue !== undefined) { return newValue; } if (oldKey) { const oldValue = process.env[oldKey]; if (oldValue !== undefined) { console.warn(`⚠️ Environment variable ${oldKey} is deprecated. Please use ${newKey} instead.`); return oldValue; } } return undefined; } /** * Environment-based cache type detection */ function detectCacheType() { // Check for explicit configuration (with fallback support) const explicitType = getEnvWithFallback("MCPI_NONCE_CACHE_TYPE", "XMCPI_NONCE_CACHE_TYPE"); if (explicitType && ["memory", "redis", "dynamodb", "cloudflare-kv"].includes(explicitType)) { return explicitType; } // Auto-detect based on environment (most specific first) // Redis detection if (process.env.REDIS_URL || getEnvWithFallback("MCPI_REDIS_URL", "XMCPI_REDIS_URL")) { return "redis"; } // DynamoDB detection (AWS Lambda environment) if ((process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION) && getEnvWithFallback("MCPI_DYNAMODB_TABLE", "XMCPI_DYNAMODB_TABLE")) { return "dynamodb"; } // Cloudflare Workers detection (more robust) if (typeof globalThis !== "undefined") { // Check for Cloudflare Workers specific globals const globalScope = globalThis; if ("caches" in globalThis && typeof globalScope.fetch === "function" && (typeof globalScope.addEventListener === "function" || typeof globalScope.Response === "function")) { return "cloudflare-kv"; } } // Warn about memory cache in production if (process.env.NODE_ENV === "production" || getEnvWithFallback("MCPI_ENV", "XMCPI_ENV") === "production") { console.warn("mcpi: Using memory cache in production environment. " + "For multi-instance deployments, configure Redis (REDIS_URL), " + "DynamoDB (AWS_REGION + MCPI_DYNAMODB_TABLE), or " + "Cloudflare KV (MCPI_KV_NAMESPACE)."); } return "memory"; } /** * Create a nonce cache instance based on configuration or environment detection */ async function createNonceCache(config, options) { const cacheType = config?.type || detectCacheType(); switch (cacheType) { case "redis": { const redisUrl = config?.redis?.url || process.env.REDIS_URL || getEnvWithFallback("MCPI_REDIS_URL", "XMCPI_REDIS_URL"); if (!redisUrl) { const error = new Error("Redis URL not found"); if (options?.throwOnError) { throw error; } console.warn("Redis URL not found, falling back to memory cache"); return new memory_nonce_cache_1.MemoryNonceCache(); } try { // Dynamic import to avoid bundling Redis in environments that don't need it const { createClient } = await import("redis"); const redis = createClient({ url: redisUrl, socket: { connectTimeout: 5000, // 5 second timeout }, }); // Test connection await redis.connect(); // Verify basic operations work await redis.ping(); return new redis_nonce_cache_1.RedisNonceCache(redis, config?.redis?.keyPrefix); } catch (error) { if (options?.throwOnError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); console.warn("Failed to connect to Redis, falling back to memory cache:", errorMessage); return new memory_nonce_cache_1.MemoryNonceCache(); } } case "dynamodb": { const tableName = config?.dynamodb?.tableName || getEnvWithFallback("MCPI_DYNAMODB_TABLE", "XMCPI_DYNAMODB_TABLE"); if (!tableName) { const error = new Error("DynamoDB table name not found"); if (options?.throwOnError) { throw error; } console.warn("DynamoDB table name not found, falling back to memory cache"); return new memory_nonce_cache_1.MemoryNonceCache(); } try { // Dynamic import to avoid bundling AWS SDK in environments that don't need it const awsSdk = await import("@aws-sdk/client-dynamodb"); const sdkModule = awsSdk; const DynamoDBClient = "DynamoDBClient" in sdkModule ? sdkModule.DynamoDBClient : sdkModule.default.DynamoDBClient; const DescribeTableCommand = "DescribeTableCommand" in sdkModule ? sdkModule.DescribeTableCommand : sdkModule.default.DescribeTableCommand; const region = config?.dynamodb?.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1"; const dynamodb = new DynamoDBClient({ region, maxAttempts: 3, }); // Verify table exists and is accessible try { await dynamodb.send(new DescribeTableCommand({ TableName: tableName })); } catch (tableError) { const error = tableError; if (error.name === "ResourceNotFoundException") { throw new Error(`DynamoDB table '${tableName}' not found in region '${region}'`); } throw tableError; } return new dynamodb_nonce_cache_1.DynamoNonceCache(dynamodb, tableName, config?.dynamodb?.keyAttribute, config?.dynamodb?.ttlAttribute); } catch (error) { if (options?.throwOnError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); console.warn("Failed to initialize DynamoDB, falling back to memory cache:", errorMessage); return new memory_nonce_cache_1.MemoryNonceCache(); } } case "cloudflare-kv": { const namespace = config?.cloudflareKv?.namespace || getEnvWithFallback("MCPI_KV_NAMESPACE", "XMCPI_KV_NAMESPACE"); if (!namespace) { const error = new Error("Cloudflare KV namespace not found"); if (options?.throwOnError) { throw error; } console.warn("Cloudflare KV namespace not found, falling back to memory cache"); return new memory_nonce_cache_1.MemoryNonceCache(); } try { // In Cloudflare Workers, KV namespaces are available as globals const globalScope = globalThis; const kv = globalScope[namespace]; if (!kv) { throw new Error(`KV namespace '${namespace}' not found in global scope`); } // Verify KV namespace has required methods const kvInterface = kv; if (typeof kvInterface.get !== "function" || typeof kvInterface.put !== "function" || typeof kvInterface.delete !== "function") { throw new Error(`KV namespace '${namespace}' does not have required methods`); } // Test basic KV operation (this will be very fast in Workers) try { const kvTest = kv; await kvTest.get("__mcpi_test_key__"); } catch (kvError) { const error = kvError; // Some KV errors are expected (like key not found), but connection errors are not if (error.message?.includes("network") || error.message?.includes("timeout")) { throw kvError; } // Other errors (like key not found) are fine } return new cloudflare_kv_nonce_cache_1.CloudflareKVNonceCache(kv, config?.cloudflareKv?.keyPrefix); } catch (error) { if (options?.throwOnError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); console.warn("Failed to initialize Cloudflare KV, falling back to memory cache:", errorMessage); return new memory_nonce_cache_1.MemoryNonceCache(); } } case "memory": default: return new memory_nonce_cache_1.MemoryNonceCache(); } } /** * Create nonce cache with explicit configuration override */ async function createNonceCacheWithConfig(options = {}) { try { return await createNonceCache(options.config, { throwOnError: options.fallbackToMemory === false, }); } catch (error) { if (options.fallbackToMemory !== false) { console.warn("Failed to create configured nonce cache, falling back to memory:", error); return new memory_nonce_cache_1.MemoryNonceCache(); } throw error; } }