typegraph-mcp 0.9.30 → 0.9.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,13 +80,13 @@ This gives you 14 MCP tools, 5 workflow skills that teach Claude *when* and *how
80
80
 
81
81
  **Other agents** (Cursor, Codex CLI, Gemini CLI, GitHub Copilot) — restart your agent session. The MCP server and skills are already configured.
82
82
 
83
- For **Codex CLI**, setup now registers the server with `codex mcp add` using absolute paths so the tools work even when Codex launches from outside your project root.
83
+ For **Codex CLI**, setup now writes a project-local `.codex/config.toml` entry using absolute paths so the tools stay scoped to the project and still work when Codex launches from a subdirectory.
84
84
 
85
85
  First query takes ~2s (tsserver warmup). Subsequent queries: 1-60ms.
86
86
 
87
87
  ## Requirements
88
88
 
89
- - **Node.js** >= 18
89
+ - **Node.js** >= 22
90
90
  - **TypeScript** >= 5.0 in the target project
91
91
  - **npm** for dependency installation
92
92
 
@@ -141,30 +141,31 @@ npx typegraph-mcp check
141
141
 
142
142
  | Symptom | Fix |
143
143
  |---|---|
144
- | Server won't start | `cd plugins/typegraph-mcp && npm install` |
144
+ | Server won't start | `cd plugins/typegraph-mcp && npm install --include=optional` |
145
145
  | "TypeScript not found" | Add `typescript` to devDependencies |
146
146
  | Tools return empty results | Check `TYPEGRAPH_TSCONFIG` points to the right tsconfig |
147
147
  | Build errors from plugins/ | Add `"plugins/**"` to tsconfig.json `exclude` array |
148
+ | `@esbuild/*` or `@rollup/*` package missing | Reinstall with Node 22: `npm install --include=optional` |
148
149
  | "npm warn Unknown project config" | Safe to ignore — caused by pnpm settings in your `.npmrc` that npm doesn't recognize |
149
150
 
150
151
  ## Manual MCP configuration
151
152
 
152
153
  ### Codex CLI
153
154
 
154
- Register the server with absolute paths:
155
+ Add this to your project's `.codex/config.toml`:
155
156
 
156
- ```bash
157
- codex mcp add typegraph \
158
- --env TYPEGRAPH_PROJECT_ROOT=/absolute/path/to/your-project \
159
- --env TYPEGRAPH_TSCONFIG=/absolute/path/to/your-project/tsconfig.json \
160
- -- npx tsx /absolute/path/to/your-project/plugins/typegraph-mcp/server.ts
157
+ ```toml
158
+ [mcp_servers.typegraph]
159
+ command = "npx"
160
+ args = ["tsx", "/absolute/path/to/your-project/plugins/typegraph-mcp/server.ts"]
161
+ env = { TYPEGRAPH_PROJECT_ROOT = "/absolute/path/to/your-project", TYPEGRAPH_TSCONFIG = "/absolute/path/to/your-project/tsconfig.json" }
161
162
  ```
162
163
 
163
- Verify with:
164
+ Codex only loads project `.codex/config.toml` files for trusted projects. If needed, add this to `~/.codex/config.toml`:
164
165
 
165
- ```bash
166
- codex mcp get typegraph
167
- codex mcp list
166
+ ```toml
167
+ [projects."/absolute/path/to/your-project"]
168
+ trust_level = "trusted"
168
169
  ```
169
170
 
170
171
  ### JSON-based MCP clients
@@ -213,7 +214,8 @@ Two subsystems start concurrently:
213
214
  ```bash
214
215
  git clone https://github.com/guyowen/typegraph-mcp.git
215
216
  cd typegraph-mcp
216
- npm install
217
+ nvm use
218
+ npm install --include=optional
217
219
  ```
218
220
 
219
221
  ### Run locally against a project
package/check.ts CHANGED
@@ -102,6 +102,66 @@ function testTsserver(projectRoot: string): Promise<boolean> {
102
102
  });
103
103
  }
104
104
 
105
+ function readProjectCodexConfig(projectRoot: string): string | null {
106
+ const configPath = path.resolve(projectRoot, ".codex/config.toml");
107
+ if (!fs.existsSync(configPath)) return null;
108
+ return fs.readFileSync(configPath, "utf-8");
109
+ }
110
+
111
+ function hasCodexTypegraphRegistration(content: string): boolean {
112
+ return /\[mcp_servers\.typegraph\]/.test(content);
113
+ }
114
+
115
+ function hasCompleteCodexTypegraphRegistration(content: string): boolean {
116
+ return (
117
+ hasCodexTypegraphRegistration(content) &&
118
+ /command\s*=\s*"[^"]+"/.test(content) &&
119
+ /args\s*=\s*\[[\s\S]*"tsx"/.test(content) &&
120
+ /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) &&
121
+ /TYPEGRAPH_TSCONFIG\s*=/.test(content)
122
+ );
123
+ }
124
+
125
+ function hasTrustedCodexProject(projectRoot: string): boolean | null {
126
+ const home = process.env.HOME;
127
+ if (!home) return null;
128
+
129
+ const globalConfigPath = path.join(home, ".codex/config.toml");
130
+ if (!fs.existsSync(globalConfigPath)) return null;
131
+
132
+ const lines = fs.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
133
+ let currentProject: string | null = null;
134
+ let currentTrusted = false;
135
+
136
+ const matchesTrustedProject = (): boolean =>
137
+ currentProject !== null &&
138
+ currentTrusted &&
139
+ (projectRoot === currentProject || projectRoot.startsWith(currentProject + path.sep));
140
+
141
+ for (const line of lines) {
142
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
143
+ if (sectionMatch) {
144
+ if (matchesTrustedProject()) return true;
145
+ currentProject = path.resolve(sectionMatch[1]!);
146
+ currentTrusted = false;
147
+ continue;
148
+ }
149
+
150
+ if (line.startsWith("[")) {
151
+ if (matchesTrustedProject()) return true;
152
+ currentProject = null;
153
+ currentTrusted = false;
154
+ continue;
155
+ }
156
+
157
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
158
+ currentTrusted = true;
159
+ }
160
+ }
161
+
162
+ return matchesTrustedProject();
163
+ }
164
+
105
165
  // ─── Main ────────────────────────────────────────────────────────────────────
106
166
 
107
167
  export async function main(configOverride?: TypegraphConfig): Promise<CheckResult> {
@@ -142,10 +202,10 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
142
202
  // 1. Node.js version
143
203
  const nodeVersion = process.version;
144
204
  const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]!, 10);
145
- if (nodeMajor >= 18) {
146
- pass(`Node.js ${nodeVersion} (>= 18 required)`);
205
+ if (nodeMajor >= 22) {
206
+ pass(`Node.js ${nodeVersion} (>= 22 required)`);
147
207
  } else {
148
- fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
208
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
149
209
  }
150
210
 
151
211
  // 2. tsx availability (if we're running, tsx works — but check it's in the project)
@@ -186,18 +246,50 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
186
246
  // Check for plugin .mcp.json in the tool directory (embedded plugin install)
187
247
  const pluginMcpPath = path.join(toolDir, ".mcp.json");
188
248
  const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path.join(toolDir, ".claude-plugin/plugin.json"));
249
+ const projectCodexConfig = readProjectCodexConfig(projectRoot);
250
+ const hasProjectCodexRegistration =
251
+ projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
189
252
  const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
190
253
  stdio: "pipe",
191
254
  encoding: "utf-8",
192
255
  });
193
- const hasCodexRegistration = codexGet.status === 0;
256
+ const hasGlobalCodexRegistration = codexGet.status === 0;
194
257
  if (process.env.CLAUDE_PLUGIN_ROOT) {
195
258
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
196
259
  } else if (hasPluginMcp) {
197
260
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
198
- } else if (hasCodexRegistration) {
199
- pass("MCP registered in Codex CLI");
261
+ } else if (projectCodexConfig !== null) {
262
+ const codexConfigPath = path.resolve(projectRoot, ".codex/config.toml");
263
+ const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
264
+ const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
265
+ const hasArgs = /args\s*=\s*\[[\s\S]*"tsx"/.test(projectCodexConfig);
266
+ const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
267
+ const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
268
+ if (hasProjectCodexRegistration) {
269
+ pass("MCP registered in project .codex/config.toml");
270
+ const trusted = hasTrustedCodexProject(projectRoot);
271
+ if (trusted === false) {
272
+ warn(
273
+ "Project Codex config may be ignored",
274
+ "Add the project (or a parent directory) to ~/.codex/config.toml with trust_level = \"trusted\""
275
+ );
276
+ }
277
+ } else {
278
+ const issues: string[] = [];
279
+ if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
280
+ if (!hasCommand) issues.push("command is missing");
281
+ if (!hasArgs) issues.push("args should include 'tsx'");
282
+ if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
283
+ if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
284
+ fail(
285
+ `Project .codex/config.toml registration incomplete: ${issues.join(", ")}`,
286
+ `Update ${codexConfigPath} with a complete [mcp_servers.typegraph] entry`
287
+ );
288
+ }
289
+ } else if (hasGlobalCodexRegistration) {
290
+ pass("MCP registered in global Codex CLI config");
200
291
  } else {
292
+ const codexConfigPath = path.resolve(projectRoot, ".codex/config.toml");
201
293
  const mcpJsonPath = path.resolve(projectRoot, ".claude/mcp.json");
202
294
  if (fs.existsSync(mcpJsonPath)) {
203
295
  try {
@@ -244,7 +336,10 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
244
336
  );
245
337
  }
246
338
  } else {
247
- fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
339
+ fail(
340
+ "No MCP registration found",
341
+ `Create ${codexConfigPath} with [mcp_servers.typegraph] or create .claude/mcp.json with typegraph server registration`
342
+ );
248
343
  }
249
344
  }
250
345
 
package/cli.ts CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
- import { execSync, spawnSync } from "node:child_process";
18
+ import { execSync } from "node:child_process";
19
19
  import * as p from "@clack/prompts";
20
20
  import { resolveConfig } from "./config.js";
21
21
 
@@ -192,6 +192,87 @@ function getAbsoluteMcpServerEntry(projectRoot: string): {
192
192
  };
193
193
  }
194
194
 
195
+ function getCodexConfigPath(projectRoot: string): string {
196
+ return path.resolve(projectRoot, ".codex/config.toml");
197
+ }
198
+
199
+ function hasCodexMcpSection(content: string): boolean {
200
+ return content.includes("[mcp_servers.typegraph]");
201
+ }
202
+
203
+ function upsertCodexMcpSection(content: string, block: string): { content: string; changed: boolean } {
204
+ const sectionRe = /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/;
205
+ const normalizedBlock = block.trim();
206
+
207
+ if (sectionRe.test(content)) {
208
+ const existingSection = (content.match(sectionRe)?.[0] ?? "").trim();
209
+ if (existingSection === normalizedBlock) {
210
+ return { content, changed: false };
211
+ }
212
+
213
+ const nextContent = content.replace(sectionRe, `\n${normalizedBlock}\n`);
214
+ return { content: nextContent.trimEnd() + "\n", changed: true };
215
+ }
216
+
217
+ const nextContent = content
218
+ ? content.trimEnd() + "\n\n" + normalizedBlock + "\n"
219
+ : normalizedBlock + "\n";
220
+ return { content: nextContent, changed: true };
221
+ }
222
+
223
+ function makeCodexMcpBlock(projectRoot: string): string {
224
+ const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot);
225
+ return [
226
+ "",
227
+ "[mcp_servers.typegraph]",
228
+ `command = "${absoluteEntry.command}"`,
229
+ `args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
230
+ `env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
231
+ "",
232
+ ].join("\n");
233
+ }
234
+
235
+ function isCodexProjectTrusted(projectRoot: string): boolean {
236
+ const home = process.env.HOME;
237
+ if (!home) return false;
238
+
239
+ const globalConfigPath = path.join(home, ".codex/config.toml");
240
+ if (!fs.existsSync(globalConfigPath)) return false;
241
+
242
+ const content = fs.readFileSync(globalConfigPath, "utf-8");
243
+ const lines = content.split(/\r?\n/);
244
+ let currentProject: string | null = null;
245
+ let currentTrusted = false;
246
+
247
+ const matchesTrustedProject = (): boolean =>
248
+ currentProject !== null &&
249
+ currentTrusted &&
250
+ (projectRoot === currentProject || projectRoot.startsWith(currentProject + path.sep));
251
+
252
+ for (const line of lines) {
253
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
254
+ if (sectionMatch) {
255
+ if (matchesTrustedProject()) return true;
256
+ currentProject = path.resolve(sectionMatch[1]!);
257
+ currentTrusted = false;
258
+ continue;
259
+ }
260
+
261
+ if (line.startsWith("[")) {
262
+ if (matchesTrustedProject()) return true;
263
+ currentProject = null;
264
+ currentTrusted = false;
265
+ continue;
266
+ }
267
+
268
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
269
+ currentTrusted = true;
270
+ }
271
+ }
272
+
273
+ return matchesTrustedProject();
274
+ }
275
+
195
276
  /** Register the typegraph MCP server in agent-specific config files */
196
277
  function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): void {
197
278
  if (selectedAgents.includes("cursor")) {
@@ -274,104 +355,41 @@ function deregisterJsonMcp(projectRoot: string, configPath: string, rootKey: str
274
355
 
275
356
  /** Register MCP server in Codex CLI's TOML config */
276
357
  function registerCodexMcp(projectRoot: string): void {
277
- const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot);
278
-
279
- const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
280
- stdio: "pipe",
281
- encoding: "utf-8",
282
- });
283
-
284
- if (codexGet.status === 0) {
285
- const output = `${codexGet.stdout ?? ""}${codexGet.stderr ?? ""}`;
286
- const hasServerPath = output.includes(absoluteEntry.args[1]!);
287
- const hasProjectRoot = output.includes("TYPEGRAPH_PROJECT_ROOT=*****") || output.includes(projectRoot);
288
- const hasTsconfig = output.includes("TYPEGRAPH_TSCONFIG=*****") || output.includes(path.resolve(projectRoot, "tsconfig.json"));
289
- if (hasServerPath && hasProjectRoot && hasTsconfig) {
290
- p.log.info("Codex CLI: typegraph MCP server already registered");
291
- return;
292
- }
293
- spawnSync("codex", ["mcp", "remove", "typegraph"], {
294
- stdio: "pipe",
295
- encoding: "utf-8",
296
- });
297
- }
298
-
299
- const codexAdd = spawnSync(
300
- "codex",
301
- [
302
- "mcp",
303
- "add",
304
- "typegraph",
305
- "--env",
306
- `TYPEGRAPH_PROJECT_ROOT=${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}`,
307
- "--env",
308
- `TYPEGRAPH_TSCONFIG=${absoluteEntry.env.TYPEGRAPH_TSCONFIG}`,
309
- "--",
310
- absoluteEntry.command,
311
- ...absoluteEntry.args,
312
- ],
313
- {
314
- stdio: "pipe",
315
- encoding: "utf-8",
316
- }
317
- );
318
-
319
- if (codexAdd.status === 0) {
320
- p.log.success("Codex CLI: registered typegraph MCP server");
321
- return;
322
- }
323
-
324
- p.log.warn(
325
- `Codex CLI registration failed — falling back to ${".codex/config.toml"}`
326
- );
327
-
328
358
  const configPath = ".codex/config.toml";
329
- const fullPath = path.resolve(projectRoot, configPath);
359
+ const fullPath = getCodexConfigPath(projectRoot);
360
+ const block = makeCodexMcpBlock(projectRoot);
330
361
  let content = "";
331
362
 
332
363
  if (fs.existsSync(fullPath)) {
333
364
  content = fs.readFileSync(fullPath, "utf-8");
334
- // Already registered?
335
- if (content.includes("[mcp_servers.typegraph]")) {
336
- p.log.info(`${configPath}: typegraph MCP server already registered`);
337
- return;
338
- }
339
365
  }
340
366
 
341
- const block = [
342
- "",
343
- "[mcp_servers.typegraph]",
344
- `command = "${absoluteEntry.command}"`,
345
- `args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
346
- `env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
347
- "",
348
- ].join("\n");
349
-
350
367
  const dir = path.dirname(fullPath);
351
368
  if (!fs.existsSync(dir)) {
352
369
  fs.mkdirSync(dir, { recursive: true });
353
370
  }
354
- const newContent = content ? content.trimEnd() + "\n" + block : block.trimStart();
355
- fs.writeFileSync(fullPath, newContent);
356
- p.log.success(`${configPath}: registered typegraph MCP server`);
371
+
372
+ const { content: nextContent, changed } = upsertCodexMcpSection(content, block);
373
+ if (changed) {
374
+ fs.writeFileSync(fullPath, nextContent);
375
+ p.log.success(`${configPath}: registered typegraph MCP server`);
376
+ } else {
377
+ p.log.info(`${configPath}: typegraph MCP server already registered`);
378
+ }
379
+
380
+ if (!isCodexProjectTrusted(projectRoot)) {
381
+ p.log.info(`Codex CLI: trust ${projectRoot} in ~/.codex/config.toml to load project MCP settings`);
382
+ }
357
383
  }
358
384
 
359
385
  /** Deregister MCP server from Codex CLI's TOML config */
360
386
  function deregisterCodexMcp(projectRoot: string): void {
361
- const codexRemove = spawnSync("codex", ["mcp", "remove", "typegraph"], {
362
- stdio: "pipe",
363
- encoding: "utf-8",
364
- });
365
- if (codexRemove.status === 0) {
366
- p.log.info("Codex CLI: removed typegraph MCP server");
367
- }
368
-
369
387
  const configPath = ".codex/config.toml";
370
- const fullPath = path.resolve(projectRoot, configPath);
388
+ const fullPath = getCodexConfigPath(projectRoot);
371
389
  if (!fs.existsSync(fullPath)) return;
372
390
 
373
391
  let content = fs.readFileSync(fullPath, "utf-8");
374
- if (!content.includes("[mcp_servers.typegraph]")) return;
392
+ if (!hasCodexMcpSection(content)) return;
375
393
 
376
394
  // Remove the [mcp_servers.typegraph] section (stops at next section header or end of file)
377
395
  content = content.replace(
@@ -643,12 +661,12 @@ async function setup(yes: boolean): Promise<void> {
643
661
 
644
662
  s.message("Installing dependencies...");
645
663
  try {
646
- execSync("npm install", { cwd: targetDir, stdio: "pipe" });
664
+ execSync("npm install --include=optional", { cwd: targetDir, stdio: "pipe" });
647
665
  s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files with dependencies`);
648
666
  } catch (err) {
649
667
  s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files`);
650
668
  p.log.warn(`Dependency install failed: ${err instanceof Error ? err.message : String(err)}`);
651
- p.log.info(`Run manually: cd ${PLUGIN_DIR_NAME} && npm install`);
669
+ p.log.info(`Run manually: cd ${PLUGIN_DIR_NAME} && npm install --include=optional`);
652
670
  }
653
671
 
654
672
  // 4. Copy skills to .agents/skills/ for cross-platform discovery
package/dist/check.js CHANGED
@@ -432,6 +432,46 @@ function testTsserver(projectRoot) {
432
432
  child.stdin.write(request + "\n");
433
433
  });
434
434
  }
435
+ function readProjectCodexConfig(projectRoot) {
436
+ const configPath = path3.resolve(projectRoot, ".codex/config.toml");
437
+ if (!fs2.existsSync(configPath)) return null;
438
+ return fs2.readFileSync(configPath, "utf-8");
439
+ }
440
+ function hasCodexTypegraphRegistration(content) {
441
+ return /\[mcp_servers\.typegraph\]/.test(content);
442
+ }
443
+ function hasCompleteCodexTypegraphRegistration(content) {
444
+ return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*"tsx"/.test(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
445
+ }
446
+ function hasTrustedCodexProject(projectRoot) {
447
+ const home = process.env.HOME;
448
+ if (!home) return null;
449
+ const globalConfigPath = path3.join(home, ".codex/config.toml");
450
+ if (!fs2.existsSync(globalConfigPath)) return null;
451
+ const lines = fs2.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
452
+ let currentProject = null;
453
+ let currentTrusted = false;
454
+ const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot === currentProject || projectRoot.startsWith(currentProject + path3.sep));
455
+ for (const line of lines) {
456
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
457
+ if (sectionMatch) {
458
+ if (matchesTrustedProject()) return true;
459
+ currentProject = path3.resolve(sectionMatch[1]);
460
+ currentTrusted = false;
461
+ continue;
462
+ }
463
+ if (line.startsWith("[")) {
464
+ if (matchesTrustedProject()) return true;
465
+ currentProject = null;
466
+ currentTrusted = false;
467
+ continue;
468
+ }
469
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
470
+ currentTrusted = true;
471
+ }
472
+ }
473
+ return matchesTrustedProject();
474
+ }
435
475
  async function main(configOverride) {
436
476
  const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
437
477
  let passed = 0;
@@ -461,10 +501,10 @@ async function main(configOverride) {
461
501
  console.log("");
462
502
  const nodeVersion = process.version;
463
503
  const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
464
- if (nodeMajor >= 18) {
465
- pass(`Node.js ${nodeVersion} (>= 18 required)`);
504
+ if (nodeMajor >= 22) {
505
+ pass(`Node.js ${nodeVersion} (>= 22 required)`);
466
506
  } else {
467
- fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
507
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
468
508
  }
469
509
  const tsxInRoot = fs2.existsSync(path3.join(projectRoot, "node_modules/.bin/tsx"));
470
510
  const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
@@ -495,18 +535,49 @@ async function main(configOverride) {
495
535
  }
496
536
  const pluginMcpPath = path3.join(toolDir, ".mcp.json");
497
537
  const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
538
+ const projectCodexConfig = readProjectCodexConfig(projectRoot);
539
+ const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
498
540
  const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
499
541
  stdio: "pipe",
500
542
  encoding: "utf-8"
501
543
  });
502
- const hasCodexRegistration = codexGet.status === 0;
544
+ const hasGlobalCodexRegistration = codexGet.status === 0;
503
545
  if (process.env.CLAUDE_PLUGIN_ROOT) {
504
546
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
505
547
  } else if (hasPluginMcp) {
506
548
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
507
- } else if (hasCodexRegistration) {
508
- pass("MCP registered in Codex CLI");
549
+ } else if (projectCodexConfig !== null) {
550
+ const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
551
+ const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
552
+ const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
553
+ const hasArgs = /args\s*=\s*\[[\s\S]*"tsx"/.test(projectCodexConfig);
554
+ const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
555
+ const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
556
+ if (hasProjectCodexRegistration) {
557
+ pass("MCP registered in project .codex/config.toml");
558
+ const trusted = hasTrustedCodexProject(projectRoot);
559
+ if (trusted === false) {
560
+ warn(
561
+ "Project Codex config may be ignored",
562
+ 'Add the project (or a parent directory) to ~/.codex/config.toml with trust_level = "trusted"'
563
+ );
564
+ }
565
+ } else {
566
+ const issues = [];
567
+ if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
568
+ if (!hasCommand) issues.push("command is missing");
569
+ if (!hasArgs) issues.push("args should include 'tsx'");
570
+ if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
571
+ if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
572
+ fail(
573
+ `Project .codex/config.toml registration incomplete: ${issues.join(", ")}`,
574
+ `Update ${codexConfigPath} with a complete [mcp_servers.typegraph] entry`
575
+ );
576
+ }
577
+ } else if (hasGlobalCodexRegistration) {
578
+ pass("MCP registered in global Codex CLI config");
509
579
  } else {
580
+ const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
510
581
  const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
511
582
  if (fs2.existsSync(mcpJsonPath)) {
512
583
  try {
@@ -551,7 +622,10 @@ async function main(configOverride) {
551
622
  );
552
623
  }
553
624
  } else {
554
- fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
625
+ fail(
626
+ "No MCP registration found",
627
+ `Create ${codexConfigPath} with [mcp_servers.typegraph] or create .claude/mcp.json with typegraph server registration`
628
+ );
555
629
  }
556
630
  }
557
631
  const toolNodeModules = path3.join(toolDir, "node_modules");
package/dist/cli.js CHANGED
@@ -439,6 +439,46 @@ function testTsserver(projectRoot3) {
439
439
  child.stdin.write(request + "\n");
440
440
  });
441
441
  }
442
+ function readProjectCodexConfig(projectRoot3) {
443
+ const configPath = path3.resolve(projectRoot3, ".codex/config.toml");
444
+ if (!fs2.existsSync(configPath)) return null;
445
+ return fs2.readFileSync(configPath, "utf-8");
446
+ }
447
+ function hasCodexTypegraphRegistration(content) {
448
+ return /\[mcp_servers\.typegraph\]/.test(content);
449
+ }
450
+ function hasCompleteCodexTypegraphRegistration(content) {
451
+ return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*"tsx"/.test(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
452
+ }
453
+ function hasTrustedCodexProject(projectRoot3) {
454
+ const home = process.env.HOME;
455
+ if (!home) return null;
456
+ const globalConfigPath = path3.join(home, ".codex/config.toml");
457
+ if (!fs2.existsSync(globalConfigPath)) return null;
458
+ const lines = fs2.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
459
+ let currentProject = null;
460
+ let currentTrusted = false;
461
+ const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot3 === currentProject || projectRoot3.startsWith(currentProject + path3.sep));
462
+ for (const line of lines) {
463
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
464
+ if (sectionMatch) {
465
+ if (matchesTrustedProject()) return true;
466
+ currentProject = path3.resolve(sectionMatch[1]);
467
+ currentTrusted = false;
468
+ continue;
469
+ }
470
+ if (line.startsWith("[")) {
471
+ if (matchesTrustedProject()) return true;
472
+ currentProject = null;
473
+ currentTrusted = false;
474
+ continue;
475
+ }
476
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
477
+ currentTrusted = true;
478
+ }
479
+ }
480
+ return matchesTrustedProject();
481
+ }
442
482
  async function main(configOverride) {
443
483
  const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
444
484
  let passed = 0;
@@ -468,10 +508,10 @@ async function main(configOverride) {
468
508
  console.log("");
469
509
  const nodeVersion = process.version;
470
510
  const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
471
- if (nodeMajor >= 18) {
472
- pass(`Node.js ${nodeVersion} (>= 18 required)`);
511
+ if (nodeMajor >= 22) {
512
+ pass(`Node.js ${nodeVersion} (>= 22 required)`);
473
513
  } else {
474
- fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
514
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
475
515
  }
476
516
  const tsxInRoot = fs2.existsSync(path3.join(projectRoot3, "node_modules/.bin/tsx"));
477
517
  const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
@@ -502,18 +542,49 @@ async function main(configOverride) {
502
542
  }
503
543
  const pluginMcpPath = path3.join(toolDir, ".mcp.json");
504
544
  const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
545
+ const projectCodexConfig = readProjectCodexConfig(projectRoot3);
546
+ const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
505
547
  const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
506
548
  stdio: "pipe",
507
549
  encoding: "utf-8"
508
550
  });
509
- const hasCodexRegistration = codexGet.status === 0;
551
+ const hasGlobalCodexRegistration = codexGet.status === 0;
510
552
  if (process.env.CLAUDE_PLUGIN_ROOT) {
511
553
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
512
554
  } else if (hasPluginMcp) {
513
555
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
514
- } else if (hasCodexRegistration) {
515
- pass("MCP registered in Codex CLI");
556
+ } else if (projectCodexConfig !== null) {
557
+ const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
558
+ const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
559
+ const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
560
+ const hasArgs = /args\s*=\s*\[[\s\S]*"tsx"/.test(projectCodexConfig);
561
+ const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
562
+ const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
563
+ if (hasProjectCodexRegistration) {
564
+ pass("MCP registered in project .codex/config.toml");
565
+ const trusted = hasTrustedCodexProject(projectRoot3);
566
+ if (trusted === false) {
567
+ warn(
568
+ "Project Codex config may be ignored",
569
+ 'Add the project (or a parent directory) to ~/.codex/config.toml with trust_level = "trusted"'
570
+ );
571
+ }
572
+ } else {
573
+ const issues = [];
574
+ if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
575
+ if (!hasCommand) issues.push("command is missing");
576
+ if (!hasArgs) issues.push("args should include 'tsx'");
577
+ if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
578
+ if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
579
+ fail(
580
+ `Project .codex/config.toml registration incomplete: ${issues.join(", ")}`,
581
+ `Update ${codexConfigPath} with a complete [mcp_servers.typegraph] entry`
582
+ );
583
+ }
584
+ } else if (hasGlobalCodexRegistration) {
585
+ pass("MCP registered in global Codex CLI config");
516
586
  } else {
587
+ const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
517
588
  const mcpJsonPath = path3.resolve(projectRoot3, ".claude/mcp.json");
518
589
  if (fs2.existsSync(mcpJsonPath)) {
519
590
  try {
@@ -558,7 +629,10 @@ async function main(configOverride) {
558
629
  );
559
630
  }
560
631
  } else {
561
- fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
632
+ fail(
633
+ "No MCP registration found",
634
+ `Create ${codexConfigPath} with [mcp_servers.typegraph] or create .claude/mcp.json with typegraph server registration`
635
+ );
562
636
  }
563
637
  }
564
638
  const toolNodeModules = path3.join(toolDir, "node_modules");
@@ -2802,7 +2876,7 @@ var init_server = __esm({
2802
2876
  init_config();
2803
2877
  import * as fs8 from "fs";
2804
2878
  import * as path9 from "path";
2805
- import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
2879
+ import { execSync as execSync2 } from "child_process";
2806
2880
  import * as p from "@clack/prompts";
2807
2881
  var AGENT_SNIPPET = `
2808
2882
  ## TypeScript Navigation (typegraph-mcp)
@@ -2932,6 +3006,69 @@ function getAbsoluteMcpServerEntry(projectRoot3) {
2932
3006
  }
2933
3007
  };
2934
3008
  }
3009
+ function getCodexConfigPath(projectRoot3) {
3010
+ return path9.resolve(projectRoot3, ".codex/config.toml");
3011
+ }
3012
+ function hasCodexMcpSection(content) {
3013
+ return content.includes("[mcp_servers.typegraph]");
3014
+ }
3015
+ function upsertCodexMcpSection(content, block) {
3016
+ const sectionRe = /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/;
3017
+ const normalizedBlock = block.trim();
3018
+ if (sectionRe.test(content)) {
3019
+ const existingSection = (content.match(sectionRe)?.[0] ?? "").trim();
3020
+ if (existingSection === normalizedBlock) {
3021
+ return { content, changed: false };
3022
+ }
3023
+ const nextContent2 = content.replace(sectionRe, `
3024
+ ${normalizedBlock}
3025
+ `);
3026
+ return { content: nextContent2.trimEnd() + "\n", changed: true };
3027
+ }
3028
+ const nextContent = content ? content.trimEnd() + "\n\n" + normalizedBlock + "\n" : normalizedBlock + "\n";
3029
+ return { content: nextContent, changed: true };
3030
+ }
3031
+ function makeCodexMcpBlock(projectRoot3) {
3032
+ const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot3);
3033
+ return [
3034
+ "",
3035
+ "[mcp_servers.typegraph]",
3036
+ `command = "${absoluteEntry.command}"`,
3037
+ `args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
3038
+ `env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
3039
+ ""
3040
+ ].join("\n");
3041
+ }
3042
+ function isCodexProjectTrusted(projectRoot3) {
3043
+ const home = process.env.HOME;
3044
+ if (!home) return false;
3045
+ const globalConfigPath = path9.join(home, ".codex/config.toml");
3046
+ if (!fs8.existsSync(globalConfigPath)) return false;
3047
+ const content = fs8.readFileSync(globalConfigPath, "utf-8");
3048
+ const lines = content.split(/\r?\n/);
3049
+ let currentProject = null;
3050
+ let currentTrusted = false;
3051
+ const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot3 === currentProject || projectRoot3.startsWith(currentProject + path9.sep));
3052
+ for (const line of lines) {
3053
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
3054
+ if (sectionMatch) {
3055
+ if (matchesTrustedProject()) return true;
3056
+ currentProject = path9.resolve(sectionMatch[1]);
3057
+ currentTrusted = false;
3058
+ continue;
3059
+ }
3060
+ if (line.startsWith("[")) {
3061
+ if (matchesTrustedProject()) return true;
3062
+ currentProject = null;
3063
+ currentTrusted = false;
3064
+ continue;
3065
+ }
3066
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
3067
+ currentTrusted = true;
3068
+ }
3069
+ }
3070
+ return matchesTrustedProject();
3071
+ }
2935
3072
  function registerMcpServers(projectRoot3, selectedAgents) {
2936
3073
  if (selectedAgents.includes("cursor")) {
2937
3074
  registerJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
@@ -2994,90 +3131,34 @@ function deregisterJsonMcp(projectRoot3, configPath, rootKey) {
2994
3131
  }
2995
3132
  }
2996
3133
  function registerCodexMcp(projectRoot3) {
2997
- const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot3);
2998
- const codexGet = spawnSync2("codex", ["mcp", "get", "typegraph"], {
2999
- stdio: "pipe",
3000
- encoding: "utf-8"
3001
- });
3002
- if (codexGet.status === 0) {
3003
- const output = `${codexGet.stdout ?? ""}${codexGet.stderr ?? ""}`;
3004
- const hasServerPath = output.includes(absoluteEntry.args[1]);
3005
- const hasProjectRoot = output.includes("TYPEGRAPH_PROJECT_ROOT=*****") || output.includes(projectRoot3);
3006
- const hasTsconfig = output.includes("TYPEGRAPH_TSCONFIG=*****") || output.includes(path9.resolve(projectRoot3, "tsconfig.json"));
3007
- if (hasServerPath && hasProjectRoot && hasTsconfig) {
3008
- p.log.info("Codex CLI: typegraph MCP server already registered");
3009
- return;
3010
- }
3011
- spawnSync2("codex", ["mcp", "remove", "typegraph"], {
3012
- stdio: "pipe",
3013
- encoding: "utf-8"
3014
- });
3015
- }
3016
- const codexAdd = spawnSync2(
3017
- "codex",
3018
- [
3019
- "mcp",
3020
- "add",
3021
- "typegraph",
3022
- "--env",
3023
- `TYPEGRAPH_PROJECT_ROOT=${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}`,
3024
- "--env",
3025
- `TYPEGRAPH_TSCONFIG=${absoluteEntry.env.TYPEGRAPH_TSCONFIG}`,
3026
- "--",
3027
- absoluteEntry.command,
3028
- ...absoluteEntry.args
3029
- ],
3030
- {
3031
- stdio: "pipe",
3032
- encoding: "utf-8"
3033
- }
3034
- );
3035
- if (codexAdd.status === 0) {
3036
- p.log.success("Codex CLI: registered typegraph MCP server");
3037
- return;
3038
- }
3039
- p.log.warn(
3040
- `Codex CLI registration failed \u2014 falling back to ${".codex/config.toml"}`
3041
- );
3042
3134
  const configPath = ".codex/config.toml";
3043
- const fullPath = path9.resolve(projectRoot3, configPath);
3135
+ const fullPath = getCodexConfigPath(projectRoot3);
3136
+ const block = makeCodexMcpBlock(projectRoot3);
3044
3137
  let content = "";
3045
3138
  if (fs8.existsSync(fullPath)) {
3046
3139
  content = fs8.readFileSync(fullPath, "utf-8");
3047
- if (content.includes("[mcp_servers.typegraph]")) {
3048
- p.log.info(`${configPath}: typegraph MCP server already registered`);
3049
- return;
3050
- }
3051
3140
  }
3052
- const block = [
3053
- "",
3054
- "[mcp_servers.typegraph]",
3055
- `command = "${absoluteEntry.command}"`,
3056
- `args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
3057
- `env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
3058
- ""
3059
- ].join("\n");
3060
3141
  const dir = path9.dirname(fullPath);
3061
3142
  if (!fs8.existsSync(dir)) {
3062
3143
  fs8.mkdirSync(dir, { recursive: true });
3063
3144
  }
3064
- const newContent = content ? content.trimEnd() + "\n" + block : block.trimStart();
3065
- fs8.writeFileSync(fullPath, newContent);
3066
- p.log.success(`${configPath}: registered typegraph MCP server`);
3145
+ const { content: nextContent, changed } = upsertCodexMcpSection(content, block);
3146
+ if (changed) {
3147
+ fs8.writeFileSync(fullPath, nextContent);
3148
+ p.log.success(`${configPath}: registered typegraph MCP server`);
3149
+ } else {
3150
+ p.log.info(`${configPath}: typegraph MCP server already registered`);
3151
+ }
3152
+ if (!isCodexProjectTrusted(projectRoot3)) {
3153
+ p.log.info(`Codex CLI: trust ${projectRoot3} in ~/.codex/config.toml to load project MCP settings`);
3154
+ }
3067
3155
  }
3068
3156
  function deregisterCodexMcp(projectRoot3) {
3069
- const codexRemove = spawnSync2("codex", ["mcp", "remove", "typegraph"], {
3070
- stdio: "pipe",
3071
- encoding: "utf-8"
3072
- });
3073
- if (codexRemove.status === 0) {
3074
- p.log.info("Codex CLI: removed typegraph MCP server");
3075
- }
3076
3157
  const configPath = ".codex/config.toml";
3077
- const fullPath = path9.resolve(projectRoot3, configPath);
3158
+ const fullPath = getCodexConfigPath(projectRoot3);
3078
3159
  if (!fs8.existsSync(fullPath)) return;
3079
3160
  let content = fs8.readFileSync(fullPath, "utf-8");
3080
- if (!content.includes("[mcp_servers.typegraph]")) return;
3161
+ if (!hasCodexMcpSection(content)) return;
3081
3162
  content = content.replace(
3082
3163
  /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/,
3083
3164
  ""
@@ -3279,12 +3360,12 @@ async function setup(yes2) {
3279
3360
  }
3280
3361
  s.message("Installing dependencies...");
3281
3362
  try {
3282
- execSync2("npm install", { cwd: targetDir, stdio: "pipe" });
3363
+ execSync2("npm install --include=optional", { cwd: targetDir, stdio: "pipe" });
3283
3364
  s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files with dependencies`);
3284
3365
  } catch (err) {
3285
3366
  s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files`);
3286
3367
  p.log.warn(`Dependency install failed: ${err instanceof Error ? err.message : String(err)}`);
3287
- p.log.info(`Run manually: cd ${PLUGIN_DIR_NAME} && npm install`);
3368
+ p.log.info(`Run manually: cd ${PLUGIN_DIR_NAME} && npm install --include=optional`);
3288
3369
  }
3289
3370
  if (needsAgentsSkills) {
3290
3371
  const agentsNames = selectedAgents.filter((id) => AGENTS[id].needsAgentsSkills).map((id) => AGENTS[id].name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph-mcp",
3
- "version": "0.9.30",
3
+ "version": "0.9.32",
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",
@@ -8,6 +8,10 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/guyowen/typegraph-mcp.git"
10
10
  },
11
+ "packageManager": "npm@11.5.2",
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
11
15
  "bin": {
12
16
  "typegraph-mcp": "./dist/cli.js",
13
17
  "typegraph": "./dist/cli.js"
@@ -9,6 +9,14 @@ set -e
9
9
 
10
10
  PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
11
11
 
12
+ if command -v node &> /dev/null; then
13
+ NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
14
+ if [ "$NODE_MAJOR" -lt 22 ]; then
15
+ echo "Warning: typegraph-mcp requires Node.js >= 22. Current: $(node -v)"
16
+ exit 1
17
+ fi
18
+ fi
19
+
12
20
  # Check if node_modules exist with required packages
13
21
  if [ -d "$PLUGIN_DIR/node_modules/@modelcontextprotocol" ] && \
14
22
  [ -d "$PLUGIN_DIR/node_modules/oxc-parser" ] && \
@@ -21,7 +29,7 @@ echo "Installing typegraph-mcp dependencies..."
21
29
  cd "$PLUGIN_DIR"
22
30
 
23
31
  if command -v npm &> /dev/null; then
24
- npm install
32
+ npm install --include=optional
25
33
  else
26
34
  echo "Warning: npm not found. Run 'npm install' in $PLUGIN_DIR manually."
27
35
  exit 1