typegraph-mcp 0.9.34 → 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 +186 -34
- package/dist/check.js +8 -3
- package/dist/cli.js +135 -28
- 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,6 +36,11 @@ 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 = `
|
|
@@ -163,8 +169,9 @@ Commands:
|
|
|
163
169
|
start Start the MCP server (stdin/stdout)
|
|
164
170
|
|
|
165
171
|
Options:
|
|
166
|
-
--yes
|
|
167
|
-
--
|
|
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
|
|
168
175
|
`.trim();
|
|
169
176
|
|
|
170
177
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -207,12 +214,78 @@ function getAbsoluteMcpServerEntry(projectRoot: string): {
|
|
|
207
214
|
};
|
|
208
215
|
}
|
|
209
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
|
+
|
|
210
232
|
function getCodexConfigPath(projectRoot: string): string {
|
|
211
233
|
return path.resolve(projectRoot, ".codex/config.toml");
|
|
212
234
|
}
|
|
213
235
|
|
|
214
|
-
function
|
|
215
|
-
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
|
+
};
|
|
216
289
|
}
|
|
217
290
|
|
|
218
291
|
function upsertCodexMcpSection(content: string, block: string): { content: string; changed: boolean } {
|
|
@@ -236,12 +309,13 @@ function upsertCodexMcpSection(content: string, block: string): { content: strin
|
|
|
236
309
|
}
|
|
237
310
|
|
|
238
311
|
function makeCodexMcpBlock(projectRoot: string): string {
|
|
239
|
-
const absoluteEntry =
|
|
312
|
+
const absoluteEntry = getCodexMcpServerEntry(projectRoot);
|
|
313
|
+
const args = absoluteEntry.args.map((arg) => `"${arg}"`).join(", ");
|
|
240
314
|
return [
|
|
241
315
|
"",
|
|
242
316
|
"[mcp_servers.typegraph]",
|
|
243
317
|
`command = "${absoluteEntry.command}"`,
|
|
244
|
-
`args = [
|
|
318
|
+
`args = [${args}]`,
|
|
245
319
|
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
246
320
|
"",
|
|
247
321
|
].join("\n");
|
|
@@ -288,6 +362,57 @@ function isCodexProjectTrusted(projectRoot: string): boolean {
|
|
|
288
362
|
return matchesTrustedProject();
|
|
289
363
|
}
|
|
290
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
|
+
|
|
291
416
|
/** Register the typegraph MCP server in agent-specific config files */
|
|
292
417
|
function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): void {
|
|
293
418
|
if (selectedAgents.includes("cursor")) {
|
|
@@ -401,26 +526,19 @@ function registerCodexMcp(projectRoot: string): void {
|
|
|
401
526
|
function deregisterCodexMcp(projectRoot: string): void {
|
|
402
527
|
const configPath = ".codex/config.toml";
|
|
403
528
|
const fullPath = getCodexConfigPath(projectRoot);
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
417
|
-
|
|
418
|
-
if (content.trim() === "") {
|
|
419
|
-
fs.unlinkSync(fullPath);
|
|
420
|
-
} else {
|
|
421
|
-
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
|
+
}
|
|
422
541
|
}
|
|
423
|
-
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
424
542
|
}
|
|
425
543
|
|
|
426
544
|
// ─── TSConfig Exclude ─────────────────────────────────────────────────────────
|
|
@@ -748,16 +866,26 @@ async function setup(yes: boolean): Promise<void> {
|
|
|
748
866
|
|
|
749
867
|
// ─── Remove Command ──────────────────────────────────────────────────────────
|
|
750
868
|
|
|
751
|
-
async function removePlugin(
|
|
869
|
+
async function removePlugin(
|
|
870
|
+
projectRoot: string,
|
|
871
|
+
pluginDir: string,
|
|
872
|
+
options: { removeGlobalCodex: boolean; legacyGlobalCodexCleanup: LegacyGlobalCodexCleanup | null }
|
|
873
|
+
): Promise<void> {
|
|
752
874
|
const s = p.spinner();
|
|
753
875
|
s.start("Removing typegraph-mcp...");
|
|
754
876
|
|
|
755
|
-
// 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
|
|
756
884
|
if (fs.existsSync(pluginDir)) {
|
|
757
885
|
fs.rmSync(pluginDir, { recursive: true });
|
|
758
886
|
}
|
|
759
887
|
|
|
760
|
-
//
|
|
888
|
+
// 3. Remove .agents/skills/ entries (only typegraph-mcp skills, not the whole dir)
|
|
761
889
|
const agentsSkillsDir = path.resolve(projectRoot, ".agents/skills");
|
|
762
890
|
for (const skill of SKILL_NAMES) {
|
|
763
891
|
const skillDir = path.join(agentsSkillsDir, skill);
|
|
@@ -774,7 +902,7 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
774
902
|
}
|
|
775
903
|
}
|
|
776
904
|
|
|
777
|
-
//
|
|
905
|
+
// 4. Remove agent instruction snippet from all known agent files
|
|
778
906
|
const allAgentFiles = AGENT_IDS
|
|
779
907
|
.map((id) => AGENTS[id].agentFile)
|
|
780
908
|
.filter((f): f is string => f !== null);
|
|
@@ -797,7 +925,7 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
797
925
|
}
|
|
798
926
|
}
|
|
799
927
|
|
|
800
|
-
//
|
|
928
|
+
// 5. Remove --plugin-dir ./plugins/typegraph-mcp from CLAUDE.md
|
|
801
929
|
const claudeMdPath = path.resolve(projectRoot, "CLAUDE.md");
|
|
802
930
|
if (fs.existsSync(claudeMdPath)) {
|
|
803
931
|
let content = fs.readFileSync(claudeMdPath, "utf-8");
|
|
@@ -807,9 +935,6 @@ async function removePlugin(projectRoot: string, pluginDir: string): Promise<voi
|
|
|
807
935
|
|
|
808
936
|
s.stop("Removed typegraph-mcp");
|
|
809
937
|
|
|
810
|
-
// 5. Deregister MCP server from agent config files
|
|
811
|
-
deregisterMcpServers(projectRoot);
|
|
812
|
-
|
|
813
938
|
p.outro("typegraph-mcp has been uninstalled from this project.");
|
|
814
939
|
}
|
|
815
940
|
|
|
@@ -913,6 +1038,7 @@ async function runVerification(pluginDir: string, selectedAgents: AgentId[]): Pr
|
|
|
913
1038
|
async function remove(yes: boolean): Promise<void> {
|
|
914
1039
|
const projectRoot = process.cwd();
|
|
915
1040
|
const pluginDir = path.resolve(projectRoot, PLUGIN_DIR_NAME);
|
|
1041
|
+
const cleanGlobalCodex = args.includes("--clean-global-codex");
|
|
916
1042
|
|
|
917
1043
|
process.stdout.write("\x1Bc");
|
|
918
1044
|
p.intro("TypeGraph MCP Remove");
|
|
@@ -930,7 +1056,33 @@ async function remove(yes: boolean): Promise<void> {
|
|
|
930
1056
|
}
|
|
931
1057
|
}
|
|
932
1058
|
|
|
933
|
-
|
|
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
|
+
}
|
|
934
1086
|
}
|
|
935
1087
|
|
|
936
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;
|
|
@@ -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(
|
|
@@ -3024,8 +3029,9 @@ Commands:
|
|
|
3024
3029
|
start Start the MCP server (stdin/stdout)
|
|
3025
3030
|
|
|
3026
3031
|
Options:
|
|
3027
|
-
--yes
|
|
3028
|
-
--
|
|
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
|
|
3029
3035
|
`.trim();
|
|
3030
3036
|
function copyFile(src, dest) {
|
|
3031
3037
|
const destDir = path9.dirname(dest);
|
|
@@ -3045,10 +3051,10 @@ var MCP_SERVER_ENTRY = {
|
|
|
3045
3051
|
TYPEGRAPH_TSCONFIG: "./tsconfig.json"
|
|
3046
3052
|
}
|
|
3047
3053
|
};
|
|
3048
|
-
function
|
|
3054
|
+
function getCodexMcpServerEntry(projectRoot3) {
|
|
3049
3055
|
return {
|
|
3050
|
-
command: "
|
|
3051
|
-
args: [
|
|
3056
|
+
command: path9.resolve(projectRoot3, PLUGIN_DIR_NAME, "node_modules/.bin/tsx"),
|
|
3057
|
+
args: [path9.resolve(projectRoot3, PLUGIN_DIR_NAME, "server.ts")],
|
|
3052
3058
|
env: {
|
|
3053
3059
|
TYPEGRAPH_PROJECT_ROOT: projectRoot3,
|
|
3054
3060
|
TYPEGRAPH_TSCONFIG: path9.resolve(projectRoot3, "tsconfig.json")
|
|
@@ -3058,8 +3064,45 @@ function getAbsoluteMcpServerEntry(projectRoot3) {
|
|
|
3058
3064
|
function getCodexConfigPath(projectRoot3) {
|
|
3059
3065
|
return path9.resolve(projectRoot3, ".codex/config.toml");
|
|
3060
3066
|
}
|
|
3061
|
-
function
|
|
3062
|
-
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
|
+
};
|
|
3063
3106
|
}
|
|
3064
3107
|
function upsertCodexMcpSection(content, block) {
|
|
3065
3108
|
const sectionRe = /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/;
|
|
@@ -3078,12 +3121,13 @@ ${normalizedBlock}
|
|
|
3078
3121
|
return { content: nextContent, changed: true };
|
|
3079
3122
|
}
|
|
3080
3123
|
function makeCodexMcpBlock(projectRoot3) {
|
|
3081
|
-
const absoluteEntry =
|
|
3124
|
+
const absoluteEntry = getCodexMcpServerEntry(projectRoot3);
|
|
3125
|
+
const args2 = absoluteEntry.args.map((arg) => `"${arg}"`).join(", ");
|
|
3082
3126
|
return [
|
|
3083
3127
|
"",
|
|
3084
3128
|
"[mcp_servers.typegraph]",
|
|
3085
3129
|
`command = "${absoluteEntry.command}"`,
|
|
3086
|
-
`args = [
|
|
3130
|
+
`args = [${args2}]`,
|
|
3087
3131
|
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
3088
3132
|
""
|
|
3089
3133
|
].join("\n");
|
|
@@ -3118,6 +3162,46 @@ function isCodexProjectTrusted(projectRoot3) {
|
|
|
3118
3162
|
}
|
|
3119
3163
|
return matchesTrustedProject();
|
|
3120
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
|
+
}
|
|
3121
3205
|
function registerMcpServers(projectRoot3, selectedAgents) {
|
|
3122
3206
|
if (selectedAgents.includes("cursor")) {
|
|
3123
3207
|
registerJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
|
|
@@ -3205,20 +3289,18 @@ function registerCodexMcp(projectRoot3) {
|
|
|
3205
3289
|
function deregisterCodexMcp(projectRoot3) {
|
|
3206
3290
|
const configPath = ".codex/config.toml";
|
|
3207
3291
|
const fullPath = getCodexConfigPath(projectRoot3);
|
|
3208
|
-
if (
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
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
|
+
}
|
|
3220
3303
|
}
|
|
3221
|
-
p.log.info(`${configPath}: removed typegraph MCP server`);
|
|
3222
3304
|
}
|
|
3223
3305
|
function ensureTsconfigExclude(projectRoot3) {
|
|
3224
3306
|
const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
|
|
@@ -3460,9 +3542,13 @@ async function setup(yes2) {
|
|
|
3460
3542
|
ensureEslintIgnore(projectRoot3);
|
|
3461
3543
|
await runVerification(targetDir, selectedAgents);
|
|
3462
3544
|
}
|
|
3463
|
-
async function removePlugin(projectRoot3, pluginDir) {
|
|
3545
|
+
async function removePlugin(projectRoot3, pluginDir, options) {
|
|
3464
3546
|
const s = p.spinner();
|
|
3465
3547
|
s.start("Removing typegraph-mcp...");
|
|
3548
|
+
deregisterMcpServers(projectRoot3);
|
|
3549
|
+
if (options.removeGlobalCodex && options.legacyGlobalCodexCleanup) {
|
|
3550
|
+
removeLegacyGlobalCodexMcp(options.legacyGlobalCodexCleanup);
|
|
3551
|
+
}
|
|
3466
3552
|
if (fs8.existsSync(pluginDir)) {
|
|
3467
3553
|
fs8.rmSync(pluginDir, { recursive: true });
|
|
3468
3554
|
}
|
|
@@ -3502,7 +3588,6 @@ async function removePlugin(projectRoot3, pluginDir) {
|
|
|
3502
3588
|
fs8.writeFileSync(claudeMdPath, content);
|
|
3503
3589
|
}
|
|
3504
3590
|
s.stop("Removed typegraph-mcp");
|
|
3505
|
-
deregisterMcpServers(projectRoot3);
|
|
3506
3591
|
p.outro("typegraph-mcp has been uninstalled from this project.");
|
|
3507
3592
|
}
|
|
3508
3593
|
async function setupAgentInstructions(projectRoot3, selectedAgents) {
|
|
@@ -3582,6 +3667,7 @@ async function runVerification(pluginDir, selectedAgents) {
|
|
|
3582
3667
|
async function remove(yes2) {
|
|
3583
3668
|
const projectRoot3 = process.cwd();
|
|
3584
3669
|
const pluginDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
|
|
3670
|
+
const cleanGlobalCodex = args.includes("--clean-global-codex");
|
|
3585
3671
|
process.stdout.write("\x1Bc");
|
|
3586
3672
|
p.intro("TypeGraph MCP Remove");
|
|
3587
3673
|
if (!fs8.existsSync(pluginDir)) {
|
|
@@ -3595,7 +3681,28 @@ async function remove(yes2) {
|
|
|
3595
3681
|
process.exit(0);
|
|
3596
3682
|
}
|
|
3597
3683
|
}
|
|
3598
|
-
|
|
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
|
+
}
|
|
3599
3706
|
}
|
|
3600
3707
|
function resolvePluginDir() {
|
|
3601
3708
|
const installed = path9.resolve(process.cwd(), PLUGIN_DIR_NAME);
|