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 +16 -14
- package/check.ts +102 -7
- package/cli.ts +100 -82
- package/dist/check.js +81 -7
- package/dist/cli.js +161 -80
- package/package.json +5 -1
- package/scripts/ensure-deps.sh +9 -1
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
|
|
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** >=
|
|
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
|
-
|
|
155
|
+
Add this to your project's `.codex/config.toml`:
|
|
155
156
|
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
164
|
+
Codex only loads project `.codex/config.toml` files for trusted projects. If needed, add this to `~/.codex/config.toml`:
|
|
164
165
|
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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 >=
|
|
146
|
-
pass(`Node.js ${nodeVersion} (>=
|
|
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 >=
|
|
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
|
|
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 (
|
|
199
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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 =
|
|
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
|
|
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 >=
|
|
465
|
-
pass(`Node.js ${nodeVersion} (>=
|
|
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 >=
|
|
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
|
|
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 (
|
|
508
|
-
|
|
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(
|
|
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 >=
|
|
472
|
-
pass(`Node.js ${nodeVersion} (>=
|
|
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 >=
|
|
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
|
|
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 (
|
|
515
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
3065
|
-
|
|
3066
|
-
|
|
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 =
|
|
3158
|
+
const fullPath = getCodexConfigPath(projectRoot3);
|
|
3078
3159
|
if (!fs8.existsSync(fullPath)) return;
|
|
3079
3160
|
let content = fs8.readFileSync(fullPath, "utf-8");
|
|
3080
|
-
if (!content
|
|
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.
|
|
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"
|
package/scripts/ensure-deps.sh
CHANGED
|
@@ -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
|