typegraph-mcp 0.9.47 → 0.9.48

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.
@@ -18,8 +18,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
18
18
  "coverage"
19
19
  ]);
20
20
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
21
- function discoverFiles(rootDir) {
21
+ function normalizeExcludedPaths(rootDir, excludedPaths) {
22
+ return excludedPaths.map(
23
+ (excludedPath) => path.resolve(rootDir, excludedPath)
24
+ );
25
+ }
26
+ function isExcluded(filePath, excludedPaths) {
27
+ return excludedPaths.some(
28
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path.sep)
29
+ );
30
+ }
31
+ function discoverFiles(rootDir, excludedPaths = []) {
22
32
  const files = [];
33
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
23
34
  function walk(dir) {
24
35
  let entries;
25
36
  try {
@@ -31,7 +42,9 @@ function discoverFiles(rootDir) {
31
42
  if (entry.isDirectory()) {
32
43
  if (SKIP_DIRS.has(entry.name)) continue;
33
44
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
34
- walk(path.join(dir, entry.name));
45
+ const childDir = path.join(dir, entry.name);
46
+ if (isExcluded(childDir, normalizedExclusions)) continue;
47
+ walk(childDir);
35
48
  } else if (entry.isFile()) {
36
49
  const name = entry.name;
37
50
  if (SKIP_FILES.has(name)) continue;
@@ -132,11 +145,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
132
145
  return null;
133
146
  }
134
147
  function createResolver(projectRoot, tsconfigPath) {
148
+ const configFile = path.resolve(projectRoot, tsconfigPath);
135
149
  return new ResolverFactory({
136
- tsconfig: {
137
- configFile: path.resolve(projectRoot, tsconfigPath),
138
- references: "auto"
139
- },
150
+ ...fs.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
140
151
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
141
152
  extensionAlias: {
142
153
  ".js": [".ts", ".tsx", ".js"],
@@ -202,10 +213,10 @@ function buildReverseMap(forward) {
202
213
  }
203
214
  return reverse;
204
215
  }
205
- async function buildGraph(projectRoot, tsconfigPath) {
216
+ async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
206
217
  const startTime = performance.now();
207
218
  const resolver = createResolver(projectRoot, tsconfigPath);
208
- const fileList = discoverFiles(projectRoot);
219
+ const fileList = discoverFiles(projectRoot, excludedPaths);
209
220
  log(`Discovered ${fileList.length} source files`);
210
221
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
211
222
  const reverse = buildReverseMap(forward);
@@ -297,8 +308,9 @@ function removeFile(graph, filePath) {
297
308
  graph.reverse.delete(filePath);
298
309
  graph.files.delete(filePath);
299
310
  }
300
- function startWatcher(projectRoot, graph, resolver, hooks) {
311
+ function startWatcher(projectRoot, graph, resolver, hooks, excludedPaths = []) {
301
312
  try {
313
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
302
314
  const watcher = fs.watch(
303
315
  projectRoot,
304
316
  { recursive: true },
@@ -312,6 +324,7 @@ function startWatcher(projectRoot, graph, resolver, hooks) {
312
324
  if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
313
325
  return;
314
326
  const absPath = path.resolve(projectRoot, filename);
327
+ if (isExcluded(absPath, normalizedExclusions)) return;
315
328
  if (fs.existsSync(absPath)) {
316
329
  updateFile(graph, absPath, resolver, projectRoot);
317
330
  void hooks?.onFileUpdated?.(absPath);
package/dist/server.js CHANGED
@@ -12,6 +12,30 @@ import * as fs from "fs";
12
12
  import { createRequire } from "module";
13
13
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
14
14
  var REQUEST_TIMEOUT_MS = 1e4;
15
+ function readPackageVersion(packagePath) {
16
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
17
+ return pkg.version ?? "unknown";
18
+ }
19
+ function resolveTsServer(projectRoot2) {
20
+ const projectRequire = createRequire(path.resolve(projectRoot2, "package.json"));
21
+ let projectVersion;
22
+ try {
23
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
24
+ projectVersion = readPackageVersion(packagePath2);
25
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
26
+ return { path: serverPath2, version: projectVersion, source: "project" };
27
+ } catch {
28
+ }
29
+ const toolRequire = createRequire(import.meta.url);
30
+ const packagePath = toolRequire.resolve("typescript/package.json");
31
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
32
+ return {
33
+ path: serverPath,
34
+ version: readPackageVersion(packagePath),
35
+ source: "typegraph",
36
+ projectVersion
37
+ };
38
+ }
15
39
  var TsServerClient = class {
16
40
  constructor(projectRoot2, tsconfigPath2 = "./tsconfig.json") {
17
41
  this.projectRoot = projectRoot2;
@@ -48,9 +72,12 @@ var TsServerClient = class {
48
72
  // ─── Lifecycle ──────────────────────────────────────────────────────────
49
73
  async start() {
50
74
  if (this.child) return;
51
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
52
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
53
- log(`Spawning tsserver: ${tsserverPath}`);
75
+ const resolution = resolveTsServer(this.projectRoot);
76
+ const tsserverPath = resolution.path;
77
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
78
+ log(
79
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
80
+ );
54
81
  log(`Project root: ${this.projectRoot}`);
55
82
  log(`tsconfig: ${this.tsconfigPath}`);
56
83
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -79,12 +106,9 @@ var TsServerClient = class {
79
106
  }
80
107
  });
81
108
  const warmStart = performance.now();
82
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
83
- if (fs.existsSync(tsconfigAbs)) {
84
- await this.sendRequest("compilerOptionsForInferredProjects", {
85
- options: { allowJs: true, checkJs: false }
86
- });
87
- }
109
+ await this.sendRequest("compilerOptionsForInferredProjects", {
110
+ options: { allowJs: true, checkJs: false }
111
+ });
88
112
  this.ready = true;
89
113
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
90
114
  }
@@ -328,8 +352,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
328
352
  "coverage"
329
353
  ]);
330
354
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
331
- function discoverFiles(rootDir) {
355
+ function normalizeExcludedPaths(rootDir, excludedPaths2) {
356
+ return excludedPaths2.map(
357
+ (excludedPath) => path2.resolve(rootDir, excludedPath)
358
+ );
359
+ }
360
+ function isExcluded(filePath, excludedPaths2) {
361
+ return excludedPaths2.some(
362
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
363
+ );
364
+ }
365
+ function discoverFiles(rootDir, excludedPaths2 = []) {
332
366
  const files = [];
367
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths2);
333
368
  function walk(dir) {
334
369
  let entries;
335
370
  try {
@@ -341,7 +376,9 @@ function discoverFiles(rootDir) {
341
376
  if (entry.isDirectory()) {
342
377
  if (SKIP_DIRS.has(entry.name)) continue;
343
378
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
344
- walk(path2.join(dir, entry.name));
379
+ const childDir = path2.join(dir, entry.name);
380
+ if (isExcluded(childDir, normalizedExclusions)) continue;
381
+ walk(childDir);
345
382
  } else if (entry.isFile()) {
346
383
  const name = entry.name;
347
384
  if (SKIP_FILES.has(name)) continue;
@@ -442,11 +479,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot2) {
442
479
  return null;
443
480
  }
444
481
  function createResolver(projectRoot2, tsconfigPath2) {
482
+ const configFile = path2.resolve(projectRoot2, tsconfigPath2);
445
483
  return new ResolverFactory({
446
- tsconfig: {
447
- configFile: path2.resolve(projectRoot2, tsconfigPath2),
448
- references: "auto"
449
- },
484
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
450
485
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
451
486
  extensionAlias: {
452
487
  ".js": [".ts", ".tsx", ".js"],
@@ -512,10 +547,10 @@ function buildReverseMap(forward) {
512
547
  }
513
548
  return reverse;
514
549
  }
515
- async function buildGraph(projectRoot2, tsconfigPath2) {
550
+ async function buildGraph(projectRoot2, tsconfigPath2, excludedPaths2 = []) {
516
551
  const startTime = performance.now();
517
552
  const resolver = createResolver(projectRoot2, tsconfigPath2);
518
- const fileList = discoverFiles(projectRoot2);
553
+ const fileList = discoverFiles(projectRoot2, excludedPaths2);
519
554
  log2(`Discovered ${fileList.length} source files`);
520
555
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
521
556
  const reverse = buildReverseMap(forward);
@@ -607,8 +642,9 @@ function removeFile(graph, filePath) {
607
642
  graph.reverse.delete(filePath);
608
643
  graph.files.delete(filePath);
609
644
  }
610
- function startWatcher(projectRoot2, graph, resolver, hooks) {
645
+ function startWatcher(projectRoot2, graph, resolver, hooks, excludedPaths2 = []) {
611
646
  try {
647
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot2, excludedPaths2);
612
648
  const watcher = fs2.watch(
613
649
  projectRoot2,
614
650
  { recursive: true },
@@ -622,6 +658,7 @@ function startWatcher(projectRoot2, graph, resolver, hooks) {
622
658
  if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
623
659
  return;
624
660
  const absPath2 = path2.resolve(projectRoot2, filename);
661
+ if (isExcluded(absPath2, normalizedExclusions)) return;
625
662
  if (fs2.existsSync(absPath2)) {
626
663
  updateFile(graph, absPath2, resolver, projectRoot2);
627
664
  void hooks?.onFileUpdated?.(absPath2);
@@ -917,17 +954,18 @@ import * as path5 from "path";
917
954
 
918
955
  // config.ts
919
956
  import * as path4 from "path";
920
- function resolveConfig(toolDir) {
957
+ function resolveConfig(toolDir2) {
921
958
  const cwd = process.cwd();
922
- const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(toolDir)) === "plugins" ? path4.resolve(toolDir, "../..") : cwd;
959
+ const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(toolDir2)) === "plugins" ? path4.resolve(toolDir2, "../..") : cwd;
923
960
  const tsconfigPath2 = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
924
- const toolIsEmbedded = toolDir.startsWith(projectRoot2 + path4.sep);
925
- const toolRelPath = toolIsEmbedded ? path4.relative(projectRoot2, toolDir) : toolDir;
926
- return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded, toolRelPath };
961
+ const toolIsEmbedded2 = toolDir2.startsWith(projectRoot2 + path4.sep);
962
+ const toolRelPath = toolIsEmbedded2 ? path4.relative(projectRoot2, toolDir2) : toolDir2;
963
+ return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2, toolRelPath };
927
964
  }
928
965
 
929
966
  // server.ts
930
- var { projectRoot, tsconfigPath } = resolveConfig(import.meta.dirname);
967
+ var { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = resolveConfig(import.meta.dirname);
968
+ var excludedPaths = toolIsEmbedded ? [toolDir] : [];
931
969
  var log3 = (...args) => console.error("[typegraph]", ...args);
932
970
  var client = new TsServerClient(projectRoot, tsconfigPath);
933
971
  var moduleGraph;
@@ -1714,25 +1752,31 @@ async function main() {
1714
1752
  log3(`tsconfig: ${tsconfigPath}`);
1715
1753
  const [, graphResult] = await Promise.all([
1716
1754
  client.start(),
1717
- buildGraph(projectRoot, tsconfigPath)
1755
+ buildGraph(projectRoot, tsconfigPath, excludedPaths)
1718
1756
  ]);
1719
1757
  moduleGraph = graphResult.graph;
1720
1758
  moduleResolver = graphResult.resolver;
1721
- startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
1722
- onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
1723
- (wasOpen) => {
1724
- if (!wasOpen) client.markProjectsDirty();
1725
- },
1726
- (err) => {
1759
+ startWatcher(
1760
+ projectRoot,
1761
+ moduleGraph,
1762
+ graphResult.resolver,
1763
+ {
1764
+ onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
1765
+ (wasOpen) => {
1766
+ if (!wasOpen) client.markProjectsDirty();
1767
+ },
1768
+ (err) => {
1769
+ client.markProjectsDirty();
1770
+ log3(`Failed to reload open file ${relPath(filePath)}:`, err);
1771
+ }
1772
+ ),
1773
+ onFileDeleted: (filePath) => {
1774
+ client.closeFile(filePath);
1727
1775
  client.markProjectsDirty();
1728
- log3(`Failed to reload open file ${relPath(filePath)}:`, err);
1729
1776
  }
1730
- ),
1731
- onFileDeleted: (filePath) => {
1732
- client.closeFile(filePath);
1733
- client.markProjectsDirty();
1734
- }
1735
- });
1777
+ },
1778
+ excludedPaths
1779
+ );
1736
1780
  const transport = new StdioServerTransport();
1737
1781
  await mcpServer.connect(transport);
1738
1782
  log3("MCP server connected and ready");
@@ -10,6 +10,30 @@ import * as fs from "fs";
10
10
  import { createRequire } from "module";
11
11
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
12
12
  var REQUEST_TIMEOUT_MS = 1e4;
13
+ function readPackageVersion(packagePath) {
14
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
15
+ return pkg.version ?? "unknown";
16
+ }
17
+ function resolveTsServer(projectRoot) {
18
+ const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
19
+ let projectVersion;
20
+ try {
21
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
22
+ projectVersion = readPackageVersion(packagePath2);
23
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
24
+ return { path: serverPath2, version: projectVersion, source: "project" };
25
+ } catch {
26
+ }
27
+ const toolRequire = createRequire(import.meta.url);
28
+ const packagePath = toolRequire.resolve("typescript/package.json");
29
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
30
+ return {
31
+ path: serverPath,
32
+ version: readPackageVersion(packagePath),
33
+ source: "typegraph",
34
+ projectVersion
35
+ };
36
+ }
13
37
  var TsServerClient = class {
14
38
  constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
15
39
  this.projectRoot = projectRoot;
@@ -46,9 +70,12 @@ var TsServerClient = class {
46
70
  // ─── Lifecycle ──────────────────────────────────────────────────────────
47
71
  async start() {
48
72
  if (this.child) return;
49
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
50
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
51
- log(`Spawning tsserver: ${tsserverPath}`);
73
+ const resolution = resolveTsServer(this.projectRoot);
74
+ const tsserverPath = resolution.path;
75
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
76
+ log(
77
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
78
+ );
52
79
  log(`Project root: ${this.projectRoot}`);
53
80
  log(`tsconfig: ${this.tsconfigPath}`);
54
81
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -77,12 +104,9 @@ var TsServerClient = class {
77
104
  }
78
105
  });
79
106
  const warmStart = performance.now();
80
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
81
- if (fs.existsSync(tsconfigAbs)) {
82
- await this.sendRequest("compilerOptionsForInferredProjects", {
83
- options: { allowJs: true, checkJs: false }
84
- });
85
- }
107
+ await this.sendRequest("compilerOptionsForInferredProjects", {
108
+ options: { allowJs: true, checkJs: false }
109
+ });
86
110
  this.ready = true;
87
111
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
88
112
  }
@@ -177,12 +201,12 @@ var TsServerClient = class {
177
201
  command,
178
202
  arguments: args
179
203
  };
180
- return new Promise((resolve5, reject) => {
204
+ return new Promise((resolve6, reject) => {
181
205
  const timer = setTimeout(() => {
182
206
  this.pending.delete(seq);
183
207
  reject(new Error(`tsserver ${command} timed out after ${REQUEST_TIMEOUT_MS}ms`));
184
208
  }, REQUEST_TIMEOUT_MS);
185
- this.pending.set(seq, { resolve: resolve5, reject, timer, command });
209
+ this.pending.set(seq, { resolve: resolve6, reject, timer, command });
186
210
  this.child.stdin.write(JSON.stringify(request) + "\n");
187
211
  });
188
212
  }
@@ -326,8 +350,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
326
350
  "coverage"
327
351
  ]);
328
352
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
329
- function discoverFiles(rootDir) {
353
+ function normalizeExcludedPaths(rootDir, excludedPaths) {
354
+ return excludedPaths.map(
355
+ (excludedPath) => path2.resolve(rootDir, excludedPath)
356
+ );
357
+ }
358
+ function isExcluded(filePath, excludedPaths) {
359
+ return excludedPaths.some(
360
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
361
+ );
362
+ }
363
+ function discoverFiles(rootDir, excludedPaths = []) {
330
364
  const files = [];
365
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
331
366
  function walk(dir) {
332
367
  let entries;
333
368
  try {
@@ -339,7 +374,9 @@ function discoverFiles(rootDir) {
339
374
  if (entry.isDirectory()) {
340
375
  if (SKIP_DIRS.has(entry.name)) continue;
341
376
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
342
- walk(path2.join(dir, entry.name));
377
+ const childDir = path2.join(dir, entry.name);
378
+ if (isExcluded(childDir, normalizedExclusions)) continue;
379
+ walk(childDir);
343
380
  } else if (entry.isFile()) {
344
381
  const name = entry.name;
345
382
  if (SKIP_FILES.has(name)) continue;
@@ -440,11 +477,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
440
477
  return null;
441
478
  }
442
479
  function createResolver(projectRoot, tsconfigPath) {
480
+ const configFile = path2.resolve(projectRoot, tsconfigPath);
443
481
  return new ResolverFactory({
444
- tsconfig: {
445
- configFile: path2.resolve(projectRoot, tsconfigPath),
446
- references: "auto"
447
- },
482
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
448
483
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
449
484
  extensionAlias: {
450
485
  ".js": [".ts", ".tsx", ".js"],
@@ -510,10 +545,10 @@ function buildReverseMap(forward) {
510
545
  }
511
546
  return reverse;
512
547
  }
513
- async function buildGraph(projectRoot, tsconfigPath) {
548
+ async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
514
549
  const startTime = performance.now();
515
550
  const resolver = createResolver(projectRoot, tsconfigPath);
516
- const fileList = discoverFiles(projectRoot);
551
+ const fileList = discoverFiles(projectRoot, excludedPaths);
517
552
  log2(`Discovered ${fileList.length} source files`);
518
553
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
519
554
  const reverse = buildReverseMap(forward);
@@ -877,8 +912,9 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
877
912
  "coverage",
878
913
  "out"
879
914
  ]);
880
- function findTestFile(rootDir) {
915
+ function findTestFile(rootDir, excludedPaths = []) {
881
916
  const candidates = [];
917
+ const normalizedExclusions = excludedPaths.map((excludedPath) => path5.resolve(excludedPath));
882
918
  function walk(dir, depth) {
883
919
  if (depth > 5 || candidates.length >= 30) return;
884
920
  let entries;
@@ -890,7 +926,11 @@ function findTestFile(rootDir) {
890
926
  for (const entry of entries) {
891
927
  if (entry.isDirectory()) {
892
928
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
893
- walk(path5.join(dir, entry.name), depth + 1);
929
+ const childDir = path5.join(dir, entry.name);
930
+ if (normalizedExclusions.some(
931
+ (excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path5.sep)
932
+ )) continue;
933
+ walk(childDir, depth + 1);
894
934
  } else if (entry.isFile()) {
895
935
  const name = entry.name;
896
936
  if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
@@ -898,7 +938,7 @@ function findTestFile(rootDir) {
898
938
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
899
939
  try {
900
940
  const stat = fs4.statSync(path5.join(dir, name));
901
- if (stat.size > 200 && stat.size < 5e4) {
941
+ if (stat.size > 0 && stat.size < 5e4) {
902
942
  candidates.push({ file: path5.join(dir, name), size: stat.size });
903
943
  }
904
944
  } catch {
@@ -922,7 +962,8 @@ function findImporter(graph, file) {
922
962
  return (preferred ?? revEdges[0]).target;
923
963
  }
924
964
  async function main(configOverride) {
925
- const { projectRoot, tsconfigPath } = configOverride ?? resolveConfig(import.meta.dirname);
965
+ const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = configOverride ?? resolveConfig(import.meta.dirname);
966
+ const excludedPaths = toolIsEmbedded ? [toolDir] : [];
926
967
  let passed = 0;
927
968
  let failed = 0;
928
969
  let skipped = 0;
@@ -945,7 +986,7 @@ async function main(configOverride) {
945
986
  console.log("=====================");
946
987
  console.log(`Project root: ${projectRoot}`);
947
988
  console.log("");
948
- const testFile = findTestFile(projectRoot);
989
+ const testFile = findTestFile(projectRoot, excludedPaths);
949
990
  if (!testFile) {
950
991
  console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
951
992
  return { passed, failed: failed + 1, skipped };
@@ -958,7 +999,7 @@ async function main(configOverride) {
958
999
  let t0;
959
1000
  t0 = performance.now();
960
1001
  try {
961
- const result = await buildGraph(projectRoot, tsconfigPath);
1002
+ const result = await buildGraph(projectRoot, tsconfigPath, excludedPaths);
962
1003
  graph = result.graph;
963
1004
  const ms = performance.now() - t0;
964
1005
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
@@ -5,6 +5,30 @@ import * as fs from "fs";
5
5
  import { createRequire } from "module";
6
6
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
7
7
  var REQUEST_TIMEOUT_MS = 1e4;
8
+ function readPackageVersion(packagePath) {
9
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
10
+ return pkg.version ?? "unknown";
11
+ }
12
+ function resolveTsServer(projectRoot) {
13
+ const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
14
+ let projectVersion;
15
+ try {
16
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
17
+ projectVersion = readPackageVersion(packagePath2);
18
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
19
+ return { path: serverPath2, version: projectVersion, source: "project" };
20
+ } catch {
21
+ }
22
+ const toolRequire = createRequire(import.meta.url);
23
+ const packagePath = toolRequire.resolve("typescript/package.json");
24
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
25
+ return {
26
+ path: serverPath,
27
+ version: readPackageVersion(packagePath),
28
+ source: "typegraph",
29
+ projectVersion
30
+ };
31
+ }
8
32
  var TsServerClient = class {
9
33
  constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
10
34
  this.projectRoot = projectRoot;
@@ -41,9 +65,12 @@ var TsServerClient = class {
41
65
  // ─── Lifecycle ──────────────────────────────────────────────────────────
42
66
  async start() {
43
67
  if (this.child) return;
44
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
45
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
46
- log(`Spawning tsserver: ${tsserverPath}`);
68
+ const resolution = resolveTsServer(this.projectRoot);
69
+ const tsserverPath = resolution.path;
70
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
71
+ log(
72
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
73
+ );
47
74
  log(`Project root: ${this.projectRoot}`);
48
75
  log(`tsconfig: ${this.tsconfigPath}`);
49
76
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -72,12 +99,9 @@ var TsServerClient = class {
72
99
  }
73
100
  });
74
101
  const warmStart = performance.now();
75
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
76
- if (fs.existsSync(tsconfigAbs)) {
77
- await this.sendRequest("compilerOptionsForInferredProjects", {
78
- options: { allowJs: true, checkJs: false }
79
- });
80
- }
102
+ await this.sendRequest("compilerOptionsForInferredProjects", {
103
+ options: { allowJs: true, checkJs: false }
104
+ });
81
105
  this.ready = true;
82
106
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
83
107
  }
@@ -301,5 +325,6 @@ var TsServerClient = class {
301
325
  }
302
326
  };
303
327
  export {
304
- TsServerClient
328
+ TsServerClient,
329
+ resolveTsServer
305
330
  };
package/module-graph.ts CHANGED
@@ -55,8 +55,22 @@ const SKIP_FILES = new Set(["routeTree.gen.ts"]);
55
55
 
56
56
  // ─── File Discovery ──────────────────────────────────────────────────────────
57
57
 
58
- export function discoverFiles(rootDir: string): string[] {
58
+ function normalizeExcludedPaths(rootDir: string, excludedPaths: string[]): string[] {
59
+ return excludedPaths.map((excludedPath) =>
60
+ path.resolve(rootDir, excludedPath)
61
+ );
62
+ }
63
+
64
+ function isExcluded(filePath: string, excludedPaths: string[]): boolean {
65
+ return excludedPaths.some(
66
+ (excludedPath) =>
67
+ filePath === excludedPath || filePath.startsWith(excludedPath + path.sep)
68
+ );
69
+ }
70
+
71
+ export function discoverFiles(rootDir: string, excludedPaths: string[] = []): string[] {
59
72
  const files: string[] = [];
73
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
60
74
 
61
75
  function walk(dir: string): void {
62
76
  let entries: fs.Dirent[];
@@ -71,7 +85,9 @@ export function discoverFiles(rootDir: string): string[] {
71
85
  if (SKIP_DIRS.has(entry.name)) continue;
72
86
  // Skip hidden directories (except the root)
73
87
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
74
- walk(path.join(dir, entry.name));
88
+ const childDir = path.join(dir, entry.name);
89
+ if (isExcluded(childDir, normalizedExclusions)) continue;
90
+ walk(childDir);
75
91
  } else if (entry.isFile()) {
76
92
  const name = entry.name;
77
93
  if (SKIP_FILES.has(name)) continue;
@@ -240,11 +256,11 @@ export function resolveProjectImport(
240
256
  }
241
257
 
242
258
  export function createResolver(projectRoot: string, tsconfigPath: string): ResolverFactory {
259
+ const configFile = path.resolve(projectRoot, tsconfigPath);
243
260
  return new ResolverFactory({
244
- tsconfig: {
245
- configFile: path.resolve(projectRoot, tsconfigPath),
246
- references: "auto",
247
- },
261
+ ...(fs.existsSync(configFile)
262
+ ? { tsconfig: { configFile, references: "auto" as const } }
263
+ : {}),
248
264
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
249
265
  extensionAlias: {
250
266
  ".js": [".ts", ".tsx", ".js"],
@@ -328,12 +344,13 @@ function buildReverseMap(forward: Map<string, ImportEdge[]>): Map<string, Import
328
344
 
329
345
  export async function buildGraph(
330
346
  projectRoot: string,
331
- tsconfigPath: string
347
+ tsconfigPath: string,
348
+ excludedPaths: string[] = []
332
349
  ): Promise<BuildGraphResult> {
333
350
  const startTime = performance.now();
334
351
 
335
352
  const resolver = createResolver(projectRoot, tsconfigPath);
336
- const fileList = discoverFiles(projectRoot);
353
+ const fileList = discoverFiles(projectRoot, excludedPaths);
337
354
 
338
355
  log(`Discovered ${fileList.length} source files`);
339
356
 
@@ -464,9 +481,11 @@ export function startWatcher(
464
481
  hooks?: {
465
482
  onFileUpdated?: (filePath: string) => void | Promise<void>;
466
483
  onFileDeleted?: (filePath: string) => void | Promise<void>;
467
- }
484
+ },
485
+ excludedPaths: string[] = []
468
486
  ): void {
469
487
  try {
488
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
470
489
  const watcher = fs.watch(
471
490
  projectRoot,
472
491
  { recursive: true },
@@ -489,6 +508,7 @@ export function startWatcher(
489
508
  return;
490
509
 
491
510
  const absPath = path.resolve(projectRoot, filename);
511
+ if (isExcluded(absPath, normalizedExclusions)) return;
492
512
 
493
513
  if (fs.existsSync(absPath)) {
494
514
  // File created or modified
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph-mcp",
3
- "version": "0.9.47",
3
+ "version": "0.9.48",
4
4
  "description": "Type-aware codebase navigation for AI coding agents — 14 MCP tools powered by tsserver + oxc",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,8 +33,9 @@
33
33
  ],
34
34
  "scripts": {
35
35
  "build": "tsup",
36
+ "setup:self": "tsx scripts/self-install.ts",
36
37
  "start": "tsx server.ts",
37
- "test": "tsx smoke-test-selection-test.ts && tsx export-surface-test.ts && tsx engine-sync-test.ts && tsx install-oxlint-test.ts",
38
+ "test": "tsx tests/smoke-test-selection-test.ts && tsx tests/biome-config-test.ts && tsx tests/self-install-test.ts && tsx tests/tsserver-resolution-test.ts && tsx tests/export-surface-test.ts && tsx tests/engine-sync-test.ts && tsx tests/install-oxlint-test.ts",
38
39
  "check": "tsx check.ts"
39
40
  },
40
41
  "dependencies": {
@@ -42,12 +43,12 @@
42
43
  "@modelcontextprotocol/sdk": "^1.26.0",
43
44
  "oxc-parser": "^0.114.0",
44
45
  "oxc-resolver": "^11.17.1",
46
+ "typescript": "^5.9.3",
45
47
  "zod": "^4.3.6"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@types/node": "^25.3.0",
49
51
  "tsup": "^8.5.0",
50
- "tsx": "^4.21.0",
51
- "typescript": "^5.8.0"
52
+ "tsx": "^4.21.0"
52
53
  }
53
54
  }