claude-flow-novice 2.16.0 → 2.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/.claude/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/.claude/commands/cfn-loop-cli.md +124 -46
- package/.claude/commands/cfn-loop-frontend.md +1 -1
- package/.claude/commands/cfn-loop-task.md +2 -2
- package/.claude/commands/deprecated/cfn-loop.md +2 -2
- package/.claude/hooks/cfn-invoke-post-edit.sh +31 -5
- package/.claude/hooks/cfn-post-edit.config.json +9 -2
- package/.claude/root-claude-distribute/CFN-CLAUDE.md +1 -1
- package/.claude/skills/cfn-backlog-management/SKILL.md +1 -1
- package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
- package/claude-assets/agents/cfn-dev-team/analysts/root-cause-analyst.md +2 -2
- package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +2 -2
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +2 -2
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +2 -2
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +1 -1
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +11 -0
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +1 -1
- package/claude-assets/agents/custom/cfn-docker-expert.md +1 -0
- package/claude-assets/agents/custom/cfn-loops-cli-expert.md +326 -17
- package/claude-assets/agents/custom/cfn-redis-operations.md +529 -529
- package/claude-assets/agents/custom/cfn-system-expert.md +1 -1
- package/claude-assets/agents/custom/trigger-dev-expert.md +369 -0
- package/claude-assets/agents/docker-team/micro-sprint-planner.md +747 -747
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/claude-assets/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/claude-assets/commands/cfn-loop-cli.md +124 -46
- package/claude-assets/commands/cfn-loop-frontend.md +1 -1
- package/claude-assets/commands/cfn-loop-task.md +2 -2
- package/claude-assets/commands/deprecated/cfn-loop.md +2 -2
- package/claude-assets/hooks/GIT-HOOKS-USAGE-EXAMPLES.md +116 -0
- package/claude-assets/hooks/README-GIT-HOOKS.md +443 -0
- package/claude-assets/hooks/cfn-invoke-post-edit.sh +31 -5
- package/claude-assets/hooks/cfn-post-edit.config.json +9 -2
- package/claude-assets/hooks/install-git-hooks.sh +243 -0
- package/claude-assets/hooks/subagent-start.sh +98 -0
- package/claude-assets/hooks/subagent-stop.sh +93 -0
- package/claude-assets/hooks/validators/credential-scanner.sh +172 -0
- package/claude-assets/root-claude-distribute/CFN-CLAUDE.md +1 -1
- package/claude-assets/skills/cfn-backlog-management/SKILL.md +1 -1
- package/claude-assets/skills/cfn-dependency-ingestion/SKILL.md +41 -13
- package/claude-assets/skills/cfn-dependency-ingestion/ingest.sh +237 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/cli-mode-dependencies.txt +73 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/shared-dependencies.txt +57 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-dev-dependencies.txt +82 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-mode-dependencies.txt +80 -0
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +14 -4
- package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
- package/claude-assets/skills/cfn-provider-routing/SKILL.md +23 -0
- package/claude-assets/skills/docker-build/build.sh +1 -1
- package/dist/agent/skill-mcp-selector.js +2 -1
- package/dist/agent/skill-mcp-selector.js.map +1 -1
- package/dist/agents/agent-loader.js +165 -146
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/cli/agent-executor.js +470 -26
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +2 -2
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +7 -4
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/agent-spawner.js +51 -4
- package/dist/cli/agent-spawner.js.map +1 -1
- package/dist/cli/agent-token-manager.js +2 -1
- package/dist/cli/agent-token-manager.js.map +1 -1
- package/dist/cli/anthropic-client.js +117 -11
- package/dist/cli/anthropic-client.js.map +1 -1
- package/dist/cli/cfn-context.js +2 -1
- package/dist/cli/cfn-context.js.map +1 -1
- package/dist/cli/cfn-metrics.js +2 -1
- package/dist/cli/cfn-metrics.js.map +1 -1
- package/dist/cli/cfn-redis.js +2 -1
- package/dist/cli/cfn-redis.js.map +1 -1
- package/dist/cli/cli-agent-context.js +2 -0
- package/dist/cli/cli-agent-context.js.map +1 -1
- package/dist/cli/config-manager.js +4 -252
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/cli/conversation-fork-cleanup.js +2 -1
- package/dist/cli/conversation-fork-cleanup.js.map +1 -1
- package/dist/cli/conversation-fork.js +2 -1
- package/dist/cli/conversation-fork.js.map +1 -1
- package/dist/cli/coordination/agent-messaging.js +415 -0
- package/dist/cli/coordination/agent-messaging.js.map +1 -0
- package/dist/cli/coordination/wait-for-threshold.js +232 -0
- package/dist/cli/coordination/wait-for-threshold.js.map +1 -0
- package/dist/cli/iteration-history.js +2 -1
- package/dist/cli/iteration-history.js.map +1 -1
- package/dist/cli/process-lifecycle.js +5 -1
- package/dist/cli/process-lifecycle.js.map +1 -1
- package/dist/cli/spawn-agent-cli.js +41 -6
- package/dist/cli/spawn-agent-cli.js.map +1 -1
- package/dist/coordination/redis-waiting-mode.js +4 -0
- package/dist/coordination/redis-waiting-mode.js.map +1 -1
- package/dist/lib/artifact-registry.js +4 -0
- package/dist/lib/artifact-registry.js.map +1 -1
- package/dist/lib/connection-pool.js +390 -0
- package/dist/lib/connection-pool.js.map +1 -0
- package/dist/lib/environment-contract.js +258 -0
- package/dist/lib/environment-contract.js.map +1 -0
- package/dist/lib/query-optimizer.js +388 -0
- package/dist/lib/query-optimizer.js.map +1 -0
- package/dist/lib/result-cache.js +285 -0
- package/dist/lib/result-cache.js.map +1 -0
- package/dist/mcp/auth-middleware.js +2 -1
- package/dist/mcp/auth-middleware.js.map +1 -1
- package/dist/mcp/playwright-mcp-server-auth.js +2 -1
- package/dist/mcp/playwright-mcp-server-auth.js.map +1 -1
- package/package.json +3 -1
- package/scripts/build-agent-image.sh +1 -1
- package/scripts/cost-allocation-tracker.sh +632 -0
- package/scripts/docker-rebuild-all-agents.sh +2 -2
- package/scripts/reorganize-tests.sh +280 -0
- package/scripts/trigger-dev-setup.sh +12 -0
- package/tests/README.md +45 -0
- package/.claude/commands/cost-savings-status.md +0 -34
- package/.claude/commands/metrics-summary.md +0 -58
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +0 -768
- package/claude-assets/agents/custom/test-mcp-access.md +0 -24
- package/claude-assets/commands/cost-savings-status.md +0 -34
- package/claude-assets/commands/metrics-summary.md +0 -58
- package/tests/test-memory-leak-task-mode.sh +0 -435
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/query-optimizer.ts"],"sourcesContent":["/**\r\n * Query Optimizer\r\n *\r\n * Implements database query optimizations including:\r\n * - Index management for agents table\r\n * - Materialized views for cost aggregation\r\n * - Query pattern optimization\r\n * - Expected: 10-20x query speedup\r\n *\r\n * Features:\r\n * - Automated index creation\r\n * - Materialized view management\r\n * - Query rewriting for optimal execution\r\n * - Performance monitoring\r\n */\r\n\r\nimport { Pool, PoolClient } from 'pg';\r\n\r\nexport interface QueryOptimizerConfig {\r\n pool: Pool;\r\n refreshInterval?: number; // Materialized view refresh interval in ms (default: 1 hour)\r\n}\r\n\r\nexport interface CostByTeam {\r\n team_id: string;\r\n agent_count: number;\r\n completed_count: number;\r\n failed_count: number;\r\n avg_confidence: number | null;\r\n total_cost: number;\r\n first_spawn: Date;\r\n last_spawn: Date;\r\n}\r\n\r\nexport interface CostByAgentType {\r\n agent_type: string;\r\n agent_count: number;\r\n completed_count: number;\r\n failed_count: number;\r\n avg_confidence: number | null;\r\n total_cost: number;\r\n avg_duration_seconds: number | null;\r\n}\r\n\r\nexport interface DailyCostSummary {\r\n date: Date;\r\n total_agents: number;\r\n completed_count: number;\r\n failed_count: number;\r\n total_cost: number;\r\n avg_confidence: number | null;\r\n}\r\n\r\nexport interface AgentRecord {\r\n id: string;\r\n team_id: string | null;\r\n type: string;\r\n status: string;\r\n spawned_at: Date;\r\n completed_at: Date | null;\r\n confidence: number | null;\r\n metadata: Record<string, unknown> | null;\r\n}\r\n\r\nexport interface IndexUsageStats {\r\n schemaname: string;\r\n tablename: string;\r\n indexname: string;\r\n index_scans: number;\r\n tuples_read: number;\r\n tuples_fetched: number;\r\n}\r\n\r\nexport interface QueryPlan {\r\n 'Plan': Record<string, unknown>;\r\n 'Planning Time': number;\r\n 'Execution Time': number;\r\n}\r\n\r\nexport class QueryOptimizer {\r\n private pool: Pool;\r\n private refreshInterval: number;\r\n private refreshTimer: NodeJS.Timeout | null = null;\r\n\r\n constructor(config: QueryOptimizerConfig) {\r\n this.pool = config.pool;\r\n this.refreshInterval = config.refreshInterval || 3600000; // 1 hour default\r\n }\r\n\r\n /**\r\n * Initialize all query optimizations\r\n */\r\n async initialize(): Promise<void> {\r\n console.log('Initializing query optimizer...');\r\n\r\n await this.createIndexes();\r\n await this.createMaterializedViews();\r\n await this.startMaterializedViewRefresh();\r\n\r\n console.log('Query optimizer initialized successfully');\r\n }\r\n\r\n /**\r\n * Create indexes on agents table for performance\r\n * Indexes: team_id, status, spawned_at\r\n */\r\n async createIndexes(): Promise<void> {\r\n const indexes = [\r\n {\r\n name: 'idx_agents_team_id',\r\n table: 'agents',\r\n columns: ['team_id'],\r\n description: 'Index for team-based queries',\r\n },\r\n {\r\n name: 'idx_agents_status',\r\n table: 'agents',\r\n columns: ['status'],\r\n description: 'Index for status filtering',\r\n },\r\n {\r\n name: 'idx_agents_spawned_at',\r\n table: 'agents',\r\n columns: ['spawned_at'],\r\n description: 'Index for time-based queries',\r\n },\r\n {\r\n name: 'idx_agents_team_status',\r\n table: 'agents',\r\n columns: ['team_id', 'status'],\r\n description: 'Composite index for team + status queries',\r\n },\r\n {\r\n name: 'idx_agents_status_spawned',\r\n table: 'agents',\r\n columns: ['status', 'spawned_at'],\r\n description: 'Composite index for status + time queries',\r\n },\r\n {\r\n name: 'idx_agents_cost_query',\r\n table: 'agents',\r\n columns: ['team_id', 'spawned_at', 'status'],\r\n description: 'Composite index for cost aggregation queries',\r\n },\r\n ];\r\n\r\n const client = await this.pool.connect();\r\n try {\r\n for (const index of indexes) {\r\n const query = `\r\n CREATE INDEX IF NOT EXISTS ${index.name}\r\n ON ${index.table} (${index.columns.join(', ')})\r\n `;\r\n\r\n await client.query(query);\r\n console.log(`Created index: ${index.name} - ${index.description}`);\r\n }\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Create materialized views for cost aggregation queries\r\n */\r\n async createMaterializedViews(): Promise<void> {\r\n const client = await this.pool.connect();\r\n try {\r\n // Drop existing views if they exist\r\n await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_cost_by_team CASCADE');\r\n await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_cost_by_agent_type CASCADE');\r\n await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_daily_cost_summary CASCADE');\r\n\r\n // Create materialized view for cost by team\r\n await client.query(`\r\n CREATE MATERIALIZED VIEW mv_cost_by_team AS\r\n SELECT\r\n team_id,\r\n COUNT(*) as agent_count,\r\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,\r\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,\r\n AVG(confidence) as avg_confidence,\r\n SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,\r\n MIN(spawned_at) as first_spawn,\r\n MAX(spawned_at) as last_spawn\r\n FROM agents\r\n WHERE team_id IS NOT NULL\r\n GROUP BY team_id\r\n `);\r\n console.log('Created materialized view: mv_cost_by_team');\r\n\r\n // Create materialized view for cost by agent type\r\n await client.query(`\r\n CREATE MATERIALIZED VIEW mv_cost_by_agent_type AS\r\n SELECT\r\n type as agent_type,\r\n COUNT(*) as agent_count,\r\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,\r\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,\r\n AVG(confidence) as avg_confidence,\r\n SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,\r\n AVG(EXTRACT(EPOCH FROM (completed_at - spawned_at))) as avg_duration_seconds\r\n FROM agents\r\n WHERE type IS NOT NULL\r\n GROUP BY type\r\n `);\r\n console.log('Created materialized view: mv_cost_by_agent_type');\r\n\r\n // Create materialized view for daily cost summary\r\n await client.query(`\r\n CREATE MATERIALIZED VIEW mv_daily_cost_summary AS\r\n SELECT\r\n DATE(spawned_at) as date,\r\n COUNT(*) as total_agents,\r\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,\r\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,\r\n SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,\r\n AVG(confidence) as avg_confidence\r\n FROM agents\r\n WHERE spawned_at IS NOT NULL\r\n GROUP BY DATE(spawned_at)\r\n ORDER BY date DESC\r\n `);\r\n console.log('Created materialized view: mv_daily_cost_summary');\r\n\r\n // Create UNIQUE indexes on materialized views (required for CONCURRENT refresh)\r\n await client.query('CREATE UNIQUE INDEX idx_mv_cost_by_team_team_id ON mv_cost_by_team (team_id)');\r\n await client.query('CREATE UNIQUE INDEX idx_mv_cost_by_agent_type_type ON mv_cost_by_agent_type (agent_type)');\r\n await client.query('CREATE UNIQUE INDEX idx_mv_daily_cost_summary_date ON mv_daily_cost_summary (date)');\r\n\r\n console.log('Created UNIQUE indexes on materialized views for concurrent refresh');\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Refresh materialized views\r\n */\r\n async refreshMaterializedViews(): Promise<void> {\r\n const client = await this.pool.connect();\r\n try {\r\n console.log('Refreshing materialized views...');\r\n\r\n await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_cost_by_team');\r\n await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_cost_by_agent_type');\r\n await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_cost_summary');\r\n\r\n console.log('Materialized views refreshed successfully');\r\n } catch (err) {\r\n console.error('Error refreshing materialized views:', err);\r\n throw err;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Start automatic materialized view refresh\r\n */\r\n startMaterializedViewRefresh(): void {\r\n if (this.refreshTimer) {\r\n clearInterval(this.refreshTimer);\r\n }\r\n\r\n // Initial refresh\r\n this.refreshMaterializedViews().catch(console.error);\r\n\r\n // Schedule periodic refresh\r\n this.refreshTimer = setInterval(() => {\r\n this.refreshMaterializedViews().catch(console.error);\r\n }, this.refreshInterval);\r\n\r\n console.log(\r\n `Started materialized view auto-refresh (interval: ${this.refreshInterval / 1000}s)`\r\n );\r\n }\r\n\r\n /**\r\n * Stop automatic materialized view refresh\r\n */\r\n stopMaterializedViewRefresh(): void {\r\n if (this.refreshTimer) {\r\n clearInterval(this.refreshTimer);\r\n this.refreshTimer = null;\r\n console.log('Stopped materialized view auto-refresh');\r\n }\r\n }\r\n\r\n /**\r\n * Optimized query: Get cost by team\r\n */\r\n async getCostByTeam(teamId?: string): Promise<CostByTeam[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n let query = 'SELECT * FROM mv_cost_by_team';\r\n const params: unknown[] = [];\r\n\r\n if (teamId) {\r\n query += ' WHERE team_id = $1';\r\n params.push(teamId);\r\n }\r\n\r\n query += ' ORDER BY total_cost DESC';\r\n\r\n const result = await client.query(query, params);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Optimized query: Get cost by agent type\r\n */\r\n async getCostByAgentType(agentType?: string): Promise<CostByAgentType[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n let query = 'SELECT * FROM mv_cost_by_agent_type';\r\n const params: unknown[] = [];\r\n\r\n if (agentType) {\r\n query += ' WHERE agent_type = $1';\r\n params.push(agentType);\r\n }\r\n\r\n query += ' ORDER BY total_cost DESC';\r\n\r\n const result = await client.query(query, params);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Optimized query: Get daily cost summary\r\n */\r\n async getDailyCostSummary(\r\n startDate?: Date,\r\n endDate?: Date\r\n ): Promise<DailyCostSummary[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n let query = 'SELECT * FROM mv_daily_cost_summary';\r\n const params: unknown[] = [];\r\n\r\n const conditions: string[] = [];\r\n if (startDate) {\r\n params.push(startDate);\r\n conditions.push(`date >= $${params.length}`);\r\n }\r\n if (endDate) {\r\n params.push(endDate);\r\n conditions.push(`date <= $${params.length}`);\r\n }\r\n\r\n if (conditions.length > 0) {\r\n query += ' WHERE ' + conditions.join(' AND ');\r\n }\r\n\r\n query += ' ORDER BY date DESC';\r\n\r\n const result = await client.query(query, params);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Optimized query: Get agents by team and status\r\n * Uses composite index idx_agents_team_status\r\n */\r\n async getAgentsByTeamAndStatus(\r\n teamId: string,\r\n status: string\r\n ): Promise<AgentRecord[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n const query = `\r\n SELECT *\r\n FROM agents\r\n WHERE team_id = $1 AND status = $2\r\n ORDER BY spawned_at DESC\r\n `;\r\n\r\n const result = await client.query(query, [teamId, status]);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Optimized query: Get agents by status and time range\r\n * Uses composite index idx_agents_status_spawned\r\n */\r\n async getAgentsByStatusAndTimeRange(\r\n status: string,\r\n startDate: Date,\r\n endDate: Date\r\n ): Promise<AgentRecord[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n const query = `\r\n SELECT *\r\n FROM agents\r\n WHERE status = $1\r\n AND spawned_at >= $2\r\n AND spawned_at <= $3\r\n ORDER BY spawned_at DESC\r\n `;\r\n\r\n const result = await client.query(query, [status, startDate, endDate]);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Analyze query performance\r\n */\r\n async analyzeQuery(query: string, params?: unknown[]): Promise<QueryPlan> {\r\n const client = await this.pool.connect();\r\n try {\r\n const explainQuery = `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${query}`;\r\n const result = await client.query(explainQuery, params);\r\n return result.rows[0]['QUERY PLAN'][0] as QueryPlan;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Get index usage statistics\r\n */\r\n async getIndexUsageStats(): Promise<IndexUsageStats[]> {\r\n const client = await this.pool.connect();\r\n try {\r\n const query = `\r\n SELECT\r\n schemaname,\r\n tablename,\r\n indexname,\r\n idx_scan as index_scans,\r\n idx_tup_read as tuples_read,\r\n idx_tup_fetch as tuples_fetched\r\n FROM pg_stat_user_indexes\r\n WHERE schemaname = 'public'\r\n ORDER BY idx_scan DESC\r\n `;\r\n\r\n const result = await client.query(query);\r\n return result.rows;\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown query optimizer\r\n */\r\n async shutdown(): Promise<void> {\r\n this.stopMaterializedViewRefresh();\r\n console.log('Query optimizer shutdown complete');\r\n }\r\n}\r\n\r\n// Singleton instance\r\nlet queryOptimizerInstance: QueryOptimizer | null = null;\r\n\r\n/**\r\n * Initialize singleton query optimizer\r\n */\r\nexport async function initQueryOptimizer(\r\n config: QueryOptimizerConfig\r\n): Promise<QueryOptimizer> {\r\n if (!queryOptimizerInstance) {\r\n queryOptimizerInstance = new QueryOptimizer(config);\r\n await queryOptimizerInstance.initialize();\r\n }\r\n\r\n return queryOptimizerInstance;\r\n}\r\n\r\n/**\r\n * Get singleton query optimizer instance\r\n */\r\nexport function getQueryOptimizer(): QueryOptimizer {\r\n if (!queryOptimizerInstance) {\r\n throw new Error(\r\n 'Query optimizer not initialized. Call initQueryOptimizer first.'\r\n );\r\n }\r\n return queryOptimizerInstance;\r\n}\r\n\r\n/**\r\n * Shutdown singleton query optimizer\r\n */\r\nexport async function shutdownQueryOptimizer(): Promise<void> {\r\n if (queryOptimizerInstance) {\r\n await queryOptimizerInstance.shutdown();\r\n queryOptimizerInstance = null;\r\n }\r\n}\r\n"],"names":["QueryOptimizer","pool","refreshInterval","refreshTimer","config","initialize","console","log","createIndexes","createMaterializedViews","startMaterializedViewRefresh","indexes","name","table","columns","description","client","connect","index","query","join","release","refreshMaterializedViews","err","error","clearInterval","catch","setInterval","stopMaterializedViewRefresh","getCostByTeam","teamId","params","push","result","rows","getCostByAgentType","agentType","getDailyCostSummary","startDate","endDate","conditions","length","getAgentsByTeamAndStatus","status","getAgentsByStatusAndTimeRange","analyzeQuery","explainQuery","getIndexUsageStats","shutdown","queryOptimizerInstance","initQueryOptimizer","getQueryOptimizer","Error","shutdownQueryOptimizer"],"mappings":"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"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Result Cache - Phase 6 Performance Optimization
|
|
3
|
+
*
|
|
4
|
+
* Implements Redis-based caching for agent results to reduce redundant work.
|
|
5
|
+
*
|
|
6
|
+
* Performance targets:
|
|
7
|
+
* - Cache hit rate: ~80% (with 1-hour TTL)
|
|
8
|
+
* - Cache operation latency: <10ms
|
|
9
|
+
* - 80% reduction in redundant agent execution
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - SHA256-based task hashing for cache keys
|
|
13
|
+
* - Configurable TTL (default 1 hour)
|
|
14
|
+
* - Prometheus metrics (hits, misses, size)
|
|
15
|
+
* - Graceful degradation if Redis unavailable
|
|
16
|
+
* - TypeScript strict mode (no any types)
|
|
17
|
+
* - Cache invalidation API
|
|
18
|
+
*/ import { createHash } from 'crypto';
|
|
19
|
+
import { getRedisPool } from './connection-pool.js';
|
|
20
|
+
/**
|
|
21
|
+
* Default cache configuration
|
|
22
|
+
*/ const DEFAULT_CONFIG = {
|
|
23
|
+
ttlSeconds: parseInt(process.env.CFN_CACHE_TTL_SECONDS || '3600', 10),
|
|
24
|
+
maxEntries: parseInt(process.env.CFN_CACHE_MAX_ENTRIES || '10000', 10),
|
|
25
|
+
prefix: process.env.CFN_CACHE_PREFIX || 'cfn:cache:'
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Metrics tracking
|
|
29
|
+
*/ let cacheHits = 0;
|
|
30
|
+
let cacheMisses = 0;
|
|
31
|
+
let cacheSizeBytes = 0;
|
|
32
|
+
let cacheEntries = 0;
|
|
33
|
+
/**
|
|
34
|
+
* Cache availability flag
|
|
35
|
+
*/ let cacheAvailable = true;
|
|
36
|
+
/**
|
|
37
|
+
* Generate SHA256 hash for task description
|
|
38
|
+
* Ensures consistent cache keys for identical tasks
|
|
39
|
+
*/ export function generateTaskHash(taskDescription) {
|
|
40
|
+
return createHash('sha256').update(taskDescription.trim()).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build cache key from agent type and task hash
|
|
44
|
+
*/ function buildCacheKey(agentType, taskHash) {
|
|
45
|
+
return `${DEFAULT_CONFIG.prefix}${agentType}:${taskHash}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get cached result for agent task
|
|
49
|
+
* Returns null if cache miss or error
|
|
50
|
+
*
|
|
51
|
+
* @param agentType - Type of agent (e.g., 'backend-developer')
|
|
52
|
+
* @param taskDescription - Task description to hash
|
|
53
|
+
* @returns Cached result or null
|
|
54
|
+
*/ export async function getCachedResult(agentType, taskDescription) {
|
|
55
|
+
if (!cacheAvailable) {
|
|
56
|
+
cacheMisses++;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const redis = getRedisPool();
|
|
61
|
+
const taskHash = generateTaskHash(taskDescription);
|
|
62
|
+
const cacheKey = buildCacheKey(agentType, taskHash);
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const cachedData = await redis.get(cacheKey);
|
|
65
|
+
const latency = Date.now() - startTime;
|
|
66
|
+
if (latency > 10) {
|
|
67
|
+
console.warn(`Cache GET latency ${latency}ms exceeds 10ms target`);
|
|
68
|
+
}
|
|
69
|
+
if (!cachedData) {
|
|
70
|
+
cacheMisses++;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const parsed = JSON.parse(cachedData);
|
|
74
|
+
// Validate expiration
|
|
75
|
+
const expiresAt = new Date(parsed.expiresAt);
|
|
76
|
+
if (expiresAt < new Date()) {
|
|
77
|
+
// Expired, delete and return null
|
|
78
|
+
await redis.del(cacheKey);
|
|
79
|
+
cacheMisses++;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
cacheHits++;
|
|
83
|
+
return parsed;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const err = error;
|
|
86
|
+
console.error('Cache GET error:', err.message);
|
|
87
|
+
// Graceful degradation
|
|
88
|
+
cacheAvailable = false;
|
|
89
|
+
cacheMisses++;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Set cached result for agent task
|
|
95
|
+
* Implements TTL-based expiration and size limits
|
|
96
|
+
*
|
|
97
|
+
* @param agentType - Type of agent
|
|
98
|
+
* @param taskDescription - Task description to hash
|
|
99
|
+
* @param result - Agent result to cache
|
|
100
|
+
* @param confidence - Confidence score
|
|
101
|
+
*/ export async function setCachedResult(agentType, taskDescription, result, confidence) {
|
|
102
|
+
if (!cacheAvailable) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const redis = getRedisPool();
|
|
107
|
+
const taskHash = generateTaskHash(taskDescription);
|
|
108
|
+
const cacheKey = buildCacheKey(agentType, taskHash);
|
|
109
|
+
// Check max entries limit
|
|
110
|
+
const currentEntries = await redis.dbsize();
|
|
111
|
+
if (currentEntries >= DEFAULT_CONFIG.maxEntries) {
|
|
112
|
+
console.warn(`Cache at max capacity (${DEFAULT_CONFIG.maxEntries} entries), evicting oldest entries`);
|
|
113
|
+
// Redis with maxmemory-policy=allkeys-lru handles eviction automatically
|
|
114
|
+
}
|
|
115
|
+
const now = new Date();
|
|
116
|
+
const expiresAt = new Date(now.getTime() + DEFAULT_CONFIG.ttlSeconds * 1000);
|
|
117
|
+
const cachedResult = {
|
|
118
|
+
agentType,
|
|
119
|
+
taskHash,
|
|
120
|
+
result,
|
|
121
|
+
confidence,
|
|
122
|
+
cachedAt: now.toISOString(),
|
|
123
|
+
expiresAt: expiresAt.toISOString()
|
|
124
|
+
};
|
|
125
|
+
const serialized = JSON.stringify(cachedResult);
|
|
126
|
+
const sizeBytes = Buffer.byteLength(serialized, 'utf8');
|
|
127
|
+
const startTime = Date.now();
|
|
128
|
+
await redis.set(cacheKey, serialized, 'EX', DEFAULT_CONFIG.ttlSeconds);
|
|
129
|
+
const latency = Date.now() - startTime;
|
|
130
|
+
if (latency > 10) {
|
|
131
|
+
console.warn(`Cache SET latency ${latency}ms exceeds 10ms target`);
|
|
132
|
+
}
|
|
133
|
+
// Update metrics
|
|
134
|
+
cacheSizeBytes += sizeBytes;
|
|
135
|
+
cacheEntries++;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const err = error;
|
|
138
|
+
console.error('Cache SET error:', err.message);
|
|
139
|
+
// Graceful degradation
|
|
140
|
+
cacheAvailable = false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Invalidate cache entries
|
|
145
|
+
* If agentType provided, only invalidates that agent's cache
|
|
146
|
+
* Otherwise, clears all cache entries
|
|
147
|
+
*
|
|
148
|
+
* @param agentType - Optional agent type to target
|
|
149
|
+
* @returns Number of entries deleted
|
|
150
|
+
*/ export async function invalidateCache(agentType) {
|
|
151
|
+
if (!cacheAvailable) {
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const redis = getRedisPool();
|
|
156
|
+
let deletedCount = 0;
|
|
157
|
+
if (agentType) {
|
|
158
|
+
// Delete specific agent type cache entries
|
|
159
|
+
const pattern = `${DEFAULT_CONFIG.prefix}${agentType}:*`;
|
|
160
|
+
const keys = await redis.keys(pattern);
|
|
161
|
+
if (keys.length > 0) {
|
|
162
|
+
deletedCount = await redis.del(...keys);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Delete all cache entries
|
|
166
|
+
const pattern = `${DEFAULT_CONFIG.prefix}*`;
|
|
167
|
+
const keys = await redis.keys(pattern);
|
|
168
|
+
if (keys.length > 0) {
|
|
169
|
+
deletedCount = await redis.del(...keys);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Reset metrics
|
|
173
|
+
if (!agentType) {
|
|
174
|
+
cacheHits = 0;
|
|
175
|
+
cacheMisses = 0;
|
|
176
|
+
cacheSizeBytes = 0;
|
|
177
|
+
cacheEntries = 0;
|
|
178
|
+
}
|
|
179
|
+
console.log(`Cache invalidated: ${deletedCount} entries deleted`);
|
|
180
|
+
return deletedCount;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const err = error;
|
|
183
|
+
console.error('Cache invalidation error:', err.message);
|
|
184
|
+
cacheAvailable = false;
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get cache metrics for Prometheus monitoring
|
|
190
|
+
* Exposes hits, misses, hit rate, size, and entry count
|
|
191
|
+
*/ export function getCacheMetrics() {
|
|
192
|
+
const totalRequests = cacheHits + cacheMisses;
|
|
193
|
+
const hitRate = totalRequests > 0 ? cacheHits / totalRequests : 0;
|
|
194
|
+
return {
|
|
195
|
+
hits: cacheHits,
|
|
196
|
+
misses: cacheMisses,
|
|
197
|
+
totalRequests,
|
|
198
|
+
hitRate: parseFloat(hitRate.toFixed(4)),
|
|
199
|
+
sizeBytes: cacheSizeBytes,
|
|
200
|
+
entries: cacheEntries,
|
|
201
|
+
lastUpdated: new Date().toISOString()
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Reset cache metrics
|
|
206
|
+
* Used for testing and monitoring reset
|
|
207
|
+
*/ export function resetCacheMetrics() {
|
|
208
|
+
cacheHits = 0;
|
|
209
|
+
cacheMisses = 0;
|
|
210
|
+
cacheSizeBytes = 0;
|
|
211
|
+
cacheEntries = 0;
|
|
212
|
+
console.log('Cache metrics reset');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if cache is available
|
|
216
|
+
* Used for graceful degradation checks
|
|
217
|
+
*/ export function isCacheAvailable() {
|
|
218
|
+
return cacheAvailable;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Reset cache availability flag
|
|
222
|
+
* Used after Redis reconnection
|
|
223
|
+
*/ export function resetCacheAvailability() {
|
|
224
|
+
cacheAvailable = true;
|
|
225
|
+
console.log('Cache availability reset');
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get cache configuration
|
|
229
|
+
* Returns current cache settings
|
|
230
|
+
*/ export function getCacheConfig() {
|
|
231
|
+
return {
|
|
232
|
+
...DEFAULT_CONFIG
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Update cache TTL for future entries
|
|
237
|
+
* Does not affect existing cached entries
|
|
238
|
+
*
|
|
239
|
+
* @param ttlSeconds - New TTL in seconds
|
|
240
|
+
*/ export function updateCacheTTL(ttlSeconds) {
|
|
241
|
+
if (ttlSeconds < 60 || ttlSeconds > 86400) {
|
|
242
|
+
throw new Error(`Invalid TTL: ${ttlSeconds}s. Must be between 60s (1 min) and 86400s (24 hours).`);
|
|
243
|
+
}
|
|
244
|
+
DEFAULT_CONFIG.ttlSeconds = ttlSeconds;
|
|
245
|
+
console.log(`Cache TTL updated to ${ttlSeconds} seconds`);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get cache entry count from Redis
|
|
249
|
+
* More accurate than tracked metric (accounts for evictions)
|
|
250
|
+
*/ export async function getCacheEntryCount() {
|
|
251
|
+
if (!cacheAvailable) {
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const redis = getRedisPool();
|
|
256
|
+
const pattern = `${DEFAULT_CONFIG.prefix}*`;
|
|
257
|
+
const keys = await redis.keys(pattern);
|
|
258
|
+
return keys.length;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const err = error;
|
|
261
|
+
console.error('Failed to get cache entry count:', err.message);
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Prewarm cache with common agent tasks
|
|
267
|
+
* Used during system initialization
|
|
268
|
+
*
|
|
269
|
+
* @param entries - Array of [agentType, taskDescription, result, confidence] tuples
|
|
270
|
+
*/ export async function prewarmCache(entries) {
|
|
271
|
+
let warmedCount = 0;
|
|
272
|
+
for (const [agentType, taskDescription, result, confidence] of entries){
|
|
273
|
+
try {
|
|
274
|
+
await setCachedResult(agentType, taskDescription, result, confidence);
|
|
275
|
+
warmedCount++;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const err = error;
|
|
278
|
+
console.error(`Failed to prewarm cache entry: ${err.message}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log(`Cache prewarmed with ${warmedCount} entries`);
|
|
282
|
+
return warmedCount;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//# sourceMappingURL=result-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/result-cache.ts"],"sourcesContent":["/**\r\n * Agent Result Cache - Phase 6 Performance Optimization\r\n *\r\n * Implements Redis-based caching for agent results to reduce redundant work.\r\n *\r\n * Performance targets:\r\n * - Cache hit rate: ~80% (with 1-hour TTL)\r\n * - Cache operation latency: <10ms\r\n * - 80% reduction in redundant agent execution\r\n *\r\n * Features:\r\n * - SHA256-based task hashing for cache keys\r\n * - Configurable TTL (default 1 hour)\r\n * - Prometheus metrics (hits, misses, size)\r\n * - Graceful degradation if Redis unavailable\r\n * - TypeScript strict mode (no any types)\r\n * - Cache invalidation API\r\n */\r\n\r\nimport { createHash } from 'crypto';\r\nimport { getRedisPool } from './connection-pool.js';\r\n\r\n/**\r\n * Cache configuration\r\n */\r\nexport interface CacheConfig {\r\n ttlSeconds: number; // Default 3600 (1 hour)\r\n maxEntries: number; // Default 10000\r\n prefix: string; // Default 'cfn:cache:'\r\n}\r\n\r\n/**\r\n * Cached result structure\r\n */\r\nexport interface CachedResult {\r\n agentType: string;\r\n taskHash: string;\r\n result: unknown;\r\n confidence: number;\r\n cachedAt: string;\r\n expiresAt: string;\r\n}\r\n\r\n/**\r\n * Cache metrics for Prometheus monitoring\r\n */\r\nexport interface CacheMetrics {\r\n hits: number;\r\n misses: number;\r\n totalRequests: number;\r\n hitRate: number;\r\n sizeBytes: number;\r\n entries: number;\r\n lastUpdated: string;\r\n}\r\n\r\n/**\r\n * Default cache configuration\r\n */\r\nconst DEFAULT_CONFIG: CacheConfig = {\r\n ttlSeconds: parseInt(process.env.CFN_CACHE_TTL_SECONDS || '3600', 10),\r\n maxEntries: parseInt(process.env.CFN_CACHE_MAX_ENTRIES || '10000', 10),\r\n prefix: process.env.CFN_CACHE_PREFIX || 'cfn:cache:',\r\n};\r\n\r\n/**\r\n * Metrics tracking\r\n */\r\nlet cacheHits = 0;\r\nlet cacheMisses = 0;\r\nlet cacheSizeBytes = 0;\r\nlet cacheEntries = 0;\r\n\r\n/**\r\n * Cache availability flag\r\n */\r\nlet cacheAvailable = true;\r\n\r\n/**\r\n * Generate SHA256 hash for task description\r\n * Ensures consistent cache keys for identical tasks\r\n */\r\nexport function generateTaskHash(taskDescription: string): string {\r\n return createHash('sha256').update(taskDescription.trim()).digest('hex');\r\n}\r\n\r\n/**\r\n * Build cache key from agent type and task hash\r\n */\r\nfunction buildCacheKey(agentType: string, taskHash: string): string {\r\n return `${DEFAULT_CONFIG.prefix}${agentType}:${taskHash}`;\r\n}\r\n\r\n/**\r\n * Get cached result for agent task\r\n * Returns null if cache miss or error\r\n *\r\n * @param agentType - Type of agent (e.g., 'backend-developer')\r\n * @param taskDescription - Task description to hash\r\n * @returns Cached result or null\r\n */\r\nexport async function getCachedResult(\r\n agentType: string,\r\n taskDescription: string\r\n): Promise<CachedResult | null> {\r\n if (!cacheAvailable) {\r\n cacheMisses++;\r\n return null;\r\n }\r\n\r\n try {\r\n const redis = getRedisPool();\r\n const taskHash = generateTaskHash(taskDescription);\r\n const cacheKey = buildCacheKey(agentType, taskHash);\r\n\r\n const startTime = Date.now();\r\n const cachedData = await redis.get(cacheKey);\r\n const latency = Date.now() - startTime;\r\n\r\n if (latency > 10) {\r\n console.warn(`Cache GET latency ${latency}ms exceeds 10ms target`);\r\n }\r\n\r\n if (!cachedData) {\r\n cacheMisses++;\r\n return null;\r\n }\r\n\r\n const parsed = JSON.parse(cachedData) as CachedResult;\r\n\r\n // Validate expiration\r\n const expiresAt = new Date(parsed.expiresAt);\r\n if (expiresAt < new Date()) {\r\n // Expired, delete and return null\r\n await redis.del(cacheKey);\r\n cacheMisses++;\r\n return null;\r\n }\r\n\r\n cacheHits++;\r\n return parsed;\r\n } catch (error) {\r\n const err = error as Error;\r\n console.error('Cache GET error:', err.message);\r\n // Graceful degradation\r\n cacheAvailable = false;\r\n cacheMisses++;\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Set cached result for agent task\r\n * Implements TTL-based expiration and size limits\r\n *\r\n * @param agentType - Type of agent\r\n * @param taskDescription - Task description to hash\r\n * @param result - Agent result to cache\r\n * @param confidence - Confidence score\r\n */\r\nexport async function setCachedResult(\r\n agentType: string,\r\n taskDescription: string,\r\n result: unknown,\r\n confidence: number\r\n): Promise<void> {\r\n if (!cacheAvailable) {\r\n return;\r\n }\r\n\r\n try {\r\n const redis = getRedisPool();\r\n const taskHash = generateTaskHash(taskDescription);\r\n const cacheKey = buildCacheKey(agentType, taskHash);\r\n\r\n // Check max entries limit\r\n const currentEntries = await redis.dbsize();\r\n if (currentEntries >= DEFAULT_CONFIG.maxEntries) {\r\n console.warn(\r\n `Cache at max capacity (${DEFAULT_CONFIG.maxEntries} entries), evicting oldest entries`\r\n );\r\n // Redis with maxmemory-policy=allkeys-lru handles eviction automatically\r\n }\r\n\r\n const now = new Date();\r\n const expiresAt = new Date(now.getTime() + DEFAULT_CONFIG.ttlSeconds * 1000);\r\n\r\n const cachedResult: CachedResult = {\r\n agentType,\r\n taskHash,\r\n result,\r\n confidence,\r\n cachedAt: now.toISOString(),\r\n expiresAt: expiresAt.toISOString(),\r\n };\r\n\r\n const serialized = JSON.stringify(cachedResult);\r\n const sizeBytes = Buffer.byteLength(serialized, 'utf8');\r\n\r\n const startTime = Date.now();\r\n await redis.set(cacheKey, serialized, 'EX', DEFAULT_CONFIG.ttlSeconds);\r\n const latency = Date.now() - startTime;\r\n\r\n if (latency > 10) {\r\n console.warn(`Cache SET latency ${latency}ms exceeds 10ms target`);\r\n }\r\n\r\n // Update metrics\r\n cacheSizeBytes += sizeBytes;\r\n cacheEntries++;\r\n } catch (error) {\r\n const err = error as Error;\r\n console.error('Cache SET error:', err.message);\r\n // Graceful degradation\r\n cacheAvailable = false;\r\n }\r\n}\r\n\r\n/**\r\n * Invalidate cache entries\r\n * If agentType provided, only invalidates that agent's cache\r\n * Otherwise, clears all cache entries\r\n *\r\n * @param agentType - Optional agent type to target\r\n * @returns Number of entries deleted\r\n */\r\nexport async function invalidateCache(agentType?: string): Promise<number> {\r\n if (!cacheAvailable) {\r\n return 0;\r\n }\r\n\r\n try {\r\n const redis = getRedisPool();\r\n let deletedCount = 0;\r\n\r\n if (agentType) {\r\n // Delete specific agent type cache entries\r\n const pattern = `${DEFAULT_CONFIG.prefix}${agentType}:*`;\r\n const keys = await redis.keys(pattern);\r\n\r\n if (keys.length > 0) {\r\n deletedCount = await redis.del(...keys);\r\n }\r\n } else {\r\n // Delete all cache entries\r\n const pattern = `${DEFAULT_CONFIG.prefix}*`;\r\n const keys = await redis.keys(pattern);\r\n\r\n if (keys.length > 0) {\r\n deletedCount = await redis.del(...keys);\r\n }\r\n }\r\n\r\n // Reset metrics\r\n if (!agentType) {\r\n cacheHits = 0;\r\n cacheMisses = 0;\r\n cacheSizeBytes = 0;\r\n cacheEntries = 0;\r\n }\r\n\r\n console.log(`Cache invalidated: ${deletedCount} entries deleted`);\r\n return deletedCount;\r\n } catch (error) {\r\n const err = error as Error;\r\n console.error('Cache invalidation error:', err.message);\r\n cacheAvailable = false;\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Get cache metrics for Prometheus monitoring\r\n * Exposes hits, misses, hit rate, size, and entry count\r\n */\r\nexport function getCacheMetrics(): CacheMetrics {\r\n const totalRequests = cacheHits + cacheMisses;\r\n const hitRate = totalRequests > 0 ? cacheHits / totalRequests : 0;\r\n\r\n return {\r\n hits: cacheHits,\r\n misses: cacheMisses,\r\n totalRequests,\r\n hitRate: parseFloat(hitRate.toFixed(4)),\r\n sizeBytes: cacheSizeBytes,\r\n entries: cacheEntries,\r\n lastUpdated: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Reset cache metrics\r\n * Used for testing and monitoring reset\r\n */\r\nexport function resetCacheMetrics(): void {\r\n cacheHits = 0;\r\n cacheMisses = 0;\r\n cacheSizeBytes = 0;\r\n cacheEntries = 0;\r\n console.log('Cache metrics reset');\r\n}\r\n\r\n/**\r\n * Check if cache is available\r\n * Used for graceful degradation checks\r\n */\r\nexport function isCacheAvailable(): boolean {\r\n return cacheAvailable;\r\n}\r\n\r\n/**\r\n * Reset cache availability flag\r\n * Used after Redis reconnection\r\n */\r\nexport function resetCacheAvailability(): void {\r\n cacheAvailable = true;\r\n console.log('Cache availability reset');\r\n}\r\n\r\n/**\r\n * Get cache configuration\r\n * Returns current cache settings\r\n */\r\nexport function getCacheConfig(): CacheConfig {\r\n return { ...DEFAULT_CONFIG };\r\n}\r\n\r\n/**\r\n * Update cache TTL for future entries\r\n * Does not affect existing cached entries\r\n *\r\n * @param ttlSeconds - New TTL in seconds\r\n */\r\nexport function updateCacheTTL(ttlSeconds: number): void {\r\n if (ttlSeconds < 60 || ttlSeconds > 86400) {\r\n throw new Error(\r\n `Invalid TTL: ${ttlSeconds}s. Must be between 60s (1 min) and 86400s (24 hours).`\r\n );\r\n }\r\n DEFAULT_CONFIG.ttlSeconds = ttlSeconds;\r\n console.log(`Cache TTL updated to ${ttlSeconds} seconds`);\r\n}\r\n\r\n/**\r\n * Get cache entry count from Redis\r\n * More accurate than tracked metric (accounts for evictions)\r\n */\r\nexport async function getCacheEntryCount(): Promise<number> {\r\n if (!cacheAvailable) {\r\n return 0;\r\n }\r\n\r\n try {\r\n const redis = getRedisPool();\r\n const pattern = `${DEFAULT_CONFIG.prefix}*`;\r\n const keys = await redis.keys(pattern);\r\n return keys.length;\r\n } catch (error) {\r\n const err = error as Error;\r\n console.error('Failed to get cache entry count:', err.message);\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Prewarm cache with common agent tasks\r\n * Used during system initialization\r\n *\r\n * @param entries - Array of [agentType, taskDescription, result, confidence] tuples\r\n */\r\nexport async function prewarmCache(\r\n entries: Array<[string, string, unknown, number]>\r\n): Promise<number> {\r\n let warmedCount = 0;\r\n\r\n for (const [agentType, taskDescription, result, confidence] of entries) {\r\n try {\r\n await setCachedResult(agentType, taskDescription, result, confidence);\r\n warmedCount++;\r\n } catch (error) {\r\n const err = error as Error;\r\n console.error(`Failed to prewarm cache entry: ${err.message}`);\r\n }\r\n }\r\n\r\n console.log(`Cache prewarmed with ${warmedCount} entries`);\r\n return warmedCount;\r\n}\r\n"],"names":["createHash","getRedisPool","DEFAULT_CONFIG","ttlSeconds","parseInt","process","env","CFN_CACHE_TTL_SECONDS","maxEntries","CFN_CACHE_MAX_ENTRIES","prefix","CFN_CACHE_PREFIX","cacheHits","cacheMisses","cacheSizeBytes","cacheEntries","cacheAvailable","generateTaskHash","taskDescription","update","trim","digest","buildCacheKey","agentType","taskHash","getCachedResult","redis","cacheKey","startTime","Date","now","cachedData","get","latency","console","warn","parsed","JSON","parse","expiresAt","del","error","err","message","setCachedResult","result","confidence","currentEntries","dbsize","getTime","cachedResult","cachedAt","toISOString","serialized","stringify","sizeBytes","Buffer","byteLength","set","invalidateCache","deletedCount","pattern","keys","length","log","getCacheMetrics","totalRequests","hitRate","hits","misses","parseFloat","toFixed","entries","lastUpdated","resetCacheMetrics","isCacheAvailable","resetCacheAvailability","getCacheConfig","updateCacheTTL","Error","getCacheEntryCount","prewarmCache","warmedCount"],"mappings":"AAAA;;;;;;;;;;;;;;;;;CAiBC,GAED,SAASA,UAAU,QAAQ,SAAS;AACpC,SAASC,YAAY,QAAQ,uBAAuB;AAoCpD;;CAEC,GACD,MAAMC,iBAA8B;IAClCC,YAAYC,SAASC,QAAQC,GAAG,CAACC,qBAAqB,IAAI,QAAQ;IAClEC,YAAYJ,SAASC,QAAQC,GAAG,CAACG,qBAAqB,IAAI,SAAS;IACnEC,QAAQL,QAAQC,GAAG,CAACK,gBAAgB,IAAI;AAC1C;AAEA;;CAEC,GACD,IAAIC,YAAY;AAChB,IAAIC,cAAc;AAClB,IAAIC,iBAAiB;AACrB,IAAIC,eAAe;AAEnB;;CAEC,GACD,IAAIC,iBAAiB;AAErB;;;CAGC,GACD,OAAO,SAASC,iBAAiBC,eAAuB;IACtD,OAAOlB,WAAW,UAAUmB,MAAM,CAACD,gBAAgBE,IAAI,IAAIC,MAAM,CAAC;AACpE;AAEA;;CAEC,GACD,SAASC,cAAcC,SAAiB,EAAEC,QAAgB;IACxD,OAAO,GAAGtB,eAAeQ,MAAM,GAAGa,UAAU,CAAC,EAAEC,UAAU;AAC3D;AAEA;;;;;;;CAOC,GACD,OAAO,eAAeC,gBACpBF,SAAiB,EACjBL,eAAuB;IAEvB,IAAI,CAACF,gBAAgB;QACnBH;QACA,OAAO;IACT;IAEA,IAAI;QACF,MAAMa,QAAQzB;QACd,MAAMuB,WAAWP,iBAAiBC;QAClC,MAAMS,WAAWL,cAAcC,WAAWC;QAE1C,MAAMI,YAAYC,KAAKC,GAAG;QAC1B,MAAMC,aAAa,MAAML,MAAMM,GAAG,CAACL;QACnC,MAAMM,UAAUJ,KAAKC,GAAG,KAAKF;QAE7B,IAAIK,UAAU,IAAI;YAChBC,QAAQC,IAAI,CAAC,CAAC,kBAAkB,EAAEF,QAAQ,sBAAsB,CAAC;QACnE;QAEA,IAAI,CAACF,YAAY;YACflB;YACA,OAAO;QACT;QAEA,MAAMuB,SAASC,KAAKC,KAAK,CAACP;QAE1B,sBAAsB;QACtB,MAAMQ,YAAY,IAAIV,KAAKO,OAAOG,SAAS;QAC3C,IAAIA,YAAY,IAAIV,QAAQ;YAC1B,kCAAkC;YAClC,MAAMH,MAAMc,GAAG,CAACb;YAChBd;YACA,OAAO;QACT;QAEAD;QACA,OAAOwB;IACT,EAAE,OAAOK,OAAO;QACd,MAAMC,MAAMD;QACZP,QAAQO,KAAK,CAAC,oBAAoBC,IAAIC,OAAO;QAC7C,uBAAuB;QACvB3B,iBAAiB;QACjBH;QACA,OAAO;IACT;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAe+B,gBACpBrB,SAAiB,EACjBL,eAAuB,EACvB2B,MAAe,EACfC,UAAkB;IAElB,IAAI,CAAC9B,gBAAgB;QACnB;IACF;IAEA,IAAI;QACF,MAAMU,QAAQzB;QACd,MAAMuB,WAAWP,iBAAiBC;QAClC,MAAMS,WAAWL,cAAcC,WAAWC;QAE1C,0BAA0B;QAC1B,MAAMuB,iBAAiB,MAAMrB,MAAMsB,MAAM;QACzC,IAAID,kBAAkB7C,eAAeM,UAAU,EAAE;YAC/C0B,QAAQC,IAAI,CACV,CAAC,uBAAuB,EAAEjC,eAAeM,UAAU,CAAC,kCAAkC,CAAC;QAEzF,yEAAyE;QAC3E;QAEA,MAAMsB,MAAM,IAAID;QAChB,MAAMU,YAAY,IAAIV,KAAKC,IAAImB,OAAO,KAAK/C,eAAeC,UAAU,GAAG;QAEvE,MAAM+C,eAA6B;YACjC3B;YACAC;YACAqB;YACAC;YACAK,UAAUrB,IAAIsB,WAAW;YACzBb,WAAWA,UAAUa,WAAW;QAClC;QAEA,MAAMC,aAAahB,KAAKiB,SAAS,CAACJ;QAClC,MAAMK,YAAYC,OAAOC,UAAU,CAACJ,YAAY;QAEhD,MAAMzB,YAAYC,KAAKC,GAAG;QAC1B,MAAMJ,MAAMgC,GAAG,CAAC/B,UAAU0B,YAAY,MAAMnD,eAAeC,UAAU;QACrE,MAAM8B,UAAUJ,KAAKC,GAAG,KAAKF;QAE7B,IAAIK,UAAU,IAAI;YAChBC,QAAQC,IAAI,CAAC,CAAC,kBAAkB,EAAEF,QAAQ,sBAAsB,CAAC;QACnE;QAEA,iBAAiB;QACjBnB,kBAAkByC;QAClBxC;IACF,EAAE,OAAO0B,OAAO;QACd,MAAMC,MAAMD;QACZP,QAAQO,KAAK,CAAC,oBAAoBC,IAAIC,OAAO;QAC7C,uBAAuB;QACvB3B,iBAAiB;IACnB;AACF;AAEA;;;;;;;CAOC,GACD,OAAO,eAAe2C,gBAAgBpC,SAAkB;IACtD,IAAI,CAACP,gBAAgB;QACnB,OAAO;IACT;IAEA,IAAI;QACF,MAAMU,QAAQzB;QACd,IAAI2D,eAAe;QAEnB,IAAIrC,WAAW;YACb,2CAA2C;YAC3C,MAAMsC,UAAU,GAAG3D,eAAeQ,MAAM,GAAGa,UAAU,EAAE,CAAC;YACxD,MAAMuC,OAAO,MAAMpC,MAAMoC,IAAI,CAACD;YAE9B,IAAIC,KAAKC,MAAM,GAAG,GAAG;gBACnBH,eAAe,MAAMlC,MAAMc,GAAG,IAAIsB;YACpC;QACF,OAAO;YACL,2BAA2B;YAC3B,MAAMD,UAAU,GAAG3D,eAAeQ,MAAM,CAAC,CAAC,CAAC;YAC3C,MAAMoD,OAAO,MAAMpC,MAAMoC,IAAI,CAACD;YAE9B,IAAIC,KAAKC,MAAM,GAAG,GAAG;gBACnBH,eAAe,MAAMlC,MAAMc,GAAG,IAAIsB;YACpC;QACF;QAEA,gBAAgB;QAChB,IAAI,CAACvC,WAAW;YACdX,YAAY;YACZC,cAAc;YACdC,iBAAiB;YACjBC,eAAe;QACjB;QAEAmB,QAAQ8B,GAAG,CAAC,CAAC,mBAAmB,EAAEJ,aAAa,gBAAgB,CAAC;QAChE,OAAOA;IACT,EAAE,OAAOnB,OAAO;QACd,MAAMC,MAAMD;QACZP,QAAQO,KAAK,CAAC,6BAA6BC,IAAIC,OAAO;QACtD3B,iBAAiB;QACjB,OAAO;IACT;AACF;AAEA;;;CAGC,GACD,OAAO,SAASiD;IACd,MAAMC,gBAAgBtD,YAAYC;IAClC,MAAMsD,UAAUD,gBAAgB,IAAItD,YAAYsD,gBAAgB;IAEhE,OAAO;QACLE,MAAMxD;QACNyD,QAAQxD;QACRqD;QACAC,SAASG,WAAWH,QAAQI,OAAO,CAAC;QACpChB,WAAWzC;QACX0D,SAASzD;QACT0D,aAAa,IAAI5C,OAAOuB,WAAW;IACrC;AACF;AAEA;;;CAGC,GACD,OAAO,SAASsB;IACd9D,YAAY;IACZC,cAAc;IACdC,iBAAiB;IACjBC,eAAe;IACfmB,QAAQ8B,GAAG,CAAC;AACd;AAEA;;;CAGC,GACD,OAAO,SAASW;IACd,OAAO3D;AACT;AAEA;;;CAGC,GACD,OAAO,SAAS4D;IACd5D,iBAAiB;IACjBkB,QAAQ8B,GAAG,CAAC;AACd;AAEA;;;CAGC,GACD,OAAO,SAASa;IACd,OAAO;QAAE,GAAG3E,cAAc;IAAC;AAC7B;AAEA;;;;;CAKC,GACD,OAAO,SAAS4E,eAAe3E,UAAkB;IAC/C,IAAIA,aAAa,MAAMA,aAAa,OAAO;QACzC,MAAM,IAAI4E,MACR,CAAC,aAAa,EAAE5E,WAAW,qDAAqD,CAAC;IAErF;IACAD,eAAeC,UAAU,GAAGA;IAC5B+B,QAAQ8B,GAAG,CAAC,CAAC,qBAAqB,EAAE7D,WAAW,QAAQ,CAAC;AAC1D;AAEA;;;CAGC,GACD,OAAO,eAAe6E;IACpB,IAAI,CAAChE,gBAAgB;QACnB,OAAO;IACT;IAEA,IAAI;QACF,MAAMU,QAAQzB;QACd,MAAM4D,UAAU,GAAG3D,eAAeQ,MAAM,CAAC,CAAC,CAAC;QAC3C,MAAMoD,OAAO,MAAMpC,MAAMoC,IAAI,CAACD;QAC9B,OAAOC,KAAKC,MAAM;IACpB,EAAE,OAAOtB,OAAO;QACd,MAAMC,MAAMD;QACZP,QAAQO,KAAK,CAAC,oCAAoCC,IAAIC,OAAO;QAC7D,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAesC,aACpBT,OAAiD;IAEjD,IAAIU,cAAc;IAElB,KAAK,MAAM,CAAC3D,WAAWL,iBAAiB2B,QAAQC,WAAW,IAAI0B,QAAS;QACtE,IAAI;YACF,MAAM5B,gBAAgBrB,WAAWL,iBAAiB2B,QAAQC;YAC1DoC;QACF,EAAE,OAAOzC,OAAO;YACd,MAAMC,MAAMD;YACZP,QAAQO,KAAK,CAAC,CAAC,+BAA+B,EAAEC,IAAIC,OAAO,EAAE;QAC/D;IACF;IAEAT,QAAQ8B,GAAG,CAAC,CAAC,qBAAqB,EAAEkB,YAAY,QAAQ,CAAC;IACzD,OAAOA;AACT"}
|
|
@@ -9,7 +9,8 @@ let MCPAuthMiddleware = class MCPAuthMiddleware {
|
|
|
9
9
|
constructor(options = {}){
|
|
10
10
|
this.redis = null;
|
|
11
11
|
this.options = {
|
|
12
|
-
|
|
12
|
+
// FIX: Default to 'localhost' for host execution, Docker deployments should set CFN_REDIS_HOST explicitly
|
|
13
|
+
redisUrl: options.redisUrl || process.env.CFN_REDIS_URL || process.env.MCP_REDIS_URL || `redis://${process.env.CFN_REDIS_HOST || 'localhost'}:${process.env.CFN_REDIS_PORT || 6379}`,
|
|
13
14
|
tokenExpiry: options.tokenExpiry || '24h',
|
|
14
15
|
authRequired: options.authRequired !== false,
|
|
15
16
|
rateLimitWindow: options.rateLimitWindow || 60,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/auth-middleware.js"],"sourcesContent":["/**\r\n * MCP Authentication Middleware\r\n * Token-based authentication and authorization for MCP containers\r\n */\r\n\r\nconst crypto = require('crypto');\r\nconst Redis = require('redis');\r\nconst fs = require('fs').promises;\r\nconst path = require('path');\r\n\r\nclass MCPAuthMiddleware {\r\n constructor(options = {}) {\r\n this.redis = null;\r\n this.options = {\r\n redisUrl: options.redisUrl || process.env.CFN_REDIS_URL || process.env.MCP_REDIS_URL || `redis://${process.env.CFN_REDIS_HOST || 'cfn-redis'}:${process.env.CFN_REDIS_PORT || 6379}`,\r\n tokenExpiry: options.tokenExpiry || '24h',\r\n authRequired: options.authRequired !== false,\r\n rateLimitWindow: options.rateLimitWindow || 60, // seconds\r\n rateLimitMax: options.rateLimitMax || 100,\r\n agentConfigPath: options.agentConfigPath || './config/agent-whitelist.json',\r\n skillConfigPath: options.skillConfigPath || './config/skill-requirements.json',\r\n ...options\r\n };\r\n\r\n this.agentWhitelist = new Map();\r\n this.skillRequirements = new Map();\r\n this.rateLimitCache = new Map();\r\n }\r\n\r\n /**\r\n * Initialize authentication middleware\r\n */\r\n async initialize() {\r\n try {\r\n // Connect to Redis\r\n this.redis = Redis.createClient({ url: this.options.redisUrl });\r\n await this.redis.connect();\r\n\r\n console.log('[MCPAuth] Connected to Redis for authentication');\r\n\r\n // Load configuration\r\n await this.loadAgentWhitelist();\r\n await this.loadSkillRequirements();\r\n\r\n // Start cleanup interval\r\n this.startCleanupInterval();\r\n\r\n console.log('[MCPAuth] Authentication middleware initialized successfully');\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to initialize:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Load agent whitelist from configuration\r\n */\r\n async loadAgentWhitelist() {\r\n try {\r\n const configPath = path.resolve(this.options.agentConfigPath);\r\n const config = await fs.readFile(configPath, 'utf8');\r\n const whitelist = JSON.parse(config);\r\n\r\n this.agentWhitelist.clear();\r\n for (const agent of whitelist.agents) {\r\n this.agentWhitelist.set(agent.type, agent);\r\n }\r\n\r\n console.log(`[MCPAuth] Loaded ${this.agentWhitelist.size} agent configurations`);\r\n } catch (error) {\r\n console.warn('[MCPAuth] Failed to load agent whitelist:', error.message);\r\n // Default to empty whitelist (deny all)\r\n this.agentWhitelist.clear();\r\n }\r\n }\r\n\r\n /**\r\n * Load skill requirements from configuration\r\n */\r\n async loadSkillRequirements() {\r\n try {\r\n const configPath = path.resolve(this.options.skillConfigPath);\r\n const config = await fs.readFile(configPath, 'utf8');\r\n const requirements = JSON.parse(config);\r\n\r\n this.skillRequirements.clear();\r\n for (const [tool, req] of Object.entries(requirements.tools)) {\r\n this.skillRequirements.set(tool, req);\r\n }\r\n\r\n console.log(`[MCPAuth] Loaded ${this.skillRequirements.size} tool skill requirements`);\r\n } catch (error) {\r\n console.warn('[MCPAuth] Failed to load skill requirements:', error.message);\r\n this.skillRequirements.clear();\r\n }\r\n }\r\n\r\n /**\r\n * Authenticate agent request\r\n */\r\n async authenticateRequest(request, response, next) {\r\n try {\r\n // Skip authentication if not required\r\n if (!this.options.authRequired) {\r\n return next();\r\n }\r\n\r\n // Extract authentication headers\r\n const agentToken = request.headers['x-agent-token'];\r\n const agentType = request.headers['x-agent-type'];\r\n const toolName = request.body?.name || request.params?.toolName;\r\n\r\n // Validate required headers\r\n if (!agentToken || !agentType) {\r\n return this.sendErrorResponse(response, 401, 'Missing authentication headers', {\r\n required: ['x-agent-token', 'x-agent-type']\r\n });\r\n }\r\n\r\n // Validate token in Redis\r\n const tokenData = await this.validateToken(agentToken, agentType);\r\n if (!tokenData) {\r\n return this.sendErrorResponse(response, 401, 'Invalid or expired token', {\r\n agentType,\r\n tokenValid: false\r\n });\r\n }\r\n\r\n // Validate agent type against whitelist\r\n if (!this.isAgentAuthorized(agentType)) {\r\n return this.sendErrorResponse(response, 403, 'Agent type not authorized', {\r\n agentType,\r\n allowedTypes: Array.from(this.agentWhitelist.keys())\r\n });\r\n }\r\n\r\n // Validate skill-based tool access\r\n if (toolName && !this.authorizeToolAccess(agentType, toolName)) {\r\n const toolRequirements = this.skillRequirements.get(toolName);\r\n return this.sendErrorResponse(response, 403, 'Insufficient skills for tool access', {\r\n agentType,\r\n toolName,\r\n requiredSkills: toolRequirements?.requiredSkills || [],\r\n agentSkills: tokenData.skills || []\r\n });\r\n }\r\n\r\n // Check rate limits\r\n if (!this.checkRateLimit(agentType, agentToken)) {\r\n return this.sendErrorResponse(response, 429, 'Rate limit exceeded', {\r\n agentType,\r\n window: this.options.rateLimitWindow,\r\n maxRequests: this.options.rateLimitMax\r\n });\r\n }\r\n\r\n // Add agent context to request\r\n request.agentContext = {\r\n agentType,\r\n agentToken,\r\n skills: tokenData.skills || [],\r\n authenticated: true,\r\n timestamp: Date.now()\r\n };\r\n\r\n // Update last activity\r\n await this.updateAgentActivity(agentToken, agentType);\r\n\r\n return next();\r\n\r\n } catch (error) {\r\n console.error('[MCPAuth] Authentication error:', error);\r\n return this.sendErrorResponse(response, 500, 'Authentication error', {\r\n error: error.message\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Validate token against Redis\r\n */\r\n async validateToken(token, agentType) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n const tokenData = await this.redis.get(key);\r\n\r\n if (!tokenData) {\r\n return null;\r\n }\r\n\r\n const data = JSON.parse(tokenData);\r\n\r\n // Check expiration\r\n if (Date.now() > data.expiresAt) {\r\n await this.redis.del(key);\r\n return null;\r\n }\r\n\r\n return data;\r\n } catch (error) {\r\n console.error('[MCPAuth] Token validation error:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Check if agent type is authorized\r\n */\r\n isAgentAuthorized(agentType) {\r\n return this.agentWhitelist.has(agentType);\r\n }\r\n\r\n /**\r\n * Authorize tool access based on skills\r\n */\r\n authorizeToolAccess(agentType, toolName) {\r\n const toolRequirements = this.skillRequirements.get(toolName);\r\n if (!toolRequirements) {\r\n // No skill requirements defined - allow access\r\n return true;\r\n }\r\n\r\n const agentConfig = this.agentWhitelist.get(agentType);\r\n if (!agentConfig) {\r\n return false;\r\n }\r\n\r\n const requiredSkills = toolRequirements.requiredSkills || [];\r\n const agentSkills = agentConfig.skills || [];\r\n\r\n // Check if agent has all required skills\r\n return requiredSkills.every(skill => agentSkills.includes(skill));\r\n }\r\n\r\n /**\r\n * Check rate limits for agent\r\n */\r\n checkRateLimit(agentType, token) {\r\n const now = Date.now();\r\n const windowStart = now - (this.options.rateLimitWindow * 1000);\r\n const key = `${agentType}:${token}`;\r\n\r\n if (!this.rateLimitCache.has(key)) {\r\n this.rateLimitCache.set(key, []);\r\n }\r\n\r\n const requests = this.rateLimitCache.get(key);\r\n\r\n // Remove old requests outside window\r\n const validRequests = requests.filter(timestamp => timestamp > windowStart);\r\n this.rateLimitCache.set(key, validRequests);\r\n\r\n // Check if under limit\r\n if (validRequests.length >= this.options.rateLimitMax) {\r\n return false;\r\n }\r\n\r\n // Add current request\r\n validRequests.push(now);\r\n return true;\r\n }\r\n\r\n /**\r\n * Update agent activity in Redis\r\n */\r\n async updateAgentActivity(token, agentType) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n const activity = {\r\n lastActivity: Date.now(),\r\n requestCount: 1\r\n };\r\n\r\n await this.redis.hIncrBy(key, 'requestCount', 1);\r\n await this.redis.hSet(key, 'lastActivity', Date.now().toString());\r\n await this.redis.expire(key, this.parseExpiry(this.options.tokenExpiry));\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to update activity:', error);\r\n }\r\n }\r\n\r\n /**\r\n * Generate agent token\r\n */\r\n generateAgentToken(agentType, skills = [], expiresIn = null) {\r\n const token = crypto.randomBytes(32).toString('hex');\r\n const expiresAt = Date.now() + (this.parseExpiry(expiresIn || this.options.tokenExpiry) * 1000);\r\n\r\n return {\r\n token,\r\n agentType,\r\n skills,\r\n expiresAt,\r\n createdAt: Date.now()\r\n };\r\n }\r\n\r\n /**\r\n * Register agent token in Redis\r\n */\r\n async registerAgentToken(tokenData) {\r\n try {\r\n const key = `mcp:agent:${tokenData.agentType}:${tokenData.token}`;\r\n const value = JSON.stringify(tokenData);\r\n\r\n await this.redis.setEx(key, this.parseExpiry(this.options.tokenExpiry), value);\r\n\r\n console.log(`[MCPAuth] Registered token for agent: ${tokenData.agentType}`);\r\n return tokenData;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to register token:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Revoke agent token\r\n */\r\n async revokeAgentToken(agentType, token) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n await this.redis.del(key);\r\n\r\n console.log(`[MCPAuth] Revoked token for agent: ${agentType}`);\r\n return true;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to revoke token:', error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Send error response\r\n */\r\n sendErrorResponse(response, statusCode, message, details = null) {\r\n const errorResponse = {\r\n jsonrpc: '2.0',\r\n error: {\r\n code: statusCode === 401 ? -32001 : statusCode === 403 ? -32002 : -32003,\r\n message,\r\n ...(details && { details })\r\n },\r\n id: response.body?.id || null\r\n };\r\n\r\n response.writeHead(statusCode, { 'Content-Type': 'application/json' });\r\n response.end(JSON.stringify(errorResponse));\r\n }\r\n\r\n /**\r\n * Parse expiry string to seconds\r\n */\r\n parseExpiry(expiry) {\r\n if (typeof expiry === 'number') {\r\n return expiry;\r\n }\r\n\r\n const match = expiry.match(/^(\\d+)([smhd])$/);\r\n if (!match) {\r\n return 3600; // Default to 1 hour\r\n }\r\n\r\n const value = parseInt(match[1]);\r\n const unit = match[2];\r\n const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };\r\n\r\n return value * (multipliers[unit] || 3600);\r\n }\r\n\r\n /**\r\n * Start cleanup interval for rate limit cache\r\n */\r\n startCleanupInterval() {\r\n setInterval(() => {\r\n const now = Date.now();\r\n const windowStart = now - (this.options.rateLimitWindow * 1000);\r\n\r\n for (const [key, requests] of this.rateLimitCache.entries()) {\r\n const validRequests = requests.filter(timestamp => timestamp > windowStart);\r\n if (validRequests.length === 0) {\r\n this.rateLimitCache.delete(key);\r\n } else {\r\n this.rateLimitCache.set(key, validRequests);\r\n }\r\n }\r\n }, 60000); // Clean up every minute\r\n }\r\n\r\n /**\r\n * Get authentication statistics\r\n */\r\n async getStats() {\r\n try {\r\n const stats = {\r\n registeredAgents: this.agentWhitelist.size,\r\n configuredTools: this.skillRequirements.size,\r\n rateLimitedClients: this.rateLimitCache.size,\r\n redisConnected: this.redis?.isOpen || false\r\n };\r\n\r\n // Get active tokens count\r\n if (this.redis?.isOpen) {\r\n const keys = await this.redis.keys('mcp:agent:*');\r\n stats.activeTokens = keys.length;\r\n }\r\n\r\n return stats;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to get stats:', error);\r\n return { error: error.message };\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown authentication middleware\r\n */\r\n async shutdown() {\r\n try {\r\n if (this.redis) {\r\n await this.redis.quit();\r\n this.redis = null;\r\n }\r\n console.log('[MCPAuth] Authentication middleware shutdown complete');\r\n } catch (error) {\r\n console.error('[MCPAuth] Shutdown error:', error);\r\n }\r\n }\r\n}\r\n\r\nmodule.exports = MCPAuthMiddleware;"],"names":["crypto","require","Redis","fs","promises","path","MCPAuthMiddleware","options","redis","redisUrl","process","env","CFN_REDIS_URL","MCP_REDIS_URL","CFN_REDIS_HOST","CFN_REDIS_PORT","tokenExpiry","authRequired","rateLimitWindow","rateLimitMax","agentConfigPath","skillConfigPath","agentWhitelist","Map","skillRequirements","rateLimitCache","initialize","createClient","url","connect","console","log","loadAgentWhitelist","loadSkillRequirements","startCleanupInterval","error","configPath","resolve","config","readFile","whitelist","JSON","parse","clear","agent","agents","set","type","size","warn","message","requirements","tool","req","Object","entries","tools","authenticateRequest","request","response","next","agentToken","headers","agentType","toolName","body","name","params","sendErrorResponse","required","tokenData","validateToken","tokenValid","isAgentAuthorized","allowedTypes","Array","from","keys","authorizeToolAccess","toolRequirements","get","requiredSkills","agentSkills","skills","checkRateLimit","window","maxRequests","agentContext","authenticated","timestamp","Date","now","updateAgentActivity","token","key","data","expiresAt","del","has","agentConfig","every","skill","includes","windowStart","requests","validRequests","filter","length","push","activity","lastActivity","requestCount","hIncrBy","hSet","toString","expire","parseExpiry","generateAgentToken","expiresIn","randomBytes","createdAt","registerAgentToken","value","stringify","setEx","revokeAgentToken","statusCode","details","errorResponse","jsonrpc","code","id","writeHead","end","expiry","match","parseInt","unit","multipliers","s","m","h","d","setInterval","delete","getStats","stats","registeredAgents","configuredTools","rateLimitedClients","redisConnected","isOpen","activeTokens","shutdown","quit","module","exports"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/auth-middleware.js"],"sourcesContent":["/**\r\n * MCP Authentication Middleware\r\n * Token-based authentication and authorization for MCP containers\r\n */\r\n\r\nconst crypto = require('crypto');\r\nconst Redis = require('redis');\r\nconst fs = require('fs').promises;\r\nconst path = require('path');\r\n\r\nclass MCPAuthMiddleware {\r\n constructor(options = {}) {\r\n this.redis = null;\r\n this.options = {\r\n // FIX: Default to 'localhost' for host execution, Docker deployments should set CFN_REDIS_HOST explicitly\r\n redisUrl: options.redisUrl || process.env.CFN_REDIS_URL || process.env.MCP_REDIS_URL || `redis://${process.env.CFN_REDIS_HOST || 'localhost'}:${process.env.CFN_REDIS_PORT || 6379}`,\r\n tokenExpiry: options.tokenExpiry || '24h',\r\n authRequired: options.authRequired !== false,\r\n rateLimitWindow: options.rateLimitWindow || 60, // seconds\r\n rateLimitMax: options.rateLimitMax || 100,\r\n agentConfigPath: options.agentConfigPath || './config/agent-whitelist.json',\r\n skillConfigPath: options.skillConfigPath || './config/skill-requirements.json',\r\n ...options\r\n };\r\n\r\n this.agentWhitelist = new Map();\r\n this.skillRequirements = new Map();\r\n this.rateLimitCache = new Map();\r\n }\r\n\r\n /**\r\n * Initialize authentication middleware\r\n */\r\n async initialize() {\r\n try {\r\n // Connect to Redis\r\n this.redis = Redis.createClient({ url: this.options.redisUrl });\r\n await this.redis.connect();\r\n\r\n console.log('[MCPAuth] Connected to Redis for authentication');\r\n\r\n // Load configuration\r\n await this.loadAgentWhitelist();\r\n await this.loadSkillRequirements();\r\n\r\n // Start cleanup interval\r\n this.startCleanupInterval();\r\n\r\n console.log('[MCPAuth] Authentication middleware initialized successfully');\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to initialize:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Load agent whitelist from configuration\r\n */\r\n async loadAgentWhitelist() {\r\n try {\r\n const configPath = path.resolve(this.options.agentConfigPath);\r\n const config = await fs.readFile(configPath, 'utf8');\r\n const whitelist = JSON.parse(config);\r\n\r\n this.agentWhitelist.clear();\r\n for (const agent of whitelist.agents) {\r\n this.agentWhitelist.set(agent.type, agent);\r\n }\r\n\r\n console.log(`[MCPAuth] Loaded ${this.agentWhitelist.size} agent configurations`);\r\n } catch (error) {\r\n console.warn('[MCPAuth] Failed to load agent whitelist:', error.message);\r\n // Default to empty whitelist (deny all)\r\n this.agentWhitelist.clear();\r\n }\r\n }\r\n\r\n /**\r\n * Load skill requirements from configuration\r\n */\r\n async loadSkillRequirements() {\r\n try {\r\n const configPath = path.resolve(this.options.skillConfigPath);\r\n const config = await fs.readFile(configPath, 'utf8');\r\n const requirements = JSON.parse(config);\r\n\r\n this.skillRequirements.clear();\r\n for (const [tool, req] of Object.entries(requirements.tools)) {\r\n this.skillRequirements.set(tool, req);\r\n }\r\n\r\n console.log(`[MCPAuth] Loaded ${this.skillRequirements.size} tool skill requirements`);\r\n } catch (error) {\r\n console.warn('[MCPAuth] Failed to load skill requirements:', error.message);\r\n this.skillRequirements.clear();\r\n }\r\n }\r\n\r\n /**\r\n * Authenticate agent request\r\n */\r\n async authenticateRequest(request, response, next) {\r\n try {\r\n // Skip authentication if not required\r\n if (!this.options.authRequired) {\r\n return next();\r\n }\r\n\r\n // Extract authentication headers\r\n const agentToken = request.headers['x-agent-token'];\r\n const agentType = request.headers['x-agent-type'];\r\n const toolName = request.body?.name || request.params?.toolName;\r\n\r\n // Validate required headers\r\n if (!agentToken || !agentType) {\r\n return this.sendErrorResponse(response, 401, 'Missing authentication headers', {\r\n required: ['x-agent-token', 'x-agent-type']\r\n });\r\n }\r\n\r\n // Validate token in Redis\r\n const tokenData = await this.validateToken(agentToken, agentType);\r\n if (!tokenData) {\r\n return this.sendErrorResponse(response, 401, 'Invalid or expired token', {\r\n agentType,\r\n tokenValid: false\r\n });\r\n }\r\n\r\n // Validate agent type against whitelist\r\n if (!this.isAgentAuthorized(agentType)) {\r\n return this.sendErrorResponse(response, 403, 'Agent type not authorized', {\r\n agentType,\r\n allowedTypes: Array.from(this.agentWhitelist.keys())\r\n });\r\n }\r\n\r\n // Validate skill-based tool access\r\n if (toolName && !this.authorizeToolAccess(agentType, toolName)) {\r\n const toolRequirements = this.skillRequirements.get(toolName);\r\n return this.sendErrorResponse(response, 403, 'Insufficient skills for tool access', {\r\n agentType,\r\n toolName,\r\n requiredSkills: toolRequirements?.requiredSkills || [],\r\n agentSkills: tokenData.skills || []\r\n });\r\n }\r\n\r\n // Check rate limits\r\n if (!this.checkRateLimit(agentType, agentToken)) {\r\n return this.sendErrorResponse(response, 429, 'Rate limit exceeded', {\r\n agentType,\r\n window: this.options.rateLimitWindow,\r\n maxRequests: this.options.rateLimitMax\r\n });\r\n }\r\n\r\n // Add agent context to request\r\n request.agentContext = {\r\n agentType,\r\n agentToken,\r\n skills: tokenData.skills || [],\r\n authenticated: true,\r\n timestamp: Date.now()\r\n };\r\n\r\n // Update last activity\r\n await this.updateAgentActivity(agentToken, agentType);\r\n\r\n return next();\r\n\r\n } catch (error) {\r\n console.error('[MCPAuth] Authentication error:', error);\r\n return this.sendErrorResponse(response, 500, 'Authentication error', {\r\n error: error.message\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Validate token against Redis\r\n */\r\n async validateToken(token, agentType) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n const tokenData = await this.redis.get(key);\r\n\r\n if (!tokenData) {\r\n return null;\r\n }\r\n\r\n const data = JSON.parse(tokenData);\r\n\r\n // Check expiration\r\n if (Date.now() > data.expiresAt) {\r\n await this.redis.del(key);\r\n return null;\r\n }\r\n\r\n return data;\r\n } catch (error) {\r\n console.error('[MCPAuth] Token validation error:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Check if agent type is authorized\r\n */\r\n isAgentAuthorized(agentType) {\r\n return this.agentWhitelist.has(agentType);\r\n }\r\n\r\n /**\r\n * Authorize tool access based on skills\r\n */\r\n authorizeToolAccess(agentType, toolName) {\r\n const toolRequirements = this.skillRequirements.get(toolName);\r\n if (!toolRequirements) {\r\n // No skill requirements defined - allow access\r\n return true;\r\n }\r\n\r\n const agentConfig = this.agentWhitelist.get(agentType);\r\n if (!agentConfig) {\r\n return false;\r\n }\r\n\r\n const requiredSkills = toolRequirements.requiredSkills || [];\r\n const agentSkills = agentConfig.skills || [];\r\n\r\n // Check if agent has all required skills\r\n return requiredSkills.every(skill => agentSkills.includes(skill));\r\n }\r\n\r\n /**\r\n * Check rate limits for agent\r\n */\r\n checkRateLimit(agentType, token) {\r\n const now = Date.now();\r\n const windowStart = now - (this.options.rateLimitWindow * 1000);\r\n const key = `${agentType}:${token}`;\r\n\r\n if (!this.rateLimitCache.has(key)) {\r\n this.rateLimitCache.set(key, []);\r\n }\r\n\r\n const requests = this.rateLimitCache.get(key);\r\n\r\n // Remove old requests outside window\r\n const validRequests = requests.filter(timestamp => timestamp > windowStart);\r\n this.rateLimitCache.set(key, validRequests);\r\n\r\n // Check if under limit\r\n if (validRequests.length >= this.options.rateLimitMax) {\r\n return false;\r\n }\r\n\r\n // Add current request\r\n validRequests.push(now);\r\n return true;\r\n }\r\n\r\n /**\r\n * Update agent activity in Redis\r\n */\r\n async updateAgentActivity(token, agentType) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n const activity = {\r\n lastActivity: Date.now(),\r\n requestCount: 1\r\n };\r\n\r\n await this.redis.hIncrBy(key, 'requestCount', 1);\r\n await this.redis.hSet(key, 'lastActivity', Date.now().toString());\r\n await this.redis.expire(key, this.parseExpiry(this.options.tokenExpiry));\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to update activity:', error);\r\n }\r\n }\r\n\r\n /**\r\n * Generate agent token\r\n */\r\n generateAgentToken(agentType, skills = [], expiresIn = null) {\r\n const token = crypto.randomBytes(32).toString('hex');\r\n const expiresAt = Date.now() + (this.parseExpiry(expiresIn || this.options.tokenExpiry) * 1000);\r\n\r\n return {\r\n token,\r\n agentType,\r\n skills,\r\n expiresAt,\r\n createdAt: Date.now()\r\n };\r\n }\r\n\r\n /**\r\n * Register agent token in Redis\r\n */\r\n async registerAgentToken(tokenData) {\r\n try {\r\n const key = `mcp:agent:${tokenData.agentType}:${tokenData.token}`;\r\n const value = JSON.stringify(tokenData);\r\n\r\n await this.redis.setEx(key, this.parseExpiry(this.options.tokenExpiry), value);\r\n\r\n console.log(`[MCPAuth] Registered token for agent: ${tokenData.agentType}`);\r\n return tokenData;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to register token:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Revoke agent token\r\n */\r\n async revokeAgentToken(agentType, token) {\r\n try {\r\n const key = `mcp:agent:${agentType}:${token}`;\r\n await this.redis.del(key);\r\n\r\n console.log(`[MCPAuth] Revoked token for agent: ${agentType}`);\r\n return true;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to revoke token:', error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Send error response\r\n */\r\n sendErrorResponse(response, statusCode, message, details = null) {\r\n const errorResponse = {\r\n jsonrpc: '2.0',\r\n error: {\r\n code: statusCode === 401 ? -32001 : statusCode === 403 ? -32002 : -32003,\r\n message,\r\n ...(details && { details })\r\n },\r\n id: response.body?.id || null\r\n };\r\n\r\n response.writeHead(statusCode, { 'Content-Type': 'application/json' });\r\n response.end(JSON.stringify(errorResponse));\r\n }\r\n\r\n /**\r\n * Parse expiry string to seconds\r\n */\r\n parseExpiry(expiry) {\r\n if (typeof expiry === 'number') {\r\n return expiry;\r\n }\r\n\r\n const match = expiry.match(/^(\\d+)([smhd])$/);\r\n if (!match) {\r\n return 3600; // Default to 1 hour\r\n }\r\n\r\n const value = parseInt(match[1]);\r\n const unit = match[2];\r\n const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };\r\n\r\n return value * (multipliers[unit] || 3600);\r\n }\r\n\r\n /**\r\n * Start cleanup interval for rate limit cache\r\n */\r\n startCleanupInterval() {\r\n setInterval(() => {\r\n const now = Date.now();\r\n const windowStart = now - (this.options.rateLimitWindow * 1000);\r\n\r\n for (const [key, requests] of this.rateLimitCache.entries()) {\r\n const validRequests = requests.filter(timestamp => timestamp > windowStart);\r\n if (validRequests.length === 0) {\r\n this.rateLimitCache.delete(key);\r\n } else {\r\n this.rateLimitCache.set(key, validRequests);\r\n }\r\n }\r\n }, 60000); // Clean up every minute\r\n }\r\n\r\n /**\r\n * Get authentication statistics\r\n */\r\n async getStats() {\r\n try {\r\n const stats = {\r\n registeredAgents: this.agentWhitelist.size,\r\n configuredTools: this.skillRequirements.size,\r\n rateLimitedClients: this.rateLimitCache.size,\r\n redisConnected: this.redis?.isOpen || false\r\n };\r\n\r\n // Get active tokens count\r\n if (this.redis?.isOpen) {\r\n const keys = await this.redis.keys('mcp:agent:*');\r\n stats.activeTokens = keys.length;\r\n }\r\n\r\n return stats;\r\n } catch (error) {\r\n console.error('[MCPAuth] Failed to get stats:', error);\r\n return { error: error.message };\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown authentication middleware\r\n */\r\n async shutdown() {\r\n try {\r\n if (this.redis) {\r\n await this.redis.quit();\r\n this.redis = null;\r\n }\r\n console.log('[MCPAuth] Authentication middleware shutdown complete');\r\n } catch (error) {\r\n console.error('[MCPAuth] Shutdown error:', error);\r\n }\r\n }\r\n}\r\n\r\nmodule.exports = MCPAuthMiddleware;"],"names":["crypto","require","Redis","fs","promises","path","MCPAuthMiddleware","options","redis","redisUrl","process","env","CFN_REDIS_URL","MCP_REDIS_URL","CFN_REDIS_HOST","CFN_REDIS_PORT","tokenExpiry","authRequired","rateLimitWindow","rateLimitMax","agentConfigPath","skillConfigPath","agentWhitelist","Map","skillRequirements","rateLimitCache","initialize","createClient","url","connect","console","log","loadAgentWhitelist","loadSkillRequirements","startCleanupInterval","error","configPath","resolve","config","readFile","whitelist","JSON","parse","clear","agent","agents","set","type","size","warn","message","requirements","tool","req","Object","entries","tools","authenticateRequest","request","response","next","agentToken","headers","agentType","toolName","body","name","params","sendErrorResponse","required","tokenData","validateToken","tokenValid","isAgentAuthorized","allowedTypes","Array","from","keys","authorizeToolAccess","toolRequirements","get","requiredSkills","agentSkills","skills","checkRateLimit","window","maxRequests","agentContext","authenticated","timestamp","Date","now","updateAgentActivity","token","key","data","expiresAt","del","has","agentConfig","every","skill","includes","windowStart","requests","validRequests","filter","length","push","activity","lastActivity","requestCount","hIncrBy","hSet","toString","expire","parseExpiry","generateAgentToken","expiresIn","randomBytes","createdAt","registerAgentToken","value","stringify","setEx","revokeAgentToken","statusCode","details","errorResponse","jsonrpc","code","id","writeHead","end","expiry","match","parseInt","unit","multipliers","s","m","h","d","setInterval","delete","getStats","stats","registeredAgents","configuredTools","rateLimitedClients","redisConnected","isOpen","activeTokens","shutdown","quit","module","exports"],"mappings":"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"}
|
|
@@ -16,7 +16,8 @@ let AuthenticatedPlaywrightMCPServer = class AuthenticatedPlaywrightMCPServer {
|
|
|
16
16
|
});
|
|
17
17
|
// Initialize authentication middleware
|
|
18
18
|
this.auth = new MCPAuthMiddleware({
|
|
19
|
-
|
|
19
|
+
// FIX: Default to 'localhost' for host execution, Docker deployments should set CFN_REDIS_HOST explicitly
|
|
20
|
+
redisUrl: options.redisUrl || process.env.CFN_REDIS_URL || process.env.MCP_REDIS_URL || `redis://${process.env.CFN_REDIS_HOST || 'localhost'}:${process.env.CFN_REDIS_PORT || 6379}`,
|
|
20
21
|
authRequired: options.authRequired !== false && process.env.MCP_AUTH_REQUIRED !== 'false',
|
|
21
22
|
agentConfigPath: options.agentConfigPath || process.env.MCP_AGENT_CONFIG || './config/agent-whitelist.json',
|
|
22
23
|
skillConfigPath: options.skillConfigPath || process.env.MCP_SKILL_CONFIG || './config/skill-requirements.json',
|