typegraph-mcp 0.9.0 → 0.9.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
5
5
  "author": {
6
6
  "name": "Owen Jones"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
5
5
  "author": {
6
6
  "name": "Owen Jones"
package/cli.ts CHANGED
@@ -356,7 +356,9 @@ async function selectAgents(projectRoot: string, yes: boolean): Promise<AgentId[
356
356
  // ─── Setup Command ───────────────────────────────────────────────────────────
357
357
 
358
358
  async function setup(yes: boolean): Promise<void> {
359
- const sourceDir = import.meta.dirname;
359
+ const sourceDir = path.basename(import.meta.dirname) === "dist"
360
+ ? path.resolve(import.meta.dirname, "..")
361
+ : import.meta.dirname;
360
362
  const projectRoot = process.cwd();
361
363
 
362
364
  process.stdout.write("\x1Bc"); // Clear terminal
package/dist/check.js ADDED
@@ -0,0 +1,376 @@
1
+
2
+ // check.ts
3
+ import * as fs from "fs";
4
+ import * as path2 from "path";
5
+ import { createRequire } from "module";
6
+ import { spawn } from "child_process";
7
+
8
+ // config.ts
9
+ import * as path from "path";
10
+ function resolveConfig(toolDir) {
11
+ const cwd = process.cwd();
12
+ const projectRoot = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path.basename(path.dirname(toolDir)) === "plugins" ? path.resolve(toolDir, "../..") : cwd;
13
+ const tsconfigPath = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
14
+ const toolIsEmbedded = toolDir.startsWith(projectRoot + path.sep);
15
+ const toolRelPath = toolIsEmbedded ? path.relative(projectRoot, toolDir) : toolDir;
16
+ return { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath };
17
+ }
18
+
19
+ // check.ts
20
+ function findFirstTsFile(dir) {
21
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
22
+ try {
23
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
24
+ if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
25
+ return path2.join(dir, entry.name);
26
+ }
27
+ }
28
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
29
+ if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
30
+ const found = findFirstTsFile(path2.join(dir, entry.name));
31
+ if (found) return found;
32
+ }
33
+ }
34
+ } catch {
35
+ }
36
+ return null;
37
+ }
38
+ function testTsserver(projectRoot) {
39
+ return new Promise((resolve3) => {
40
+ let tsserverPath;
41
+ try {
42
+ const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
43
+ tsserverPath = require2.resolve("typescript/lib/tsserver.js");
44
+ } catch {
45
+ resolve3(false);
46
+ return;
47
+ }
48
+ const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
49
+ cwd: projectRoot,
50
+ stdio: ["pipe", "pipe", "pipe"]
51
+ });
52
+ const timeout = setTimeout(() => {
53
+ child.kill();
54
+ resolve3(false);
55
+ }, 1e4);
56
+ let buffer = "";
57
+ child.stdout.on("data", (chunk) => {
58
+ buffer += chunk.toString();
59
+ if (buffer.includes('"success":true')) {
60
+ clearTimeout(timeout);
61
+ child.kill();
62
+ resolve3(true);
63
+ }
64
+ });
65
+ child.on("error", () => {
66
+ clearTimeout(timeout);
67
+ resolve3(false);
68
+ });
69
+ child.on("exit", () => {
70
+ clearTimeout(timeout);
71
+ });
72
+ const request = JSON.stringify({
73
+ seq: 1,
74
+ type: "request",
75
+ command: "configure",
76
+ arguments: {
77
+ preferences: { disableSuggestions: true }
78
+ }
79
+ });
80
+ child.stdin.write(request + "\n");
81
+ });
82
+ }
83
+ async function main(configOverride) {
84
+ const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
85
+ let passed = 0;
86
+ let failed = 0;
87
+ let warned = 0;
88
+ function pass(msg) {
89
+ console.log(` \u2713 ${msg}`);
90
+ passed++;
91
+ }
92
+ function fail(msg, fix) {
93
+ console.log(` \u2717 ${msg}`);
94
+ console.log(` Fix: ${fix}`);
95
+ failed++;
96
+ }
97
+ function warn(msg, note) {
98
+ console.log(` ! ${msg}`);
99
+ console.log(` ${note}`);
100
+ warned++;
101
+ }
102
+ function skip(msg) {
103
+ console.log(` - ${msg} (skipped)`);
104
+ }
105
+ console.log("");
106
+ console.log("typegraph-mcp Health Check");
107
+ console.log("=======================");
108
+ console.log(`Project root: ${projectRoot}`);
109
+ console.log("");
110
+ const nodeVersion = process.version;
111
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
112
+ if (nodeMajor >= 18) {
113
+ pass(`Node.js ${nodeVersion} (>= 18 required)`);
114
+ } else {
115
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
116
+ }
117
+ const tsxInRoot = fs.existsSync(path2.join(projectRoot, "node_modules/.bin/tsx"));
118
+ const tsxInTool = fs.existsSync(path2.join(toolDir, "node_modules/.bin/tsx"));
119
+ if (tsxInRoot || tsxInTool) {
120
+ pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
121
+ } else {
122
+ pass("tsx available (via npx/global)");
123
+ }
124
+ let tsVersion = null;
125
+ try {
126
+ const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
127
+ const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
128
+ const tsPkgPath = path2.resolve(path2.dirname(tsserverPath), "..", "package.json");
129
+ const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
130
+ tsVersion = tsPkg.version;
131
+ pass(`TypeScript found (v${tsVersion})`);
132
+ } catch {
133
+ fail(
134
+ "TypeScript not found in project",
135
+ "Add `typescript` to devDependencies and run `pnpm install`"
136
+ );
137
+ }
138
+ const tsconfigAbs = path2.resolve(projectRoot, tsconfigPath);
139
+ if (fs.existsSync(tsconfigAbs)) {
140
+ pass(`tsconfig.json exists at ${tsconfigPath}`);
141
+ } else {
142
+ fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
143
+ }
144
+ const pluginMcpPath = path2.join(toolDir, ".mcp.json");
145
+ const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path2.join(toolDir, ".claude-plugin/plugin.json"));
146
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
147
+ pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
148
+ } else if (hasPluginMcp) {
149
+ pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
150
+ } else {
151
+ const mcpJsonPath = path2.resolve(projectRoot, ".claude/mcp.json");
152
+ if (fs.existsSync(mcpJsonPath)) {
153
+ try {
154
+ const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
155
+ const tsNav = mcpJson?.mcpServers?.["typegraph"];
156
+ if (tsNav) {
157
+ const hasCommand = tsNav.command === "npx";
158
+ const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
159
+ const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
160
+ if (hasCommand && hasArgs && hasEnv) {
161
+ pass("MCP registered in .claude/mcp.json");
162
+ } else {
163
+ const issues = [];
164
+ if (!hasCommand) issues.push("command should be 'npx'");
165
+ if (!hasArgs) issues.push("args should include 'tsx'");
166
+ if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
167
+ fail(
168
+ `MCP registration incomplete: ${issues.join(", ")}`,
169
+ "See README for correct .claude/mcp.json format"
170
+ );
171
+ }
172
+ } else {
173
+ const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path2.resolve(toolDir, "server.ts");
174
+ fail(
175
+ "MCP entry 'typegraph' not found in .claude/mcp.json",
176
+ `Add to .claude/mcp.json:
177
+ {
178
+ "mcpServers": {
179
+ "typegraph": {
180
+ "command": "npx",
181
+ "args": ["tsx", "${serverPath}"],
182
+ "env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }
183
+ }
184
+ }
185
+ }`
186
+ );
187
+ }
188
+ } catch (err) {
189
+ fail(
190
+ "Failed to parse .claude/mcp.json",
191
+ `Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
192
+ );
193
+ }
194
+ } else {
195
+ fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
196
+ }
197
+ }
198
+ const toolNodeModules = path2.join(toolDir, "node_modules");
199
+ if (fs.existsSync(toolNodeModules)) {
200
+ const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
201
+ const missing = requiredPkgs.filter(
202
+ (pkg) => !fs.existsSync(path2.join(toolNodeModules, ...pkg.split("/")))
203
+ );
204
+ if (missing.length === 0) {
205
+ pass(`Dependencies installed (${requiredPkgs.length} packages)`);
206
+ } else {
207
+ fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && pnpm install\``);
208
+ }
209
+ } else {
210
+ fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && pnpm install\``);
211
+ }
212
+ try {
213
+ const oxcParserReq = createRequire(path2.join(toolDir, "package.json"));
214
+ const { parseSync } = await import(oxcParserReq.resolve("oxc-parser"));
215
+ const result = parseSync("test.ts", 'import { x } from "./y";');
216
+ if (result.module?.staticImports?.length === 1) {
217
+ pass("oxc-parser working");
218
+ } else {
219
+ fail(
220
+ "oxc-parser parseSync returned unexpected result",
221
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
222
+ );
223
+ }
224
+ } catch (err) {
225
+ fail(
226
+ `oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
227
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
228
+ );
229
+ }
230
+ try {
231
+ const oxcResolverReq = createRequire(path2.join(toolDir, "package.json"));
232
+ const { ResolverFactory } = await import(oxcResolverReq.resolve("oxc-resolver"));
233
+ const resolver = new ResolverFactory({
234
+ tsconfig: { configFile: tsconfigAbs, references: "auto" },
235
+ extensions: [".ts", ".tsx", ".js"],
236
+ extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
237
+ });
238
+ let resolveOk = false;
239
+ const testFile = findFirstTsFile(projectRoot);
240
+ if (testFile) {
241
+ const dir = path2.dirname(testFile);
242
+ const base = "./" + path2.basename(testFile);
243
+ const result = resolver.sync(dir, base);
244
+ resolveOk = !!result.path;
245
+ }
246
+ if (resolveOk) {
247
+ pass("oxc-resolver working");
248
+ } else {
249
+ warn(
250
+ "oxc-resolver loaded but couldn't resolve a test import",
251
+ "Check tsconfig.json is valid and has correct `references`"
252
+ );
253
+ }
254
+ } catch (err) {
255
+ fail(
256
+ `oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
257
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
258
+ );
259
+ }
260
+ if (tsVersion) {
261
+ try {
262
+ const ok = await testTsserver(projectRoot);
263
+ if (ok) {
264
+ pass("tsserver responds to configure");
265
+ } else {
266
+ fail(
267
+ "tsserver did not respond",
268
+ "Verify `typescript` is installed and tsconfig.json is valid"
269
+ );
270
+ }
271
+ } catch (err) {
272
+ fail(
273
+ `tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
274
+ "Verify `typescript` is installed and tsconfig.json is valid"
275
+ );
276
+ }
277
+ } else {
278
+ skip("tsserver test (TypeScript not found)");
279
+ }
280
+ try {
281
+ const { buildGraph } = await import(path2.resolve(toolDir, "module-graph.js"));
282
+ const start = performance.now();
283
+ const { graph } = await buildGraph(projectRoot, tsconfigPath);
284
+ const elapsed = (performance.now() - start).toFixed(0);
285
+ const edgeCount = [...graph.forward.values()].reduce(
286
+ (s, e) => s + e.length,
287
+ 0
288
+ );
289
+ if (graph.files.size > 0 && edgeCount > 0) {
290
+ pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
291
+ } else if (graph.files.size > 0) {
292
+ warn(
293
+ `Module graph: ${graph.files.size} files but 0 edges`,
294
+ "Files found but no internal imports resolved. Check tsconfig references."
295
+ );
296
+ } else {
297
+ fail(
298
+ "Module graph: 0 files discovered",
299
+ "Check tsconfig.json includes source files and project root is correct"
300
+ );
301
+ }
302
+ } catch (err) {
303
+ fail(
304
+ `Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
305
+ "Check that oxc-parser and oxc-resolver are installed correctly"
306
+ );
307
+ }
308
+ if (toolIsEmbedded) {
309
+ const eslintConfigPath = path2.resolve(projectRoot, "eslint.config.mjs");
310
+ if (fs.existsSync(eslintConfigPath)) {
311
+ const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
312
+ const parentDir = path2.basename(path2.dirname(toolDir));
313
+ const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
314
+ const hasParentIgnore = parentIgnorePattern.test(eslintContent);
315
+ if (hasParentIgnore) {
316
+ pass(`ESLint ignores ${parentDir}/`);
317
+ } else {
318
+ fail(
319
+ `ESLint missing ignore: "${parentDir}/**"`,
320
+ `Add to the ignores array in eslint.config.mjs:
321
+ "${parentDir}/**",`
322
+ );
323
+ }
324
+ } else {
325
+ skip("ESLint config check (no eslint.config.mjs)");
326
+ }
327
+ } else {
328
+ skip("ESLint config check (typegraph-mcp is external to project)");
329
+ }
330
+ const gitignorePath = path2.resolve(projectRoot, ".gitignore");
331
+ if (fs.existsSync(gitignorePath)) {
332
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
333
+ const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
334
+ const ignoresClaude = lines.some(
335
+ (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
336
+ );
337
+ const parentDir = toolIsEmbedded ? path2.basename(path2.dirname(toolDir)) : null;
338
+ const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
339
+ if (!ignoresParent && !ignoresClaude) {
340
+ pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
341
+ } else {
342
+ const excluded = [];
343
+ if (ignoresParent) excluded.push(`${parentDir}/`);
344
+ if (ignoresClaude) excluded.push(".claude/");
345
+ warn(
346
+ `.gitignore excludes ${excluded.join(" and ")}`,
347
+ "Remove these entries so MCP config and tool source are tracked in git"
348
+ );
349
+ }
350
+ } else {
351
+ skip(".gitignore check (no .gitignore)");
352
+ }
353
+ console.log("");
354
+ const total = passed + failed;
355
+ if (failed === 0) {
356
+ console.log(
357
+ `${passed}/${total} checks passed` + (warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") + " -- typegraph-mcp is ready"
358
+ );
359
+ } else {
360
+ console.log(
361
+ `${passed}/${total} checks passed, ${failed} failed` + (warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") + " -- fix issues above"
362
+ );
363
+ }
364
+ console.log("");
365
+ return { passed, failed, warned };
366
+ }
367
+ var isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
368
+ if (isDirectRun) {
369
+ main().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
370
+ console.error("Fatal error:", err);
371
+ process.exit(1);
372
+ });
373
+ }
374
+ export {
375
+ main
376
+ };