typegraph-mcp 0.9.33 → 0.9.35
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 +9 -3
- package/check.ts +13 -3
- package/cli.ts +221 -58
- package/dist/check.js +8 -3
- package/dist/cli.js +167 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,9 +128,13 @@ typegraph-mcp <command> [options]
|
|
|
128
128
|
bench Run benchmarks (token, latency, accuracy)
|
|
129
129
|
start Start the MCP server (stdin/stdout)
|
|
130
130
|
|
|
131
|
-
--yes
|
|
131
|
+
--yes Skip prompts
|
|
132
|
+
--clean-global-codex Also remove a stale global Codex MCP entry for this project
|
|
133
|
+
--help Show help
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
`remove` always cleans up project-local config. If it detects a legacy global `~/.codex/config.toml` entry that points at the current project, it will ask before removing it in interactive mode. In non-interactive mode, pass `--clean-global-codex` to allow that global cleanup.
|
|
137
|
+
|
|
134
138
|
## Troubleshooting
|
|
135
139
|
|
|
136
140
|
Run the health check first — it catches most issues:
|
|
@@ -156,11 +160,13 @@ Add this to your project's `.codex/config.toml`:
|
|
|
156
160
|
|
|
157
161
|
```toml
|
|
158
162
|
[mcp_servers.typegraph]
|
|
159
|
-
command = "
|
|
160
|
-
args = ["
|
|
163
|
+
command = "/absolute/path/to/your-project/plugins/typegraph-mcp/node_modules/.bin/tsx"
|
|
164
|
+
args = ["/absolute/path/to/your-project/plugins/typegraph-mcp/server.ts"]
|
|
161
165
|
env = { TYPEGRAPH_PROJECT_ROOT = "/absolute/path/to/your-project", TYPEGRAPH_TSCONFIG = "/absolute/path/to/your-project/tsconfig.json" }
|
|
162
166
|
```
|
|
163
167
|
|
|
168
|
+
Using the plugin-local `tsx` binary avoids relying on `npx tsx` being resolvable when Codex launches the MCP server.
|
|
169
|
+
|
|
164
170
|
Codex only loads project `.codex/config.toml` files for trusted projects. If needed, add this to `~/.codex/config.toml`:
|
|
165
171
|
|
|
166
172
|
```toml
|
package/check.ts
CHANGED
|
@@ -112,11 +112,19 @@ function hasCodexTypegraphRegistration(content: string): boolean {
|
|
|
112
112
|
return /\[mcp_servers\.typegraph\]/.test(content);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function hasCodexTsxLauncher(content: string): boolean {
|
|
116
|
+
return (
|
|
117
|
+
/command\s*=\s*"[^"]*tsx(?:\.cmd)?"/.test(content) ||
|
|
118
|
+
/args\s*=\s*\[[\s\S]*"tsx"/.test(content)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
function hasCompleteCodexTypegraphRegistration(content: string): boolean {
|
|
116
123
|
return (
|
|
117
124
|
hasCodexTypegraphRegistration(content) &&
|
|
118
125
|
/command\s*=\s*"[^"]+"/.test(content) &&
|
|
119
|
-
/args\s*=\s*\[[\s\S]
|
|
126
|
+
/args\s*=\s*\[[\s\S]*\]/.test(content) &&
|
|
127
|
+
hasCodexTsxLauncher(content) &&
|
|
120
128
|
/TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) &&
|
|
121
129
|
/TYPEGRAPH_TSCONFIG\s*=/.test(content)
|
|
122
130
|
);
|
|
@@ -308,7 +316,8 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
308
316
|
const codexConfigPath = path.resolve(projectRoot, ".codex/config.toml");
|
|
309
317
|
const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
|
|
310
318
|
const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
|
|
311
|
-
const hasArgs = /args\s*=\s*\[[\s\S]
|
|
319
|
+
const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
|
|
320
|
+
const hasTsxLauncher = hasCodexTsxLauncher(projectCodexConfig);
|
|
312
321
|
const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
|
|
313
322
|
const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
|
|
314
323
|
if (hasProjectCodexRegistration) {
|
|
@@ -324,7 +333,8 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
324
333
|
const issues: string[] = [];
|
|
325
334
|
if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
|
|
326
335
|
if (!hasCommand) issues.push("command is missing");
|
|
327
|
-
if (!hasArgs) issues.push("args
|
|
336
|
+
if (!hasArgs) issues.push("args are missing");
|
|
337
|
+
if (!hasTsxLauncher) issues.push("command should point to tsx or args should include 'tsx'");
|
|
328
338
|
if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
|
|
329
339
|
if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
|
|
330
340
|
fail(
|
package/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Options:
|
|
12
12
|
* --yes Skip confirmation prompts (accept all defaults)
|
|
13
|
+
* --clean-global-codex Also remove a stale global Codex MCP entry for this project
|
|
13
14
|
* --help Show help
|
|
14
15
|
*/
|
|
15
16
|
|
|
@@ -35,15 +36,35 @@ interface AgentDef {
|
|
|
35
36
|
detect: (projectRoot: string) => boolean;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
interface LegacyGlobalCodexCleanup {
|
|
40
|
+
globalConfigPath: string;
|
|
41
|
+
nextContent: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
39
45
|
|
|
40
46
|
const AGENT_SNIPPET = `
|
|
41
47
|
## TypeScript Navigation (typegraph-mcp)
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Where suitable, use the \`ts_*\` MCP tools instead of grep/glob for navigating TypeScript code. They resolve through barrel files, re-exports, and project references and return semantic results instead of string matches.
|
|
50
|
+
|
|
51
|
+
- Point queries: \`ts_find_symbol\`, \`ts_definition\`, \`ts_references\`, \`ts_type_info\`, \`ts_navigate_to\`, \`ts_trace_chain\`, \`ts_blast_radius\`, \`ts_module_exports\`
|
|
52
|
+
- Graph queries: \`ts_dependency_tree\`, \`ts_dependents\`, \`ts_import_cycles\`, \`ts_shortest_path\`, \`ts_subgraph\`, \`ts_module_boundary\`
|
|
53
|
+
|
|
54
|
+
Start with the navigation tools before reading entire files. Use direct file reads only after the MCP tools identify the exact symbols or lines that matter.
|
|
55
|
+
|
|
56
|
+
Use \`rg\` or \`grep\` when semantic symbol navigation is not the right tool, especially for:
|
|
57
|
+
|
|
58
|
+
- docs, config, SQL, migrations, JSON, env vars, route strings, and other non-TypeScript assets
|
|
59
|
+
- broad text discovery when you do not yet know the symbol name
|
|
60
|
+
- exact string matching across the repo
|
|
61
|
+
- validating wording or finding repeated plan/document references
|
|
62
|
+
|
|
63
|
+
Practical rule:
|
|
44
64
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
65
|
+
- use \`ts_*\` first for TypeScript symbol definition, references, types, and dependency analysis
|
|
66
|
+
- use \`rg\`/\`grep\` for text search and non-TypeScript exploration
|
|
67
|
+
- combine both when a task spans TypeScript code and surrounding docs/config
|
|
47
68
|
`.trimStart();
|
|
48
69
|
|
|
49
70
|
const SNIPPET_MARKER = "## TypeScript Navigation (typegraph-mcp)";
|
|
@@ -148,8 +169,9 @@ Commands:
|
|
|
148
169
|
start Start the MCP server (stdin/stdout)
|
|
149
170
|
|
|
150
171
|
Options:
|
|
151
|
-
--yes
|
|
152
|
-
--
|
|
172
|
+
--yes Skip confirmation prompts (accept all defaults)
|
|
173
|
+
--clean-global-codex Also remove a stale global Codex MCP entry for this project
|
|
174
|
+
--help Show this help
|
|
153
175
|
`.trim();
|
|
154
176
|
|
|
155
177
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -192,12 +214,78 @@ function getAbsoluteMcpServerEntry(projectRoot: string): {
|
|
|
192
214
|
};
|
|
193
215
|
}
|
|
194
216
|
|
|
217
|
+
function getCodexMcpServerEntry(projectRoot: string): {
|
|
218
|
+
command: string;
|
|
219
|
+
args: string[];
|
|
220
|
+
env: Record<string, string>;
|
|
221
|
+
} {
|
|
222
|
+
return {
|
|
223
|
+
command: path.resolve(projectRoot, PLUGIN_DIR_NAME, "node_modules/.bin/tsx"),
|
|
224
|
+
args: [path.resolve(projectRoot, PLUGIN_DIR_NAME, "server.ts")],
|
|
225
|
+
env: {
|
|
226
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot,
|
|
227
|
+
TYPEGRAPH_TSCONFIG: path.resolve(projectRoot, "tsconfig.json"),
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
195
232
|
function getCodexConfigPath(projectRoot: string): string {
|
|
196
233
|
return path.resolve(projectRoot, ".codex/config.toml");
|
|
197
234
|
}
|
|
198
235
|
|
|
199
|
-
function
|
|
200
|
-
return
|
|
236
|
+
function isTomlSectionGroup(sectionName: string | null, prefix: string): boolean {
|
|
237
|
+
return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function splitTomlBlocks(content: string): Array<{ sectionName: string | null; raw: string }> {
|
|
241
|
+
const lines = content.split(/\r?\n/);
|
|
242
|
+
const blocks: Array<{ sectionName: string | null; raw: string }> = [];
|
|
243
|
+
let sectionName: string | null = null;
|
|
244
|
+
let currentLines: string[] = [];
|
|
245
|
+
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
const match = line.match(/^\[([^\]]+)\]\s*$/);
|
|
248
|
+
if (match) {
|
|
249
|
+
if (currentLines.length > 0 || sectionName !== null) {
|
|
250
|
+
blocks.push({ sectionName, raw: currentLines.join("\n") });
|
|
251
|
+
}
|
|
252
|
+
sectionName = match[1]!;
|
|
253
|
+
currentLines = [line];
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
currentLines.push(line);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (currentLines.length > 0 || sectionName !== null) {
|
|
261
|
+
blocks.push({ sectionName, raw: currentLines.join("\n") });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return blocks;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function removeTomlSectionGroup(
|
|
268
|
+
content: string,
|
|
269
|
+
prefix: string
|
|
270
|
+
): { content: string; removed: boolean; removedContent: string } {
|
|
271
|
+
const blocks = splitTomlBlocks(content);
|
|
272
|
+
const removedBlocks = blocks.filter((block) => isTomlSectionGroup(block.sectionName, prefix));
|
|
273
|
+
if (removedBlocks.length === 0) {
|
|
274
|
+
return { content, removed: false, removedContent: "" };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const keptBlocks = blocks.filter((block) => !isTomlSectionGroup(block.sectionName, prefix));
|
|
278
|
+
const nextContent = keptBlocks
|
|
279
|
+
.map((block) => block.raw)
|
|
280
|
+
.join("\n")
|
|
281
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
282
|
+
.trimEnd();
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
content: nextContent ? `${nextContent}\n` : "",
|
|
286
|
+
removed: true,
|
|
287
|
+
removedContent: removedBlocks.map((block) => block.raw).join("\n").trim(),
|
|
288
|
+
};
|
|
201
289
|
}
|
|
202
290
|
|
|
203
291
|
function upsertCodexMcpSection(content: string, block: string): { content: string; changed: boolean } {
|
|
@@ -221,12 +309,13 @@ function upsertCodexMcpSection(content: string, block: string): { content: strin
|
|
|
221
309
|
}
|
|
222
310
|
|
|
223
311
|
function makeCodexMcpBlock(projectRoot: string): string {
|
|
224
|
-
const absoluteEntry =
|
|
312
|
+
const absoluteEntry = getCodexMcpServerEntry(projectRoot);
|
|
313
|
+
const args = absoluteEntry.args.map((arg) => `"${arg}"`).join(", ");
|
|
225
314
|
return [
|
|
226
315
|
"",
|
|
227
316
|
"[mcp_servers.typegraph]",
|
|
228
317
|
`command = "${absoluteEntry.command}"`,
|
|
229
|
-
`args = [
|
|
318
|
+
`args = [${args}]`,
|
|
230
319
|
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
231
320
|
"",
|
|
232
321
|
].join("\n");
|
|
@@ -273,6 +362,57 @@ function isCodexProjectTrusted(projectRoot: string): boolean {
|
|
|
273
362
|
return matchesTrustedProject();
|
|
274
363
|
}
|
|
275
364
|
|
|
365
|
+
function pathEqualsOrContains(candidatePath: string, targetPath: string): boolean {
|
|
366
|
+
const resolvedCandidate = path.resolve(candidatePath);
|
|
367
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
368
|
+
if (resolvedCandidate === resolvedTarget || resolvedCandidate.startsWith(`${resolvedTarget}${path.sep}`)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const realCandidate = fs.realpathSync(candidatePath);
|
|
374
|
+
const realTarget = fs.realpathSync(targetPath);
|
|
375
|
+
return realCandidate === realTarget || realCandidate.startsWith(`${realTarget}${path.sep}`);
|
|
376
|
+
} catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function findLegacyGlobalCodexCleanup(projectRoot: string): LegacyGlobalCodexCleanup | null {
|
|
382
|
+
const home = process.env.HOME;
|
|
383
|
+
if (!home) return null;
|
|
384
|
+
|
|
385
|
+
const globalConfigPath = path.join(home, ".codex/config.toml");
|
|
386
|
+
if (!fs.existsSync(globalConfigPath)) return null;
|
|
387
|
+
|
|
388
|
+
const content = fs.readFileSync(globalConfigPath, "utf-8");
|
|
389
|
+
const { content: nextContent, removed, removedContent } = removeTomlSectionGroup(content, "mcp_servers.typegraph");
|
|
390
|
+
if (!removed) return null;
|
|
391
|
+
|
|
392
|
+
const pluginRoot = path.resolve(projectRoot, PLUGIN_DIR_NAME);
|
|
393
|
+
const quotedPaths = Array.from(removedContent.matchAll(/"([^"\n]+)"/g), (match) => match[1]!);
|
|
394
|
+
const looksProjectSpecific = quotedPaths.some((quotedPath) =>
|
|
395
|
+
pathEqualsOrContains(quotedPath, projectRoot) ||
|
|
396
|
+
pathEqualsOrContains(quotedPath, pluginRoot)
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (!looksProjectSpecific) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { globalConfigPath, nextContent };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function removeLegacyGlobalCodexMcp(cleanup: LegacyGlobalCodexCleanup): void {
|
|
407
|
+
if (cleanup.nextContent === "") {
|
|
408
|
+
fs.unlinkSync(cleanup.globalConfigPath);
|
|
409
|
+
} else {
|
|
410
|
+
fs.writeFileSync(cleanup.globalConfigPath, cleanup.nextContent);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
p.log.info("~/.codex/config.toml: removed stale global typegraph MCP server entry for this project");
|
|
414
|
+
}
|
|
415
|
+
|
|
276
416
|
/** Register the typegraph MCP server in agent-specific config files */
|
|
277
417
|
function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): void {
|
|
278
418
|
if (selectedAgents.includes("cursor")) {
|
|
@@ -386,26 +526,19 @@ function registerCodexMcp(projectRoot: string): void {
|
|
|
386
526
|
function deregisterCodexMcp(projectRoot: string): void {
|
|
387
527
|
const configPath = ".codex/config.toml";
|
|
388
528
|
const fullPath = getCodexConfigPath(projectRoot);
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
402
|
-
|
|
403
|
-
if (content.trim() === "") {
|
|
404
|
-
fs.unlinkSync(fullPath);
|
|
405
|
-
} else {
|
|
406
|
-
fs.writeFileSync(fullPath, content);
|
|
529
|
+
if (fs.existsSync(fullPath)) {
|
|
530
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
531
|
+
const { content: nextContent, removed } = removeTomlSectionGroup(content, "mcp_servers.typegraph");
|
|
532
|
+
|
|
533
|
+
if (removed) {
|
|
534
|
+
if (nextContent === "") {
|
|
535
|
+
fs.unlinkSync(fullPath);
|
|
536
|
+
} else {
|
|
537
|
+
fs.writeFileSync(fullPath, nextContent);
|
|
538
|
+
}
|
|
539
|
+
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
540
|
+
}
|
|
407
541
|
}
|
|
408
|
-
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
409
542
|
}
|
|
410
543
|
|
|
411
544
|
// ─── TSConfig Exclude ─────────────────────────────────────────────────────────
|
|
@@ -733,16 +866,26 @@ async function setup(yes: boolean): Promise<void> {
|
|
|
733
866
|
|
|
734
867
|
// ─── Remove Command ──────────────────────────────────────────────────────────
|
|
735
868
|
|
|
736
|
-
async function removePlugin(
|
|
869
|
+
async function removePlugin(
|
|
870
|
+
projectRoot: string,
|
|
871
|
+
pluginDir: string,
|
|
872
|
+
options: { removeGlobalCodex: boolean; legacyGlobalCodexCleanup: LegacyGlobalCodexCleanup | null }
|
|
873
|
+
): Promise<void> {
|
|
737
874
|
const s = p.spinner();
|
|
738
875
|
s.start("Removing typegraph-mcp...");
|
|
739
876
|
|
|
740
|
-
// 1.
|
|
877
|
+
// 1. Deregister MCP server from agent config files while project paths still exist
|
|
878
|
+
deregisterMcpServers(projectRoot);
|
|
879
|
+
if (options.removeGlobalCodex && options.legacyGlobalCodexCleanup) {
|
|
880
|
+
removeLegacyGlobalCodexMcp(options.legacyGlobalCodexCleanup);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// 2. Remove plugin directory
|
|
741
884
|
if (fs.existsSync(pluginDir)) {
|
|
742
885
|
fs.rmSync(pluginDir, { recursive: true });
|
|
743
886
|
}
|
|
744
887
|
|
|
745
|
-
//
|
|
888
|
+
// 3. Remove .agents/skills/ entries (only typegraph-mcp skills, not the whole dir)
|
|
746
889
|
const agentsSkillsDir = path.resolve(projectRoot, ".agents/skills");
|
|
747
890
|
for (const skill of SKILL_NAMES) {
|
|
748
891
|
const skillDir = path.join(agentsSkillsDir, skill);
|
|
@@ -759,7 +902,7 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
759
902
|
}
|
|
760
903
|
}
|
|
761
904
|
|
|
762
|
-
//
|
|
905
|
+
// 4. Remove agent instruction snippet from all known agent files
|
|
763
906
|
const allAgentFiles = AGENT_IDS
|
|
764
907
|
.map((id) => AGENTS[id].agentFile)
|
|
765
908
|
.filter((f): f is string => f !== null);
|
|
@@ -782,7 +925,7 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
782
925
|
}
|
|
783
926
|
}
|
|
784
927
|
|
|
785
|
-
//
|
|
928
|
+
// 5. Remove --plugin-dir ./plugins/typegraph-mcp from CLAUDE.md
|
|
786
929
|
const claudeMdPath = path.resolve(projectRoot, "CLAUDE.md");
|
|
787
930
|
if (fs.existsSync(claudeMdPath)) {
|
|
788
931
|
let content = fs.readFileSync(claudeMdPath, "utf-8");
|
|
@@ -792,9 +935,6 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
792
935
|
|
|
793
936
|
s.stop("Removed typegraph-mcp");
|
|
794
937
|
|
|
795
|
-
// 5. Deregister MCP server from agent config files
|
|
796
|
-
deregisterMcpServers(projectRoot);
|
|
797
|
-
|
|
798
938
|
p.outro("typegraph-mcp has been uninstalled from this project.");
|
|
799
939
|
}
|
|
800
940
|
|
|
@@ -808,39 +948,35 @@ async function setupAgentInstructions(projectRoot: string, selectedAgents: Agent
|
|
|
808
948
|
return; // No agents with instruction files selected (e.g. Cursor only)
|
|
809
949
|
}
|
|
810
950
|
|
|
811
|
-
//
|
|
951
|
+
// Ensure each selected agent file exists and has the snippet once. Resolve
|
|
952
|
+
// symlinks to avoid writing duplicate content through multiple aliases.
|
|
812
953
|
const seenRealPaths = new Map<string, string>(); // realPath -> first agentFile name
|
|
813
|
-
const existingFiles: { file: string; realPath: string; hasSnippet: boolean }[] = [];
|
|
814
954
|
for (const agentFile of agentFiles) {
|
|
815
955
|
const filePath = path.resolve(projectRoot, agentFile);
|
|
816
|
-
if (!fs.existsSync(filePath))
|
|
817
|
-
|
|
956
|
+
if (!fs.existsSync(filePath)) {
|
|
957
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
958
|
+
fs.writeFileSync(filePath, AGENT_SNIPPET + "\n");
|
|
959
|
+
p.log.success(`${agentFile}: created with typegraph-mcp instructions`);
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
818
962
|
|
|
963
|
+
const realPath = fs.realpathSync(filePath);
|
|
819
964
|
const previousFile = seenRealPaths.get(realPath);
|
|
820
965
|
if (previousFile) {
|
|
821
966
|
p.log.info(`${agentFile}: same file as ${previousFile} (skipped)`);
|
|
822
967
|
continue;
|
|
823
968
|
}
|
|
824
|
-
seenRealPaths.set(realPath, agentFile);
|
|
825
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
826
|
-
existingFiles.push({ file: agentFile, realPath, hasSnippet: content.includes(SNIPPET_MARKER) });
|
|
827
|
-
}
|
|
828
969
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (f.hasSnippet) {
|
|
835
|
-
p.log.info(`${f.file}: already has typegraph-mcp instructions`);
|
|
836
|
-
}
|
|
970
|
+
seenRealPaths.set(realPath, agentFile);
|
|
971
|
+
const content = fs.readFileSync(realPath, "utf-8");
|
|
972
|
+
if (content.includes(SNIPPET_MARKER)) {
|
|
973
|
+
p.log.info(`${agentFile}: already has typegraph-mcp instructions`);
|
|
974
|
+
continue;
|
|
837
975
|
}
|
|
838
|
-
|
|
839
|
-
const target = existingFiles[0]!;
|
|
840
|
-
const content = fs.readFileSync(target.realPath, "utf-8");
|
|
976
|
+
|
|
841
977
|
const appendContent = (content.endsWith("\n") ? "" : "\n") + "\n" + AGENT_SNIPPET;
|
|
842
|
-
fs.appendFileSync(
|
|
843
|
-
p.log.success(`${
|
|
978
|
+
fs.appendFileSync(realPath, appendContent);
|
|
979
|
+
p.log.success(`${agentFile}: appended typegraph-mcp instructions`);
|
|
844
980
|
}
|
|
845
981
|
|
|
846
982
|
// Update --plugin-dir line in CLAUDE.md if Claude Code is selected
|
|
@@ -902,6 +1038,7 @@ async function runVerification(pluginDir: string, selectedAgents: AgentId[]): Pr
|
|
|
902
1038
|
async function remove(yes: boolean): Promise<void> {
|
|
903
1039
|
const projectRoot = process.cwd();
|
|
904
1040
|
const pluginDir = path.resolve(projectRoot, PLUGIN_DIR_NAME);
|
|
1041
|
+
const cleanGlobalCodex = args.includes("--clean-global-codex");
|
|
905
1042
|
|
|
906
1043
|
process.stdout.write("\x1Bc");
|
|
907
1044
|
p.intro("TypeGraph MCP Remove");
|
|
@@ -919,7 +1056,33 @@ async function remove(yes: boolean): Promise<void> {
|
|
|
919
1056
|
}
|
|
920
1057
|
}
|
|
921
1058
|
|
|
922
|
-
|
|
1059
|
+
const legacyGlobalCodexCleanup = findLegacyGlobalCodexCleanup(projectRoot);
|
|
1060
|
+
let removeGlobalCodex = cleanGlobalCodex;
|
|
1061
|
+
|
|
1062
|
+
if (legacyGlobalCodexCleanup && !cleanGlobalCodex && !yes) {
|
|
1063
|
+
const shouldRemoveGlobal = await p.confirm({
|
|
1064
|
+
message: "Also remove the stale global Codex MCP entry for this project from ~/.codex/config.toml?",
|
|
1065
|
+
initialValue: false,
|
|
1066
|
+
});
|
|
1067
|
+
if (p.isCancel(shouldRemoveGlobal)) {
|
|
1068
|
+
p.cancel("Removal cancelled.");
|
|
1069
|
+
process.exit(0);
|
|
1070
|
+
}
|
|
1071
|
+
removeGlobalCodex = shouldRemoveGlobal;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
await removePlugin(projectRoot, pluginDir, {
|
|
1075
|
+
removeGlobalCodex,
|
|
1076
|
+
legacyGlobalCodexCleanup,
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
if (legacyGlobalCodexCleanup && !removeGlobalCodex) {
|
|
1080
|
+
p.log.warn(
|
|
1081
|
+
"Left a stale global Codex MCP entry for this project in ~/.codex/config.toml. " +
|
|
1082
|
+
"Codex may show MCP startup warnings or errors until you remove it. " +
|
|
1083
|
+
"Re-run `typegraph-mcp remove --clean-global-codex` or remove the `typegraph` block manually."
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
923
1086
|
}
|
|
924
1087
|
|
|
925
1088
|
// ─── Check Command ───────────────────────────────────────────────────────────
|
package/dist/check.js
CHANGED
|
@@ -440,8 +440,11 @@ function readProjectCodexConfig(projectRoot) {
|
|
|
440
440
|
function hasCodexTypegraphRegistration(content) {
|
|
441
441
|
return /\[mcp_servers\.typegraph\]/.test(content);
|
|
442
442
|
}
|
|
443
|
+
function hasCodexTsxLauncher(content) {
|
|
444
|
+
return /command\s*=\s*"[^"]*tsx(?:\.cmd)?"/.test(content) || /args\s*=\s*\[[\s\S]*"tsx"/.test(content);
|
|
445
|
+
}
|
|
443
446
|
function hasCompleteCodexTypegraphRegistration(content) {
|
|
444
|
-
return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]
|
|
447
|
+
return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*\]/.test(content) && hasCodexTsxLauncher(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
|
|
445
448
|
}
|
|
446
449
|
function hasTrustedCodexProject(projectRoot) {
|
|
447
450
|
const home = process.env.HOME;
|
|
@@ -584,7 +587,8 @@ async function main(configOverride) {
|
|
|
584
587
|
const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
|
|
585
588
|
const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
|
|
586
589
|
const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
|
|
587
|
-
const hasArgs = /args\s*=\s*\[[\s\S]
|
|
590
|
+
const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
|
|
591
|
+
const hasTsxLauncher = hasCodexTsxLauncher(projectCodexConfig);
|
|
588
592
|
const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
|
|
589
593
|
const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
|
|
590
594
|
if (hasProjectCodexRegistration) {
|
|
@@ -600,7 +604,8 @@ async function main(configOverride) {
|
|
|
600
604
|
const issues = [];
|
|
601
605
|
if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
|
|
602
606
|
if (!hasCommand) issues.push("command is missing");
|
|
603
|
-
if (!hasArgs) issues.push("args
|
|
607
|
+
if (!hasArgs) issues.push("args are missing");
|
|
608
|
+
if (!hasTsxLauncher) issues.push("command should point to tsx or args should include 'tsx'");
|
|
604
609
|
if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
|
|
605
610
|
if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
|
|
606
611
|
fail(
|
package/dist/cli.js
CHANGED
|
@@ -447,8 +447,11 @@ function readProjectCodexConfig(projectRoot3) {
|
|
|
447
447
|
function hasCodexTypegraphRegistration(content) {
|
|
448
448
|
return /\[mcp_servers\.typegraph\]/.test(content);
|
|
449
449
|
}
|
|
450
|
+
function hasCodexTsxLauncher(content) {
|
|
451
|
+
return /command\s*=\s*"[^"]*tsx(?:\.cmd)?"/.test(content) || /args\s*=\s*\[[\s\S]*"tsx"/.test(content);
|
|
452
|
+
}
|
|
450
453
|
function hasCompleteCodexTypegraphRegistration(content) {
|
|
451
|
-
return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]
|
|
454
|
+
return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*\]/.test(content) && hasCodexTsxLauncher(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
|
|
452
455
|
}
|
|
453
456
|
function hasTrustedCodexProject(projectRoot3) {
|
|
454
457
|
const home = process.env.HOME;
|
|
@@ -526,9 +529,9 @@ async function main(configOverride) {
|
|
|
526
529
|
console.log(` Fix: ${fix}`);
|
|
527
530
|
failed++;
|
|
528
531
|
}
|
|
529
|
-
function warn(msg,
|
|
532
|
+
function warn(msg, note) {
|
|
530
533
|
console.log(` ! ${msg}`);
|
|
531
|
-
console.log(` ${
|
|
534
|
+
console.log(` ${note}`);
|
|
532
535
|
warned++;
|
|
533
536
|
}
|
|
534
537
|
function skip(msg) {
|
|
@@ -591,7 +594,8 @@ async function main(configOverride) {
|
|
|
591
594
|
const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
|
|
592
595
|
const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
|
|
593
596
|
const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
|
|
594
|
-
const hasArgs = /args\s*=\s*\[[\s\S]
|
|
597
|
+
const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
|
|
598
|
+
const hasTsxLauncher = hasCodexTsxLauncher(projectCodexConfig);
|
|
595
599
|
const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
|
|
596
600
|
const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
|
|
597
601
|
if (hasProjectCodexRegistration) {
|
|
@@ -607,7 +611,8 @@ async function main(configOverride) {
|
|
|
607
611
|
const issues = [];
|
|
608
612
|
if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
|
|
609
613
|
if (!hasCommand) issues.push("command is missing");
|
|
610
|
-
if (!hasArgs) issues.push("args
|
|
614
|
+
if (!hasArgs) issues.push("args are missing");
|
|
615
|
+
if (!hasTsxLauncher) issues.push("command should point to tsx or args should include 'tsx'");
|
|
611
616
|
if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
|
|
612
617
|
if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
|
|
613
618
|
fail(
|
|
@@ -2915,10 +2920,25 @@ import * as p from "@clack/prompts";
|
|
|
2915
2920
|
var AGENT_SNIPPET = `
|
|
2916
2921
|
## TypeScript Navigation (typegraph-mcp)
|
|
2917
2922
|
|
|
2918
|
-
|
|
2923
|
+
Where suitable, use the \`ts_*\` MCP tools instead of grep/glob for navigating TypeScript code. They resolve through barrel files, re-exports, and project references and return semantic results instead of string matches.
|
|
2924
|
+
|
|
2925
|
+
- Point queries: \`ts_find_symbol\`, \`ts_definition\`, \`ts_references\`, \`ts_type_info\`, \`ts_navigate_to\`, \`ts_trace_chain\`, \`ts_blast_radius\`, \`ts_module_exports\`
|
|
2926
|
+
- Graph queries: \`ts_dependency_tree\`, \`ts_dependents\`, \`ts_import_cycles\`, \`ts_shortest_path\`, \`ts_subgraph\`, \`ts_module_boundary\`
|
|
2927
|
+
|
|
2928
|
+
Start with the navigation tools before reading entire files. Use direct file reads only after the MCP tools identify the exact symbols or lines that matter.
|
|
2929
|
+
|
|
2930
|
+
Use \`rg\` or \`grep\` when semantic symbol navigation is not the right tool, especially for:
|
|
2919
2931
|
|
|
2920
|
-
-
|
|
2921
|
-
-
|
|
2932
|
+
- docs, config, SQL, migrations, JSON, env vars, route strings, and other non-TypeScript assets
|
|
2933
|
+
- broad text discovery when you do not yet know the symbol name
|
|
2934
|
+
- exact string matching across the repo
|
|
2935
|
+
- validating wording or finding repeated plan/document references
|
|
2936
|
+
|
|
2937
|
+
Practical rule:
|
|
2938
|
+
|
|
2939
|
+
- use \`ts_*\` first for TypeScript symbol definition, references, types, and dependency analysis
|
|
2940
|
+
- use \`rg\`/\`grep\` for text search and non-TypeScript exploration
|
|
2941
|
+
- combine both when a task spans TypeScript code and surrounding docs/config
|
|
2922
2942
|
`.trimStart();
|
|
2923
2943
|
var SNIPPET_MARKER = "## TypeScript Navigation (typegraph-mcp)";
|
|
2924
2944
|
var PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
|
|
@@ -3009,8 +3029,9 @@ Commands:
|
|
|
3009
3029
|
start Start the MCP server (stdin/stdout)
|
|
3010
3030
|
|
|
3011
3031
|
Options:
|
|
3012
|
-
--yes
|
|
3013
|
-
--
|
|
3032
|
+
--yes Skip confirmation prompts (accept all defaults)
|
|
3033
|
+
--clean-global-codex Also remove a stale global Codex MCP entry for this project
|
|
3034
|
+
--help Show this help
|
|
3014
3035
|
`.trim();
|
|
3015
3036
|
function copyFile(src, dest) {
|
|
3016
3037
|
const destDir = path9.dirname(dest);
|
|
@@ -3030,10 +3051,10 @@ var MCP_SERVER_ENTRY = {
|
|
|
3030
3051
|
TYPEGRAPH_TSCONFIG: "./tsconfig.json"
|
|
3031
3052
|
}
|
|
3032
3053
|
};
|
|
3033
|
-
function
|
|
3054
|
+
function getCodexMcpServerEntry(projectRoot3) {
|
|
3034
3055
|
return {
|
|
3035
|
-
command: "
|
|
3036
|
-
args: [
|
|
3056
|
+
command: path9.resolve(projectRoot3, PLUGIN_DIR_NAME, "node_modules/.bin/tsx"),
|
|
3057
|
+
args: [path9.resolve(projectRoot3, PLUGIN_DIR_NAME, "server.ts")],
|
|
3037
3058
|
env: {
|
|
3038
3059
|
TYPEGRAPH_PROJECT_ROOT: projectRoot3,
|
|
3039
3060
|
TYPEGRAPH_TSCONFIG: path9.resolve(projectRoot3, "tsconfig.json")
|
|
@@ -3043,8 +3064,45 @@ function getAbsoluteMcpServerEntry(projectRoot3) {
|
|
|
3043
3064
|
function getCodexConfigPath(projectRoot3) {
|
|
3044
3065
|
return path9.resolve(projectRoot3, ".codex/config.toml");
|
|
3045
3066
|
}
|
|
3046
|
-
function
|
|
3047
|
-
return
|
|
3067
|
+
function isTomlSectionGroup(sectionName, prefix) {
|
|
3068
|
+
return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
|
|
3069
|
+
}
|
|
3070
|
+
function splitTomlBlocks(content) {
|
|
3071
|
+
const lines = content.split(/\r?\n/);
|
|
3072
|
+
const blocks = [];
|
|
3073
|
+
let sectionName = null;
|
|
3074
|
+
let currentLines = [];
|
|
3075
|
+
for (const line of lines) {
|
|
3076
|
+
const match = line.match(/^\[([^\]]+)\]\s*$/);
|
|
3077
|
+
if (match) {
|
|
3078
|
+
if (currentLines.length > 0 || sectionName !== null) {
|
|
3079
|
+
blocks.push({ sectionName, raw: currentLines.join("\n") });
|
|
3080
|
+
}
|
|
3081
|
+
sectionName = match[1];
|
|
3082
|
+
currentLines = [line];
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
currentLines.push(line);
|
|
3086
|
+
}
|
|
3087
|
+
if (currentLines.length > 0 || sectionName !== null) {
|
|
3088
|
+
blocks.push({ sectionName, raw: currentLines.join("\n") });
|
|
3089
|
+
}
|
|
3090
|
+
return blocks;
|
|
3091
|
+
}
|
|
3092
|
+
function removeTomlSectionGroup(content, prefix) {
|
|
3093
|
+
const blocks = splitTomlBlocks(content);
|
|
3094
|
+
const removedBlocks = blocks.filter((block) => isTomlSectionGroup(block.sectionName, prefix));
|
|
3095
|
+
if (removedBlocks.length === 0) {
|
|
3096
|
+
return { content, removed: false, removedContent: "" };
|
|
3097
|
+
}
|
|
3098
|
+
const keptBlocks = blocks.filter((block) => !isTomlSectionGroup(block.sectionName, prefix));
|
|
3099
|
+
const nextContent = keptBlocks.map((block) => block.raw).join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
3100
|
+
return {
|
|
3101
|
+
content: nextContent ? `${nextContent}
|
|
3102
|
+
` : "",
|
|
3103
|
+
removed: true,
|
|
3104
|
+
removedContent: removedBlocks.map((block) => block.raw).join("\n").trim()
|
|
3105
|
+
};
|
|
3048
3106
|
}
|
|
3049
3107
|
function upsertCodexMcpSection(content, block) {
|
|
3050
3108
|
const sectionRe = /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/;
|
|
@@ -3063,12 +3121,13 @@ ${normalizedBlock}
|
|
|
3063
3121
|
return { content: nextContent, changed: true };
|
|
3064
3122
|
}
|
|
3065
3123
|
function makeCodexMcpBlock(projectRoot3) {
|
|
3066
|
-
const absoluteEntry =
|
|
3124
|
+
const absoluteEntry = getCodexMcpServerEntry(projectRoot3);
|
|
3125
|
+
const args2 = absoluteEntry.args.map((arg) => `"${arg}"`).join(", ");
|
|
3067
3126
|
return [
|
|
3068
3127
|
"",
|
|
3069
3128
|
"[mcp_servers.typegraph]",
|
|
3070
3129
|
`command = "${absoluteEntry.command}"`,
|
|
3071
|
-
`args = [
|
|
3130
|
+
`args = [${args2}]`,
|
|
3072
3131
|
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
3073
3132
|
""
|
|
3074
3133
|
].join("\n");
|
|
@@ -3103,6 +3162,46 @@ function isCodexProjectTrusted(projectRoot3) {
|
|
|
3103
3162
|
}
|
|
3104
3163
|
return matchesTrustedProject();
|
|
3105
3164
|
}
|
|
3165
|
+
function pathEqualsOrContains(candidatePath, targetPath) {
|
|
3166
|
+
const resolvedCandidate = path9.resolve(candidatePath);
|
|
3167
|
+
const resolvedTarget = path9.resolve(targetPath);
|
|
3168
|
+
if (resolvedCandidate === resolvedTarget || resolvedCandidate.startsWith(`${resolvedTarget}${path9.sep}`)) {
|
|
3169
|
+
return true;
|
|
3170
|
+
}
|
|
3171
|
+
try {
|
|
3172
|
+
const realCandidate = fs8.realpathSync(candidatePath);
|
|
3173
|
+
const realTarget = fs8.realpathSync(targetPath);
|
|
3174
|
+
return realCandidate === realTarget || realCandidate.startsWith(`${realTarget}${path9.sep}`);
|
|
3175
|
+
} catch {
|
|
3176
|
+
return false;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
function findLegacyGlobalCodexCleanup(projectRoot3) {
|
|
3180
|
+
const home = process.env.HOME;
|
|
3181
|
+
if (!home) return null;
|
|
3182
|
+
const globalConfigPath = path9.join(home, ".codex/config.toml");
|
|
3183
|
+
if (!fs8.existsSync(globalConfigPath)) return null;
|
|
3184
|
+
const content = fs8.readFileSync(globalConfigPath, "utf-8");
|
|
3185
|
+
const { content: nextContent, removed, removedContent } = removeTomlSectionGroup(content, "mcp_servers.typegraph");
|
|
3186
|
+
if (!removed) return null;
|
|
3187
|
+
const pluginRoot = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
|
|
3188
|
+
const quotedPaths = Array.from(removedContent.matchAll(/"([^"\n]+)"/g), (match) => match[1]);
|
|
3189
|
+
const looksProjectSpecific = quotedPaths.some(
|
|
3190
|
+
(quotedPath) => pathEqualsOrContains(quotedPath, projectRoot3) || pathEqualsOrContains(quotedPath, pluginRoot)
|
|
3191
|
+
);
|
|
3192
|
+
if (!looksProjectSpecific) {
|
|
3193
|
+
return null;
|
|
3194
|
+
}
|
|
3195
|
+
return { globalConfigPath, nextContent };
|
|
3196
|
+
}
|
|
3197
|
+
function removeLegacyGlobalCodexMcp(cleanup) {
|
|
3198
|
+
if (cleanup.nextContent === "") {
|
|
3199
|
+
fs8.unlinkSync(cleanup.globalConfigPath);
|
|
3200
|
+
} else {
|
|
3201
|
+
fs8.writeFileSync(cleanup.globalConfigPath, cleanup.nextContent);
|
|
3202
|
+
}
|
|
3203
|
+
p.log.info("~/.codex/config.toml: removed stale global typegraph MCP server entry for this project");
|
|
3204
|
+
}
|
|
3106
3205
|
function registerMcpServers(projectRoot3, selectedAgents) {
|
|
3107
3206
|
if (selectedAgents.includes("cursor")) {
|
|
3108
3207
|
registerJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
|
|
@@ -3190,20 +3289,18 @@ function registerCodexMcp(projectRoot3) {
|
|
|
3190
3289
|
function deregisterCodexMcp(projectRoot3) {
|
|
3191
3290
|
const configPath = ".codex/config.toml";
|
|
3192
3291
|
const fullPath = getCodexConfigPath(projectRoot3);
|
|
3193
|
-
if (
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
fs8.writeFileSync(fullPath, content);
|
|
3292
|
+
if (fs8.existsSync(fullPath)) {
|
|
3293
|
+
const content = fs8.readFileSync(fullPath, "utf-8");
|
|
3294
|
+
const { content: nextContent, removed } = removeTomlSectionGroup(content, "mcp_servers.typegraph");
|
|
3295
|
+
if (removed) {
|
|
3296
|
+
if (nextContent === "") {
|
|
3297
|
+
fs8.unlinkSync(fullPath);
|
|
3298
|
+
} else {
|
|
3299
|
+
fs8.writeFileSync(fullPath, nextContent);
|
|
3300
|
+
}
|
|
3301
|
+
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
3302
|
+
}
|
|
3205
3303
|
}
|
|
3206
|
-
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
3207
3304
|
}
|
|
3208
3305
|
function ensureTsconfigExclude(projectRoot3) {
|
|
3209
3306
|
const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
|
|
@@ -3445,9 +3542,13 @@ async function setup(yes2) {
|
|
|
3445
3542
|
ensureEslintIgnore(projectRoot3);
|
|
3446
3543
|
await runVerification(targetDir, selectedAgents);
|
|
3447
3544
|
}
|
|
3448
|
-
async function removePlugin(projectRoot3, pluginDir) {
|
|
3545
|
+
async function removePlugin(projectRoot3, pluginDir, options) {
|
|
3449
3546
|
const s = p.spinner();
|
|
3450
3547
|
s.start("Removing typegraph-mcp...");
|
|
3548
|
+
deregisterMcpServers(projectRoot3);
|
|
3549
|
+
if (options.removeGlobalCodex && options.legacyGlobalCodexCleanup) {
|
|
3550
|
+
removeLegacyGlobalCodexMcp(options.legacyGlobalCodexCleanup);
|
|
3551
|
+
}
|
|
3451
3552
|
if (fs8.existsSync(pluginDir)) {
|
|
3452
3553
|
fs8.rmSync(pluginDir, { recursive: true });
|
|
3453
3554
|
}
|
|
@@ -3487,7 +3588,6 @@ async function removePlugin(projectRoot3, pluginDir) {
|
|
|
3487
3588
|
fs8.writeFileSync(claudeMdPath, content);
|
|
3488
3589
|
}
|
|
3489
3590
|
s.stop("Removed typegraph-mcp");
|
|
3490
|
-
deregisterMcpServers(projectRoot3);
|
|
3491
3591
|
p.outro("typegraph-mcp has been uninstalled from this project.");
|
|
3492
3592
|
}
|
|
3493
3593
|
async function setupAgentInstructions(projectRoot3, selectedAgents) {
|
|
@@ -3496,10 +3596,14 @@ async function setupAgentInstructions(projectRoot3, selectedAgents) {
|
|
|
3496
3596
|
return;
|
|
3497
3597
|
}
|
|
3498
3598
|
const seenRealPaths = /* @__PURE__ */ new Map();
|
|
3499
|
-
const existingFiles = [];
|
|
3500
3599
|
for (const agentFile of agentFiles) {
|
|
3501
3600
|
const filePath = path9.resolve(projectRoot3, agentFile);
|
|
3502
|
-
if (!fs8.existsSync(filePath))
|
|
3601
|
+
if (!fs8.existsSync(filePath)) {
|
|
3602
|
+
fs8.mkdirSync(path9.dirname(filePath), { recursive: true });
|
|
3603
|
+
fs8.writeFileSync(filePath, AGENT_SNIPPET + "\n");
|
|
3604
|
+
p.log.success(`${agentFile}: created with typegraph-mcp instructions`);
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3503
3607
|
const realPath = fs8.realpathSync(filePath);
|
|
3504
3608
|
const previousFile = seenRealPaths.get(realPath);
|
|
3505
3609
|
if (previousFile) {
|
|
@@ -3507,24 +3611,14 @@ async function setupAgentInstructions(projectRoot3, selectedAgents) {
|
|
|
3507
3611
|
continue;
|
|
3508
3612
|
}
|
|
3509
3613
|
seenRealPaths.set(realPath, agentFile);
|
|
3510
|
-
const content = fs8.readFileSync(
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
p.log.warn(`No agent instruction files found (${agentFiles.join(", ")})`);
|
|
3515
|
-
p.note(AGENT_SNIPPET, "Add this snippet to your agent instructions file");
|
|
3516
|
-
} else if (existingFiles.some((f) => f.hasSnippet)) {
|
|
3517
|
-
for (const f of existingFiles) {
|
|
3518
|
-
if (f.hasSnippet) {
|
|
3519
|
-
p.log.info(`${f.file}: already has typegraph-mcp instructions`);
|
|
3520
|
-
}
|
|
3614
|
+
const content = fs8.readFileSync(realPath, "utf-8");
|
|
3615
|
+
if (content.includes(SNIPPET_MARKER)) {
|
|
3616
|
+
p.log.info(`${agentFile}: already has typegraph-mcp instructions`);
|
|
3617
|
+
continue;
|
|
3521
3618
|
}
|
|
3522
|
-
} else {
|
|
3523
|
-
const target = existingFiles[0];
|
|
3524
|
-
const content = fs8.readFileSync(target.realPath, "utf-8");
|
|
3525
3619
|
const appendContent = (content.endsWith("\n") ? "" : "\n") + "\n" + AGENT_SNIPPET;
|
|
3526
|
-
fs8.appendFileSync(
|
|
3527
|
-
p.log.success(`${
|
|
3620
|
+
fs8.appendFileSync(realPath, appendContent);
|
|
3621
|
+
p.log.success(`${agentFile}: appended typegraph-mcp instructions`);
|
|
3528
3622
|
}
|
|
3529
3623
|
if (selectedAgents.includes("claude-code")) {
|
|
3530
3624
|
const claudeMdPath = path9.resolve(projectRoot3, "CLAUDE.md");
|
|
@@ -3573,6 +3667,7 @@ async function runVerification(pluginDir, selectedAgents) {
|
|
|
3573
3667
|
async function remove(yes2) {
|
|
3574
3668
|
const projectRoot3 = process.cwd();
|
|
3575
3669
|
const pluginDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
|
|
3670
|
+
const cleanGlobalCodex = args.includes("--clean-global-codex");
|
|
3576
3671
|
process.stdout.write("\x1Bc");
|
|
3577
3672
|
p.intro("TypeGraph MCP Remove");
|
|
3578
3673
|
if (!fs8.existsSync(pluginDir)) {
|
|
@@ -3586,7 +3681,28 @@ async function remove(yes2) {
|
|
|
3586
3681
|
process.exit(0);
|
|
3587
3682
|
}
|
|
3588
3683
|
}
|
|
3589
|
-
|
|
3684
|
+
const legacyGlobalCodexCleanup = findLegacyGlobalCodexCleanup(projectRoot3);
|
|
3685
|
+
let removeGlobalCodex = cleanGlobalCodex;
|
|
3686
|
+
if (legacyGlobalCodexCleanup && !cleanGlobalCodex && !yes2) {
|
|
3687
|
+
const shouldRemoveGlobal = await p.confirm({
|
|
3688
|
+
message: "Also remove the stale global Codex MCP entry for this project from ~/.codex/config.toml?",
|
|
3689
|
+
initialValue: false
|
|
3690
|
+
});
|
|
3691
|
+
if (p.isCancel(shouldRemoveGlobal)) {
|
|
3692
|
+
p.cancel("Removal cancelled.");
|
|
3693
|
+
process.exit(0);
|
|
3694
|
+
}
|
|
3695
|
+
removeGlobalCodex = shouldRemoveGlobal;
|
|
3696
|
+
}
|
|
3697
|
+
await removePlugin(projectRoot3, pluginDir, {
|
|
3698
|
+
removeGlobalCodex,
|
|
3699
|
+
legacyGlobalCodexCleanup
|
|
3700
|
+
});
|
|
3701
|
+
if (legacyGlobalCodexCleanup && !removeGlobalCodex) {
|
|
3702
|
+
p.log.warn(
|
|
3703
|
+
"Left a stale global Codex MCP entry for this project in ~/.codex/config.toml. Codex may show MCP startup warnings or errors until you remove it. Re-run `typegraph-mcp remove --clean-global-codex` or remove the `typegraph` block manually."
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3590
3706
|
}
|
|
3591
3707
|
function resolvePluginDir() {
|
|
3592
3708
|
const installed = path9.resolve(process.cwd(), PLUGIN_DIR_NAME);
|