infrawise 0.5.0 → 0.7.0

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.
@@ -1,22 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.clearCache = exports.readCache = exports.writeCache = exports.formatError = exports.ConfigError = exports.RepositoryScanError = exports.PostgresConnectionError = exports.DynamoDBError = exports.AWSConnectionError = exports.InfrawiseError = exports.logger = exports.ConfigValidationError = exports.InfrawiseConfigSchema = exports.generateDefaultConfig = exports.loadConfig = void 0;
4
- var config_1 = require("./config");
5
- Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_1.loadConfig; } });
6
- Object.defineProperty(exports, "generateDefaultConfig", { enumerable: true, get: function () { return config_1.generateDefaultConfig; } });
7
- Object.defineProperty(exports, "InfrawiseConfigSchema", { enumerable: true, get: function () { return config_1.InfrawiseConfigSchema; } });
8
- Object.defineProperty(exports, "ConfigValidationError", { enumerable: true, get: function () { return config_1.ConfigError; } });
9
- var logger_1 = require("./logger");
10
- Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
11
- var errors_1 = require("./errors");
12
- Object.defineProperty(exports, "InfrawiseError", { enumerable: true, get: function () { return errors_1.InfrawiseError; } });
13
- Object.defineProperty(exports, "AWSConnectionError", { enumerable: true, get: function () { return errors_1.AWSConnectionError; } });
14
- Object.defineProperty(exports, "DynamoDBError", { enumerable: true, get: function () { return errors_1.DynamoDBError; } });
15
- Object.defineProperty(exports, "PostgresConnectionError", { enumerable: true, get: function () { return errors_1.PostgresConnectionError; } });
16
- Object.defineProperty(exports, "RepositoryScanError", { enumerable: true, get: function () { return errors_1.RepositoryScanError; } });
17
- Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return errors_1.ConfigError; } });
18
- Object.defineProperty(exports, "formatError", { enumerable: true, get: function () { return errors_1.formatError; } });
19
- var cache_1 = require("./cache");
20
- Object.defineProperty(exports, "writeCache", { enumerable: true, get: function () { return cache_1.writeCache; } });
21
- Object.defineProperty(exports, "readCache", { enumerable: true, get: function () { return cache_1.readCache; } });
22
- Object.defineProperty(exports, "clearCache", { enumerable: true, get: function () { return cache_1.clearCache; } });
1
+ export { loadConfig, generateDefaultConfig, InfrawiseConfigSchema, ConfigError as ConfigValidationError } from './config.js';
2
+ export { logger } from './logger.js';
3
+ export { InfrawiseError, AWSConnectionError, DynamoDBError, PostgresConnectionError, RepositoryScanError, ConfigError, formatError, } from './errors.js';
4
+ export { writeCache, readCache, clearCache } from './cache.js';
@@ -1,14 +1,8 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.logger = void 0;
7
- const pino_1 = __importDefault(require("pino"));
1
+ import pino from 'pino';
8
2
  function createLogger() {
9
3
  const isDevelopment = process.env.NODE_ENV !== 'production';
10
4
  if (isDevelopment) {
11
- return (0, pino_1.default)({
5
+ return pino({
12
6
  level: process.env.LOG_LEVEL ?? 'info',
13
7
  transport: {
14
8
  target: 'pino-pretty',
@@ -21,8 +15,8 @@ function createLogger() {
21
15
  },
22
16
  });
23
17
  }
24
- return (0, pino_1.default)({
18
+ return pino({
25
19
  level: process.env.LOG_LEVEL ?? 'info',
26
20
  });
27
21
  }
28
- exports.logger = createLogger();
22
+ export const logger = createLogger();
@@ -1,21 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildGraph = buildGraph;
4
- exports.getTableNodes = getTableNodes;
5
- exports.getFunctionNodes = getFunctionNodes;
6
- exports.getIndexNodes = getIndexNodes;
7
- exports.getQueueNodes = getQueueNodes;
8
- exports.getTopicNodes = getTopicNodes;
9
- exports.getSecretNodes = getSecretNodes;
10
- exports.getParameterNodes = getParameterNodes;
11
- exports.getLogGroupNodes = getLogGroupNodes;
12
- exports.getLambdaNodes = getLambdaNodes;
13
- exports.getEdgesForNode = getEdgesForNode;
14
- exports.getOutgoingEdges = getOutgoingEdges;
15
- exports.getIncomingEdges = getIncomingEdges;
16
- exports.getScanEdges = getScanEdges;
17
- exports.getEdgeFrequency = getEdgeFrequency;
18
- function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoMeta = [], servicesMeta = {}) {
1
+ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoMeta = [], servicesMeta = {}) {
19
2
  const nodes = [];
20
3
  const edges = [];
21
4
  const nodeIds = new Set();
@@ -119,15 +102,69 @@ function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoM
119
102
  });
120
103
  }
121
104
  for (const fn of servicesMeta.lambda ?? []) {
105
+ const lambdaId = `lambda:aws:${fn.name}`;
122
106
  addNode({
123
- id: `lambda:aws:${fn.name}`,
107
+ id: lambdaId,
124
108
  type: 'lambda',
125
109
  name: fn.name,
126
110
  runtime: fn.runtime,
127
111
  memoryMB: fn.memoryMB,
128
112
  timeoutSec: fn.timeoutSec,
129
113
  envVarKeys: fn.envVarKeys,
114
+ triggers: fn.triggers,
130
115
  });
116
+ // Add trigger edges from source → lambda (only for enabled/active mappings)
117
+ for (const trigger of fn.triggers ?? []) {
118
+ if (trigger.state && trigger.state !== 'Enabled')
119
+ continue;
120
+ let sourceId;
121
+ if (trigger.type === 'sqs') {
122
+ sourceId = `queue:aws:${trigger.sourceName}`;
123
+ addNode({ id: sourceId, type: 'queue', name: trigger.sourceName, provider: 'aws', hasDLQ: false, encrypted: false });
124
+ }
125
+ else if (trigger.type === 'dynamodb') {
126
+ sourceId = `table:dynamo:${trigger.sourceName}`;
127
+ addNode({ id: sourceId, type: 'table', name: trigger.sourceName, databaseType: 'dynamodb' });
128
+ }
129
+ else if (trigger.type === 'kinesis') {
130
+ sourceId = `queue:aws:${trigger.sourceName}`;
131
+ addNode({ id: sourceId, type: 'queue', name: trigger.sourceName, provider: 'aws', hasDLQ: false, encrypted: false });
132
+ }
133
+ else {
134
+ continue;
135
+ }
136
+ edges.push({ from: sourceId, to: lambdaId, type: 'triggers' });
137
+ }
138
+ }
139
+ for (const rule of servicesMeta.eventbridge ?? []) {
140
+ const ruleId = `eventbridge_rule:aws:${rule.name}`;
141
+ addNode({
142
+ id: ruleId,
143
+ type: 'eventbridge_rule',
144
+ name: rule.name,
145
+ state: rule.state,
146
+ scheduleExpression: rule.scheduleExpression,
147
+ eventPattern: rule.eventPattern,
148
+ });
149
+ for (const targetArn of rule.targetArns) {
150
+ const fnName = targetArn.split(':').pop() ?? '';
151
+ const lambdaId = `lambda:aws:${fnName}`;
152
+ if (!nodeIds.has(lambdaId))
153
+ continue;
154
+ edges.push({ from: ruleId, to: lambdaId, type: 'triggers' });
155
+ const lambdaNodeRef = nodes.find((n) => n.id === lambdaId);
156
+ if (lambdaNodeRef && lambdaNodeRef.type === 'lambda') {
157
+ const trigger = {
158
+ type: 'eventbridge',
159
+ sourceArn: rule.arn,
160
+ sourceName: rule.name,
161
+ eventShape: 'event.detail',
162
+ ruleName: rule.name,
163
+ eventPattern: rule.scheduleExpression ?? rule.eventPattern ?? '',
164
+ };
165
+ lambdaNodeRef.triggers = [...(lambdaNodeRef.triggers ?? []), trigger];
166
+ }
167
+ }
131
168
  }
132
169
  for (const db of servicesMeta.rds ?? []) {
133
170
  addNode({
@@ -226,46 +263,49 @@ function resolveEdgeType(operationType) {
226
263
  return 'query';
227
264
  }
228
265
  // ── Typed node selectors ─────────────────────────────────────────────────────
229
- function getTableNodes(graph) {
266
+ export function getTableNodes(graph) {
230
267
  return graph.nodes.filter((n) => n.type === 'table');
231
268
  }
232
- function getFunctionNodes(graph) {
269
+ export function getFunctionNodes(graph) {
233
270
  return graph.nodes.filter((n) => n.type === 'function');
234
271
  }
235
- function getIndexNodes(graph) {
272
+ export function getIndexNodes(graph) {
236
273
  return graph.nodes.filter((n) => n.type === 'index');
237
274
  }
238
- function getQueueNodes(graph) {
275
+ export function getQueueNodes(graph) {
239
276
  return graph.nodes.filter((n) => n.type === 'queue');
240
277
  }
241
- function getTopicNodes(graph) {
278
+ export function getTopicNodes(graph) {
242
279
  return graph.nodes.filter((n) => n.type === 'topic');
243
280
  }
244
- function getSecretNodes(graph) {
281
+ export function getSecretNodes(graph) {
245
282
  return graph.nodes.filter((n) => n.type === 'secret');
246
283
  }
247
- function getParameterNodes(graph) {
284
+ export function getParameterNodes(graph) {
248
285
  return graph.nodes.filter((n) => n.type === 'parameter');
249
286
  }
250
- function getLogGroupNodes(graph) {
287
+ export function getLogGroupNodes(graph) {
251
288
  return graph.nodes.filter((n) => n.type === 'log_group');
252
289
  }
253
- function getLambdaNodes(graph) {
290
+ export function getLambdaNodes(graph) {
254
291
  return graph.nodes.filter((n) => n.type === 'lambda');
255
292
  }
256
- function getEdgesForNode(graph, nodeId) {
293
+ export function getEventBridgeRuleNodes(graph) {
294
+ return graph.nodes.filter((n) => n.type === 'eventbridge_rule');
295
+ }
296
+ export function getEdgesForNode(graph, nodeId) {
257
297
  return graph.edges.filter((e) => e.from === nodeId || e.to === nodeId);
258
298
  }
259
- function getOutgoingEdges(graph, nodeId) {
299
+ export function getOutgoingEdges(graph, nodeId) {
260
300
  return graph.edges.filter((e) => e.from === nodeId);
261
301
  }
262
- function getIncomingEdges(graph, nodeId) {
302
+ export function getIncomingEdges(graph, nodeId) {
263
303
  return graph.edges.filter((e) => e.to === nodeId);
264
304
  }
265
- function getScanEdges(graph) {
305
+ export function getScanEdges(graph) {
266
306
  return graph.edges.filter((e) => e.type === 'scan');
267
307
  }
268
- function getEdgeFrequency(graph) {
308
+ export function getEdgeFrequency(graph) {
269
309
  const freq = new Map();
270
310
  for (const edge of graph.edges) {
271
311
  const key = `${edge.from}->${edge.to}`;
@@ -1,31 +1,20 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.currentFindings = exports.currentGraph = void 0;
7
- exports.setGraphState = setGraphState;
8
- exports.createMcpServer = createMcpServer;
9
- exports.createServer = createServer;
10
- const fs_1 = require("fs");
11
- const path_1 = require("path");
12
- const fastify_1 = __importDefault(require("fastify"));
13
- const cors_1 = __importDefault(require("@fastify/cors"));
14
- const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
15
- const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
16
- const zod_1 = require("zod");
17
- const core_1 = require("../core");
18
- const { version } = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../package.json'), 'utf8'));
19
- const analyzers_1 = require("../analyzers");
20
- const graph_1 = require("../graph");
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import Fastify from 'fastify';
4
+ import cors from '@fastify/cors';
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
+ import { z } from 'zod';
8
+ import { logger } from '../core/index.js';
9
+ const { version } = JSON.parse(readFileSync(join(import.meta.dirname, '../../package.json'), 'utf8'));
10
+ import { summarizeFindings } from '../analyzers/index.js';
11
+ import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
21
12
  // ── State ────────────────────────────────────────────────────────────────────
22
13
  let currentGraph = { nodes: [], edges: [] };
23
- exports.currentGraph = currentGraph;
24
14
  let currentFindings = [];
25
- exports.currentFindings = currentFindings;
26
- function setGraphState(graph, findings) {
27
- exports.currentGraph = currentGraph = graph;
28
- exports.currentFindings = currentFindings = findings;
15
+ export function setGraphState(graph, findings, _config) {
16
+ currentGraph = graph;
17
+ currentFindings = findings;
29
18
  }
30
19
  // ── Helpers ──────────────────────────────────────────────────────────────────
31
20
  function toText(data) {
@@ -34,25 +23,25 @@ function toText(data) {
34
23
  function logged(name, fn) {
35
24
  return async (args) => {
36
25
  const hasArgs = Object.keys(args).length > 0;
37
- core_1.logger.info(`→ ${name}${hasArgs ? ` ${JSON.stringify(args)}` : ''}`);
26
+ logger.info(`→ ${name}${hasArgs ? ` ${JSON.stringify(args)}` : ''}`);
38
27
  return fn(args);
39
28
  };
40
29
  }
41
30
  // ── MCP Server ────────────────────────────────────────────────────────────────
42
- function createMcpServer() {
43
- const mcp = new mcp_js_1.McpServer({ name: 'infrawise', version });
31
+ export function createMcpServer() {
32
+ const mcp = new McpServer({ name: 'infrawise', version });
44
33
  mcp.registerTool('get_infra_overview', {
45
34
  description: 'Returns a complete snapshot of all infrastructure: databases, queues, topics, secrets, parameters, log groups, lambdas, and all findings. Start here for a full picture.',
46
- inputSchema: zod_1.z.object({}),
35
+ inputSchema: z.object({}),
47
36
  }, logged('get_infra_overview', async () => {
48
- const tables = (0, graph_1.getTableNodes)(currentGraph);
49
- const queues = (0, graph_1.getQueueNodes)(currentGraph);
50
- const topics = (0, graph_1.getTopicNodes)(currentGraph);
51
- const secrets = (0, graph_1.getSecretNodes)(currentGraph);
52
- const parameters = (0, graph_1.getParameterNodes)(currentGraph);
53
- const logGroups = (0, graph_1.getLogGroupNodes)(currentGraph);
54
- const lambdas = (0, graph_1.getLambdaNodes)(currentGraph);
55
- const functions = (0, graph_1.getFunctionNodes)(currentGraph);
37
+ const tables = getTableNodes(currentGraph);
38
+ const queues = getQueueNodes(currentGraph);
39
+ const topics = getTopicNodes(currentGraph);
40
+ const secrets = getSecretNodes(currentGraph);
41
+ const parameters = getParameterNodes(currentGraph);
42
+ const logGroups = getLogGroupNodes(currentGraph);
43
+ const lambdas = getLambdaNodes(currentGraph);
44
+ const functions = getFunctionNodes(currentGraph);
56
45
  return toText({
57
46
  summary: {
58
47
  tables: tables.length, functions: functions.length,
@@ -60,7 +49,7 @@ function createMcpServer() {
60
49
  secrets: secrets.length, parameters: parameters.length,
61
50
  logGroups: logGroups.length, lambdas: lambdas.length,
62
51
  totalNodes: currentGraph.nodes.length, totalEdges: currentGraph.edges.length,
63
- findings: (0, analyzers_1.summarizeFindings)(currentFindings),
52
+ findings: summarizeFindings(currentFindings),
64
53
  },
65
54
  databases: tables.map((t) => ({ name: t.name, type: t.databaseType })),
66
55
  queues: queues.map((q) => ({ name: q.name, hasDLQ: q.hasDLQ, encrypted: q.encrypted, approximateMessages: q.approximateMessages })),
@@ -74,34 +63,41 @@ function createMcpServer() {
74
63
  }));
75
64
  mcp.registerTool('get_graph_summary', {
76
65
  description: 'Returns the full infrastructure graph (all nodes and edges) plus findings summary.',
77
- inputSchema: zod_1.z.object({}),
66
+ inputSchema: z.object({}),
78
67
  }, logged('get_graph_summary', async () => toText({
79
68
  nodes: currentGraph.nodes,
80
69
  edges: currentGraph.edges,
81
70
  findings: currentFindings,
82
71
  summary: {
83
72
  totalNodes: currentGraph.nodes.length, totalEdges: currentGraph.edges.length,
84
- tables: (0, graph_1.getTableNodes)(currentGraph).length, functions: (0, graph_1.getFunctionNodes)(currentGraph).length,
85
- queues: (0, graph_1.getQueueNodes)(currentGraph).length, scans: (0, graph_1.getScanEdges)(currentGraph).length,
86
- ...(0, analyzers_1.summarizeFindings)(currentFindings),
73
+ tables: getTableNodes(currentGraph).length, functions: getFunctionNodes(currentGraph).length,
74
+ queues: getQueueNodes(currentGraph).length, scans: getScanEdges(currentGraph).length,
75
+ ...summarizeFindings(currentFindings),
87
76
  },
88
77
  })));
89
78
  mcp.registerTool('analyze_function', {
90
- description: 'Analyze a specific function for all infrastructure issues: DB queries, queue publishing, secret access, etc.',
91
- inputSchema: zod_1.z.object({ function: zod_1.z.string().describe('Function name to analyze') }),
79
+ description: 'Analyze a specific function for all infrastructure issues: DB queries, queue publishing, secret access, trigger event shapes, etc.',
80
+ inputSchema: z.object({ function: z.string().describe('Function name to analyze') }),
92
81
  }, logged('analyze_function', async ({ function: functionName }) => {
93
82
  const funcNode = currentGraph.nodes.find((n) => n.type === 'function' && n.name === functionName);
94
- if (!funcNode) {
83
+ // Also check if there's a Lambda node with this name (for AWS-deployed functions)
84
+ const lambdaNode = currentGraph.nodes.find((n) => n.type === 'lambda' && n.name === functionName);
85
+ if (!funcNode && !lambdaNode) {
95
86
  return toText({ function: functionName, found: false, issues: [], recommendations: [`Function "${functionName}" not found in the analyzed codebase.`] });
96
87
  }
97
- const outEdges = (0, graph_1.getOutgoingEdges)(currentGraph, funcNode.id);
88
+ const outEdges = funcNode ? getOutgoingEdges(currentGraph, funcNode.id) : [];
98
89
  const relatedFindings = currentFindings.filter((f) => {
99
90
  const meta = f.metadata;
100
91
  return meta?.functionName === functionName || String(meta?.callerFunctions ?? '').includes(functionName);
101
92
  });
93
+ const allTriggers = lambdaNode?.type === 'lambda' ? (lambdaNode.triggers ?? []) : [];
102
94
  return toText({
103
95
  function: functionName, found: true,
104
- file: funcNode.type === 'function' ? funcNode.file : undefined,
96
+ file: funcNode?.type === 'function' ? funcNode.file : undefined,
97
+ triggers: allTriggers.map((t) => ({
98
+ type: t.type, source: t.sourceName, eventShape: t.eventShape,
99
+ ...(t.ruleName ? { ruleName: t.ruleName, eventPattern: t.eventPattern } : {}),
100
+ })),
105
101
  accesses: outEdges.map((e) => {
106
102
  const target = currentGraph.nodes.find((n) => n.id === e.to);
107
103
  return { targetId: e.to, edgeType: e.type, targetName: target && 'name' in target ? target.name : e.to, targetType: target?.type };
@@ -112,13 +108,14 @@ function createMcpServer() {
112
108
  }));
113
109
  mcp.registerTool('suggest_gsi', {
114
110
  description: 'Get GSI suggestions for a DynamoDB table and attribute',
115
- inputSchema: zod_1.z.object({
116
- table: zod_1.z.string().describe('DynamoDB table name'),
117
- attribute: zod_1.z.string().describe('Attribute to create the GSI on'),
111
+ inputSchema: z.object({
112
+ table: z.string().describe('DynamoDB table name'),
113
+ attribute: z.string().describe('Attribute to create the GSI on'),
118
114
  }),
119
115
  }, logged('suggest_gsi', async ({ table: tableName, attribute }) => {
120
116
  const sanitizedAttr = attribute.replace(/[^a-zA-Z0-9_]/g, '_');
121
- const indexName = `${tableName}-${sanitizedAttr}-index`;
117
+ const sanitizedTable = tableName.replace(/[^a-zA-Z0-9_-]/g, '_');
118
+ const indexName = `${sanitizedTable}-${sanitizedAttr}-index`;
122
119
  const tableNode = currentGraph.nodes.find((n) => n.type === 'table' && n.databaseType === 'dynamodb' && 'name' in n && n.name === tableName);
123
120
  return toText({
124
121
  table: tableName, attribute, found: !!tableNode,
@@ -129,9 +126,9 @@ function createMcpServer() {
129
126
  }));
130
127
  mcp.registerTool('postgres_index_suggestions', {
131
128
  description: 'Get PostgreSQL index suggestions for a table column',
132
- inputSchema: zod_1.z.object({
133
- table: zod_1.z.string().describe('PostgreSQL table name'),
134
- column: zod_1.z.string().describe('Column name to index'),
129
+ inputSchema: z.object({
130
+ table: z.string().describe('PostgreSQL table name'),
131
+ column: z.string().describe('Column name to index'),
135
132
  }),
136
133
  }, logged('postgres_index_suggestions', async ({ table: tableName, column }) => {
137
134
  const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, '_');
@@ -139,33 +136,37 @@ function createMcpServer() {
139
136
  const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
140
137
  return toText({
141
138
  table: tableName, column,
142
- recommendation: `CREATE INDEX CONCURRENTLY ${indexName} ON ${tableName} (${column});`,
139
+ recommendation: `CREATE INDEX CONCURRENTLY ${indexName} ON ${sanitizedTable} (${sanitizedCol});`,
143
140
  rationale: `An index on "${column}" eliminates sequential scans when filtering on this column.`,
144
141
  notes: ['Use CONCURRENTLY to avoid locking the table', 'Run ANALYZE after creation',
145
- `Partial index: CREATE INDEX CONCURRENTLY ${indexName}_partial ON ${tableName} (${column}) WHERE ${column} IS NOT NULL;`],
142
+ `Partial index: CREATE INDEX CONCURRENTLY ${indexName}_partial ON ${sanitizedTable} (${sanitizedCol}) WHERE ${sanitizedCol} IS NOT NULL;`],
146
143
  });
147
144
  }));
148
145
  mcp.registerTool('suggest_mongo_index', {
149
146
  description: 'Get index suggestions for a MongoDB collection field',
150
- inputSchema: zod_1.z.object({
151
- collection: zod_1.z.string().describe('MongoDB collection name'),
152
- field: zod_1.z.string().describe('Field name to index'),
147
+ inputSchema: z.object({
148
+ collection: z.string().describe('MongoDB collection name'),
149
+ field: z.string().describe('Field name to index'),
153
150
  }),
154
- }, logged('suggest_mongo_index', async ({ collection, field }) => toText({
155
- collection, field,
156
- recommendation: `db.${collection}.createIndex({ ${field}: 1 })`,
157
- rationale: `An index on "${field}" eliminates full collection scans when filtering on this field.`,
158
- notes: [
159
- `Compound: db.${collection}.createIndex({ ${field}: 1, otherField: 1 })`,
160
- `Text: db.${collection}.createIndex({ ${field}: "text" })`,
161
- `Verify: db.${collection}.explain("executionStats").find({ ${field}: value })`,
162
- ],
163
- })));
151
+ }, logged('suggest_mongo_index', async ({ collection, field }) => {
152
+ const sanitizedCollection = collection.replace(/[^a-zA-Z0-9_]/g, '_');
153
+ const sanitizedField = field.replace(/[^a-zA-Z0-9_.]/g, '_');
154
+ return toText({
155
+ collection, field,
156
+ recommendation: `db.${sanitizedCollection}.createIndex({ ${sanitizedField}: 1 })`,
157
+ rationale: `An index on "${field}" eliminates full collection scans when filtering on this field.`,
158
+ notes: [
159
+ `Compound: db.${sanitizedCollection}.createIndex({ ${sanitizedField}: 1, otherField: 1 })`,
160
+ `Text: db.${sanitizedCollection}.createIndex({ ${sanitizedField}: "text" })`,
161
+ `Verify: db.${sanitizedCollection}.explain("executionStats").find({ ${sanitizedField}: value })`,
162
+ ],
163
+ });
164
+ }));
164
165
  mcp.registerTool('mysql_index_suggestions', {
165
166
  description: 'Get MySQL index suggestions for a table column',
166
- inputSchema: zod_1.z.object({
167
- table: zod_1.z.string().describe('MySQL table name'),
168
- column: zod_1.z.string().describe('Column name to index'),
167
+ inputSchema: z.object({
168
+ table: z.string().describe('MySQL table name'),
169
+ column: z.string().describe('Column name to index'),
169
170
  }),
170
171
  }, logged('mysql_index_suggestions', async ({ table: tableName, column }) => {
171
172
  const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, '_');
@@ -173,17 +174,17 @@ function createMcpServer() {
173
174
  const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
174
175
  return toText({
175
176
  table: tableName, column,
176
- recommendation: `ALTER TABLE ${tableName} ADD INDEX ${indexName} (${column});`,
177
+ recommendation: `ALTER TABLE ${sanitizedTable} ADD INDEX ${indexName} (${sanitizedCol});`,
177
178
  rationale: `An index on "${column}" eliminates full table scans when filtering on this column.`,
178
179
  notes: ['MySQL InnoDB adds indexes online (no full lock for 5.6+)', 'EXPLAIN SELECT ... to verify after adding',
179
- `Composite: ALTER TABLE ${tableName} ADD INDEX idx_composite (${column}, other_column);`],
180
+ `Composite: ALTER TABLE ${sanitizedTable} ADD INDEX idx_composite (${sanitizedCol}, other_column);`],
180
181
  });
181
182
  }));
182
183
  mcp.registerTool('get_queue_details', {
183
184
  description: 'Returns all SQS queues with DLQ status, encryption, message counts, and retention.',
184
- inputSchema: zod_1.z.object({}),
185
+ inputSchema: z.object({}),
185
186
  }, logged('get_queue_details', async () => {
186
- const queues = (0, graph_1.getQueueNodes)(currentGraph);
187
+ const queues = getQueueNodes(currentGraph);
187
188
  const queueFindings = currentFindings.filter((f) => f.metadata?.queueName);
188
189
  return toText({
189
190
  total: queues.length,
@@ -196,16 +197,16 @@ function createMcpServer() {
196
197
  }));
197
198
  mcp.registerTool('get_topic_details', {
198
199
  description: 'Returns all SNS topics with subscription counts and protocols.',
199
- inputSchema: zod_1.z.object({}),
200
+ inputSchema: z.object({}),
200
201
  }, logged('get_topic_details', async () => {
201
- const topics = (0, graph_1.getTopicNodes)(currentGraph);
202
+ const topics = getTopicNodes(currentGraph);
202
203
  return toText({ total: topics.length, topics: topics.map((t) => ({ name: t.name, provider: t.provider, subscriptionCount: t.subscriptionCount, encrypted: t.encrypted })) });
203
204
  }));
204
205
  mcp.registerTool('get_secrets_overview', {
205
206
  description: 'Returns all Secrets Manager secrets: names, rotation status. Secret VALUES are never included.',
206
- inputSchema: zod_1.z.object({}),
207
+ inputSchema: z.object({}),
207
208
  }, logged('get_secrets_overview', async () => {
208
- const secrets = (0, graph_1.getSecretNodes)(currentGraph);
209
+ const secrets = getSecretNodes(currentGraph);
209
210
  const secretFindings = currentFindings.filter((f) => f.metadata?.secretName);
210
211
  return toText({
211
212
  total: secrets.length, note: 'Secret values are never included in this response.',
@@ -217,34 +218,55 @@ function createMcpServer() {
217
218
  }));
218
219
  mcp.registerTool('get_parameter_overview', {
219
220
  description: 'Returns all SSM Parameter Store parameters: names, types, tiers. Parameter VALUES are never included.',
220
- inputSchema: zod_1.z.object({}),
221
+ inputSchema: z.object({}),
221
222
  }, logged('get_parameter_overview', async () => {
222
- const parameters = (0, graph_1.getParameterNodes)(currentGraph);
223
+ const parameters = getParameterNodes(currentGraph);
223
224
  return toText({
224
225
  total: parameters.length, note: 'Parameter values are never included in this response.',
225
226
  parameters: parameters.map((p) => ({ name: p.name, provider: p.provider, type: p.paramType, tier: p.tier })),
226
227
  });
227
228
  }));
228
229
  mcp.registerTool('get_lambda_overview', {
229
- description: 'Returns all Lambda functions: runtime, memory, timeout, env var key names (values never included).',
230
- inputSchema: zod_1.z.object({}),
230
+ description: 'Returns all Lambda functions: runtime, memory, timeout, env var key names (values never included), and known event source triggers with correct handler event shapes.',
231
+ inputSchema: z.object({}),
231
232
  }, logged('get_lambda_overview', async () => {
232
- const lambdas = (0, graph_1.getLambdaNodes)(currentGraph);
233
+ const lambdas = getLambdaNodes(currentGraph);
233
234
  const lambdaFindings = currentFindings.filter((f) => f.metadata?.functionName);
234
235
  return toText({
235
236
  total: lambdas.length, note: 'Environment variable values are never included.',
236
237
  lambdas: lambdas.map((l) => ({
237
238
  name: l.name, runtime: l.runtime, memoryMB: l.memoryMB, timeoutSec: l.timeoutSec,
238
239
  envVarCount: l.envVarKeys?.length ?? 0, envVarKeys: l.envVarKeys,
240
+ triggers: (l.triggers ?? []).map((t) => ({ type: t.type, source: t.sourceName, eventShape: t.eventShape, state: t.state })),
239
241
  findings: lambdaFindings.filter((f) => f.metadata.functionName === l.name).map((f) => ({ severity: f.severity, issue: f.issue })),
240
242
  })),
241
243
  });
242
244
  }));
245
+ mcp.registerTool('get_eventbridge_details', {
246
+ description: 'Returns all EventBridge rules: name, state, schedule expression or event pattern, and target Lambda functions.',
247
+ inputSchema: z.object({}),
248
+ }, logged('get_eventbridge_details', async () => {
249
+ const rules = getEventBridgeRuleNodes(currentGraph);
250
+ return toText({
251
+ total: rules.length,
252
+ rules: rules.map((r) => ({
253
+ name: r.name,
254
+ state: r.state,
255
+ scheduleExpression: r.scheduleExpression,
256
+ eventPattern: r.eventPattern,
257
+ targets: currentGraph.edges
258
+ .filter((e) => e.from === r.id && e.type === 'triggers')
259
+ .map((e) => currentGraph.nodes.find((n) => n.id === e.to))
260
+ .filter(Boolean)
261
+ .map((n) => n && 'name' in n ? n.name : ''),
262
+ })),
263
+ });
264
+ }));
243
265
  mcp.registerTool('get_log_errors', {
244
266
  description: 'Returns recent error patterns from CloudWatch log groups. Returns pattern counts and frequencies — never raw log messages.',
245
- inputSchema: zod_1.z.object({ logGroup: zod_1.z.string().describe('Filter to a specific log group name (optional)').optional() }),
267
+ inputSchema: z.object({ logGroup: z.string().describe('Filter to a specific log group name (optional)').optional() }),
246
268
  }, logged('get_log_errors', async ({ logGroup: filterName }) => {
247
- const logGroups = (0, graph_1.getLogGroupNodes)(currentGraph).filter((lg) => !filterName || lg.name.includes(filterName));
269
+ const logGroups = getLogGroupNodes(currentGraph).filter((lg) => !filterName || lg.name.includes(filterName));
248
270
  return toText({
249
271
  note: 'Only error patterns and counts are returned — no raw log messages.',
250
272
  windowHours: 24,
@@ -254,9 +276,9 @@ function createMcpServer() {
254
276
  return mcp;
255
277
  }
256
278
  // ── Fastify server ────────────────────────────────────────────────────────────
257
- function createServer(port = 3000) {
258
- const fastify = (0, fastify_1.default)({ logger: false });
259
- fastify.register(cors_1.default, { origin: true });
279
+ export function createServer(port = 3000) {
280
+ const fastify = Fastify({ logger: false });
281
+ fastify.register(cors, { origin: true });
260
282
  const mcp = createMcpServer();
261
283
  fastify.get('/health', async () => ({
262
284
  status: 'ok', version,
@@ -265,7 +287,7 @@ function createServer(port = 3000) {
265
287
  findings: currentFindings.length,
266
288
  }));
267
289
  fastify.post('/mcp', async (request, reply) => {
268
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
290
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
269
291
  reply.raw.on('close', () => transport.close());
270
292
  await mcp.connect(transport);
271
293
  await transport.handleRequest(request.raw, reply.raw, request.body);
@@ -276,12 +298,13 @@ function createServer(port = 3000) {
276
298
  start: async () => {
277
299
  try {
278
300
  await fastify.listen({ port, host: '0.0.0.0' });
279
- core_1.logger.info(`Infrawise MCP server running at http://localhost:${port}`);
301
+ logger.info(`Infrawise MCP server running at http://localhost:${port}`);
280
302
  }
281
303
  catch (e) {
282
- core_1.logger.error(`Failed to start server: ${e instanceof Error ? e.message : String(e)}`);
304
+ logger.error(`Failed to start server: ${e instanceof Error ? e.message : String(e)}`);
283
305
  process.exit(1);
284
306
  }
285
307
  },
286
308
  };
287
309
  }
310
+ export { currentGraph, currentFindings };
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};