infrawise 0.13.2 → 0.15.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 +33 -4
- package/dist/adapters/aws/index.js +1 -0
- package/dist/adapters/aws/metrics.js +94 -0
- package/dist/adapters/aws/services.js +197 -1
- package/dist/adapters/db/mysql.js +37 -1
- package/dist/adapters/db/postgres.js +49 -2
- package/dist/adapters/iac/terraform.js +66 -1
- package/dist/analyzers/aws-services.js +87 -0
- package/dist/analyzers/index.js +1 -1
- package/dist/cli/commands/analyze.js +46 -4
- package/dist/cli/commands/discover.js +5 -0
- package/dist/cli/commands/serve.js +5 -0
- package/dist/core/config.js +18 -0
- package/dist/graph/index.js +96 -10
- package/dist/server/index.js +149 -3
- package/package.json +6 -1
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, getBucketNodes, getAPINodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
|
|
11
|
+
import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getBucketNodes, getAPINodes, getStackOutputNodes, getUserPoolNodes, getStreamNodes, getKafkaClusterNodes, getCacheClusterNodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
|
|
12
12
|
// ── State ────────────────────────────────────────────────────────────────────
|
|
13
13
|
let currentGraph = { nodes: [], edges: [] };
|
|
14
14
|
let currentFindings = [];
|
|
@@ -70,6 +70,7 @@ export function createMcpServer() {
|
|
|
70
70
|
const lambdas = getLambdaNodes(currentGraph);
|
|
71
71
|
const functions = getFunctionNodes(currentGraph);
|
|
72
72
|
const buckets = getBucketNodes(currentGraph);
|
|
73
|
+
const userPools = getUserPoolNodes(currentGraph);
|
|
73
74
|
return toText({
|
|
74
75
|
configured,
|
|
75
76
|
...(configured ? {} : { setupHint: NOT_CONFIGURED_HINT }),
|
|
@@ -84,6 +85,9 @@ export function createMcpServer() {
|
|
|
84
85
|
logGroups: logGroups.length,
|
|
85
86
|
lambdas: lambdas.length,
|
|
86
87
|
buckets: buckets.length,
|
|
88
|
+
userPools: userPools.length,
|
|
89
|
+
streams: getStreamNodes(currentGraph).length,
|
|
90
|
+
cacheClusters: getCacheClusterNodes(currentGraph).length,
|
|
87
91
|
totalNodes: currentGraph.nodes.length,
|
|
88
92
|
totalEdges: currentGraph.edges.length,
|
|
89
93
|
findings: summarizeFindings(currentFindings),
|
|
@@ -300,7 +304,7 @@ export function createMcpServer() {
|
|
|
300
304
|
});
|
|
301
305
|
}));
|
|
302
306
|
mcp.registerTool('get_queue_details', {
|
|
303
|
-
description: 'Returns all SQS queues with DLQ presence, encryption status, FIFO type (isFifo), visibility timeout, approximate message count, and retention days. When isFifo is true, all SendMessage calls must include a MessageGroupId. Call this when reviewing messaging architecture, investigating a message backlog, checking DLQ coverage, or verifying visibility timeout is set correctly relative to Lambda timeout (should be 6× the Lambda timeout). Use get_infra_overview for a quick queue count only.',
|
|
307
|
+
description: 'Returns all SQS queues with DLQ presence, encryption status, FIFO type (isFifo), visibility timeout, approximate message count, and retention days. When isFifo is true, all SendMessage calls must include a MessageGroupId. Call this when reviewing messaging architecture, investigating a message backlog, checking DLQ coverage, or verifying visibility timeout is set correctly relative to Lambda timeout (should be 6× the Lambda timeout). Use get_infra_overview for a quick queue count only. When runtime signals are enabled, oldestMessageAgeSec reports the age of the oldest message from CloudWatch.',
|
|
304
308
|
inputSchema: z.object({}),
|
|
305
309
|
}, logged('get_queue_details', async () => {
|
|
306
310
|
const queues = getQueueNodes(currentGraph);
|
|
@@ -316,6 +320,7 @@ export function createMcpServer() {
|
|
|
316
320
|
visibilityTimeoutSec: q.visibilityTimeoutSec,
|
|
317
321
|
approximateMessages: q.approximateMessages,
|
|
318
322
|
retentionDays: q.retentionDays,
|
|
323
|
+
oldestMessageAgeSec: q.oldestMessageAgeSec,
|
|
319
324
|
findings: queueFindings
|
|
320
325
|
.filter((f) => f.metadata.queueName === q.name)
|
|
321
326
|
.map((f) => ({ severity: f.severity, issue: f.issue })),
|
|
@@ -375,7 +380,7 @@ export function createMcpServer() {
|
|
|
375
380
|
});
|
|
376
381
|
}));
|
|
377
382
|
mcp.registerTool('get_lambda_overview', {
|
|
378
|
-
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.',
|
|
383
|
+
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. When runtime signals are enabled, recentThrottles and recentErrors report CloudWatch counts for the analysis window.',
|
|
379
384
|
inputSchema: z.object({}),
|
|
380
385
|
}, logged('get_lambda_overview', async () => {
|
|
381
386
|
const lambdas = getLambdaNodes(currentGraph);
|
|
@@ -391,6 +396,8 @@ export function createMcpServer() {
|
|
|
391
396
|
envVarCount: l.envVarKeys?.length ?? 0,
|
|
392
397
|
envVarKeys: l.envVarKeys,
|
|
393
398
|
roleArn: l.roleArn,
|
|
399
|
+
recentThrottles: l.recentThrottles,
|
|
400
|
+
recentErrors: l.recentErrors,
|
|
394
401
|
triggers: (l.triggers ?? []).map((t) => ({
|
|
395
402
|
type: t.type,
|
|
396
403
|
source: t.sourceName,
|
|
@@ -480,6 +487,140 @@ export function createMcpServer() {
|
|
|
480
487
|
})),
|
|
481
488
|
});
|
|
482
489
|
}));
|
|
490
|
+
mcp.registerTool('get_table_schema', {
|
|
491
|
+
description: 'Returns the full schema for specific tables or collections by name: columns with data types and nullability, primary keys, foreign keys (join paths), indexes, DynamoDB partition/sort keys, and MongoDB estimated document counts. Accepts short names ("orders" matches "public.orders") and is case-insensitive. Call this after get_infra_overview when you need column-level detail to write a SQL query, DynamoDB expression, or MongoDB filter for specific tables — instead of pulling every schema with get_graph_summary. Do NOT call for a table inventory; use get_infra_overview for that. Row data is never included.',
|
|
492
|
+
inputSchema: z.object({
|
|
493
|
+
tables: z
|
|
494
|
+
.array(z.string())
|
|
495
|
+
.min(1)
|
|
496
|
+
.max(20)
|
|
497
|
+
.describe('Table or collection names to fetch schemas for'),
|
|
498
|
+
}),
|
|
499
|
+
}, logged('get_table_schema', async ({ tables }) => {
|
|
500
|
+
const tableNodes = getTableNodes(currentGraph);
|
|
501
|
+
const indexNamesFor = (nodeId) => currentGraph.edges
|
|
502
|
+
.filter((e) => e.from === nodeId && e.type === 'uses_index')
|
|
503
|
+
.map((e) => currentGraph.nodes.find((n) => n.id === e.to))
|
|
504
|
+
.filter((n) => n?.type === 'index')
|
|
505
|
+
.map((n) => n.name);
|
|
506
|
+
const results = tables.map((requested) => {
|
|
507
|
+
const lower = requested.toLowerCase();
|
|
508
|
+
const matches = tableNodes.filter((t) => {
|
|
509
|
+
const name = t.name.toLowerCase();
|
|
510
|
+
return name === lower || name.split('.').pop() === lower;
|
|
511
|
+
});
|
|
512
|
+
if (matches.length === 0) {
|
|
513
|
+
const suggestions = tableNodes
|
|
514
|
+
.filter((t) => t.name.toLowerCase().includes(lower))
|
|
515
|
+
.map((t) => t.name)
|
|
516
|
+
.slice(0, 5);
|
|
517
|
+
return { requested, found: false, ...(suggestions.length ? { suggestions } : {}) };
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
requested,
|
|
521
|
+
found: true,
|
|
522
|
+
matches: matches.map((t) => ({
|
|
523
|
+
name: t.name,
|
|
524
|
+
databaseType: t.databaseType,
|
|
525
|
+
...(t.columns ? { columns: t.columns } : {}),
|
|
526
|
+
...(t.primaryKeys?.length ? { primaryKeys: t.primaryKeys } : {}),
|
|
527
|
+
...(t.foreignKeys?.length ? { foreignKeys: t.foreignKeys } : {}),
|
|
528
|
+
...(t.partitionKey ? { partitionKey: t.partitionKey } : {}),
|
|
529
|
+
...(t.sortKey ? { sortKey: t.sortKey } : {}),
|
|
530
|
+
...(t.estimatedCount !== undefined ? { estimatedCount: t.estimatedCount } : {}),
|
|
531
|
+
indexes: indexNamesFor(t.id),
|
|
532
|
+
})),
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
return toText({
|
|
536
|
+
note: 'Row data is never included.',
|
|
537
|
+
tables: results,
|
|
538
|
+
});
|
|
539
|
+
}));
|
|
540
|
+
mcp.registerTool('get_cache_overview', {
|
|
541
|
+
description: 'Returns all ElastiCache clusters with engine, version, node type, node count, in-transit and at-rest encryption status, replication group, and automatic failover state. Call this before writing cache client code (TLS is required when transit encryption is on — rediss:// for Redis) or when reviewing cache availability and security posture. Cached data is never read or included.',
|
|
542
|
+
inputSchema: z.object({}),
|
|
543
|
+
}, logged('get_cache_overview', async () => {
|
|
544
|
+
const caches = getCacheClusterNodes(currentGraph);
|
|
545
|
+
const cacheFindings = currentFindings.filter((f) => f.metadata?.cacheClusterId);
|
|
546
|
+
return toText({
|
|
547
|
+
total: caches.length,
|
|
548
|
+
note: 'Cached data is never included.',
|
|
549
|
+
caches: caches.map((c) => ({
|
|
550
|
+
id: c.name,
|
|
551
|
+
engine: c.engine,
|
|
552
|
+
engineVersion: c.engineVersion,
|
|
553
|
+
nodeType: c.nodeType,
|
|
554
|
+
numNodes: c.numNodes,
|
|
555
|
+
transitEncryption: c.transitEncryption,
|
|
556
|
+
atRestEncryption: c.atRestEncryption,
|
|
557
|
+
replicationGroupId: c.replicationGroupId,
|
|
558
|
+
automaticFailover: c.automaticFailover,
|
|
559
|
+
findings: cacheFindings
|
|
560
|
+
.filter((f) => f.metadata.cacheClusterId === c.name)
|
|
561
|
+
.map((f) => ({ severity: f.severity, issue: f.issue })),
|
|
562
|
+
})),
|
|
563
|
+
});
|
|
564
|
+
}));
|
|
565
|
+
mcp.registerTool('get_stream_details', {
|
|
566
|
+
description: 'Returns all Kinesis data streams (status, shard count, retention hours, encryption, capacity mode) and Amazon MSK clusters (state, cluster type, Kafka version, broker count). Call this when writing Kinesis producer or consumer code, checking whether a stream is PROVISIONED or ON_DEMAND before writing PutRecord calls, or reviewing streaming architecture. For Kafka topic-level producer/consumer mappings extracted from application code, use get_topic_details instead.',
|
|
567
|
+
inputSchema: z.object({}),
|
|
568
|
+
}, logged('get_stream_details', async () => {
|
|
569
|
+
const streams = getStreamNodes(currentGraph);
|
|
570
|
+
const clusters = getKafkaClusterNodes(currentGraph);
|
|
571
|
+
return toText({
|
|
572
|
+
totalStreams: streams.length,
|
|
573
|
+
totalKafkaClusters: clusters.length,
|
|
574
|
+
streams: streams.map((s) => ({
|
|
575
|
+
name: s.name,
|
|
576
|
+
status: s.status,
|
|
577
|
+
shardCount: s.shardCount,
|
|
578
|
+
retentionHours: s.retentionHours,
|
|
579
|
+
encrypted: s.encrypted,
|
|
580
|
+
mode: s.mode,
|
|
581
|
+
})),
|
|
582
|
+
kafkaClusters: clusters.map((c) => ({
|
|
583
|
+
name: c.name,
|
|
584
|
+
state: c.state,
|
|
585
|
+
clusterType: c.clusterType,
|
|
586
|
+
kafkaVersion: c.kafkaVersion,
|
|
587
|
+
brokerNodes: c.brokerNodes,
|
|
588
|
+
})),
|
|
589
|
+
});
|
|
590
|
+
}));
|
|
591
|
+
mcp.registerTool('get_cognito_overview', {
|
|
592
|
+
description: 'Returns all Cognito user pools with MFA configuration and every app client config: allowed auth flows, OAuth flows/scopes, callback URLs, token validity, and whether the client has a secret (SDK auth calls must send SECRET_HASH when true). Client secret values are never returned. Call this before writing any Cognito sign-in, sign-up, or token-refresh code to use the correct auth flow and client settings. Do NOT call to look up users or tokens — infrawise never reads user data.',
|
|
593
|
+
inputSchema: z.object({}),
|
|
594
|
+
}, logged('get_cognito_overview', async () => {
|
|
595
|
+
const pools = getUserPoolNodes(currentGraph);
|
|
596
|
+
return toText({
|
|
597
|
+
total: pools.length,
|
|
598
|
+
note: 'Client secret values and user data are never included.',
|
|
599
|
+
userPools: pools.map((p) => ({
|
|
600
|
+
name: p.name,
|
|
601
|
+
id: p.poolId,
|
|
602
|
+
mfaConfiguration: p.mfaConfiguration,
|
|
603
|
+
clients: p.clients ?? [],
|
|
604
|
+
})),
|
|
605
|
+
});
|
|
606
|
+
}));
|
|
607
|
+
mcp.registerTool('get_stack_outputs', {
|
|
608
|
+
description: 'Returns all stack outputs and cross-stack exports parsed from local IaC files: Terraform output blocks and CloudFormation/CDK Outputs sections, with name, description, export name, and the raw value expression. Call this when wiring cross-stack references (Fn::ImportValue, terraform_remote_state) or when you need the exported name of a resource defined in another stack. Do NOT call for live resource attributes — outputs come from local IaC files, not the deployed stack.',
|
|
609
|
+
inputSchema: z.object({}),
|
|
610
|
+
}, logged('get_stack_outputs', async () => {
|
|
611
|
+
const outputs = getStackOutputNodes(currentGraph);
|
|
612
|
+
return toText({
|
|
613
|
+
total: outputs.length,
|
|
614
|
+
outputs: outputs.map((o) => ({
|
|
615
|
+
name: o.name,
|
|
616
|
+
description: o.description,
|
|
617
|
+
exportName: o.exportName,
|
|
618
|
+
value: o.value,
|
|
619
|
+
source: o.iacSource,
|
|
620
|
+
file: o.file,
|
|
621
|
+
})),
|
|
622
|
+
});
|
|
623
|
+
}));
|
|
483
624
|
return mcp;
|
|
484
625
|
}
|
|
485
626
|
// ── Fastify server ────────────────────────────────────────────────────────────
|
|
@@ -519,6 +660,11 @@ export function createServer(port = 3000) {
|
|
|
519
660
|
'get_s3_overview',
|
|
520
661
|
'get_api_routes',
|
|
521
662
|
'get_log_errors',
|
|
663
|
+
'get_stack_outputs',
|
|
664
|
+
'get_cognito_overview',
|
|
665
|
+
'get_stream_details',
|
|
666
|
+
'get_cache_overview',
|
|
667
|
+
'get_table_schema',
|
|
522
668
|
],
|
|
523
669
|
}));
|
|
524
670
|
fastify.post('/mcp', async (request, reply) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infrawise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"mcpName": "io.github.Sidd27/infrawise",
|
|
5
5
|
"description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, S3, API Gateway, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
|
|
6
6
|
"keywords": [
|
|
@@ -76,10 +76,15 @@
|
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@aws-sdk/client-api-gateway": "^3.1068.0",
|
|
78
78
|
"@aws-sdk/client-apigatewayv2": "^3.1068.0",
|
|
79
|
+
"@aws-sdk/client-cloudwatch": "^3.1079.0",
|
|
79
80
|
"@aws-sdk/client-cloudwatch-logs": "^3.1048.0",
|
|
81
|
+
"@aws-sdk/client-cognito-identity-provider": "^3.1079.0",
|
|
80
82
|
"@aws-sdk/client-dynamodb": "^3.1048.0",
|
|
83
|
+
"@aws-sdk/client-elasticache": "^3.1079.0",
|
|
81
84
|
"@aws-sdk/client-eventbridge": "^3.1051.0",
|
|
82
85
|
"@aws-sdk/client-iam": "^3.1073.0",
|
|
86
|
+
"@aws-sdk/client-kafka": "^3.1079.0",
|
|
87
|
+
"@aws-sdk/client-kinesis": "^3.1079.0",
|
|
83
88
|
"@aws-sdk/client-lambda": "^3.1048.0",
|
|
84
89
|
"@aws-sdk/client-rds": "^3.1048.0",
|
|
85
90
|
"@aws-sdk/client-s3": "^3.1048.0",
|