@gnaws/mcp 0.1.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/dist/app.js ADDED
@@ -0,0 +1,771 @@
1
+ #!/usr/bin/env node
2
+ function _array_like_to_array(arr, len) {
3
+ if (len == null || len > arr.length) len = arr.length;
4
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
5
+ return arr2;
6
+ }
7
+ function _array_without_holes(arr) {
8
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
9
+ }
10
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
11
+ try {
12
+ var info = gen[key](arg);
13
+ var value = info.value;
14
+ } catch (error) {
15
+ reject(error);
16
+ return;
17
+ }
18
+ if (info.done) resolve(value);
19
+ else Promise.resolve(value).then(_next, _throw);
20
+ }
21
+ function _async_to_generator(fn) {
22
+ return function() {
23
+ var self = this, args = arguments;
24
+ return new Promise(function(resolve, reject) {
25
+ var gen = fn.apply(self, args);
26
+ function _next(value) {
27
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
28
+ }
29
+ function _throw(err) {
30
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
31
+ }
32
+ _next(undefined);
33
+ });
34
+ };
35
+ }
36
+ function _instanceof(left, right) {
37
+ "@swc/helpers - instanceof";
38
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
39
+ return !!right[Symbol.hasInstance](left);
40
+ } else return left instanceof right;
41
+ }
42
+ function _iterable_to_array(iter) {
43
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
44
+ return Array.from(iter);
45
+ }
46
+ }
47
+ function _non_iterable_spread() {
48
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
49
+ }
50
+ function _to_consumable_array(arr) {
51
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
52
+ }
53
+ function _ts_generator(thisArg, body) {
54
+ var f, y, t, _ = {
55
+ label: 0,
56
+ sent: function() {
57
+ if (t[0] & 1) throw t[1];
58
+ return t[1];
59
+ },
60
+ trys: [],
61
+ ops: []
62
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
63
+ return d(g, "next", {
64
+ value: verb(0)
65
+ }), d(g, "throw", {
66
+ value: verb(1)
67
+ }), d(g, "return", {
68
+ value: verb(2)
69
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
70
+ value: function() {
71
+ return this;
72
+ }
73
+ }), g;
74
+ function verb(n) {
75
+ return function(v) {
76
+ return step([
77
+ n,
78
+ v
79
+ ]);
80
+ };
81
+ }
82
+ function step(op) {
83
+ if (f) throw new TypeError("Generator is already executing.");
84
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
85
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
86
+ if (y = 0, t) op = [
87
+ op[0] & 2,
88
+ t.value
89
+ ];
90
+ switch(op[0]){
91
+ case 0:
92
+ case 1:
93
+ t = op;
94
+ break;
95
+ case 4:
96
+ _.label++;
97
+ return {
98
+ value: op[1],
99
+ done: false
100
+ };
101
+ case 5:
102
+ _.label++;
103
+ y = op[1];
104
+ op = [
105
+ 0
106
+ ];
107
+ continue;
108
+ case 7:
109
+ op = _.ops.pop();
110
+ _.trys.pop();
111
+ continue;
112
+ default:
113
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
114
+ _ = 0;
115
+ continue;
116
+ }
117
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
118
+ _.label = op[1];
119
+ break;
120
+ }
121
+ if (op[0] === 6 && _.label < t[1]) {
122
+ _.label = t[1];
123
+ t = op;
124
+ break;
125
+ }
126
+ if (t && _.label < t[2]) {
127
+ _.label = t[2];
128
+ _.ops.push(op);
129
+ break;
130
+ }
131
+ if (t[2]) _.ops.pop();
132
+ _.trys.pop();
133
+ continue;
134
+ }
135
+ op = body.call(thisArg, _);
136
+ } catch (e) {
137
+ op = [
138
+ 6,
139
+ e
140
+ ];
141
+ y = 0;
142
+ } finally{
143
+ f = t = 0;
144
+ }
145
+ if (op[0] & 5) throw op[1];
146
+ return {
147
+ value: op[0] ? op[1] : void 0,
148
+ done: true
149
+ };
150
+ }
151
+ }
152
+ function _unsupported_iterable_to_array(o, minLen) {
153
+ if (!o) return;
154
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
155
+ var n = Object.prototype.toString.call(o).slice(8, -1);
156
+ if (n === "Object" && o.constructor) n = o.constructor.name;
157
+ if (n === "Map" || n === "Set") return Array.from(n);
158
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
159
+ }
160
+ var _process_env_LOG_LEVEL;
161
+ import { existsSync, readFileSync, writeFileSync } from "fs";
162
+ import { join } from "node:path";
163
+ // Load .env if present (local dev convenience — ignored if no .env exists)
164
+ if (existsSync(".env")) {
165
+ process.loadEnvFile(".env");
166
+ }
167
+ import { fromIni } from "@aws-sdk/credential-providers";
168
+ import { Inventory, GraphBuilder, MarkdownExporter, GexfExporter, JsonExporter, LiveServiceFactory, CacheServiceFactory, CacheWriter, UnusedDetector } from "@gnaws/core";
169
+ import { McpServer } from "@modelcontextprotocol/server";
170
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
171
+ import * as z from "zod/v4";
172
+ import { createLogger } from "./logger.js";
173
+ // State
174
+ var inventory;
175
+ var graph;
176
+ var logLevel = (_process_env_LOG_LEVEL = process.env.LOG_LEVEL) !== null && _process_env_LOG_LEVEL !== void 0 ? _process_env_LOG_LEVEL : "info";
177
+ var logger = createLogger(logLevel);
178
+ var server = new McpServer({
179
+ "name": "gnaws-mcp",
180
+ "version": "0.1.0"
181
+ });
182
+ // ─── Tool: status ────────────────────────────────────────────────────────────
183
+ server.registerTool("status", {
184
+ "title": "Server Status",
185
+ "description": "Check the current state of the gnaws server: whether resources are loaded, graph size, and available actions. Call this first to understand what tools are available.",
186
+ "inputSchema": z.object({})
187
+ }, function() {
188
+ var lines = [];
189
+ if (graph && inventory) {
190
+ var regions = inventory.getAccountRegions();
191
+ lines.push("✓ Resources loaded.", " Nodes: ".concat(String(graph.order)), " Edges: ".concat(String(graph.size)), " Regions: ".concat(String(regions.length)), "", "Available tools:", " • 'detect' — find unused/orphaned resources", " • 'export' — export graph to gexf, json, or markdown", " • 'dump' — save raw data to disk", " • 'regions' — list enabled AWS regions", " • 'scan' — re-scan with a different profile", " • 'load' — load a different dump");
192
+ } else {
193
+ lines.push("✗ No resources loaded yet.", "", "To get started, call one of:", " • 'scan' — scan live AWS resources (requires an AWS profile name)", " • 'load' — load from a dump directory (requires a path)");
194
+ }
195
+ return {
196
+ "content": [
197
+ {
198
+ "type": "text",
199
+ "text": lines.join("\n")
200
+ }
201
+ ]
202
+ };
203
+ });
204
+ // ─── Tool: scan ──────────────────────────────────────────────────────────────
205
+ server.registerTool("scan", {
206
+ "title": "Scan AWS Resources",
207
+ "description": "Scan AWS resources live using an AWS profile. This builds an in-memory graph of all resources and their relationships. This is a long-running operation (may take several minutes). After scanning, use 'regions' to list regions, 'detect' to find unused resources, 'export' to export the graph, or 'dump' to save raw data for offline use.",
208
+ "inputSchema": z.object({
209
+ "profile": z.string().describe("AWS profile name from ~/.aws/credentials (e.g., 'default', 'production')")
210
+ })
211
+ }, function(param, ctx) {
212
+ var profile = param.profile;
213
+ return _async_to_generator(function() {
214
+ var _ctx_mcpReq__meta, progressToken, reportProgress, credentials, error;
215
+ return _ts_generator(this, function(_state) {
216
+ switch(_state.label){
217
+ case 0:
218
+ progressToken = (_ctx_mcpReq__meta = ctx.mcpReq._meta) === null || _ctx_mcpReq__meta === void 0 ? void 0 : _ctx_mcpReq__meta.progressToken;
219
+ reportProgress = function reportProgress(progress, total, message) {
220
+ return _async_to_generator(function() {
221
+ return _ts_generator(this, function(_state) {
222
+ switch(_state.label){
223
+ case 0:
224
+ if (!(progressToken !== undefined)) return [
225
+ 3,
226
+ 2
227
+ ];
228
+ return [
229
+ 4,
230
+ ctx.mcpReq.notify({
231
+ "method": "notifications/progress",
232
+ "params": {
233
+ progressToken: progressToken,
234
+ progress: progress,
235
+ total: total,
236
+ message: message
237
+ }
238
+ })
239
+ ];
240
+ case 1:
241
+ _state.sent();
242
+ _state.label = 2;
243
+ case 2:
244
+ return [
245
+ 2
246
+ ];
247
+ }
248
+ });
249
+ })();
250
+ };
251
+ _state.label = 1;
252
+ case 1:
253
+ _state.trys.push([
254
+ 1,
255
+ 8,
256
+ ,
257
+ 9
258
+ ]);
259
+ return [
260
+ 4,
261
+ reportProgress(1, 4, "Resolving credentials...")
262
+ ];
263
+ case 2:
264
+ _state.sent();
265
+ credentials = fromIni({
266
+ profile: profile
267
+ });
268
+ logger.info("Scanning AWS resources with profile [".concat(profile, "]..."));
269
+ return [
270
+ 4,
271
+ reportProgress(2, 4, "Initializing services...")
272
+ ];
273
+ case 3:
274
+ _state.sent();
275
+ inventory = new Inventory(credentials, new LiveServiceFactory(logger.child("aws")));
276
+ return [
277
+ 4,
278
+ inventory.init()
279
+ ];
280
+ case 4:
281
+ _state.sent();
282
+ return [
283
+ 4,
284
+ reportProgress(3, 4, "Loading resources (this may take several minutes)...")
285
+ ];
286
+ case 5:
287
+ _state.sent();
288
+ return [
289
+ 4,
290
+ inventory.loadResources()
291
+ ];
292
+ case 6:
293
+ _state.sent();
294
+ return [
295
+ 4,
296
+ reportProgress(4, 4, "Building graph...")
297
+ ];
298
+ case 7:
299
+ _state.sent();
300
+ graph = new GraphBuilder().build(inventory);
301
+ logger.info("Scan complete: ".concat(String(graph.order), " nodes, ").concat(String(graph.size), " edges"));
302
+ return [
303
+ 2,
304
+ {
305
+ "content": [
306
+ {
307
+ "type": "text",
308
+ "text": [
309
+ "✓ Scan complete: ".concat(String(graph.order), " nodes, ").concat(String(graph.size), " edges."),
310
+ "",
311
+ "Available next steps:",
312
+ " • 'regions' — list enabled AWS regions",
313
+ " • 'detect' — find unused/orphaned resources",
314
+ " • 'export' — export graph to gexf, json, or markdown",
315
+ " • 'dump' — save raw data to disk for offline analysis"
316
+ ].join("\n")
317
+ }
318
+ ]
319
+ }
320
+ ];
321
+ case 8:
322
+ error = _state.sent();
323
+ graph = undefined;
324
+ inventory = undefined;
325
+ return [
326
+ 2,
327
+ {
328
+ "content": [
329
+ {
330
+ "type": "text",
331
+ "text": "✗ Scan failed: ".concat(_instanceof(error, Error) ? error.message : String(error), "\n\nEnsure the AWS profile '").concat(profile, "' exists in ~/.aws/credentials and has valid credentials.")
332
+ }
333
+ ],
334
+ "isError": true
335
+ }
336
+ ];
337
+ case 9:
338
+ return [
339
+ 2
340
+ ];
341
+ }
342
+ });
343
+ })();
344
+ });
345
+ // ─── Tool: load ──────────────────────────────────────────────────────────────
346
+ server.registerTool("load", {
347
+ "title": "Load Cached Resources",
348
+ "description": "Load resources from a previously dumped directory (offline mode). No AWS credentials needed. After loading, use 'regions', 'detect', or 'export' to work with the data.",
349
+ "inputSchema": z.object({
350
+ "path": z.string().describe("Path to a gnaws dump directory containing manifest.json and resource files")
351
+ })
352
+ }, function(param, ctx) {
353
+ var path = param.path;
354
+ return _async_to_generator(function() {
355
+ var _ctx_mcpReq__meta, progressToken, reportProgress, manifestPath, manifestInfo, _manifest_scannedAt, _ref, _manifest_regions, manifest, error;
356
+ return _ts_generator(this, function(_state) {
357
+ switch(_state.label){
358
+ case 0:
359
+ progressToken = (_ctx_mcpReq__meta = ctx.mcpReq._meta) === null || _ctx_mcpReq__meta === void 0 ? void 0 : _ctx_mcpReq__meta.progressToken;
360
+ reportProgress = function reportProgress(progress, total, message) {
361
+ return _async_to_generator(function() {
362
+ return _ts_generator(this, function(_state) {
363
+ switch(_state.label){
364
+ case 0:
365
+ if (!(progressToken !== undefined)) return [
366
+ 3,
367
+ 2
368
+ ];
369
+ return [
370
+ 4,
371
+ ctx.mcpReq.notify({
372
+ "method": "notifications/progress",
373
+ "params": {
374
+ progressToken: progressToken,
375
+ progress: progress,
376
+ total: total,
377
+ message: message
378
+ }
379
+ })
380
+ ];
381
+ case 1:
382
+ _state.sent();
383
+ _state.label = 2;
384
+ case 2:
385
+ return [
386
+ 2
387
+ ];
388
+ }
389
+ });
390
+ })();
391
+ };
392
+ if (!existsSync(path)) {
393
+ return [
394
+ 2,
395
+ {
396
+ "content": [
397
+ {
398
+ "type": "text",
399
+ "text": "✗ Path not found: ".concat(path, "\n\nProvide the path to a directory created by the 'dump' tool (or gnaws-cli /dump command).")
400
+ }
401
+ ],
402
+ "isError": true
403
+ }
404
+ ];
405
+ }
406
+ _state.label = 1;
407
+ case 1:
408
+ _state.trys.push([
409
+ 1,
410
+ 7,
411
+ ,
412
+ 8
413
+ ]);
414
+ return [
415
+ 4,
416
+ reportProgress(1, 3, "Reading manifest...")
417
+ ];
418
+ case 2:
419
+ _state.sent();
420
+ manifestPath = join(path, "manifest.json");
421
+ manifestInfo = "";
422
+ if (existsSync(manifestPath)) {
423
+ ;
424
+ ;
425
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
426
+ manifestInfo = " Scanned at: ".concat((_manifest_scannedAt = manifest.scannedAt) !== null && _manifest_scannedAt !== void 0 ? _manifest_scannedAt : "unknown", "\n Regions: ").concat(String((_ref = (_manifest_regions = manifest.regions) === null || _manifest_regions === void 0 ? void 0 : _manifest_regions.length) !== null && _ref !== void 0 ? _ref : "unknown"));
427
+ }
428
+ return [
429
+ 4,
430
+ reportProgress(2, 3, "Loading cached resources...")
431
+ ];
432
+ case 3:
433
+ _state.sent();
434
+ inventory = new Inventory(new CacheServiceFactory(path, logger.child("cache")));
435
+ return [
436
+ 4,
437
+ inventory.init()
438
+ ];
439
+ case 4:
440
+ _state.sent();
441
+ return [
442
+ 4,
443
+ inventory.loadResources()
444
+ ];
445
+ case 5:
446
+ _state.sent();
447
+ return [
448
+ 4,
449
+ reportProgress(3, 3, "Building graph...")
450
+ ];
451
+ case 6:
452
+ _state.sent();
453
+ graph = new GraphBuilder().build(inventory);
454
+ return [
455
+ 2,
456
+ {
457
+ "content": [
458
+ {
459
+ "type": "text",
460
+ "text": [
461
+ "✓ Loaded from ".concat(path, ": ").concat(String(graph.order), " nodes, ").concat(String(graph.size), " edges."),
462
+ manifestInfo ? "\n".concat(manifestInfo) : "",
463
+ "",
464
+ "Available next steps:",
465
+ " • 'regions' — list enabled AWS regions",
466
+ " • 'detect' — find unused/orphaned resources",
467
+ " • 'export' — export graph to gexf, json, or markdown"
468
+ ].join("\n")
469
+ }
470
+ ]
471
+ }
472
+ ];
473
+ case 7:
474
+ error = _state.sent();
475
+ graph = undefined;
476
+ inventory = undefined;
477
+ return [
478
+ 2,
479
+ {
480
+ "content": [
481
+ {
482
+ "type": "text",
483
+ "text": "✗ Load failed: ".concat(_instanceof(error, Error) ? error.message : String(error), "\n\nEnsure the directory was created by gnaws dump and contains valid resource files.")
484
+ }
485
+ ],
486
+ "isError": true
487
+ }
488
+ ];
489
+ case 8:
490
+ return [
491
+ 2
492
+ ];
493
+ }
494
+ });
495
+ })();
496
+ });
497
+ // ─── Tool: regions ───────────────────────────────────────────────────────────
498
+ server.registerTool("regions", {
499
+ "title": "List Regions",
500
+ "description": "List enabled AWS regions from the loaded inventory. Requires 'scan' or 'load' to be called first.",
501
+ "inputSchema": z.object({})
502
+ }, function() {
503
+ if (!inventory) {
504
+ return {
505
+ "content": [
506
+ {
507
+ "type": "text",
508
+ "text": "✗ No inventory loaded. Call 'scan' (with an AWS profile) or 'load' (with a dump directory) first."
509
+ }
510
+ ],
511
+ "isError": true
512
+ };
513
+ }
514
+ var regions = inventory.getAccountRegions();
515
+ var regionNames = regions.map(function(r) {
516
+ var _r_RegionName;
517
+ return (_r_RegionName = r.RegionName) !== null && _r_RegionName !== void 0 ? _r_RegionName : "unknown";
518
+ });
519
+ return {
520
+ "content": [
521
+ {
522
+ "type": "text",
523
+ "text": [
524
+ "".concat(String(regions.length), " enabled regions:"),
525
+ ""
526
+ ].concat(_to_consumable_array(regionNames.map(function(name) {
527
+ return " • ".concat(name);
528
+ }))).join("\n")
529
+ }
530
+ ]
531
+ };
532
+ });
533
+ // ─── Tool: detect ────────────────────────────────────────────────────────────
534
+ server.registerTool("detect", {
535
+ "title": "Detect Unused Resources",
536
+ "description": "Detect structurally unused/orphaned resources in the loaded graph. Requires 'scan' or 'load' to be called first. Optionally export findings to a file (.json or .md).",
537
+ "inputSchema": z.object({
538
+ "outputPath": z.string().optional().describe("Optional path to write findings (.json or .md). If omitted, returns findings as text.")
539
+ })
540
+ }, function(param) {
541
+ var outputPath = param.outputPath;
542
+ if (!graph || !inventory) {
543
+ return {
544
+ "content": [
545
+ {
546
+ "type": "text",
547
+ "text": "✗ No graph built yet. Call 'scan' or 'load' first to build the resource graph."
548
+ }
549
+ ],
550
+ "isError": true
551
+ };
552
+ }
553
+ try {
554
+ var detector = new UnusedDetector();
555
+ var findings = detector.detect(inventory, graph);
556
+ if (findings.length === 0) {
557
+ return {
558
+ "content": [
559
+ {
560
+ "type": "text",
561
+ "text": "✓ No unused resources detected. All resources have structural connections."
562
+ }
563
+ ]
564
+ };
565
+ }
566
+ // Export to file if path provided
567
+ if (outputPath) {
568
+ if (outputPath.endsWith(".json")) {
569
+ writeFileSync(outputPath, JSON.stringify(findings, null, 2));
570
+ } else if (outputPath.endsWith(".md")) {
571
+ var lines = [
572
+ "# Unused Resources",
573
+ "",
574
+ "> ".concat(String(findings.length), " findings detected"),
575
+ "",
576
+ "| Confidence | Resource | ARN | Type | Region | Reason |",
577
+ "|------------|----------|-----|------|--------|--------|"
578
+ ].concat(_to_consumable_array(findings.map(function(f) {
579
+ return "| ".concat(f.confidence, " | ").concat(f.name, " | ").concat(f.arn, " | ").concat(f.resourceType, " | ").concat(f.region, " | ").concat(f.reason, " |");
580
+ })));
581
+ writeFileSync(outputPath, lines.join("\n"));
582
+ } else {
583
+ return {
584
+ "content": [
585
+ {
586
+ "type": "text",
587
+ "text": "✗ Unsupported output format. Use .json or .md extension."
588
+ }
589
+ ],
590
+ "isError": true
591
+ };
592
+ }
593
+ return {
594
+ "content": [
595
+ {
596
+ "type": "text",
597
+ "text": "✓ ".concat(String(findings.length), " unused resources found and written to ").concat(outputPath, ".")
598
+ }
599
+ ]
600
+ };
601
+ }
602
+ // Return findings as text
603
+ var summary = findings.map(function(f) {
604
+ return " [".concat(f.confidence, "] ").concat(f.resourceType, " | ").concat(f.region, " | ").concat(f.name, " — ").concat(f.reason);
605
+ });
606
+ return {
607
+ "content": [
608
+ {
609
+ "type": "text",
610
+ "text": [
611
+ "".concat(String(findings.length), " unused resources detected:"),
612
+ ""
613
+ ].concat(_to_consumable_array(summary), [
614
+ "",
615
+ "Tip: call 'detect' with outputPath (.json or .md) to save findings to a file."
616
+ ]).join("\n")
617
+ }
618
+ ]
619
+ };
620
+ } catch (error) {
621
+ return {
622
+ "content": [
623
+ {
624
+ "type": "text",
625
+ "text": "✗ Detection failed: ".concat(_instanceof(error, Error) ? error.message : String(error))
626
+ }
627
+ ],
628
+ "isError": true
629
+ };
630
+ }
631
+ });
632
+ // ─── Tool: export ────────────────────────────────────────────────────────────
633
+ server.registerTool("export", {
634
+ "title": "Export Graph",
635
+ "description": "Export the resource graph to a file. Requires 'scan' or 'load' to be called first. Supported formats: gexf (Gephi), json (sigma.js viewer), md (markdown report).",
636
+ "inputSchema": z.object({
637
+ "format": z.enum([
638
+ "gexf",
639
+ "json",
640
+ "md"
641
+ ]).describe("Export format: 'gexf' for Gephi, 'json' for sigma.js viewer, 'md' for markdown report"),
642
+ "path": z.string().optional().describe("Output file path. Defaults to graph.gexf, graph.json, or report.md")
643
+ })
644
+ }, function(param) {
645
+ var format = param.format, path = param.path;
646
+ if (!graph || !inventory) {
647
+ return {
648
+ "content": [
649
+ {
650
+ "type": "text",
651
+ "text": "✗ No graph built yet. Call 'scan' or 'load' first to build the resource graph."
652
+ }
653
+ ],
654
+ "isError": true
655
+ };
656
+ }
657
+ try {
658
+ var ext = format === "md" ? ".md" : ".".concat(format);
659
+ var defaultName = format === "md" ? "report.md" : "graph.".concat(format);
660
+ var outputPath = path !== null && path !== void 0 ? path : defaultName;
661
+ if (!outputPath.endsWith(ext)) {
662
+ outputPath += ext;
663
+ }
664
+ if (format === "gexf") {
665
+ new GexfExporter().export(outputPath, inventory, graph);
666
+ } else if (format === "json") {
667
+ new JsonExporter().export(outputPath, inventory, graph);
668
+ } else {
669
+ new MarkdownExporter().export(outputPath, inventory, graph);
670
+ }
671
+ return {
672
+ "content": [
673
+ {
674
+ "type": "text",
675
+ "text": "✓ Exported ".concat(String(graph.order), " nodes and ").concat(String(graph.size), " edges to ").concat(outputPath, ".")
676
+ }
677
+ ]
678
+ };
679
+ } catch (error) {
680
+ return {
681
+ "content": [
682
+ {
683
+ "type": "text",
684
+ "text": "✗ Export failed: ".concat(_instanceof(error, Error) ? error.message : String(error))
685
+ }
686
+ ],
687
+ "isError": true
688
+ };
689
+ }
690
+ });
691
+ // ─── Tool: dump ──────────────────────────────────────────────────────────────
692
+ server.registerTool("dump", {
693
+ "title": "Dump Resources",
694
+ "description": "Dump loaded resources to a directory for offline use. Requires 'scan' or 'load' to be called first. The output can later be used with 'load' for offline analysis.",
695
+ "inputSchema": z.object({
696
+ "path": z.string().optional().describe("Output directory path. Defaults to 'dump'.")
697
+ })
698
+ }, function(param) {
699
+ var path = param.path;
700
+ if (!inventory) {
701
+ return {
702
+ "content": [
703
+ {
704
+ "type": "text",
705
+ "text": "✗ No inventory loaded. Call 'scan' or 'load' first."
706
+ }
707
+ ],
708
+ "isError": true
709
+ };
710
+ }
711
+ try {
712
+ var outputDir = path !== null && path !== void 0 ? path : "dump";
713
+ var writer = new CacheWriter(outputDir);
714
+ writer.writeAll(inventory);
715
+ return {
716
+ "content": [
717
+ {
718
+ "type": "text",
719
+ "text": [
720
+ "✓ Resources dumped to ".concat(outputDir, "."),
721
+ "",
722
+ "The dump can be loaded later with the 'load' tool for offline analysis."
723
+ ].join("\n")
724
+ }
725
+ ]
726
+ };
727
+ } catch (error) {
728
+ return {
729
+ "content": [
730
+ {
731
+ "type": "text",
732
+ "text": "✗ Dump failed: ".concat(_instanceof(error, Error) ? error.message : String(error))
733
+ }
734
+ ],
735
+ "isError": true
736
+ };
737
+ }
738
+ });
739
+ // ─── Main ────────────────────────────────────────────────────────────────────
740
+ process.on("uncaughtException", function(error) {
741
+ console.error("[gnaws-mcp] Uncaught exception:", error);
742
+ process.exit(1);
743
+ });
744
+ process.on("unhandledRejection", function(reason) {
745
+ console.error("[gnaws-mcp] Unhandled rejection:", reason);
746
+ process.exit(1);
747
+ });
748
+ function main() {
749
+ return _async_to_generator(function() {
750
+ var transport;
751
+ return _ts_generator(this, function(_state) {
752
+ switch(_state.label){
753
+ case 0:
754
+ transport = new StdioServerTransport();
755
+ return [
756
+ 4,
757
+ server.connect(transport)
758
+ ];
759
+ case 1:
760
+ _state.sent();
761
+ return [
762
+ 2
763
+ ];
764
+ }
765
+ });
766
+ })();
767
+ }
768
+ main().catch(function(error) {
769
+ console.error("[gnaws-mcp] Fatal error in main():", error);
770
+ process.exit(1);
771
+ });