infrawise 0.7.0 → 0.7.2
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 +134 -148
- package/dist/adapters/{dynamodb.js → aws/dynamodb.js} +1 -1
- package/dist/adapters/aws/index.js +3 -0
- package/dist/adapters/{logs.js → aws/logs.js} +1 -1
- 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/cli/commands/analyze.js +51 -13
- package/dist/cli/commands/auth.js +1 -1
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/doctor.js +6 -6
- package/dist/cli/commands/stdio.js +28 -0
- package/dist/cli/index.js +12 -0
- package/dist/server/index.js +30 -14
- package/package.json +19 -4
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://github.com/Sidd27/infrawise/actions/workflows/npm-publish.yml)
|
|
5
5
|
[](https://github.com/Sidd27/infrawise/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](https://glama.ai/mcp/servers/Sidd27/infrawise)
|
|
7
8
|
|
|
8
9
|
**Understand your infrastructure, not just your code.**
|
|
9
10
|
|
|
@@ -22,12 +23,14 @@ AI coding assistants can read your source files but have no deterministic knowle
|
|
|
22
23
|
Infrawise replaces guessing with infrastructure-aware context.
|
|
23
24
|
|
|
24
25
|
**Without Infrawise**, an AI assistant might:
|
|
26
|
+
|
|
25
27
|
- Suggest a `.scan()` on your Orders table that has 50M rows
|
|
26
28
|
- Recommend adding a GSI on `status` that you already have
|
|
27
29
|
- Write a `SELECT *` when you need to keep query cost low
|
|
28
30
|
- Not notice that 5 functions are already hammering the same partition key
|
|
29
31
|
|
|
30
32
|
**With Infrawise**, it knows:
|
|
33
|
+
|
|
31
34
|
- Your exact table schemas, partition keys, sort keys, and GSIs
|
|
32
35
|
- Which functions query which tables and how
|
|
33
36
|
- Which patterns are already flagged as high severity
|
|
@@ -157,34 +160,56 @@ To let Claude Code manage the server lifecycle automatically:
|
|
|
157
160
|
|
|
158
161
|
### MCP tools
|
|
159
162
|
|
|
160
|
-
| Tool
|
|
161
|
-
|
|
162
|
-
| `get_infra_overview`
|
|
163
|
-
| `get_graph_summary`
|
|
164
|
-
| `analyze_function`
|
|
165
|
-
| `suggest_gsi`
|
|
166
|
-
| `postgres_index_suggestions` | Exact `CREATE INDEX` SQL for your actual table
|
|
167
|
-
| `suggest_mongo_index`
|
|
168
|
-
| `mysql_index_suggestions`
|
|
169
|
-
| `get_queue_details`
|
|
170
|
-
| `get_topic_details`
|
|
171
|
-
| `get_secrets_overview`
|
|
172
|
-
| `get_parameter_overview`
|
|
173
|
-
| `get_lambda_overview`
|
|
174
|
-
| `get_eventbridge_details`
|
|
175
|
-
| `get_log_errors`
|
|
163
|
+
| Tool | What it provides |
|
|
164
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `get_infra_overview` | Complete snapshot — all services, counts, and high-severity findings |
|
|
166
|
+
| `get_graph_summary` | Full infrastructure graph — all nodes, edges, and findings |
|
|
167
|
+
| `analyze_function` | Issues in a specific function — scans, missing indexes, N+1, trigger event shapes |
|
|
168
|
+
| `suggest_gsi` | Exact GSI config for a DynamoDB table + attribute |
|
|
169
|
+
| `postgres_index_suggestions` | Exact `CREATE INDEX` SQL for your actual table |
|
|
170
|
+
| `suggest_mongo_index` | Exact `createIndex` command for a MongoDB collection + field |
|
|
171
|
+
| `mysql_index_suggestions` | Exact `ALTER TABLE ADD INDEX` SQL for your MySQL table |
|
|
172
|
+
| `get_queue_details` | SQS queues — DLQ status, encryption, message counts |
|
|
173
|
+
| `get_topic_details` | SNS topics — subscription counts and protocols |
|
|
174
|
+
| `get_secrets_overview` | Secrets Manager — names and rotation status (values never included) |
|
|
175
|
+
| `get_parameter_overview` | SSM Parameter Store — names, types, tiers (values never included) |
|
|
176
|
+
| `get_lambda_overview` | Lambda functions — runtime, memory, timeout, triggers (SQS/DynamoDB/Kinesis/EventBridge), env var key names |
|
|
177
|
+
| `get_eventbridge_details` | EventBridge rules — name, state, schedule/event pattern, target functions |
|
|
178
|
+
| `get_log_errors` | CloudWatch error patterns and counts (no raw log messages) |
|
|
176
179
|
|
|
177
180
|
---
|
|
178
181
|
|
|
179
182
|
## CLI reference
|
|
180
183
|
|
|
181
|
-
| Command
|
|
182
|
-
|
|
183
|
-
| `infrawise init`
|
|
184
|
-
| `infrawise auth`
|
|
185
|
-
| `infrawise analyze` | Scan repo + AWS, build graph, print findings
|
|
186
|
-
| `infrawise dev`
|
|
187
|
-
| `infrawise
|
|
184
|
+
| Command | What it does |
|
|
185
|
+
| ------------------- | ---------------------------------------------------------------------------- |
|
|
186
|
+
| `infrawise init` | Detect AWS + repo, generate `infrawise.yaml` |
|
|
187
|
+
| `infrawise auth` | Select or switch AWS profile |
|
|
188
|
+
| `infrawise analyze` | Scan repo + AWS, build graph, print findings |
|
|
189
|
+
| `infrawise dev` | Start MCP server — auto-analyzes if no cache, watches files for live refresh |
|
|
190
|
+
| `infrawise stdio` | Start MCP server on stdio transport (for Claude Desktop) |
|
|
191
|
+
| `infrawise doctor` | Validate AWS access, DB connectivity, and config |
|
|
192
|
+
|
|
193
|
+
### `infrawise analyze` options
|
|
194
|
+
|
|
195
|
+
| Flag | Description |
|
|
196
|
+
| --------------------- | ---------------------------------------------------------------------- |
|
|
197
|
+
| `-c, --config <path>` | Path to `infrawise.yaml` (default: `infrawise.yaml`) |
|
|
198
|
+
| `-r, --repo <path>` | Repository to scan (default: current directory) |
|
|
199
|
+
| `--no-cache` | Skip reading/writing the cache |
|
|
200
|
+
| `-o, --output <path>` | Save findings as a markdown report, e.g. `report.md` |
|
|
201
|
+
| `--severity <level>` | Only show findings at or above this level: `high` \| `medium` \| `low` |
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Export a shareable findings report
|
|
205
|
+
infrawise analyze --output report.md
|
|
206
|
+
|
|
207
|
+
# Only show high-severity issues
|
|
208
|
+
infrawise analyze --severity high
|
|
209
|
+
|
|
210
|
+
# High-severity issues only, saved to a file
|
|
211
|
+
infrawise analyze --severity high --output report.md
|
|
212
|
+
```
|
|
188
213
|
|
|
189
214
|
---
|
|
190
215
|
|
|
@@ -206,12 +231,12 @@ Full example:
|
|
|
206
231
|
project: payments-service
|
|
207
232
|
|
|
208
233
|
aws:
|
|
209
|
-
profile: default
|
|
234
|
+
profile: default # AWS profile from ~/.aws/credentials
|
|
210
235
|
region: ap-south-1
|
|
211
236
|
|
|
212
237
|
dynamodb:
|
|
213
238
|
enabled: true
|
|
214
|
-
includeTables:
|
|
239
|
+
includeTables: # omit to include all tables
|
|
215
240
|
- Orders
|
|
216
241
|
- Users
|
|
217
242
|
|
|
@@ -221,11 +246,11 @@ postgres:
|
|
|
221
246
|
|
|
222
247
|
mysql:
|
|
223
248
|
enabled: false
|
|
224
|
-
connectionString:
|
|
249
|
+
connectionString: ''
|
|
225
250
|
|
|
226
251
|
mongodb:
|
|
227
252
|
enabled: false
|
|
228
|
-
connectionString:
|
|
253
|
+
connectionString: ''
|
|
229
254
|
|
|
230
255
|
sqs:
|
|
231
256
|
enabled: true
|
|
@@ -235,16 +260,16 @@ sns:
|
|
|
235
260
|
|
|
236
261
|
ssm:
|
|
237
262
|
enabled: true
|
|
238
|
-
paths: []
|
|
263
|
+
paths: [] # filter by prefix e.g. ["/myapp/prod"]
|
|
239
264
|
|
|
240
265
|
secretsManager:
|
|
241
266
|
enabled: true
|
|
242
267
|
|
|
243
268
|
lambda:
|
|
244
269
|
enabled: true
|
|
245
|
-
includeFunctions:
|
|
246
|
-
-
|
|
247
|
-
-
|
|
270
|
+
includeFunctions: # omit to include all functions
|
|
271
|
+
- myFunction
|
|
272
|
+
- anotherFunction
|
|
248
273
|
|
|
249
274
|
eventbridge:
|
|
250
275
|
enabled: true
|
|
@@ -274,10 +299,7 @@ Infrawise is **read-only**. Minimum IAM policy required:
|
|
|
274
299
|
"Statement": [
|
|
275
300
|
{
|
|
276
301
|
"Effect": "Allow",
|
|
277
|
-
"Action": [
|
|
278
|
-
"dynamodb:ListTables",
|
|
279
|
-
"dynamodb:DescribeTable"
|
|
280
|
-
],
|
|
302
|
+
"Action": ["dynamodb:ListTables", "dynamodb:DescribeTable"],
|
|
281
303
|
"Resource": "*"
|
|
282
304
|
}
|
|
283
305
|
]
|
|
@@ -313,36 +335,36 @@ Infrawise has two analysis layers:
|
|
|
313
335
|
|
|
314
336
|
Works from AWS APIs, database schema introspection, and IaC files — no dependency on application code:
|
|
315
337
|
|
|
316
|
-
| Service
|
|
317
|
-
|
|
318
|
-
| DynamoDB schema
|
|
319
|
-
| PostgreSQL / MySQL schema
|
|
320
|
-
| MongoDB schema
|
|
321
|
-
| SQS
|
|
322
|
-
| Kafka (kafkajs)
|
|
323
|
-
| Secrets Manager
|
|
324
|
-
| Lambda
|
|
325
|
-
| EventBridge
|
|
326
|
-
| RDS
|
|
327
|
-
| CloudWatch Logs
|
|
328
|
-
| Terraform / CloudFormation / CDK | IaC drift vs deployed state
|
|
338
|
+
| Service | What it checks |
|
|
339
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
340
|
+
| DynamoDB schema | Tables, GSIs, partition keys |
|
|
341
|
+
| PostgreSQL / MySQL schema | Tables, indexes, column types |
|
|
342
|
+
| MongoDB schema | Collections, indexes |
|
|
343
|
+
| SQS | Missing DLQs, unencrypted queues, large backlogs |
|
|
344
|
+
| Kafka (kafkajs) | Producer/consumer topic mapping from code |
|
|
345
|
+
| Secrets Manager | Missing secret rotation |
|
|
346
|
+
| Lambda | Default memory (128 MB), high timeouts, triggers (SQS/DynamoDB/Kinesis/EventBridge), missing DLQ on trigger source |
|
|
347
|
+
| EventBridge | Rules, schedules, event patterns, target Lambda functions |
|
|
348
|
+
| RDS | Publicly accessible, no backups, unencrypted, no deletion protection, single-AZ |
|
|
349
|
+
| CloudWatch Logs | Log groups with no retention policy |
|
|
350
|
+
| Terraform / CloudFormation / CDK | IaC drift vs deployed state |
|
|
329
351
|
|
|
330
352
|
### Code correlation analysis (TypeScript / JavaScript)
|
|
331
353
|
|
|
332
354
|
Uses [ts-morph](https://ts-morph.com/) AST analysis to detect which functions call which tables and how:
|
|
333
355
|
|
|
334
|
-
| Analyzer
|
|
335
|
-
|
|
336
|
-
| Full Table Scan (DynamoDB) | High
|
|
337
|
-
| Missing GSI
|
|
338
|
-
| Hot Partition
|
|
339
|
-
| Missing Index (PostgreSQL) | Medium
|
|
340
|
-
| N+1 Query
|
|
341
|
-
| Large SELECT
|
|
342
|
-
| Missing MySQL Index
|
|
343
|
-
| MySQL Full Table Scan
|
|
344
|
-
| Missing Mongo Index
|
|
345
|
-
| Collection Scan
|
|
356
|
+
| Analyzer | Severity | What it detects |
|
|
357
|
+
| -------------------------- | -------- | --------------------------------------------- |
|
|
358
|
+
| Full Table Scan (DynamoDB) | High | `.scan()` calls without filters |
|
|
359
|
+
| Missing GSI | Medium | Queries on attributes without a matching GSI |
|
|
360
|
+
| Hot Partition | Medium | 5+ distinct code paths hitting the same table |
|
|
361
|
+
| Missing Index (PostgreSQL) | Medium | Tables queried without indexes |
|
|
362
|
+
| N+1 Query | Medium | Repeated query patterns from ORM loops |
|
|
363
|
+
| Large SELECT | Low | `SELECT *` usage |
|
|
364
|
+
| Missing MySQL Index | Medium | MySQL tables queried without indexes |
|
|
365
|
+
| MySQL Full Table Scan | High | Full table scan patterns in MySQL queries |
|
|
366
|
+
| Missing Mongo Index | Medium | Collections queried without secondary indexes |
|
|
367
|
+
| Collection Scan | High | `find()` calls without filter predicates |
|
|
346
368
|
|
|
347
369
|
Non-TypeScript/JavaScript projects still get full value from infrastructure-level analyzers — code correlation (function-to-table mapping, N+1 patterns) is skipped.
|
|
348
370
|
|
|
@@ -377,29 +399,52 @@ Infrawise does not use an LLM to analyze your infrastructure. All extraction and
|
|
|
377
399
|
|
|
378
400
|
## Architecture overview
|
|
379
401
|
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
402
|
+
```mermaid
|
|
403
|
+
flowchart LR
|
|
404
|
+
subgraph IN["Your Infrastructure & Code"]
|
|
405
|
+
direction TB
|
|
406
|
+
D["DynamoDB"]
|
|
407
|
+
L["Lambda · SQS · SNS\nEventBridge · RDS"]
|
|
408
|
+
S["Secrets Manager · SSM\nCloudWatch"]
|
|
409
|
+
P["PostgreSQL · MySQL"]
|
|
410
|
+
M["MongoDB"]
|
|
411
|
+
T["Terraform · CDK\nCloudFormation"]
|
|
412
|
+
C["TypeScript / JS"]
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
A["Adapters"]
|
|
416
|
+
G["Graph Engine"]
|
|
417
|
+
AN["23 Analyzers"]
|
|
418
|
+
CA["Cache"]
|
|
419
|
+
|
|
420
|
+
subgraph SV["infrawise dev"]
|
|
421
|
+
MCP["MCP Server\nlocalhost:3000/mcp"]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
subgraph AI["AI Coding Assistants"]
|
|
425
|
+
direction TB
|
|
426
|
+
CC["Claude Code"]
|
|
427
|
+
CU["Cursor"]
|
|
428
|
+
WS["Windsurf"]
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
D & L & S & P & M & T & C --> A
|
|
432
|
+
A --> G --> AN --> CA --> MCP
|
|
433
|
+
MCP --> CC & CU & WS
|
|
434
|
+
|
|
435
|
+
classDef aws fill:#FF9900,stroke:#232F3E,color:#000
|
|
436
|
+
classDef db fill:#336791,stroke:#1a3a5c,color:#fff
|
|
437
|
+
classDef iac fill:#7B42BC,stroke:#4a2080,color:#fff
|
|
438
|
+
classDef code fill:#3178C6,stroke:#1a4a80,color:#fff
|
|
439
|
+
classDef iw fill:#1a1a2e,stroke:#e94560,color:#fff
|
|
440
|
+
classDef ai fill:#10a37f,stroke:#0a6b54,color:#fff
|
|
441
|
+
|
|
442
|
+
class D,L,S aws
|
|
443
|
+
class P,M db
|
|
444
|
+
class T iac
|
|
445
|
+
class C code
|
|
446
|
+
class A,G,AN,CA,MCP iw
|
|
447
|
+
class CC,CU,WS ai
|
|
403
448
|
```
|
|
404
449
|
|
|
405
450
|
### Source layout
|
|
@@ -409,8 +454,10 @@ src/
|
|
|
409
454
|
types.ts Shared type definitions
|
|
410
455
|
core/ Config (Zod + YAML), logger (Pino), local cache
|
|
411
456
|
graph/ Graph engine — nodes, edges, builder
|
|
412
|
-
adapters/
|
|
413
|
-
|
|
457
|
+
adapters/
|
|
458
|
+
aws/ DynamoDB, Lambda, SQS/SNS/SSM/Secrets/EventBridge/RDS, CloudWatch
|
|
459
|
+
db/ PostgreSQL, MySQL, MongoDB
|
|
460
|
+
iac/ Terraform, CDK, CloudFormation (local file parsing)
|
|
414
461
|
analyzers/ 23 rule-based analyzers
|
|
415
462
|
context/ Repository scanner (ts-morph AST)
|
|
416
463
|
server/ Fastify MCP server (@modelcontextprotocol/sdk, Streamable HTTP)
|
|
@@ -430,20 +477,7 @@ src/
|
|
|
430
477
|
|
|
431
478
|
## Roadmap
|
|
432
479
|
|
|
433
|
-
Feature roadmap is tracked in the [GitHub Project](https://github.com/users/Sidd27/projects/1).
|
|
434
|
-
|
|
435
|
-
### Planned
|
|
436
|
-
- Runtime tracing integration
|
|
437
|
-
- Incremental analysis for large monorepos
|
|
438
|
-
- Kubernetes workload graphing
|
|
439
|
-
- VS Code extension
|
|
440
|
-
- Infrastructure drift detection
|
|
441
|
-
- MSK (Amazon Managed Streaming for Apache Kafka) — cluster metadata + topic listing via MSK API and Kafka admin client
|
|
442
|
-
|
|
443
|
-
### Under consideration
|
|
444
|
-
- OpenTelemetry integration
|
|
445
|
-
- CI/CD reporting mode
|
|
446
|
-
- Multi-repository graph correlation
|
|
480
|
+
Feature roadmap is tracked in the [GitHub Project](https://github.com/users/Sidd27/projects/1). Feature requests and upvotes welcome.
|
|
447
481
|
|
|
448
482
|
---
|
|
449
483
|
|
|
@@ -455,47 +489,7 @@ The `demo/localstack/` directory runs infrawise against real AWS APIs emulated l
|
|
|
455
489
|
|
|
456
490
|
## Contributing
|
|
457
491
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
Node.js 24+, pnpm 9+, AWS CLI (for integration testing).
|
|
461
|
-
|
|
462
|
-
### Setup
|
|
463
|
-
|
|
464
|
-
```bash
|
|
465
|
-
git clone https://github.com/Sidd27/infrawise
|
|
466
|
-
cd infrawise
|
|
467
|
-
pnpm install
|
|
468
|
-
pnpm build
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Development workflow
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
pnpm build # compile
|
|
475
|
-
pnpm test # run all tests
|
|
476
|
-
pnpm typecheck # TypeScript strict check
|
|
477
|
-
pnpm lint # ESLint
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
### Adding a new analyzer
|
|
481
|
-
|
|
482
|
-
1. Create your analyzer in `src/analyzers/`
|
|
483
|
-
2. Implement the `Analyzer` interface:
|
|
484
|
-
```ts
|
|
485
|
-
export class MyAnalyzer implements Analyzer {
|
|
486
|
-
name = 'MyAnalyzer';
|
|
487
|
-
async analyze(graph: SystemGraph): Promise<Finding[]> { ... }
|
|
488
|
-
}
|
|
489
|
-
```
|
|
490
|
-
3. Export it from `src/analyzers/index.ts`
|
|
491
|
-
4. Add tests in `src/analyzers/__tests__/`
|
|
492
|
-
|
|
493
|
-
### Adding a new database adapter
|
|
494
|
-
|
|
495
|
-
1. Create your extractor as `src/adapters/yourdb.ts`
|
|
496
|
-
2. Export a function returning `Promise<YourTableMetadata[]>`
|
|
497
|
-
3. Add the metadata type to `src/types.ts` if needed
|
|
498
|
-
4. Wire it into `src/cli/commands/analyze.ts`
|
|
492
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for a full walkthrough — including how to add a new service adapter, a new analyzer, and the PR checklist.
|
|
499
493
|
|
|
500
494
|
### Releasing
|
|
501
495
|
|
|
@@ -508,14 +502,6 @@ pnpm release 1.5.0 # explicit version
|
|
|
508
502
|
|
|
509
503
|
Bumps `package.json`, commits, tags, pushes, and creates a draft GitHub release with notes from commit messages. Then publish the draft on GitHub to trigger npm publish.
|
|
510
504
|
|
|
511
|
-
### PR checklist
|
|
512
|
-
|
|
513
|
-
- `pnpm lint` passes
|
|
514
|
-
- `pnpm typecheck` passes
|
|
515
|
-
- `pnpm test` passes
|
|
516
|
-
- New analyzers have unit tests with mock graph data
|
|
517
|
-
- No hardcoded AWS regions or credentials
|
|
518
|
-
|
|
519
505
|
---
|
|
520
506
|
|
|
521
507
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, } from '@aws-sdk/client-dynamodb';
|
|
2
2
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
3
|
-
import { DynamoDBError, logger } from '
|
|
3
|
+
import { DynamoDBError, logger } from '../../core/index.js';
|
|
4
4
|
function createDynamoClient(config) {
|
|
5
5
|
const region = config.aws?.region ?? 'us-east-1';
|
|
6
6
|
const profile = config.aws?.profile;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CloudWatchLogsClient, DescribeLogGroupsCommand, FilterLogEventsCommand, } from '@aws-sdk/client-cloudwatch-logs';
|
|
2
2
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
3
|
-
import { logger } from '
|
|
3
|
+
import { logger } from '../../core/index.js';
|
|
4
4
|
// Hard caps to prevent context bloat
|
|
5
5
|
const MAX_LOG_GROUPS = 50;
|
|
6
6
|
const MAX_EVENTS_PER_GROUP = 50;
|
|
@@ -6,7 +6,7 @@ import { LambdaClient, ListFunctionsCommand, ListEventSourceMappingsCommand, } f
|
|
|
6
6
|
import { EventBridgeClient, ListRulesCommand, ListTargetsByRuleCommand, } from '@aws-sdk/client-eventbridge';
|
|
7
7
|
import { RDSClient, DescribeDBInstancesCommand, } from '@aws-sdk/client-rds';
|
|
8
8
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
9
|
-
import { logger } from '
|
|
9
|
+
import { logger } from '../../core/index.js';
|
|
10
10
|
function clientConfig(cfg) {
|
|
11
11
|
const region = cfg.region ?? 'us-east-1';
|
|
12
12
|
const base = { region };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MongoClient } from 'mongodb';
|
|
2
|
-
import { InfrawiseError, logger } from '
|
|
2
|
+
import { InfrawiseError, logger } from '../../core/index.js';
|
|
3
3
|
const SYSTEM_DATABASES = new Set(['admin', 'local', 'config']);
|
|
4
4
|
export class MongoConnectionError extends InfrawiseError {
|
|
5
5
|
constructor(details) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mysql from 'mysql2/promise';
|
|
2
|
-
import { InfrawiseError, logger } from '
|
|
2
|
+
import { InfrawiseError, logger } from '../../core/index.js';
|
|
3
3
|
const SYSTEM_SCHEMAS = new Set(['information_schema', 'performance_schema', 'mysql', 'sys']);
|
|
4
4
|
export class MySQLConnectionError extends InfrawiseError {
|
|
5
5
|
constructor(details) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pool } from 'pg';
|
|
2
|
-
import { PostgresConnectionError, logger } from '
|
|
2
|
+
import { PostgresConnectionError, logger } from '../../core/index.js';
|
|
3
3
|
export async function extractPostgresMetadata(connectionString) {
|
|
4
4
|
const pool = new Pool({
|
|
5
5
|
connectionString,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './terraform.js';
|
|
@@ -1,18 +1,43 @@
|
|
|
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';
|
|
12
13
|
import { scanRepository } from '../../context/index.js';
|
|
13
14
|
import { buildGraph } from '../../graph/index.js';
|
|
14
15
|
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';
|
|
15
16
|
import { printFinding, printSummaryBox, log, printHeader } from '../utils.js';
|
|
17
|
+
const SEVERITY_ORDER = { high: 3, medium: 2, low: 1 };
|
|
18
|
+
function buildMarkdownReport(findings, projectName) {
|
|
19
|
+
const date = new Date().toISOString().split('T')[0];
|
|
20
|
+
const high = findings.filter((f) => f.severity === 'high');
|
|
21
|
+
const medium = findings.filter((f) => f.severity === 'medium');
|
|
22
|
+
const low = findings.filter((f) => f.severity === 'low');
|
|
23
|
+
const renderGroup = (label, emoji, group) => {
|
|
24
|
+
if (group.length === 0)
|
|
25
|
+
return '';
|
|
26
|
+
const rows = group.map((f) => `| ${f.issue} | ${f.recommendation} |`).join('\n');
|
|
27
|
+
return `\n## ${emoji} ${label} (${group.length})\n\n| Issue | Recommendation |\n|---|---|\n${rows}\n`;
|
|
28
|
+
};
|
|
29
|
+
return [
|
|
30
|
+
`# infrawise report — ${projectName}`,
|
|
31
|
+
`_Generated: ${date}_`,
|
|
32
|
+
'',
|
|
33
|
+
`**${findings.length} finding(s)**: ${high.length} high · ${medium.length} medium · ${low.length} low`,
|
|
34
|
+
renderGroup('High severity', '🔴', high),
|
|
35
|
+
renderGroup('Medium severity', '🟡', medium),
|
|
36
|
+
renderGroup('Low severity', '🟢', low),
|
|
37
|
+
'',
|
|
38
|
+
'_Generated by [infrawise](https://github.com/Sidd27/infrawise) — MCP server for AWS infrastructure analysis_',
|
|
39
|
+
].filter((l) => l !== undefined).join('\n');
|
|
40
|
+
}
|
|
16
41
|
function mkSpinner(text) {
|
|
17
42
|
return ora({ text: chalk.dim(text), color: 'cyan' }).start();
|
|
18
43
|
}
|
|
@@ -28,6 +53,7 @@ export async function runAnalyze(options = {}) {
|
|
|
28
53
|
process.exit(1);
|
|
29
54
|
}
|
|
30
55
|
const repoPath = options.repo ?? process.cwd();
|
|
56
|
+
const minSeverity = options.severity ? SEVERITY_ORDER[options.severity] ?? 1 : 0;
|
|
31
57
|
const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
|
|
32
58
|
const dynamoMeta = [];
|
|
33
59
|
const postgresMeta = [];
|
|
@@ -278,18 +304,30 @@ export async function runAnalyze(options = {}) {
|
|
|
278
304
|
writeCache('operations', operations);
|
|
279
305
|
writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
|
|
280
306
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
307
|
+
const displayFindings = minSeverity > 0
|
|
308
|
+
? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
|
|
309
|
+
: findings;
|
|
281
310
|
console.log('');
|
|
282
|
-
if (
|
|
283
|
-
|
|
311
|
+
if (displayFindings.length === 0) {
|
|
312
|
+
const msg = minSeverity > 0 ? `No ${options.severity} (or higher) severity issues found.` : 'Your infrastructure looks clean.';
|
|
313
|
+
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim(msg)}`);
|
|
284
314
|
}
|
|
285
315
|
else {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
316
|
+
const filterNote = minSeverity > 0 ? chalk.dim(` (${options.severity}+ only)`) : '';
|
|
317
|
+
console.log(chalk.bold(` Findings`) + chalk.dim(` ${displayFindings.length} total`) + filterNote);
|
|
318
|
+
displayFindings.forEach((f, i) => printFinding(f, i));
|
|
319
|
+
printSummaryBox(displayFindings);
|
|
320
|
+
if (displayFindings.some((f) => f.severity === 'high')) {
|
|
290
321
|
console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
|
|
291
322
|
}
|
|
292
323
|
}
|
|
324
|
+
if (options.output) {
|
|
325
|
+
const report = buildMarkdownReport(displayFindings, config.project);
|
|
326
|
+
const outPath = path.resolve(options.output);
|
|
327
|
+
fs.writeFileSync(outPath, report, 'utf8');
|
|
328
|
+
console.log('');
|
|
329
|
+
log.success('Report saved', outPath);
|
|
330
|
+
}
|
|
293
331
|
console.log('');
|
|
294
332
|
log.dim(`Results cached in .infrawise/cache/`);
|
|
295
333
|
log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
|
|
@@ -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,12 @@ 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
13
|
import { printHeader } from '../utils.js';
|
|
14
14
|
async function runCheck(label, fn) {
|
|
15
15
|
const spin = ora({ text: chalk.dim(label), color: 'cyan' }).start();
|
|
@@ -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/server/index.js
CHANGED
|
@@ -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);
|
|
@@ -62,7 +62,7 @@ export function createMcpServer() {
|
|
|
62
62
|
});
|
|
63
63
|
}));
|
|
64
64
|
mcp.registerTool('get_graph_summary', {
|
|
65
|
-
description: 'Returns
|
|
65
|
+
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
66
|
inputSchema: z.object({}),
|
|
67
67
|
}, logged('get_graph_summary', async () => toText({
|
|
68
68
|
nodes: currentGraph.nodes,
|
|
@@ -76,7 +76,7 @@ export function createMcpServer() {
|
|
|
76
76
|
},
|
|
77
77
|
})));
|
|
78
78
|
mcp.registerTool('analyze_function', {
|
|
79
|
-
description: '
|
|
79
|
+
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
80
|
inputSchema: z.object({ function: z.string().describe('Function name to analyze') }),
|
|
81
81
|
}, logged('analyze_function', async ({ function: functionName }) => {
|
|
82
82
|
const funcNode = currentGraph.nodes.find((n) => n.type === 'function' && n.name === functionName);
|
|
@@ -107,7 +107,7 @@ export function createMcpServer() {
|
|
|
107
107
|
});
|
|
108
108
|
}));
|
|
109
109
|
mcp.registerTool('suggest_gsi', {
|
|
110
|
-
description: '
|
|
110
|
+
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
111
|
inputSchema: z.object({
|
|
112
112
|
table: z.string().describe('DynamoDB table name'),
|
|
113
113
|
attribute: z.string().describe('Attribute to create the GSI on'),
|
|
@@ -125,7 +125,7 @@ export function createMcpServer() {
|
|
|
125
125
|
});
|
|
126
126
|
}));
|
|
127
127
|
mcp.registerTool('postgres_index_suggestions', {
|
|
128
|
-
description: '
|
|
128
|
+
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
129
|
inputSchema: z.object({
|
|
130
130
|
table: z.string().describe('PostgreSQL table name'),
|
|
131
131
|
column: z.string().describe('Column name to index'),
|
|
@@ -143,7 +143,7 @@ export function createMcpServer() {
|
|
|
143
143
|
});
|
|
144
144
|
}));
|
|
145
145
|
mcp.registerTool('suggest_mongo_index', {
|
|
146
|
-
description: '
|
|
146
|
+
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
147
|
inputSchema: z.object({
|
|
148
148
|
collection: z.string().describe('MongoDB collection name'),
|
|
149
149
|
field: z.string().describe('Field name to index'),
|
|
@@ -163,7 +163,7 @@ export function createMcpServer() {
|
|
|
163
163
|
});
|
|
164
164
|
}));
|
|
165
165
|
mcp.registerTool('mysql_index_suggestions', {
|
|
166
|
-
description: '
|
|
166
|
+
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
167
|
inputSchema: z.object({
|
|
168
168
|
table: z.string().describe('MySQL table name'),
|
|
169
169
|
column: z.string().describe('Column name to index'),
|
|
@@ -181,7 +181,7 @@ export function createMcpServer() {
|
|
|
181
181
|
});
|
|
182
182
|
}));
|
|
183
183
|
mcp.registerTool('get_queue_details', {
|
|
184
|
-
description: 'Returns all SQS queues with DLQ
|
|
184
|
+
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
185
|
inputSchema: z.object({}),
|
|
186
186
|
}, logged('get_queue_details', async () => {
|
|
187
187
|
const queues = getQueueNodes(currentGraph);
|
|
@@ -196,14 +196,14 @@ export function createMcpServer() {
|
|
|
196
196
|
});
|
|
197
197
|
}));
|
|
198
198
|
mcp.registerTool('get_topic_details', {
|
|
199
|
-
description: 'Returns all SNS topics with subscription
|
|
199
|
+
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
200
|
inputSchema: z.object({}),
|
|
201
201
|
}, logged('get_topic_details', async () => {
|
|
202
202
|
const topics = getTopicNodes(currentGraph);
|
|
203
203
|
return toText({ total: topics.length, topics: topics.map((t) => ({ name: t.name, provider: t.provider, subscriptionCount: t.subscriptionCount, encrypted: t.encrypted })) });
|
|
204
204
|
}));
|
|
205
205
|
mcp.registerTool('get_secrets_overview', {
|
|
206
|
-
description: 'Returns all Secrets Manager secrets
|
|
206
|
+
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
207
|
inputSchema: z.object({}),
|
|
208
208
|
}, logged('get_secrets_overview', async () => {
|
|
209
209
|
const secrets = getSecretNodes(currentGraph);
|
|
@@ -217,7 +217,7 @@ export function createMcpServer() {
|
|
|
217
217
|
});
|
|
218
218
|
}));
|
|
219
219
|
mcp.registerTool('get_parameter_overview', {
|
|
220
|
-
description: 'Returns all SSM Parameter Store parameters
|
|
220
|
+
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
221
|
inputSchema: z.object({}),
|
|
222
222
|
}, logged('get_parameter_overview', async () => {
|
|
223
223
|
const parameters = getParameterNodes(currentGraph);
|
|
@@ -227,7 +227,7 @@ export function createMcpServer() {
|
|
|
227
227
|
});
|
|
228
228
|
}));
|
|
229
229
|
mcp.registerTool('get_lambda_overview', {
|
|
230
|
-
description: 'Returns all Lambda functions
|
|
230
|
+
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
231
|
inputSchema: z.object({}),
|
|
232
232
|
}, logged('get_lambda_overview', async () => {
|
|
233
233
|
const lambdas = getLambdaNodes(currentGraph);
|
|
@@ -243,7 +243,7 @@ export function createMcpServer() {
|
|
|
243
243
|
});
|
|
244
244
|
}));
|
|
245
245
|
mcp.registerTool('get_eventbridge_details', {
|
|
246
|
-
description: 'Returns all EventBridge rules
|
|
246
|
+
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
247
|
inputSchema: z.object({}),
|
|
248
248
|
}, logged('get_eventbridge_details', async () => {
|
|
249
249
|
const rules = getEventBridgeRuleNodes(currentGraph);
|
|
@@ -263,7 +263,7 @@ export function createMcpServer() {
|
|
|
263
263
|
});
|
|
264
264
|
}));
|
|
265
265
|
mcp.registerTool('get_log_errors', {
|
|
266
|
-
description: 'Returns recent error
|
|
266
|
+
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
267
|
inputSchema: z.object({ logGroup: z.string().describe('Filter to a specific log group name (optional)').optional() }),
|
|
268
268
|
}, logged('get_log_errors', async ({ logGroup: filterName }) => {
|
|
269
269
|
const logGroups = getLogGroupNodes(currentGraph).filter((lg) => !filterName || lg.name.includes(filterName));
|
|
@@ -286,6 +286,22 @@ export function createServer(port = 3000) {
|
|
|
286
286
|
graphEdges: currentGraph.edges.length,
|
|
287
287
|
findings: currentFindings.length,
|
|
288
288
|
}));
|
|
289
|
+
fastify.get('/.well-known/mcp/server-card.json', async () => ({
|
|
290
|
+
schema_version: '2026-01',
|
|
291
|
+
name: 'io.github.Sidd27/infrawise',
|
|
292
|
+
display_name: 'Infrawise',
|
|
293
|
+
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.',
|
|
295
|
+
homepage: 'https://github.com/Sidd27/infrawise',
|
|
296
|
+
repository: 'https://github.com/Sidd27/infrawise',
|
|
297
|
+
transports: [{ type: 'streamable-http', url: `http://localhost:${port}/mcp` }],
|
|
298
|
+
tools: [
|
|
299
|
+
'get_infra_overview', 'get_graph_summary', 'analyze_function',
|
|
300
|
+
'suggest_gsi', 'postgres_index_suggestions', 'suggest_mongo_index', 'mysql_index_suggestions',
|
|
301
|
+
'get_queue_details', 'get_topic_details', 'get_secrets_overview', 'get_parameter_overview',
|
|
302
|
+
'get_lambda_overview', 'get_eventbridge_details', 'get_log_errors',
|
|
303
|
+
],
|
|
304
|
+
}));
|
|
289
305
|
fastify.post('/mcp', async (request, reply) => {
|
|
290
306
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
291
307
|
reply.raw.on('close', () => transport.close());
|
package/package.json
CHANGED
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infrawise",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
|
|
6
6
|
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"mcp-server",
|
|
9
|
+
"model-context-protocol",
|
|
7
10
|
"infrastructure",
|
|
11
|
+
"infrastructure-analysis",
|
|
8
12
|
"aws",
|
|
9
13
|
"dynamodb",
|
|
10
14
|
"sqs",
|
|
11
15
|
"sns",
|
|
12
16
|
"lambda",
|
|
17
|
+
"eventbridge",
|
|
13
18
|
"cloudwatch",
|
|
14
19
|
"secrets-manager",
|
|
15
20
|
"ssm",
|
|
16
21
|
"postgresql",
|
|
17
22
|
"mysql",
|
|
18
23
|
"mongodb",
|
|
19
|
-
"
|
|
24
|
+
"serverless",
|
|
20
25
|
"claude",
|
|
21
26
|
"claude-code",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
27
|
+
"cursor",
|
|
28
|
+
"ai-coding",
|
|
29
|
+
"ai-tools",
|
|
24
30
|
"devtools",
|
|
31
|
+
"cli",
|
|
25
32
|
"terraform",
|
|
26
33
|
"cloudformation",
|
|
27
34
|
"cdk"
|
|
28
35
|
],
|
|
36
|
+
"agentskills": {
|
|
37
|
+
"infrawise": "./AGENTS.md"
|
|
38
|
+
},
|
|
29
39
|
"homepage": "https://github.com/Sidd27/infrawise#readme",
|
|
30
40
|
"repository": {
|
|
31
41
|
"type": "git",
|
|
@@ -97,5 +107,10 @@
|
|
|
97
107
|
],
|
|
98
108
|
"publishConfig": {
|
|
99
109
|
"access": "public"
|
|
110
|
+
},
|
|
111
|
+
"pnpm": {
|
|
112
|
+
"overrides": {
|
|
113
|
+
"qs": ">=6.15.2"
|
|
114
|
+
}
|
|
100
115
|
}
|
|
101
116
|
}
|