@ebowwa/dependency-graph-mcp 1.0.0 → 1.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/src/index.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Dependency Graph MCP Server
3
+ * @ebowwa/dependency-graph-mcp
4
4
  *
5
- * Analyzes and visualizes dependency relationships in monorepos.
6
- * Provides tools for:
7
- * - Building dependency graphs from package.json and imports
8
- * - Visualizing dependency relationships
9
- * - Impact analysis for refactoring decisions
10
- * - Finding circular dependencies
11
- * - Identifying unused code
5
+ * MCP interface layer for dependency graph analysis.
6
+ * Uses @ebowwa/dependency-graph for core logic.
12
7
  */
13
8
 
14
9
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -17,351 +12,21 @@ import {
17
12
  CallToolRequestSchema,
18
13
  ListToolsRequestSchema,
19
14
  } from "@modelcontextprotocol/sdk/types.js";
20
- import { z } from "zod";
21
- import { readdirSync, readFileSync, existsSync } from "node:fs";
22
- import { join, dirname, relative, resolve } from "node:path";
23
- import { fileURLToPath } from "node:url";
24
15
 
25
- const TOOLING_DIR = dirname(fileURLToPath(
26
- import.meta.url.replace("/MCP/packages/dependency-graph", "/packages/src/tooling")
27
- ));
28
-
29
- // ==============
30
- // Types
31
- // ==============
32
-
33
- interface DependencyNode {
34
- name: string;
35
- path: string;
36
- type: "package" | "workspace" | "external";
37
- version?: string;
38
- }
39
-
40
- interface DependencyEdge {
41
- from: string;
42
- to: string;
43
- type: "workspace" | "external" | "import";
44
- importPath?: string;
45
- }
46
-
47
- interface DependencyGraph {
48
- nodes: Map<string, DependencyNode>;
49
- edges: DependencyEdge[];
50
- reverseEdges: Map<string, Set<string>>;
51
- }
52
-
53
- interface ImportInfo {
54
- from: string;
55
- imports: string[];
56
- file: string;
57
- }
58
-
59
- // ==============
60
- // Graph Builder
61
- // ==============
62
-
63
- class DependencyGraphBuilder {
64
- private monorepoRoot: string;
65
- private graph: DependencyGraph;
66
- private packageCache: Map<string, any> = new Map();
67
-
68
- constructor(monorepoRoot: string) {
69
- this.monorepoRoot = monorepoRoot;
70
- this.graph = {
71
- nodes: new Map(),
72
- edges: [],
73
- reverseEdges: new Map(),
74
- };
75
- }
76
-
77
- /**
78
- * Build the complete dependency graph
79
- */
80
- async build(options: {
81
- includeDevDependencies?: boolean;
82
- analyzeImports?: boolean;
83
- excludePatterns?: string[];
84
- } = {}): Promise<DependencyGraph> {
85
- const { includeDevDependencies = false, analyzeImports = true, excludePatterns = [] } = options;
86
-
87
- // Discover all packages in the monorepo
88
- const packages = await this.discoverPackages();
89
-
90
- // Add nodes for all packages
91
- for (const pkg of packages) {
92
- this.addNode(pkg.name, pkg.path, "workspace", pkg.version);
93
- }
94
-
95
- // Analyze dependencies for each package
96
- for (const pkg of packages) {
97
- await this.analyzePackageDependencies(pkg, includeDevDependencies, excludePatterns);
98
-
99
- if (analyzeImports) {
100
- await this.analyzeImports(pkg);
101
- }
102
- }
103
-
104
- // Build reverse edges for impact analysis
105
- this.buildReverseEdges();
106
-
107
- return this.graph;
108
- }
109
-
110
- /**
111
- * Discover all package.json files in the monorepo
112
- */
113
- private async discoverPackages(): Promise<Array<{ name: string; path: string; version: string }>> {
114
- const packages: Array<{ name: string; path: string; version: string }> = [];
115
- const seen = new Set<string>();
116
-
117
- // Search in common locations
118
- const searchPaths = [
119
- this.monorepoRoot,
120
- join(this.monorepoRoot, "packages"),
121
- join(this.monorepoRoot, "packages/src"),
122
- join(this.monorepoRoot, "apps"),
123
- join(this.monorepoRoot, "MCP/packages"),
124
- ];
125
-
126
- for (const searchPath of searchPaths) {
127
- if (!existsSync(searchPath)) continue;
128
-
129
- await this.searchDirectory(searchPath, packages, seen, 0, 4);
130
- }
131
-
132
- return packages;
133
- }
134
-
135
- /**
136
- * Recursively search for package.json files
137
- */
138
- private async searchDirectory(
139
- dir: string,
140
- packages: Array<{ name: string; path: string; version: string }>,
141
- seen: Set<string>,
142
- depth: number,
143
- maxDepth: number
144
- ): Promise<void> {
145
- if (depth > maxDepth) return;
146
-
147
- try {
148
- const entries = readdirSync(dir, { withFileTypes: true });
149
-
150
- for (const entry of entries) {
151
- // Skip node_modules and hidden dirs
152
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
153
- if (entry.name === "dist" || entry.name === "build") continue;
154
-
155
- const fullPath = join(dir, entry.name);
156
-
157
- if (entry.isDirectory()) {
158
- await this.searchDirectory(fullPath, packages, seen, depth + 1, maxDepth);
159
- } else if (entry.name === "package.json") {
160
- const packageDir = dirname(fullPath);
161
- const relativePath = relative(this.monorepoRoot, packageDir);
162
-
163
- if (seen.has(relativePath)) continue;
164
- seen.add(relativePath);
165
-
166
- try {
167
- const pkg = JSON.parse(readFileSync(fullPath, "utf-8"));
168
- if (pkg.name && !pkg.private) {
169
- packages.push({
170
- name: pkg.name,
171
- path: relativePath,
172
- version: pkg.version || "0.0.0",
173
- });
174
- this.packageCache.set(pkg.name, { path: relativePath, pkg });
175
- }
176
- } catch {
177
- // Invalid package.json, skip
178
- }
179
- }
180
- }
181
- } catch {
182
- // Directory not accessible, skip
183
- }
184
- }
185
-
186
- /**
187
- * Analyze package.json dependencies
188
- */
189
- private async analyzePackageDependencies(
190
- pkg: { name: string; path: string; version: string },
191
- includeDev: boolean,
192
- excludePatterns: string[]
193
- ): Promise<void> {
194
- const pkgPath = join(this.monorepoRoot, pkg.path, "package.json");
195
- if (!existsSync(pkgPath)) return;
196
-
197
- const packageJson = JSON.parse(readFileSync(pkgPath, "utf-8"));
198
-
199
- const depTypes = ["dependencies"];
200
- if (includeDev) depTypes.push("devDependencies", "peerDependencies", "optionalDependencies");
201
-
202
- for (const depType of depTypes) {
203
- const deps = packageJson[depType];
204
- if (!deps) continue;
205
-
206
- for (const [depName, depVersion] of Object.entries(deps as Record<string, string>)) {
207
- // Check if this matches any exclude pattern
208
- if (excludePatterns.some(pattern => depName.match(pattern))) continue;
209
-
210
- const isWorkspace = depVersion === "workspace:*" || depVersion === "workspace:^" || depVersion === "workspace:~";
211
-
212
- if (isWorkspace) {
213
- // This is a workspace dependency
214
- // Find the actual package in our cache
215
- const targetPkg = this.packageCache.get(depName);
216
- if (targetPkg) {
217
- this.addEdge(pkg.name, depName, "workspace");
218
- }
219
- } else if (this.packageCache.has(depName)) {
220
- // It's a local package but not using workspace: protocol
221
- this.addEdge(pkg.name, depName, "workspace");
222
- } else {
223
- // External dependency
224
- this.addNode(depName, "", "external", depVersion as string);
225
- this.addEdge(pkg.name, depName, "external");
226
- }
227
- }
228
- }
229
- }
230
-
231
- /**
232
- * Analyze TypeScript/JavaScript imports
233
- */
234
- private async analyzeImports(pkg: { name: string; path: string; version: string }): Promise<void> {
235
- const pkgDir = join(this.monorepoRoot, pkg.path);
236
-
237
- // Common source directories
238
- const sourceDirs = ["src", "lib", ""];
239
-
240
- for (const sourceDir of sourceDirs) {
241
- const searchPath = sourceDir ? join(pkgDir, sourceDir) : pkgDir;
242
- if (!existsSync(searchPath)) continue;
243
-
244
- await this.analyzeImportsInDirectory(pkg.name, searchPath);
245
- }
246
- }
247
-
248
- /**
249
- * Recursively analyze imports in a directory
250
- */
251
- private async analyzeImportsInDirectory(packageName: string, dir: string): Promise<void> {
252
- try {
253
- const entries = readdirSync(dir, { withFileTypes: true });
254
-
255
- for (const entry of entries) {
256
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
257
- if (entry.name === "dist" || entry.name === "build") continue;
258
-
259
- const fullPath = join(dir, entry.name);
260
-
261
- if (entry.isDirectory()) {
262
- await this.analyzeImportsInDirectory(packageName, fullPath);
263
- } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx") || entry.name.endsWith(".mjs")) {
264
- await this.analyzeImportsInFile(packageName, fullPath);
265
- }
266
- }
267
- } catch {
268
- // Directory not accessible
269
- }
270
- }
271
-
272
- /**
273
- * Analyze imports in a single file
274
- */
275
- private async analyzeImportsInFile(packageName: string, filePath: string): Promise<void> {
276
- try {
277
- const content = readFileSync(filePath, "utf-8");
278
-
279
- // Match various import patterns
280
- const importPatterns = [
281
- // ES imports: import ... from '...' | import ... from "..."
282
- /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s*,?\s*)*\s+from\s+['"]([^'"]+)['"]/g,
283
- // Dynamic imports: import('...')
284
- /import\(['"]([^'"]+)['"]\)/g,
285
- // require(): require('...') | require("...")
286
- /require\(['"]([^'"]+)['"]\)/g,
287
- // Export from: export ... from '...'
288
- /export\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+)?['"]([^'"]+)['"]/g,
289
- ];
290
-
291
- for (const pattern of importPatterns) {
292
- let match;
293
- while ((match = pattern.exec(content)) !== null) {
294
- const importPath = match[1];
295
-
296
- // Skip relative imports
297
- if (importPath.startsWith(".") || importPath.startsWith("/")) continue;
298
-
299
- // Check if this is a known workspace package
300
- for (const [pkgName, pkgData] of this.packageCache) {
301
- if (importPath === pkgName || importPath.startsWith(pkgName + "/")) {
302
- this.addEdge(packageName, pkgName, "import", importPath);
303
- break;
304
- }
305
- }
306
- }
307
- }
308
- } catch {
309
- // File not readable
310
- }
311
- }
312
-
313
- /**
314
- * Add a node to the graph
315
- */
316
- private addNode(name: string, path: string, type: "package" | "workspace" | "external", version?: string): void {
317
- if (!this.graph.nodes.has(name)) {
318
- this.graph.nodes.set(name, { name, path, type, version });
319
- this.graph.reverseEdges.set(name, new Set());
320
- }
321
- }
322
-
323
- /**
324
- * Add an edge to the graph
325
- */
326
- private addEdge(from: string, to: string, type: "workspace" | "external" | "import", importPath?: string): void {
327
- // Skip self-references
328
- if (from === to) return;
329
-
330
- // Check if edge already exists
331
- const existing = this.graph.edges.find(e => e.from === from && e.to === to);
332
- if (existing) return;
333
-
334
- this.graph.edges.push({ from, to, type, importPath });
335
-
336
- // Update reverse edges
337
- if (!this.graph.reverseEdges.has(to)) {
338
- this.graph.reverseEdges.set(to, new Set());
339
- }
340
- this.graph.reverseEdges.get(to)!.add(from);
341
- }
342
-
343
- /**
344
- * Build reverse edges for impact analysis
345
- */
346
- private buildReverseEdges(): void {
347
- for (const [to, dependents] of this.graph.reverseEdges) {
348
- // Ensure node exists
349
- if (!this.graph.nodes.has(to)) {
350
- this.graph.nodes.set(to, { name: to, path: "", type: "external" });
351
- }
352
- }
353
- }
354
-
355
- /**
356
- * Get the built graph
357
- */
358
- getGraph(): DependencyGraph {
359
- return this.graph;
360
- }
361
- }
16
+ // Import from core package
17
+ import {
18
+ DependencyGraphBuilder,
19
+ formatGraph,
20
+ findCircularDependencies,
21
+ analyzeImpact,
22
+ findUnusedPackages,
23
+ getPackageInfo,
24
+ type DependencyGraph,
25
+ type OutputFormat,
26
+ } from "@ebowwa/dependency-graph";
362
27
 
363
28
  // ==============
364
- // MCP Server
29
+ // MCP Tool Schemas
365
30
  // ==============
366
31
 
367
32
  const DEPENDENCY_GRAPH_SCHEMA = {
@@ -467,242 +132,14 @@ const PACKAGE_INFO_SCHEMA = {
467
132
  },
468
133
  };
469
134
 
470
- // Format the graph for output
471
- function formatGraph(graph: DependencyGraph, format: string): string {
472
- switch (format) {
473
- case "mermaid":
474
- return formatAsMermaid(graph);
475
- case "dot":
476
- return formatAsDot(graph);
477
- case "tree":
478
- return formatAsTree(graph);
479
- case "json":
480
- default:
481
- return JSON.stringify(
482
- {
483
- nodes: Array.from(graph.nodes.values()),
484
- edges: graph.edges,
485
- },
486
- null,
487
- 2
488
- );
489
- }
490
- }
491
-
492
- function formatAsMermaid(graph: DependencyGraph): string {
493
- const lines = ["graph TD"];
494
-
495
- // Add nodes
496
- for (const [name, node] of graph.nodes) {
497
- const label = node.type === "workspace" ? `📦 ${name}` : `📚 ${name}`;
498
- lines.push(` ${name.replace(/[^a-zA-Z0-9]/g, "_")}["${label}"]`);
499
- }
500
-
501
- // Add edges
502
- for (const edge of graph.edges) {
503
- const from = edge.from.replace(/[^a-zA-Z0-9]/g, "_");
504
- const to = edge.to.replace(/[^a-zA-Z0-9]/g, "_");
505
- const label = edge.type === "workspace" ? "workspace" : edge.type === "import" ? "imports" : "external";
506
- lines.push(` ${from} -->|${label}| ${to}`);
507
- }
508
-
509
- // Add styles
510
- lines.push(' classDef workspace fill:#e1f5fe');
511
- lines.push(' classDef external fill:#f5f5f5');
512
- lines.push(' classDef import fill:#fff3e0');
513
-
514
- for (const [name, node] of graph.nodes) {
515
- const id = name.replace(/[^a-zA-Z0-9]/g, "_");
516
- if (node.type === "workspace") {
517
- lines.push(` class ${id} workspace`);
518
- } else if (node.type === "external") {
519
- lines.push(` class ${id} external`);
520
- }
521
- }
522
-
523
- return lines.join("\n");
524
- }
525
-
526
- function formatAsDot(graph: DependencyGraph): string {
527
- const lines = ["digraph dependencies {"];
528
- lines.push(' rankdir=LR;');
529
- lines.push(' node [shape=box];');
530
-
531
- // Add nodes
532
- for (const [name, node] of graph.nodes) {
533
- const color = node.type === "workspace" ? "lightblue" : "lightgray";
534
- lines.push(` "${name}" [fillcolor=${color}, style=filled];`);
535
- }
536
-
537
- // Add edges
538
- for (const edge of graph.edges) {
539
- const style = edge.type === "workspace" ? "solid" : "dashed";
540
- lines.push(` "${edge.from}" -> "${edge.to}" [style=${style}, label="${edge.type}"];`);
541
- }
542
-
543
- lines.push("}");
544
- return lines.join("\n");
545
- }
546
-
547
- function formatAsTree(graph: DependencyGraph): string {
548
- const lines: string[] = [];
549
- const seen = new Set<string>();
550
-
551
- // Find root nodes (no incoming edges from other workspace packages)
552
- const workspaceNodes = Array.from(graph.nodes.values()).filter(n => n.type === "workspace");
553
- const incomingEdges = new Map<string, number>();
554
-
555
- for (const node of workspaceNodes) {
556
- incomingEdges.set(node.name, 0);
557
- }
558
-
559
- for (const edge of graph.edges) {
560
- if (graph.nodes.get(edge.to)?.type === "workspace") {
561
- incomingEdges.set(edge.to, (incomingEdges.get(edge.to) || 0) + 1);
562
- }
563
- }
564
-
565
- // Start from roots
566
- for (const [name, node] of graph.nodes) {
567
- if (node.type === "workspace" && (incomingEdges.get(name) || 0) === 0) {
568
- lines.push(`📦 ${name}`);
569
- printTree(graph, name, "", seen, lines);
570
- }
571
- }
572
-
573
- // Add any disconnected nodes
574
- for (const [name, node] of graph.nodes) {
575
- if (!seen.has(name) && node.type === "workspace") {
576
- lines.push(`📦 ${name} (disconnected)`);
577
- }
578
- }
579
-
580
- return lines.join("\n");
581
- }
582
-
583
- function printTree(
584
- graph: DependencyGraph,
585
- nodeName: string,
586
- prefix: string,
587
- seen: Set<string>,
588
- lines: string[]
589
- ): void {
590
- seen.add(nodeName);
591
-
592
- // Find outgoing edges to other workspace packages
593
- const outgoing = graph.edges.filter(e => e.from === nodeName && graph.nodes.get(e.to)?.type === "workspace");
594
-
595
- for (let i = 0; i < outgoing.length; i++) {
596
- const edge = outgoing[i];
597
- const isLast = i === outgoing.length - 1;
598
- const connector = isLast ? "└──" : "├──";
599
- const childPrefix = prefix + (isLast ? " " : "│ ");
600
-
601
- lines.push(`${prefix}${connector} 📦 ${edge.to} [${edge.type}]`);
602
- if (!seen.has(edge.to)) {
603
- printTree(graph, edge.to, childPrefix, seen, lines);
604
- }
605
- }
606
-
607
- // Show external dependencies count
608
- const externals = graph.edges.filter(e => e.from === nodeName && graph.nodes.get(e.to)?.type === "external");
609
- if (externals.length > 0) {
610
- lines.push(`${prefix}└── 📚 ${externals.length} external dependencies`);
611
- }
612
- }
613
-
614
- // Find circular dependencies using DFS
615
- function findCircularDependencies(graph: DependencyGraph, maxDepth: number = 10): string[][] {
616
- const cycles: string[][] = [];
617
- const visited = new Set<string>();
618
- const recursionStack = new Set<string>();
619
-
620
- function dfs(node: string, path: string[]): void {
621
- if (path.length > maxDepth) return;
622
-
623
- visited.add(node);
624
- recursionStack.add(node);
625
- path.push(node);
626
-
627
- // Check all outgoing edges
628
- for (const edge of graph.edges) {
629
- if (edge.from === node) {
630
- if (recursionStack.has(edge.to)) {
631
- // Found a cycle
632
- const cycleStart = path.indexOf(edge.to);
633
- if (cycleStart >= 0) {
634
- cycles.push([...path.slice(cycleStart), edge.to]);
635
- }
636
- } else if (!visited.has(edge.to)) {
637
- dfs(edge.to, [...path]);
638
- }
639
- }
640
- }
641
-
642
- recursionStack.delete(node);
643
- }
644
-
645
- // Start DFS from each workspace node
646
- for (const [name, node] of graph.nodes) {
647
- if (node.type === "workspace" && !visited.has(name)) {
648
- dfs(name, []);
649
- }
650
- }
651
-
652
- return cycles;
653
- }
654
-
655
- // Impact analysis
656
- function analyzeImpact(graph: DependencyGraph, packageName: string, includeTransitive: boolean = true): {
657
- direct: string[];
658
- transitive: string[];
659
- all: string[];
660
- } {
661
- const direct: string[] = [];
662
- const transitive: string[] = [];
663
- const visited = new Set<string>();
664
-
665
- // Get direct dependents
666
- const directDependents = graph.reverseEdges.get(packageName);
667
- if (directDependents) {
668
- for (const dep of directDependents) {
669
- direct.push(dep);
670
- visited.add(dep);
671
- }
672
- }
673
-
674
- // Get transitive dependents
675
- if (includeTransitive) {
676
- for (const dep of direct) {
677
- collectTransitive(graph, dep, visited, transitive);
678
- }
679
- }
680
-
681
- return {
682
- direct,
683
- transitive,
684
- all: Array.from(visited),
685
- };
686
- }
687
-
688
- function collectTransitive(graph: DependencyGraph, packageName: string, visited: Set<string>, result: string[]): void {
689
- const dependents = graph.reverseEdges.get(packageName);
690
- if (!dependents) return;
691
-
692
- for (const dep of dependents) {
693
- if (!visited.has(dep)) {
694
- visited.add(dep);
695
- result.push(dep);
696
- collectTransitive(graph, dep, visited, result);
697
- }
698
- }
699
- }
135
+ // ==============
136
+ // MCP Server
137
+ // ==============
700
138
 
701
- // Start the MCP server
702
139
  const server = new Server(
703
140
  {
704
- name: "@mcp/dependency-graph",
705
- version: "0.1.0",
141
+ name: "@ebowwa/dependency-graph-mcp",
142
+ version: "1.0.1",
706
143
  },
707
144
  {
708
145
  capabilities: {
@@ -753,10 +190,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
753
190
  includeDevDependencies?: boolean;
754
191
  analyzeImports?: boolean;
755
192
  excludePatterns?: string[];
756
- format?: "json" | "mermaid" | "dot" | "tree";
193
+ format?: OutputFormat;
757
194
  };
758
195
 
759
- await cachedBuilder.build({ includeDevDependencies, analyzeImports, excludePatterns });
196
+ await cachedBuilder.build({
197
+ includeDevDependencies,
198
+ analyzeImports,
199
+ excludePatterns,
200
+ });
760
201
  const graph = cachedBuilder.getGraph();
761
202
  cachedGraph = graph;
762
203
 
@@ -766,11 +207,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
766
207
  }
767
208
 
768
209
  case "impact_analysis": {
769
- const { package: packageName, includeTransitive = true, format = "tree" } = args as {
770
- package: string;
771
- includeTransitive?: boolean;
772
- format?: "json" | "tree";
773
- };
210
+ const { package: packageName, includeTransitive = true, format = "tree" } =
211
+ args as {
212
+ package: string;
213
+ includeTransitive?: boolean;
214
+ format?: "json" | "tree";
215
+ };
774
216
 
775
217
  const impact = analyzeImpact(cachedGraph, packageName, includeTransitive);
776
218
 
@@ -819,16 +261,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
819
261
  case "unused_code": {
820
262
  const { includeExternal = false } = args as { includeExternal?: boolean };
821
263
 
822
- const unused: string[] = [];
823
-
824
- for (const [name, node] of cachedGraph.nodes) {
825
- if (node.type === "workspace" || includeExternal) {
826
- const dependents = cachedGraph.reverseEdges.get(name);
827
- if (!dependents || dependents.size === 0) {
828
- unused.push(name);
829
- }
830
- }
831
- }
264
+ const unused = findUnusedPackages(cachedGraph, includeExternal);
832
265
 
833
266
  if (unused.length === 0) {
834
267
  return {
@@ -840,7 +273,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
840
273
  content: [
841
274
  {
842
275
  type: "text",
843
- text: `Found ${unused.length} potentially unused packages:\n\n${unused.map(n => ` └── ${n}`).join("\n")}`,
276
+ text: `Found ${unused.length} potentially unused packages:\n\n${unused.map((n) => ` └── ${n}`).join("\n")}`,
844
277
  },
845
278
  ],
846
279
  };
@@ -849,34 +282,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
849
282
  case "package_info": {
850
283
  const { package: packageName } = args as { package: string };
851
284
 
852
- const node = cachedGraph.nodes.get(packageName);
853
- if (!node) {
285
+ const info = getPackageInfo(cachedGraph, packageName);
286
+ if (!info) {
854
287
  return {
855
- content: [{ type: "text", text: `Package "${packageName}" not found in dependency graph.` }],
288
+ content: [
289
+ {
290
+ type: "text",
291
+ text: `Package "${packageName}" not found in dependency graph.`,
292
+ },
293
+ ],
856
294
  };
857
295
  }
858
296
 
859
- // Get dependencies
860
- const dependencies = cachedGraph.edges.filter(e => e.from === packageName);
861
- // Get dependents
862
- const dependents = Array.from(cachedGraph.reverseEdges.get(packageName) || []);
863
-
864
297
  const lines = [
865
- `Package: ${packageName}`,
866
- `Type: ${node.type}`,
867
- `Path: ${node.path || "N/A"}`,
868
- node.version ? `Version: ${node.version}` : "",
298
+ `Package: ${info.name}`,
299
+ `Type: ${info.type}`,
300
+ `Path: ${info.path}`,
301
+ info.version ? `Version: ${info.version}` : "",
869
302
  "",
870
- `Dependencies (${dependencies.length}):`,
303
+ `Dependencies (${info.dependencies.length}):`,
871
304
  ];
872
305
 
873
- for (const dep of dependencies) {
874
- const depNode = cachedGraph.nodes.get(dep.to);
875
- lines.push(` └── ${dep.to} [${dep.type}]${depNode?.version ? ` @ ${depNode.version}` : ""}`);
306
+ for (const dep of info.dependencies) {
307
+ lines.push(
308
+ ` └── ${dep.name} [${dep.type}]${dep.version ? ` @ ${dep.version}` : ""}`
309
+ );
876
310
  }
877
311
 
878
- lines.push(``, `Dependents (${dependents.length}):`);
879
- for (const dep of dependents) {
312
+ lines.push(``, `Dependents (${info.dependents.length}):`);
313
+ for (const dep of info.dependents) {
880
314
  lines.push(` └── ${dep}`);
881
315
  }
882
316
 
@@ -888,7 +322,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
888
322
  }
889
323
  } catch (error) {
890
324
  return {
891
- content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
325
+ content: [
326
+ {
327
+ type: "text",
328
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
329
+ },
330
+ ],
892
331
  isError: true,
893
332
  };
894
333
  }
@@ -897,17 +336,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
897
336
  // Start server
898
337
  async function main() {
899
338
  // Set up error handling
900
- server.onerror = (error) => console.error('[MCP] Error:', error);
339
+ server.onerror = (error) => console.error("[MCP] Error:", error);
901
340
 
902
341
  // Signal handlers for graceful shutdown
903
- process.on('SIGINT', async () => {
904
- console.error('[MCP] Shutting down...');
342
+ process.on("SIGINT", async () => {
343
+ console.error("[MCP] Shutting down...");
905
344
  await server.close();
906
345
  process.exit(0);
907
346
  });
908
347
 
909
- process.on('SIGTERM', async () => {
910
- console.error('[MCP] Shutting down...');
348
+ process.on("SIGTERM", async () => {
349
+ console.error("[MCP] Shutting down...");
911
350
  await server.close();
912
351
  process.exit(0);
913
352
  });
@@ -917,13 +356,13 @@ async function main() {
917
356
  await server.connect(transport);
918
357
 
919
358
  // Log to stderr (doesn't interfere with JSON-RPC)
920
- console.error('[MCP] @mcp/dependency-graph server running on stdio');
359
+ console.error("[MCP] @ebowwa/dependency-graph-mcp server running on stdio");
921
360
 
922
361
  // Keep stdin open for requests
923
362
  process.stdin.resume();
924
363
  }
925
364
 
926
365
  main().catch((error) => {
927
- console.error('[MCP] Fatal error:', error);
366
+ console.error("[MCP] Fatal error:", error);
928
367
  process.exit(1);
929
368
  });