infrawise 0.7.1 → 0.8.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.
- package/README.md +137 -146
- package/dist/adapters/{dynamodb.js → aws/dynamodb.js} +1 -1
- package/dist/adapters/aws/index.js +4 -0
- package/dist/adapters/{logs.js → aws/logs.js} +1 -1
- package/dist/adapters/aws/s3.js +72 -0
- package/dist/adapters/{aws.js → aws/services.js} +1 -1
- package/dist/adapters/db/index.js +3 -0
- package/dist/adapters/{mongodb.js → db/mongodb.js} +1 -1
- package/dist/adapters/{mysql.js → db/mysql.js} +1 -1
- package/dist/adapters/{postgres.js → db/postgres.js} +1 -1
- package/dist/adapters/iac/index.js +1 -0
- package/dist/adapters/{terraform.js → iac/terraform.js} +1 -1
- package/dist/analyzers/aws-services.js +61 -0
- package/dist/analyzers/index.js +3 -3
- package/dist/cli/commands/analyze.js +77 -14
- package/dist/cli/commands/auth.js +1 -1
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/doctor.js +23 -6
- package/dist/cli/commands/stdio.js +28 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/utils.js +3 -0
- package/dist/core/config.js +1 -0
- package/dist/graph/index.js +39 -5
- package/dist/server/index.js +41 -17
- package/package.json +11 -4
|
@@ -1,18 +1,46 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
1
2
|
import * as path from 'path';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
import ora from 'ora';
|
|
4
5
|
import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
|
|
5
|
-
import { extractDynamoMetadata } from '../../adapters/dynamodb.js';
|
|
6
|
-
import { extractPostgresMetadata } from '../../adapters/postgres.js';
|
|
7
|
-
import { extractMySQLMetadata } from '../../adapters/mysql.js';
|
|
8
|
-
import { extractMongoMetadata } from '../../adapters/mongodb.js';
|
|
9
|
-
import { extractIaCSchema } from '../../adapters/terraform.js';
|
|
10
|
-
import { extractSQSMetadata, extractSNSMetadata, extractSSMMetadata, extractSecretsMetadata, extractLambdaMetadata, extractEventBridgeMetadata, extractRDSMetadata, } from '../../adapters/aws.js';
|
|
11
|
-
import { extractLogsMetadata } from '../../adapters/logs.js';
|
|
6
|
+
import { extractDynamoMetadata } from '../../adapters/aws/dynamodb.js';
|
|
7
|
+
import { extractPostgresMetadata } from '../../adapters/db/postgres.js';
|
|
8
|
+
import { extractMySQLMetadata } from '../../adapters/db/mysql.js';
|
|
9
|
+
import { extractMongoMetadata } from '../../adapters/db/mongodb.js';
|
|
10
|
+
import { extractIaCSchema } from '../../adapters/iac/terraform.js';
|
|
11
|
+
import { extractSQSMetadata, extractSNSMetadata, extractSSMMetadata, extractSecretsMetadata, extractLambdaMetadata, extractEventBridgeMetadata, extractRDSMetadata, } from '../../adapters/aws/services.js';
|
|
12
|
+
import { extractLogsMetadata } from '../../adapters/aws/logs.js';
|
|
13
|
+
import { extractS3Metadata } from '../../adapters/aws/s3.js';
|
|
12
14
|
import { scanRepository } from '../../context/index.js';
|
|
13
15
|
import { buildGraph } from '../../graph/index.js';
|
|
14
|
-
import { runAllAnalyzers, IaCDriftAnalyzer, FullTableScanAnalyzer, MissingGSIAnalyzer, HotPartitionAnalyzer, MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer, MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer, MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer, MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, LambdaMissingTriggerDLQAnalyzer, RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, } from '../../analyzers/index.js';
|
|
16
|
+
import { runAllAnalyzers, IaCDriftAnalyzer, FullTableScanAnalyzer, MissingGSIAnalyzer, HotPartitionAnalyzer, MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer, MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer, MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer, MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, LambdaMissingTriggerDLQAnalyzer, RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, S3PublicAccessAnalyzer, S3MissingVersioningAnalyzer, S3UnencryptedAnalyzer, } from '../../analyzers/index.js';
|
|
15
17
|
import { printFinding, printSummaryBox, log, printHeader } from '../utils.js';
|
|
18
|
+
const SEVERITY_ORDER = { high: 3, medium: 2, low: 1, verify: 0 };
|
|
19
|
+
function buildMarkdownReport(findings, projectName) {
|
|
20
|
+
const date = new Date().toISOString().split('T')[0];
|
|
21
|
+
const high = findings.filter((f) => f.severity === 'high');
|
|
22
|
+
const medium = findings.filter((f) => f.severity === 'medium');
|
|
23
|
+
const low = findings.filter((f) => f.severity === 'low');
|
|
24
|
+
const verify = findings.filter((f) => f.severity === 'verify');
|
|
25
|
+
const renderGroup = (label, emoji, group) => {
|
|
26
|
+
if (group.length === 0)
|
|
27
|
+
return '';
|
|
28
|
+
const rows = group.map((f) => `| ${f.issue} | ${f.recommendation} |`).join('\n');
|
|
29
|
+
return `\n## ${emoji} ${label} (${group.length})\n\n| Issue | Recommendation |\n|---|---|\n${rows}\n`;
|
|
30
|
+
};
|
|
31
|
+
return [
|
|
32
|
+
`# infrawise report — ${projectName}`,
|
|
33
|
+
`_Generated: ${date}_`,
|
|
34
|
+
'',
|
|
35
|
+
`**${findings.length} finding(s)**: ${high.length} high · ${medium.length} medium · ${low.length} low · ${verify.length} verify`,
|
|
36
|
+
renderGroup('High severity', '🔴', high),
|
|
37
|
+
renderGroup('Medium severity', '🟡', medium),
|
|
38
|
+
renderGroup('Low severity', '🟢', low),
|
|
39
|
+
renderGroup('Verify (check intent)', '🔵', verify),
|
|
40
|
+
'',
|
|
41
|
+
'_Generated by [infrawise](https://github.com/Sidd27/infrawise) — MCP server for AWS infrastructure analysis_',
|
|
42
|
+
].filter((l) => l !== undefined).join('\n');
|
|
43
|
+
}
|
|
16
44
|
function mkSpinner(text) {
|
|
17
45
|
return ora({ text: chalk.dim(text), color: 'cyan' }).start();
|
|
18
46
|
}
|
|
@@ -28,6 +56,7 @@ export async function runAnalyze(options = {}) {
|
|
|
28
56
|
process.exit(1);
|
|
29
57
|
}
|
|
30
58
|
const repoPath = options.repo ?? process.cwd();
|
|
59
|
+
const minSeverity = options.severity ? SEVERITY_ORDER[options.severity] ?? 1 : 0;
|
|
31
60
|
const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
|
|
32
61
|
const dynamoMeta = [];
|
|
33
62
|
const postgresMeta = [];
|
|
@@ -166,6 +195,18 @@ export async function runAnalyze(options = {}) {
|
|
|
166
195
|
s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
167
196
|
}
|
|
168
197
|
}
|
|
198
|
+
// ── S3 ────────────────────────────────────────────────────────────────────────
|
|
199
|
+
if (config.s3?.enabled === true) {
|
|
200
|
+
const s = mkSpinner('Extracting S3 buckets...');
|
|
201
|
+
try {
|
|
202
|
+
const result = await extractS3Metadata(awsCfg);
|
|
203
|
+
servicesMeta.s3 = result;
|
|
204
|
+
s.succeed(chalk.green('S3') + chalk.dim(` ${result.length} bucket(s)`));
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
s.warn(chalk.yellow('S3 skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
169
210
|
// ── CloudWatch Logs ──────────────────────────────────────────────────────────
|
|
170
211
|
if (config.cloudwatchLogs?.enabled) {
|
|
171
212
|
const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...');
|
|
@@ -267,6 +308,11 @@ export async function runAnalyze(options = {}) {
|
|
|
267
308
|
new RDSNoDeletionProtectionAnalyzer(),
|
|
268
309
|
new RDSNoMultiAZAnalyzer(),
|
|
269
310
|
] : []),
|
|
311
|
+
...(config.s3?.enabled === true ? [
|
|
312
|
+
new S3PublicAccessAnalyzer(),
|
|
313
|
+
new S3MissingVersioningAnalyzer(),
|
|
314
|
+
new S3UnencryptedAnalyzer(),
|
|
315
|
+
] : []),
|
|
270
316
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
271
317
|
];
|
|
272
318
|
findings = await runAllAnalyzers(graph, analyzers);
|
|
@@ -278,18 +324,30 @@ export async function runAnalyze(options = {}) {
|
|
|
278
324
|
writeCache('operations', operations);
|
|
279
325
|
writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
|
|
280
326
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
327
|
+
const displayFindings = minSeverity > 0
|
|
328
|
+
? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
|
|
329
|
+
: findings;
|
|
281
330
|
console.log('');
|
|
282
|
-
if (
|
|
283
|
-
|
|
331
|
+
if (displayFindings.length === 0) {
|
|
332
|
+
const msg = minSeverity > 0 ? `No ${options.severity} (or higher) severity issues found.` : 'Your infrastructure looks clean.';
|
|
333
|
+
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim(msg)}`);
|
|
284
334
|
}
|
|
285
335
|
else {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
336
|
+
const filterNote = minSeverity > 0 ? chalk.dim(` (${options.severity}+ only)`) : '';
|
|
337
|
+
console.log(chalk.bold(` Findings`) + chalk.dim(` ${displayFindings.length} total`) + filterNote);
|
|
338
|
+
displayFindings.forEach((f, i) => printFinding(f, i));
|
|
339
|
+
printSummaryBox(displayFindings);
|
|
340
|
+
if (displayFindings.some((f) => f.severity === 'high')) {
|
|
290
341
|
console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
|
|
291
342
|
}
|
|
292
343
|
}
|
|
344
|
+
if (options.output) {
|
|
345
|
+
const report = buildMarkdownReport(displayFindings, config.project);
|
|
346
|
+
const outPath = path.resolve(options.output);
|
|
347
|
+
fs.writeFileSync(outPath, report, 'utf8');
|
|
348
|
+
console.log('');
|
|
349
|
+
log.success('Report saved', outPath);
|
|
350
|
+
}
|
|
293
351
|
console.log('');
|
|
294
352
|
log.dim(`Results cached in .infrawise/cache/`);
|
|
295
353
|
log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
|
|
@@ -346,6 +404,11 @@ export async function runCodeRefresh(repoPath, config) {
|
|
|
346
404
|
new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
|
|
347
405
|
new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
|
|
348
406
|
] : []),
|
|
407
|
+
...(config.s3?.enabled === true ? [
|
|
408
|
+
new S3PublicAccessAnalyzer(),
|
|
409
|
+
new S3MissingVersioningAnalyzer(),
|
|
410
|
+
new S3UnencryptedAnalyzer(),
|
|
411
|
+
] : []),
|
|
349
412
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
350
413
|
];
|
|
351
414
|
const findings = await runAllAnalyzers(graph, analyzers);
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { readAWSProfiles, log, printHeader } from '../utils.js';
|
|
5
|
-
import { validateDynamoAccess } from '../../adapters/dynamodb.js';
|
|
5
|
+
import { validateDynamoAccess } from '../../adapters/aws/dynamodb.js';
|
|
6
6
|
export async function runAuth() {
|
|
7
7
|
printHeader('AWS Authentication');
|
|
8
8
|
const profiles = readAWSProfiles();
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -54,7 +54,7 @@ function groupTools(tools) {
|
|
|
54
54
|
return lines;
|
|
55
55
|
}
|
|
56
56
|
export async function runDev(options = {}) {
|
|
57
|
-
const port = options.port ?? 3000;
|
|
57
|
+
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : 3000);
|
|
58
58
|
printHeader('MCP Server');
|
|
59
59
|
let config;
|
|
60
60
|
try {
|
|
@@ -4,12 +4,13 @@ import * as os from 'os';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
import { loadConfig } from '../../core/index.js';
|
|
7
|
-
import { probeDynamoAccess } from '../../adapters/dynamodb.js';
|
|
8
|
-
import { validatePostgresAccess } from '../../adapters/postgres.js';
|
|
9
|
-
import { validateMySQLAccess } from '../../adapters/mysql.js';
|
|
10
|
-
import { validateMongoAccess } from '../../adapters/mongodb.js';
|
|
11
|
-
import { validateSQSAccess, validateSNSAccess, validateSSMAccess, validateSecretsAccess, validateLambdaAccess, validateEventBridgeAccess, } from '../../adapters/aws.js';
|
|
12
|
-
import { validateLogsAccess } from '../../adapters/logs.js';
|
|
7
|
+
import { probeDynamoAccess } from '../../adapters/aws/dynamodb.js';
|
|
8
|
+
import { validatePostgresAccess } from '../../adapters/db/postgres.js';
|
|
9
|
+
import { validateMySQLAccess } from '../../adapters/db/mysql.js';
|
|
10
|
+
import { validateMongoAccess } from '../../adapters/db/mongodb.js';
|
|
11
|
+
import { validateSQSAccess, validateSNSAccess, validateSSMAccess, validateSecretsAccess, validateLambdaAccess, validateEventBridgeAccess, } from '../../adapters/aws/services.js';
|
|
12
|
+
import { validateLogsAccess } from '../../adapters/aws/logs.js';
|
|
13
|
+
import { validateS3Access } from '../../adapters/aws/s3.js';
|
|
13
14
|
import { printHeader } from '../utils.js';
|
|
14
15
|
async function runCheck(label, fn) {
|
|
15
16
|
const spin = ora({ text: chalk.dim(label), color: 'cyan' }).start();
|
|
@@ -189,6 +190,22 @@ export async function runDoctor(options = {}) {
|
|
|
189
190
|
};
|
|
190
191
|
}
|
|
191
192
|
}));
|
|
193
|
+
// S3
|
|
194
|
+
results.push(await runCheck('Testing S3 access...', async () => {
|
|
195
|
+
if (config?.s3?.enabled !== true)
|
|
196
|
+
return { name: 'S3', status: 'skip', message: 'Disabled in config' };
|
|
197
|
+
try {
|
|
198
|
+
await validateS3Access(awsCfg);
|
|
199
|
+
return { name: 'S3', status: 'pass', message: 'Connected' };
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return {
|
|
203
|
+
name: 'S3', status: 'warn',
|
|
204
|
+
message: err instanceof Error ? err.message : String(err),
|
|
205
|
+
detail: 'Check IAM: s3:ListBuckets, s3:GetBucketNotificationConfiguration, s3:GetBucketVersioning, s3:GetBucketEncryption, s3:GetPublicAccessBlock',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}));
|
|
192
209
|
// CloudWatch Logs
|
|
193
210
|
results.push(await runCheck('Testing CloudWatch Logs access...', async () => {
|
|
194
211
|
if (!config?.cloudwatchLogs?.enabled)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
import { loadConfig, formatError, readCache } from '../../core/index.js';
|
|
3
|
+
import { createMcpServer, setGraphState } from '../../server/index.js';
|
|
4
|
+
import { runAnalyze } from './analyze.js';
|
|
5
|
+
export async function runStdio(configPath) {
|
|
6
|
+
let config;
|
|
7
|
+
try {
|
|
8
|
+
config = loadConfig(configPath);
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
process.stderr.write(formatError(err) + '\n');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const cachedGraph = readCache('graph');
|
|
15
|
+
const cachedFindings = readCache('findings');
|
|
16
|
+
if (cachedGraph && cachedFindings) {
|
|
17
|
+
setGraphState(cachedGraph, cachedFindings, config);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await runAnalyze({ config: configPath });
|
|
21
|
+
const graph = readCache('graph') ?? { nodes: [], edges: [] };
|
|
22
|
+
const findings = readCache('findings') ?? [];
|
|
23
|
+
setGraphState(graph, findings, config);
|
|
24
|
+
}
|
|
25
|
+
const mcp = createMcpServer();
|
|
26
|
+
const transport = new StdioServerTransport();
|
|
27
|
+
await mcp.connect(transport);
|
|
28
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runAuth } from './commands/auth.js';
|
|
|
8
8
|
import { runAnalyze } from './commands/analyze.js';
|
|
9
9
|
import { runDev } from './commands/dev.js';
|
|
10
10
|
import { runDoctor } from './commands/doctor.js';
|
|
11
|
+
import { runStdio } from './commands/stdio.js';
|
|
11
12
|
const { version } = JSON.parse(readFileSync(join(import.meta.dirname, '../../package.json'), 'utf8'));
|
|
12
13
|
const program = new Command();
|
|
13
14
|
program
|
|
@@ -35,12 +36,16 @@ program
|
|
|
35
36
|
.option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
|
|
36
37
|
.option('-r, --repo <path>', 'Path to repository to scan', process.cwd())
|
|
37
38
|
.option('--no-cache', 'Skip reading/writing the cache')
|
|
39
|
+
.option('-o, --output <path>', 'Save findings as a markdown report (e.g. report.md)')
|
|
40
|
+
.option('--severity <level>', 'Only show findings at or above this level: high | medium | low')
|
|
38
41
|
.action(async (options) => {
|
|
39
42
|
printBanner();
|
|
40
43
|
await runAnalyze({
|
|
41
44
|
config: options.config !== 'infrawise.yaml' ? options.config : undefined,
|
|
42
45
|
repo: options.repo,
|
|
43
46
|
noCache: !options.cache,
|
|
47
|
+
output: options.output,
|
|
48
|
+
severity: options.severity,
|
|
44
49
|
});
|
|
45
50
|
});
|
|
46
51
|
program
|
|
@@ -55,6 +60,13 @@ program
|
|
|
55
60
|
port: parseInt(options.port, 10),
|
|
56
61
|
});
|
|
57
62
|
});
|
|
63
|
+
program
|
|
64
|
+
.command('stdio')
|
|
65
|
+
.description('Start MCP server on stdio transport (for Claude Desktop and Glama)')
|
|
66
|
+
.option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
|
|
67
|
+
.action(async (options) => {
|
|
68
|
+
await runStdio(options.config !== 'infrawise.yaml' ? options.config : undefined);
|
|
69
|
+
});
|
|
58
70
|
program
|
|
59
71
|
.command('doctor')
|
|
60
72
|
.description('Validate AWS access, postgres connectivity, config, and repo scan')
|
package/dist/cli/utils.js
CHANGED
|
@@ -93,6 +93,7 @@ function severityBadge(severity) {
|
|
|
93
93
|
case 'high': return chalk.bgRed.white.bold(` HIGH `);
|
|
94
94
|
case 'medium': return chalk.bgYellow.black.bold(` MED `);
|
|
95
95
|
case 'low': return chalk.bgCyan.black.bold(` LOW `);
|
|
96
|
+
case 'verify': return chalk.bgBlue.white.bold(` VER? `);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
export function printFinding(finding, index) {
|
|
@@ -106,6 +107,7 @@ export function printSummaryBox(findings) {
|
|
|
106
107
|
const high = findings.filter((f) => f.severity === 'high').length;
|
|
107
108
|
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
108
109
|
const low = findings.filter((f) => f.severity === 'low').length;
|
|
110
|
+
const verify = findings.filter((f) => f.severity === 'verify').length;
|
|
109
111
|
console.log('');
|
|
110
112
|
console.log(chalk.dim(' ┌─────────────────────────────┐'));
|
|
111
113
|
console.log(chalk.dim(' │') + chalk.bold(' Analysis Summary ') + chalk.dim('│'));
|
|
@@ -113,6 +115,7 @@ export function printSummaryBox(findings) {
|
|
|
113
115
|
console.log(chalk.dim(' │') + ` ${chalk.red('●')} High ${chalk.red.bold(String(high).padStart(3))} ` + chalk.dim('│'));
|
|
114
116
|
console.log(chalk.dim(' │') + ` ${chalk.yellow('●')} Medium ${chalk.yellow.bold(String(medium).padStart(3))} ` + chalk.dim('│'));
|
|
115
117
|
console.log(chalk.dim(' │') + ` ${chalk.cyan('●')} Low ${chalk.cyan.bold(String(low).padStart(3))} ` + chalk.dim('│'));
|
|
118
|
+
console.log(chalk.dim(' │') + ` ${chalk.blue('●')} Verify ${chalk.blue.bold(String(verify).padStart(3))} ` + chalk.dim('│'));
|
|
116
119
|
console.log(chalk.dim(' ├─────────────────────────────┤'));
|
|
117
120
|
console.log(chalk.dim(' │') + ` Total ${chalk.bold(String(findings.length).padStart(3))} ` + chalk.dim('│'));
|
|
118
121
|
console.log(chalk.dim(' └─────────────────────────────┘'));
|
package/dist/core/config.js
CHANGED
|
@@ -40,6 +40,7 @@ export const InfrawiseConfigSchema = z.object({
|
|
|
40
40
|
}).optional(),
|
|
41
41
|
eventbridge: z.object({ enabled: z.boolean().optional().default(true) }).optional(),
|
|
42
42
|
rds: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
|
|
43
|
+
s3: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
|
|
43
44
|
kafka: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
|
|
44
45
|
cloudwatchLogs: z.object({
|
|
45
46
|
enabled: z.boolean().optional().default(false),
|
package/dist/graph/index.js
CHANGED
|
@@ -2,10 +2,12 @@ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [],
|
|
|
2
2
|
const nodes = [];
|
|
3
3
|
const edges = [];
|
|
4
4
|
const nodeIds = new Set();
|
|
5
|
+
const nodeMap = new Map();
|
|
5
6
|
function addNode(node) {
|
|
6
7
|
if (!nodeIds.has(node.id)) {
|
|
7
8
|
nodes.push(node);
|
|
8
9
|
nodeIds.add(node.id);
|
|
10
|
+
nodeMap.set(node.id, node);
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
// ── Database tables ──────────────────────────────────────────────────────
|
|
@@ -152,7 +154,7 @@ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [],
|
|
|
152
154
|
if (!nodeIds.has(lambdaId))
|
|
153
155
|
continue;
|
|
154
156
|
edges.push({ from: ruleId, to: lambdaId, type: 'triggers' });
|
|
155
|
-
const lambdaNodeRef =
|
|
157
|
+
const lambdaNodeRef = nodeMap.get(lambdaId);
|
|
156
158
|
if (lambdaNodeRef && lambdaNodeRef.type === 'lambda') {
|
|
157
159
|
const trigger = {
|
|
158
160
|
type: 'eventbridge',
|
|
@@ -182,13 +184,42 @@ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [],
|
|
|
182
184
|
multiAZ: db.multiAZ,
|
|
183
185
|
});
|
|
184
186
|
}
|
|
187
|
+
// ── S3 Buckets ────────────────────────────────────────────────────────────────
|
|
188
|
+
for (const bucket of servicesMeta.s3 ?? []) {
|
|
189
|
+
const bucketId = `bucket:aws:${bucket.name}`;
|
|
190
|
+
addNode({
|
|
191
|
+
id: bucketId,
|
|
192
|
+
type: 'bucket',
|
|
193
|
+
name: bucket.name,
|
|
194
|
+
provider: 'aws',
|
|
195
|
+
versioned: bucket.versioned,
|
|
196
|
+
encrypted: bucket.encrypted,
|
|
197
|
+
publicAccessBlocked: bucket.publicAccessBlocked,
|
|
198
|
+
});
|
|
199
|
+
for (const notification of bucket.notifications) {
|
|
200
|
+
const lambdaId = `lambda:aws:${notification.lambdaName}`;
|
|
201
|
+
if (!nodeMap.has(lambdaId))
|
|
202
|
+
continue;
|
|
203
|
+
edges.push({ from: bucketId, to: lambdaId, type: 'triggers' });
|
|
204
|
+
const lambdaNode = nodeMap.get(lambdaId);
|
|
205
|
+
if (lambdaNode && lambdaNode.type === 'lambda') {
|
|
206
|
+
lambdaNode.triggers = [
|
|
207
|
+
...(lambdaNode.triggers ?? []),
|
|
208
|
+
{
|
|
209
|
+
type: 's3',
|
|
210
|
+
sourceArn: bucket.arn,
|
|
211
|
+
sourceName: bucket.name,
|
|
212
|
+
eventShape: 'event.Records[0].s3.object.key',
|
|
213
|
+
events: notification.events,
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
185
219
|
// ── Code operations (functions + edges) ───────────────────────────────────
|
|
186
220
|
for (const op of operations) {
|
|
187
221
|
const funcNodeId = `function:${op.filePath}:${op.functionName}`;
|
|
188
|
-
|
|
189
|
-
nodes.push({ id: funcNodeId, type: 'function', name: op.functionName, file: op.filePath });
|
|
190
|
-
nodeIds.add(funcNodeId);
|
|
191
|
-
}
|
|
222
|
+
addNode({ id: funcNodeId, type: 'function', name: op.functionName, file: op.filePath });
|
|
192
223
|
// AWS service operations create edges to service nodes
|
|
193
224
|
if (op.serviceType === 'sqs') {
|
|
194
225
|
const queueId = `queue:aws:${op.target}`;
|
|
@@ -293,6 +324,9 @@ export function getLambdaNodes(graph) {
|
|
|
293
324
|
export function getEventBridgeRuleNodes(graph) {
|
|
294
325
|
return graph.nodes.filter((n) => n.type === 'eventbridge_rule');
|
|
295
326
|
}
|
|
327
|
+
export function getBucketNodes(graph) {
|
|
328
|
+
return graph.nodes.filter((n) => n.type === 'bucket');
|
|
329
|
+
}
|
|
296
330
|
export function getEdgesForNode(graph, nodeId) {
|
|
297
331
|
return graph.edges.filter((e) => e.from === nodeId || e.to === nodeId);
|
|
298
332
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { z } from 'zod';
|
|
|
8
8
|
import { logger } from '../core/index.js';
|
|
9
9
|
const { version } = JSON.parse(readFileSync(join(import.meta.dirname, '../../package.json'), 'utf8'));
|
|
10
10
|
import { summarizeFindings } from '../analyzers/index.js';
|
|
11
|
-
import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
|
|
11
|
+
import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getBucketNodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
|
|
12
12
|
// ── State ────────────────────────────────────────────────────────────────────
|
|
13
13
|
let currentGraph = { nodes: [], edges: [] };
|
|
14
14
|
let currentFindings = [];
|
|
@@ -31,7 +31,7 @@ function logged(name, fn) {
|
|
|
31
31
|
export function createMcpServer() {
|
|
32
32
|
const mcp = new McpServer({ name: 'infrawise', version });
|
|
33
33
|
mcp.registerTool('get_infra_overview', {
|
|
34
|
-
description: 'Returns a
|
|
34
|
+
description: 'Returns a compact infrastructure snapshot: service counts, all databases, queues, topics, secrets, lambdas, and high-severity findings. Call this first at the start of any database or infrastructure task to understand what services are in scope. Prefer this over get_graph_summary for quick orientation; use get_graph_summary only when you need every node, edge, and finding in full.',
|
|
35
35
|
inputSchema: z.object({}),
|
|
36
36
|
}, logged('get_infra_overview', async () => {
|
|
37
37
|
const tables = getTableNodes(currentGraph);
|
|
@@ -42,12 +42,14 @@ export function createMcpServer() {
|
|
|
42
42
|
const logGroups = getLogGroupNodes(currentGraph);
|
|
43
43
|
const lambdas = getLambdaNodes(currentGraph);
|
|
44
44
|
const functions = getFunctionNodes(currentGraph);
|
|
45
|
+
const buckets = getBucketNodes(currentGraph);
|
|
45
46
|
return toText({
|
|
46
47
|
summary: {
|
|
47
48
|
tables: tables.length, functions: functions.length,
|
|
48
49
|
queues: queues.length, topics: topics.length,
|
|
49
50
|
secrets: secrets.length, parameters: parameters.length,
|
|
50
51
|
logGroups: logGroups.length, lambdas: lambdas.length,
|
|
52
|
+
buckets: buckets.length,
|
|
51
53
|
totalNodes: currentGraph.nodes.length, totalEdges: currentGraph.edges.length,
|
|
52
54
|
findings: summarizeFindings(currentFindings),
|
|
53
55
|
},
|
|
@@ -58,11 +60,12 @@ export function createMcpServer() {
|
|
|
58
60
|
parameters: parameters.map((p) => ({ name: p.name, type: p.paramType, tier: p.tier })),
|
|
59
61
|
lambdas: lambdas.map((l) => ({ name: l.name, runtime: l.runtime, memoryMB: l.memoryMB })),
|
|
60
62
|
logGroups: logGroups.map((lg) => ({ name: lg.name, retentionDays: lg.retentionDays ?? 'never', errorCount: lg.errorCount })),
|
|
63
|
+
buckets: buckets.map((b) => ({ name: b.name, versioned: b.versioned, publicAccessBlocked: b.publicAccessBlocked })),
|
|
61
64
|
highFindings: currentFindings.filter((f) => f.severity === 'high').map((f) => ({ issue: f.issue, recommendation: f.recommendation })),
|
|
62
65
|
});
|
|
63
66
|
}));
|
|
64
67
|
mcp.registerTool('get_graph_summary', {
|
|
65
|
-
description: 'Returns
|
|
68
|
+
description: 'Returns every node (tables, functions, lambdas, queues, etc.), every edge (query, scan, triggers, publishes_to), and all findings. Use this when you need to trace relationships across multiple services or require the complete finding set — not just high-severity ones. For a quick overview use get_infra_overview instead.',
|
|
66
69
|
inputSchema: z.object({}),
|
|
67
70
|
}, logged('get_graph_summary', async () => toText({
|
|
68
71
|
nodes: currentGraph.nodes,
|
|
@@ -76,7 +79,7 @@ export function createMcpServer() {
|
|
|
76
79
|
},
|
|
77
80
|
})));
|
|
78
81
|
mcp.registerTool('analyze_function', {
|
|
79
|
-
description: '
|
|
82
|
+
description: 'Analyzes a single named function or Lambda handler for infrastructure issues: which tables it queries, how it queries them (scan vs query), queue publishing, secret access, and the correct event shape for each trigger (SQS, DynamoDB Streams, Kinesis, EventBridge). Call this before writing or reviewing a Lambda handler to get the exact trigger event shape and all findings scoped to this function. Returns found: false if the function name was not discovered during analysis.',
|
|
80
83
|
inputSchema: z.object({ function: z.string().describe('Function name to analyze') }),
|
|
81
84
|
}, logged('analyze_function', async ({ function: functionName }) => {
|
|
82
85
|
const funcNode = currentGraph.nodes.find((n) => n.type === 'function' && n.name === functionName);
|
|
@@ -107,7 +110,7 @@ export function createMcpServer() {
|
|
|
107
110
|
});
|
|
108
111
|
}));
|
|
109
112
|
mcp.registerTool('suggest_gsi', {
|
|
110
|
-
description: '
|
|
113
|
+
description: 'Generates a ready-to-use DynamoDB GSI definition — index name, partition key, projection type, billing mode — for a given table and attribute. Call this when a query pattern needs an index that does not exist yet, or when the analyzer flags a missing GSI finding. Does not verify whether the GSI already exists; check the table schema in get_infra_overview first.',
|
|
111
114
|
inputSchema: z.object({
|
|
112
115
|
table: z.string().describe('DynamoDB table name'),
|
|
113
116
|
attribute: z.string().describe('Attribute to create the GSI on'),
|
|
@@ -125,7 +128,7 @@ export function createMcpServer() {
|
|
|
125
128
|
});
|
|
126
129
|
}));
|
|
127
130
|
mcp.registerTool('postgres_index_suggestions', {
|
|
128
|
-
description: '
|
|
131
|
+
description: 'Generates the exact CREATE INDEX CONCURRENTLY SQL for a PostgreSQL table column, including a partial index variant and a post-creation ANALYZE reminder. Call this when the analyzer flags a missing index finding or when writing a query that filters on a column without an existing index. Does not verify whether the index already exists.',
|
|
129
132
|
inputSchema: z.object({
|
|
130
133
|
table: z.string().describe('PostgreSQL table name'),
|
|
131
134
|
column: z.string().describe('Column name to index'),
|
|
@@ -143,7 +146,7 @@ export function createMcpServer() {
|
|
|
143
146
|
});
|
|
144
147
|
}));
|
|
145
148
|
mcp.registerTool('suggest_mongo_index', {
|
|
146
|
-
description: '
|
|
149
|
+
description: 'Generates the exact db.collection.createIndex() command for a MongoDB field, plus compound and text index variants and an explain query to verify. Call this when a collection scan is flagged by the analyzer or when writing a query that filters on an unindexed field. Does not check whether the index already exists.',
|
|
147
150
|
inputSchema: z.object({
|
|
148
151
|
collection: z.string().describe('MongoDB collection name'),
|
|
149
152
|
field: z.string().describe('Field name to index'),
|
|
@@ -163,7 +166,7 @@ export function createMcpServer() {
|
|
|
163
166
|
});
|
|
164
167
|
}));
|
|
165
168
|
mcp.registerTool('mysql_index_suggestions', {
|
|
166
|
-
description: '
|
|
169
|
+
description: 'Generates the exact ALTER TABLE ADD INDEX SQL for a MySQL table column, including a composite variant and EXPLAIN guidance to verify the index is used. Call this when the analyzer flags a missing MySQL index or full table scan finding. Does not verify whether the index already exists.',
|
|
167
170
|
inputSchema: z.object({
|
|
168
171
|
table: z.string().describe('MySQL table name'),
|
|
169
172
|
column: z.string().describe('Column name to index'),
|
|
@@ -181,7 +184,7 @@ export function createMcpServer() {
|
|
|
181
184
|
});
|
|
182
185
|
}));
|
|
183
186
|
mcp.registerTool('get_queue_details', {
|
|
184
|
-
description: 'Returns all SQS queues with DLQ
|
|
187
|
+
description: 'Returns all SQS queues with DLQ presence, encryption status, approximate message count, and retention days. Call this when reviewing messaging architecture, investigating a message backlog, or checking DLQ coverage before adding a new consumer. Use get_infra_overview for a quick queue count only.',
|
|
185
188
|
inputSchema: z.object({}),
|
|
186
189
|
}, logged('get_queue_details', async () => {
|
|
187
190
|
const queues = getQueueNodes(currentGraph);
|
|
@@ -196,14 +199,14 @@ export function createMcpServer() {
|
|
|
196
199
|
});
|
|
197
200
|
}));
|
|
198
201
|
mcp.registerTool('get_topic_details', {
|
|
199
|
-
description: 'Returns all SNS topics with subscription
|
|
202
|
+
description: 'Returns all SNS topics with subscription count and encryption status. Call this when reviewing event fan-out patterns or checking whether a topic has the expected number of subscribers.',
|
|
200
203
|
inputSchema: z.object({}),
|
|
201
204
|
}, logged('get_topic_details', async () => {
|
|
202
205
|
const topics = getTopicNodes(currentGraph);
|
|
203
206
|
return toText({ total: topics.length, topics: topics.map((t) => ({ name: t.name, provider: t.provider, subscriptionCount: t.subscriptionCount, encrypted: t.encrypted })) });
|
|
204
207
|
}));
|
|
205
208
|
mcp.registerTool('get_secrets_overview', {
|
|
206
|
-
description: 'Returns all Secrets Manager secrets
|
|
209
|
+
description: 'Returns all Secrets Manager secrets with rotation status and rotation interval. Secret values are never returned. Call this when checking which secrets exist, confirming rotation is enabled before a security review, or identifying secrets that lack rotation.',
|
|
207
210
|
inputSchema: z.object({}),
|
|
208
211
|
}, logged('get_secrets_overview', async () => {
|
|
209
212
|
const secrets = getSecretNodes(currentGraph);
|
|
@@ -217,7 +220,7 @@ export function createMcpServer() {
|
|
|
217
220
|
});
|
|
218
221
|
}));
|
|
219
222
|
mcp.registerTool('get_parameter_overview', {
|
|
220
|
-
description: 'Returns all SSM Parameter Store parameters
|
|
223
|
+
description: 'Returns all SSM Parameter Store parameters with type (String, SecureString, StringList) and tier (Standard, Advanced). Parameter values are never returned. Call this when checking which config parameters exist for a service or verifying parameter types.',
|
|
221
224
|
inputSchema: z.object({}),
|
|
222
225
|
}, logged('get_parameter_overview', async () => {
|
|
223
226
|
const parameters = getParameterNodes(currentGraph);
|
|
@@ -227,7 +230,7 @@ export function createMcpServer() {
|
|
|
227
230
|
});
|
|
228
231
|
}));
|
|
229
232
|
mcp.registerTool('get_lambda_overview', {
|
|
230
|
-
description: 'Returns all Lambda functions
|
|
233
|
+
description: 'Returns all Lambda functions with runtime, memory (MB), timeout (sec), environment variable key names (values never returned), and event source triggers with the correct handler event shape for each. Call this when auditing Lambda configuration for default memory (128 MB) or high timeouts, or when you need the trigger event shape for a specific function without running analyze_function.',
|
|
231
234
|
inputSchema: z.object({}),
|
|
232
235
|
}, logged('get_lambda_overview', async () => {
|
|
233
236
|
const lambdas = getLambdaNodes(currentGraph);
|
|
@@ -243,7 +246,7 @@ export function createMcpServer() {
|
|
|
243
246
|
});
|
|
244
247
|
}));
|
|
245
248
|
mcp.registerTool('get_eventbridge_details', {
|
|
246
|
-
description: 'Returns all EventBridge rules
|
|
249
|
+
description: 'Returns all EventBridge rules with name, ENABLED/DISABLED state, schedule expression (rate/cron rules), event pattern (event-driven rules), and target Lambda function names. Call this when checking what schedule or event triggers a Lambda, or when reviewing rule coverage across the account.',
|
|
247
250
|
inputSchema: z.object({}),
|
|
248
251
|
}, logged('get_eventbridge_details', async () => {
|
|
249
252
|
const rules = getEventBridgeRuleNodes(currentGraph);
|
|
@@ -262,8 +265,29 @@ export function createMcpServer() {
|
|
|
262
265
|
})),
|
|
263
266
|
});
|
|
264
267
|
}));
|
|
268
|
+
mcp.registerTool('get_s3_overview', {
|
|
269
|
+
description: 'Returns all S3 buckets with versioning status, encryption, public access configuration, and security findings. Call this when checking which S3 buckets exist, reviewing bucket security posture, or before writing S3 upload/delete handlers to confirm the bucket name. Do NOT call when you only need a quick infrastructure count — use get_infra_overview for that. Object contents are never included.',
|
|
270
|
+
inputSchema: z.object({}),
|
|
271
|
+
}, logged('get_s3_overview', async () => {
|
|
272
|
+
const buckets = getBucketNodes(currentGraph);
|
|
273
|
+
const bucketFindings = currentFindings.filter((f) => f.metadata?.bucketName);
|
|
274
|
+
return toText({
|
|
275
|
+
total: buckets.length,
|
|
276
|
+
note: 'Object contents are never included.',
|
|
277
|
+
buckets: buckets.map((b) => ({
|
|
278
|
+
name: b.name,
|
|
279
|
+
provider: b.provider,
|
|
280
|
+
versioned: b.versioned,
|
|
281
|
+
encrypted: b.encrypted,
|
|
282
|
+
publicAccessBlocked: b.publicAccessBlocked,
|
|
283
|
+
findings: bucketFindings
|
|
284
|
+
.filter((f) => f.metadata.bucketName === b.name)
|
|
285
|
+
.map((f) => ({ severity: f.severity, issue: f.issue })),
|
|
286
|
+
})),
|
|
287
|
+
});
|
|
288
|
+
}));
|
|
265
289
|
mcp.registerTool('get_log_errors', {
|
|
266
|
-
description: 'Returns recent error
|
|
290
|
+
description: 'Returns recent error pattern summaries from CloudWatch log groups: pattern counts and frequencies grouped by log group. Raw log messages are never returned. Use the optional logGroup filter to scope to one group by name substring. Call this when investigating errors or identifying log groups with no retention policy.',
|
|
267
291
|
inputSchema: z.object({ logGroup: z.string().describe('Filter to a specific log group name (optional)').optional() }),
|
|
268
292
|
}, logged('get_log_errors', async ({ logGroup: filterName }) => {
|
|
269
293
|
const logGroups = getLogGroupNodes(currentGraph).filter((lg) => !filterName || lg.name.includes(filterName));
|
|
@@ -291,7 +315,7 @@ export function createServer(port = 3000) {
|
|
|
291
315
|
name: 'io.github.Sidd27/infrawise',
|
|
292
316
|
display_name: 'Infrawise',
|
|
293
317
|
version,
|
|
294
|
-
description: 'Infrastructure analysis MCP server — scans DynamoDB, PostgreSQL, MySQL, MongoDB, Lambda, SQS, SNS, EventBridge, Secrets Manager, SSM, CloudWatch, Terraform, CDK, and source code. Surfaces missing indexes, DLQ gaps, Lambda misconfig, and correct trigger event shapes.',
|
|
318
|
+
description: 'Infrastructure analysis MCP server — scans DynamoDB, PostgreSQL, MySQL, MongoDB, S3, Lambda, SQS, SNS, EventBridge, Secrets Manager, SSM, CloudWatch, Terraform, CDK, and source code. Surfaces missing indexes, DLQ gaps, Lambda misconfig, S3 security posture, and correct trigger event shapes.',
|
|
295
319
|
homepage: 'https://github.com/Sidd27/infrawise',
|
|
296
320
|
repository: 'https://github.com/Sidd27/infrawise',
|
|
297
321
|
transports: [{ type: 'streamable-http', url: `http://localhost:${port}/mcp` }],
|
|
@@ -299,7 +323,7 @@ export function createServer(port = 3000) {
|
|
|
299
323
|
'get_infra_overview', 'get_graph_summary', 'analyze_function',
|
|
300
324
|
'suggest_gsi', 'postgres_index_suggestions', 'suggest_mongo_index', 'mysql_index_suggestions',
|
|
301
325
|
'get_queue_details', 'get_topic_details', 'get_secrets_overview', 'get_parameter_overview',
|
|
302
|
-
'get_lambda_overview', 'get_eventbridge_details', 'get_log_errors',
|
|
326
|
+
'get_lambda_overview', 'get_eventbridge_details', 'get_s3_overview', 'get_log_errors',
|
|
303
327
|
],
|
|
304
328
|
}));
|
|
305
329
|
fastify.post('/mcp', async (request, reply) => {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infrawise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"mcpName": "io.github.Sidd27/infrawise",
|
|
5
|
-
"description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
|
|
5
|
+
"description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, S3, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
8
8
|
"mcp-server",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"sqs",
|
|
15
15
|
"sns",
|
|
16
16
|
"lambda",
|
|
17
|
+
"s3",
|
|
17
18
|
"eventbridge",
|
|
18
19
|
"cloudwatch",
|
|
19
20
|
"secrets-manager",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@aws-sdk/client-cloudwatch-logs": "^3.1048.0",
|
|
67
|
+
"@aws-sdk/client-s3": "^3.1048.0",
|
|
66
68
|
"@aws-sdk/client-dynamodb": "^3.1048.0",
|
|
67
69
|
"@aws-sdk/client-eventbridge": "^3.1051.0",
|
|
68
70
|
"@aws-sdk/client-lambda": "^3.1048.0",
|
|
@@ -74,9 +76,9 @@
|
|
|
74
76
|
"@aws-sdk/credential-providers": "^3.1048.0",
|
|
75
77
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
76
78
|
"chalk": "^5.6.2",
|
|
77
|
-
"commander": "^
|
|
79
|
+
"commander": "^15.0.0",
|
|
78
80
|
"fastify": "^5.8.5",
|
|
79
|
-
"inquirer": "^
|
|
81
|
+
"inquirer": "^14.0.2",
|
|
80
82
|
"js-yaml": "^4.1.0",
|
|
81
83
|
"mongodb": "^7.2.0",
|
|
82
84
|
"mysql2": "^3.9.0",
|
|
@@ -107,5 +109,10 @@
|
|
|
107
109
|
],
|
|
108
110
|
"publishConfig": {
|
|
109
111
|
"access": "public"
|
|
112
|
+
},
|
|
113
|
+
"pnpm": {
|
|
114
|
+
"overrides": {
|
|
115
|
+
"qs": ">=6.15.2"
|
|
116
|
+
}
|
|
110
117
|
}
|
|
111
118
|
}
|