import { KubernetesClient, type KubernetesClientConfig } from '../kubernetes/KubernetesClient.js'; export type OptionalToolMode = 'auto' | 'on' | 'off'; export interface OptionalCapabilities { argo: boolean; argoWorkflows: boolean; argoCronWorkflows: boolean; argocd: boolean; } interface CacheEntry { expiresAt: number; value: OptionalCapabilities; } const DISCOVERY_TTL_MS = 60_000; const cache = new Map(); export async function detectOptionalCapabilities( config: KubernetesClientConfig = {}, now = Date.now(), ): Promise { const argoMode = resolveMode('MCP_ARGO_TOOLS', 'MCP_DISABLE_ARGO_PLUGIN'); const argocdMode = resolveMode('MCP_ARGOCD_TOOLS', 'MCP_DISABLE_ARGOCD_PLUGIN'); const remoteArgoCD = hasRemoteArgoCDConfig(); const needsDiscovery = argoMode === 'auto' || (argocdMode === 'auto' && !remoteArgoCD); let client: KubernetesClient | undefined; let contextKey = config.context ?? process.env.MCP_KUBE_CONTEXT ?? 'current'; if (needsDiscovery) { try { client = new KubernetesClient(config); contextKey = client.getCurrentContext() || contextKey; } catch { client = undefined; } } const cacheKey = `${contextKey}:${argoMode}:${argocdMode}:${remoteArgoCD}`; const cached = cache.get(cacheKey); if (cached && cached.expiresAt > now) return cached.value; let resources = new Set(); if (needsDiscovery && client) { try { await client.refreshCurrentContext(); const discovery = await client.getRaw('/apis/argoproj.io/v1alpha1'); const items = discovery?.resources ?? discovery?.body?.resources ?? []; resources = new Set( Array.isArray(items) ? items.map((resource: any) => String(resource?.name ?? '')).filter(Boolean) : [], ); } catch { resources = new Set(); } } const argoWorkflows = argoMode === 'on' || (argoMode === 'auto' && resources.has('workflows')); const argoCronWorkflows = argoMode === 'on' || (argoMode === 'auto' && resources.has('cronworkflows')); const argocd = argocdMode === 'on' || (argocdMode === 'auto' && (resources.has('applications') || remoteArgoCD)); const value = { argo: argoWorkflows || argoCronWorkflows, argoWorkflows, argoCronWorkflows, argocd, }; cache.set(cacheKey, { value, expiresAt: now + DISCOVERY_TTL_MS }); return value; } export function clearOptionalCapabilityCache(): void { cache.clear(); } function resolveMode(primaryName: string, legacyDisableName: string): OptionalToolMode { const primary = process.env[primaryName]?.trim().toLowerCase(); if (primary === 'auto' || primary === 'on' || primary === 'off') return primary; if (primary) throw new Error(`${primaryName} must be one of: auto, on, off`); const legacy = process.env[legacyDisableName]; if (legacy === 'true' || legacy === '1') return 'off'; return 'auto'; } function hasRemoteArgoCDConfig(): boolean { return Boolean(process.env.ARGOCD_SERVER?.trim() && process.env.ARGOCD_AUTH_TOKEN?.trim()); }