typegraph-mcp 0.9.42 → 0.9.44
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/check.ts +61 -0
- package/cli.ts +110 -3
- package/dist/benchmark.js +15 -0
- package/dist/check.js +44 -1
- package/dist/cli.js +165 -4
- package/dist/module-graph.js +3 -1
- package/dist/server.js +26 -2
- package/dist/smoke-test.js +15 -0
- package/dist/tsserver-client.js +15 -0
- package/engine-sync-test.ts +236 -0
- package/install-oxlint-test.ts +18 -4
- package/module-graph.ts +7 -1
- package/package.json +2 -2
- package/server.ts +9 -1
- package/tsserver-client.ts +17 -0
package/check.ts
CHANGED
|
@@ -170,6 +170,64 @@ function hasTrustedCodexProject(projectRoot: string): boolean | null {
|
|
|
170
170
|
return matchesTrustedProject();
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
function hasCompleteJsonTypegraphRegistration(
|
|
174
|
+
config: unknown,
|
|
175
|
+
serverName: string,
|
|
176
|
+
projectRoot: string
|
|
177
|
+
): boolean {
|
|
178
|
+
if (typeof config !== "object" || config === null) return false;
|
|
179
|
+
const servers = (config as Record<string, unknown>)["mcpServers"];
|
|
180
|
+
if (typeof servers !== "object" || servers === null) return false;
|
|
181
|
+
const entry = (servers as Record<string, unknown>)[serverName];
|
|
182
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
183
|
+
|
|
184
|
+
const record = entry as Record<string, unknown>;
|
|
185
|
+
const command = record["command"];
|
|
186
|
+
const args = record["args"];
|
|
187
|
+
const env = record["env"];
|
|
188
|
+
const serializedArgs = JSON.stringify(args ?? []);
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
typeof command === "string" &&
|
|
192
|
+
command.length > 0 &&
|
|
193
|
+
Array.isArray(args) &&
|
|
194
|
+
serializedArgs.includes("server.ts") &&
|
|
195
|
+
serializedArgs.includes("tsx") &&
|
|
196
|
+
typeof env === "object" &&
|
|
197
|
+
env !== null &&
|
|
198
|
+
(env as Record<string, unknown>)["TYPEGRAPH_PROJECT_ROOT"] === projectRoot &&
|
|
199
|
+
typeof (env as Record<string, unknown>)["TYPEGRAPH_TSCONFIG"] === "string"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function readJsonConfig(configPath: string): unknown | null {
|
|
204
|
+
if (!fs.existsSync(configPath)) return null;
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function getAntigravityMcpConfigPaths(): string[] {
|
|
213
|
+
const home = process.env.HOME || "";
|
|
214
|
+
return [
|
|
215
|
+
path.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
216
|
+
path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json"),
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function findAntigravityRegistration(projectRoot: string): string | null {
|
|
221
|
+
const home = process.env.HOME || "";
|
|
222
|
+
for (const configPath of getAntigravityMcpConfigPaths()) {
|
|
223
|
+
const config = readJsonConfig(configPath);
|
|
224
|
+
if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot)) {
|
|
225
|
+
return configPath.replace(home, "~");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
173
231
|
function readProjectPackageJson(projectRoot: string): Record<string, unknown> | null {
|
|
174
232
|
const packageJsonPath = path.resolve(projectRoot, "package.json");
|
|
175
233
|
if (!fs.existsSync(packageJsonPath)) return null;
|
|
@@ -346,6 +404,7 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
346
404
|
encoding: "utf-8",
|
|
347
405
|
});
|
|
348
406
|
const hasGlobalCodexRegistration = codexGet.status === 0;
|
|
407
|
+
const antigravityRegistrationPath = findAntigravityRegistration(projectRoot);
|
|
349
408
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
350
409
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
351
410
|
} else if (hasPluginMcp) {
|
|
@@ -382,6 +441,8 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
382
441
|
}
|
|
383
442
|
} else if (hasGlobalCodexRegistration) {
|
|
384
443
|
pass("MCP registered in global Codex CLI config");
|
|
444
|
+
} else if (antigravityRegistrationPath !== null) {
|
|
445
|
+
pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
|
|
385
446
|
} else {
|
|
386
447
|
const codexConfigPath = path.resolve(projectRoot, ".codex/config.toml");
|
|
387
448
|
const mcpJsonPath = path.resolve(projectRoot, ".claude/mcp.json");
|
package/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { resolveConfig } from "./config.js";
|
|
|
22
22
|
|
|
23
23
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
type AgentId = "claude-code" | "cursor" | "codex" | "gemini" | "copilot";
|
|
25
|
+
type AgentId = "claude-code" | "cursor" | "codex" | "gemini" | "copilot" | "antigravity";
|
|
26
26
|
|
|
27
27
|
interface AgentDef {
|
|
28
28
|
name: string;
|
|
@@ -80,7 +80,7 @@ const CLAUDE_NODE_PLACEHOLDER = "__TYPEGRAPH_NODE__";
|
|
|
80
80
|
|
|
81
81
|
const PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
|
|
82
82
|
|
|
83
|
-
const AGENT_IDS: AgentId[] = ["claude-code", "cursor", "codex", "gemini", "copilot"];
|
|
83
|
+
const AGENT_IDS: AgentId[] = ["claude-code", "cursor", "codex", "gemini", "copilot", "antigravity"];
|
|
84
84
|
|
|
85
85
|
const AGENTS: Record<AgentId, AgentDef> = {
|
|
86
86
|
"claude-code": {
|
|
@@ -129,6 +129,13 @@ const AGENTS: Record<AgentId, AgentDef> = {
|
|
|
129
129
|
detect: (root) =>
|
|
130
130
|
fs.existsSync(path.join(root, ".github/copilot-instructions.md")),
|
|
131
131
|
},
|
|
132
|
+
antigravity: {
|
|
133
|
+
name: "Antigravity",
|
|
134
|
+
pluginFiles: [],
|
|
135
|
+
agentFile: "AGENTS.md",
|
|
136
|
+
needsAgentsSkills: true,
|
|
137
|
+
detect: (root) => fs.existsSync(path.join(root, ".gemini/antigravity")),
|
|
138
|
+
},
|
|
132
139
|
};
|
|
133
140
|
|
|
134
141
|
/** Core files always installed (server, modules, config, package manifest) */
|
|
@@ -250,6 +257,35 @@ function getCodexConfigPath(projectRoot: string): string {
|
|
|
250
257
|
return path.resolve(projectRoot, ".codex/config.toml");
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
function getAntigravityMcpConfigPaths(): string[] {
|
|
261
|
+
const home = process.env.HOME || "";
|
|
262
|
+
return [
|
|
263
|
+
path.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
264
|
+
path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json"),
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function ensureAntigravityCliPlugin(): void {
|
|
269
|
+
const home = process.env.HOME || "";
|
|
270
|
+
const pluginDir = path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp");
|
|
271
|
+
const pluginJsonPath = path.join(pluginDir, "plugin.json");
|
|
272
|
+
if (!fs.existsSync(pluginJsonPath)) {
|
|
273
|
+
fs.mkdirSync(pluginDir, { recursive: true });
|
|
274
|
+
fs.writeFileSync(
|
|
275
|
+
pluginJsonPath,
|
|
276
|
+
JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
name: "typegraph-mcp",
|
|
279
|
+
version: "1.0.0",
|
|
280
|
+
description: "TypeGraph MCP server for TypeScript navigation",
|
|
281
|
+
},
|
|
282
|
+
null,
|
|
283
|
+
2
|
|
284
|
+
) + "\n"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
253
289
|
function isTomlSectionGroup(sectionName: string | null, prefix: string): boolean {
|
|
254
290
|
return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
|
|
255
291
|
}
|
|
@@ -476,6 +512,9 @@ function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): voi
|
|
|
476
512
|
if (selectedAgents.includes("copilot")) {
|
|
477
513
|
registerJsonMcp(projectRoot, ".vscode/mcp.json", "servers");
|
|
478
514
|
}
|
|
515
|
+
if (selectedAgents.includes("antigravity")) {
|
|
516
|
+
registerAntigravityMcp(projectRoot);
|
|
517
|
+
}
|
|
479
518
|
}
|
|
480
519
|
|
|
481
520
|
/** Deregister the typegraph MCP server from all agent config files */
|
|
@@ -483,6 +522,7 @@ function deregisterMcpServers(projectRoot: string): void {
|
|
|
483
522
|
deregisterJsonMcp(projectRoot, ".cursor/mcp.json", "mcpServers");
|
|
484
523
|
deregisterCodexMcp(projectRoot);
|
|
485
524
|
deregisterJsonMcp(projectRoot, ".vscode/mcp.json", "servers");
|
|
525
|
+
deregisterAntigravityMcp(projectRoot);
|
|
486
526
|
}
|
|
487
527
|
|
|
488
528
|
/** Register MCP server in a JSON config file (Cursor or Copilot format) */
|
|
@@ -593,6 +633,73 @@ function deregisterCodexMcp(projectRoot: string): void {
|
|
|
593
633
|
}
|
|
594
634
|
}
|
|
595
635
|
|
|
636
|
+
/** Register MCP server in Antigravity's config files */
|
|
637
|
+
function registerAntigravityMcp(projectRoot: string): void {
|
|
638
|
+
const home = process.env.HOME || "";
|
|
639
|
+
const pluginDir = path.resolve(projectRoot, PLUGIN_DIR_NAME);
|
|
640
|
+
const tsConfigPath = path.resolve(projectRoot, "tsconfig.json");
|
|
641
|
+
ensureAntigravityCliPlugin();
|
|
642
|
+
|
|
643
|
+
const entry = {
|
|
644
|
+
command: process.execPath,
|
|
645
|
+
args: [
|
|
646
|
+
path.join(pluginDir, "node_modules/tsx/dist/cli.mjs"),
|
|
647
|
+
path.join(pluginDir, "server.ts"),
|
|
648
|
+
],
|
|
649
|
+
env: {
|
|
650
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot,
|
|
651
|
+
TYPEGRAPH_TSCONFIG: tsConfigPath,
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
for (const configPath of getAntigravityMcpConfigPaths()) {
|
|
656
|
+
let config: any = { mcpServers: {} };
|
|
657
|
+
if (fs.existsSync(configPath)) {
|
|
658
|
+
try {
|
|
659
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
660
|
+
} catch {
|
|
661
|
+
p.log.warn(`Could not parse ~${configPath.replace(home, "")} — skipping MCP registration`);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
666
|
+
config.mcpServers["typegraph-mcp"] = entry;
|
|
667
|
+
|
|
668
|
+
const dir = path.dirname(configPath);
|
|
669
|
+
if (!fs.existsSync(dir)) {
|
|
670
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
671
|
+
}
|
|
672
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
673
|
+
p.log.success(`~${configPath.replace(home, "")}: registered typegraph-mcp server`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Deregister MCP server from Antigravity's config files */
|
|
678
|
+
function deregisterAntigravityMcp(projectRoot: string): void {
|
|
679
|
+
const home = process.env.HOME || "";
|
|
680
|
+
|
|
681
|
+
for (const configPath of getAntigravityMcpConfigPaths()) {
|
|
682
|
+
if (!fs.existsSync(configPath)) continue;
|
|
683
|
+
try {
|
|
684
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
685
|
+
if (config.mcpServers && config.mcpServers["typegraph-mcp"]) {
|
|
686
|
+
delete config.mcpServers["typegraph-mcp"];
|
|
687
|
+
if (Object.keys(config.mcpServers).length === 0) {
|
|
688
|
+
delete config.mcpServers;
|
|
689
|
+
}
|
|
690
|
+
if (Object.keys(config).length === 0) {
|
|
691
|
+
fs.unlinkSync(configPath);
|
|
692
|
+
} else {
|
|
693
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
694
|
+
}
|
|
695
|
+
p.log.info(`~${configPath.replace(home, "")}: removed typegraph-mcp server`);
|
|
696
|
+
}
|
|
697
|
+
} catch {
|
|
698
|
+
// Ignore
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
596
703
|
// ─── TSConfig Exclude ─────────────────────────────────────────────────────────
|
|
597
704
|
|
|
598
705
|
function ensureTsconfigExclude(projectRoot: string): void {
|
|
@@ -876,7 +983,7 @@ async function setup(yes: boolean): Promise<void> {
|
|
|
876
983
|
// 3. Agent selection
|
|
877
984
|
const selectedAgents = await selectAgents(projectRoot, yes);
|
|
878
985
|
|
|
879
|
-
const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor");
|
|
986
|
+
const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor") || selectedAgents.includes("antigravity");
|
|
880
987
|
const needsAgentsSkills = selectedAgents.some((id) => AGENTS[id].needsAgentsSkills);
|
|
881
988
|
|
|
882
989
|
p.log.step(`Installing to ${PLUGIN_DIR_NAME}/...`);
|
package/dist/benchmark.js
CHANGED
|
@@ -201,6 +201,21 @@ var TsServerClient = class {
|
|
|
201
201
|
this.sendNotification("open", { file: absPath });
|
|
202
202
|
await new Promise((r) => setTimeout(r, 50));
|
|
203
203
|
}
|
|
204
|
+
async reloadOpenFile(file) {
|
|
205
|
+
const absPath = this.resolvePath(file);
|
|
206
|
+
if (!this.openFiles.has(absPath)) return false;
|
|
207
|
+
await this.sendRequest("reload", {
|
|
208
|
+
file: absPath,
|
|
209
|
+
tmpfile: absPath
|
|
210
|
+
});
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
closeFile(file) {
|
|
214
|
+
const absPath = this.resolvePath(file);
|
|
215
|
+
if (!this.openFiles.delete(absPath)) return false;
|
|
216
|
+
this.sendNotification("close", { file: absPath });
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
204
219
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
205
220
|
async definition(file, line, offset) {
|
|
206
221
|
const absPath = this.resolvePath(file);
|
package/dist/check.js
CHANGED
|
@@ -301,7 +301,7 @@ function removeFile(graph, filePath) {
|
|
|
301
301
|
graph.reverse.delete(filePath);
|
|
302
302
|
graph.files.delete(filePath);
|
|
303
303
|
}
|
|
304
|
-
function startWatcher(projectRoot, graph, resolver) {
|
|
304
|
+
function startWatcher(projectRoot, graph, resolver, hooks) {
|
|
305
305
|
try {
|
|
306
306
|
const watcher = fs.watch(
|
|
307
307
|
projectRoot,
|
|
@@ -318,8 +318,10 @@ function startWatcher(projectRoot, graph, resolver) {
|
|
|
318
318
|
const absPath = path2.resolve(projectRoot, filename);
|
|
319
319
|
if (fs.existsSync(absPath)) {
|
|
320
320
|
updateFile(graph, absPath, resolver, projectRoot);
|
|
321
|
+
void hooks?.onFileUpdated?.(absPath);
|
|
321
322
|
} else {
|
|
322
323
|
removeFile(graph, absPath);
|
|
324
|
+
void hooks?.onFileDeleted?.(absPath);
|
|
323
325
|
}
|
|
324
326
|
}
|
|
325
327
|
);
|
|
@@ -476,6 +478,44 @@ function hasTrustedCodexProject(projectRoot) {
|
|
|
476
478
|
}
|
|
477
479
|
return matchesTrustedProject();
|
|
478
480
|
}
|
|
481
|
+
function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot) {
|
|
482
|
+
if (typeof config !== "object" || config === null) return false;
|
|
483
|
+
const servers = config["mcpServers"];
|
|
484
|
+
if (typeof servers !== "object" || servers === null) return false;
|
|
485
|
+
const entry = servers[serverName];
|
|
486
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
487
|
+
const record = entry;
|
|
488
|
+
const command = record["command"];
|
|
489
|
+
const args = record["args"];
|
|
490
|
+
const env = record["env"];
|
|
491
|
+
const serializedArgs = JSON.stringify(args ?? []);
|
|
492
|
+
return typeof command === "string" && command.length > 0 && Array.isArray(args) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
|
|
493
|
+
}
|
|
494
|
+
function readJsonConfig(configPath) {
|
|
495
|
+
if (!fs2.existsSync(configPath)) return null;
|
|
496
|
+
try {
|
|
497
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
498
|
+
} catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function getAntigravityMcpConfigPaths() {
|
|
503
|
+
const home = process.env.HOME || "";
|
|
504
|
+
return [
|
|
505
|
+
path3.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
506
|
+
path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
function findAntigravityRegistration(projectRoot) {
|
|
510
|
+
const home = process.env.HOME || "";
|
|
511
|
+
for (const configPath of getAntigravityMcpConfigPaths()) {
|
|
512
|
+
const config = readJsonConfig(configPath);
|
|
513
|
+
if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot)) {
|
|
514
|
+
return configPath.replace(home, "~");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
479
519
|
function readProjectPackageJson(projectRoot) {
|
|
480
520
|
const packageJsonPath = path3.resolve(projectRoot, "package.json");
|
|
481
521
|
if (!fs2.existsSync(packageJsonPath)) return null;
|
|
@@ -609,6 +649,7 @@ async function main(configOverride) {
|
|
|
609
649
|
encoding: "utf-8"
|
|
610
650
|
});
|
|
611
651
|
const hasGlobalCodexRegistration = codexGet.status === 0;
|
|
652
|
+
const antigravityRegistrationPath = findAntigravityRegistration(projectRoot);
|
|
612
653
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
613
654
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
614
655
|
} else if (hasPluginMcp) {
|
|
@@ -645,6 +686,8 @@ async function main(configOverride) {
|
|
|
645
686
|
}
|
|
646
687
|
} else if (hasGlobalCodexRegistration) {
|
|
647
688
|
pass("MCP registered in global Codex CLI config");
|
|
689
|
+
} else if (antigravityRegistrationPath !== null) {
|
|
690
|
+
pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
|
|
648
691
|
} else {
|
|
649
692
|
const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
|
|
650
693
|
const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
|
package/dist/cli.js
CHANGED
|
@@ -317,7 +317,7 @@ function removeFile(graph, filePath) {
|
|
|
317
317
|
graph.reverse.delete(filePath);
|
|
318
318
|
graph.files.delete(filePath);
|
|
319
319
|
}
|
|
320
|
-
function startWatcher(projectRoot3, graph, resolver) {
|
|
320
|
+
function startWatcher(projectRoot3, graph, resolver, hooks) {
|
|
321
321
|
try {
|
|
322
322
|
const watcher = fs.watch(
|
|
323
323
|
projectRoot3,
|
|
@@ -334,8 +334,10 @@ function startWatcher(projectRoot3, graph, resolver) {
|
|
|
334
334
|
const absPath2 = path2.resolve(projectRoot3, filename);
|
|
335
335
|
if (fs.existsSync(absPath2)) {
|
|
336
336
|
updateFile(graph, absPath2, resolver, projectRoot3);
|
|
337
|
+
void hooks?.onFileUpdated?.(absPath2);
|
|
337
338
|
} else {
|
|
338
339
|
removeFile(graph, absPath2);
|
|
340
|
+
void hooks?.onFileDeleted?.(absPath2);
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
);
|
|
@@ -483,6 +485,44 @@ function hasTrustedCodexProject(projectRoot3) {
|
|
|
483
485
|
}
|
|
484
486
|
return matchesTrustedProject();
|
|
485
487
|
}
|
|
488
|
+
function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot3) {
|
|
489
|
+
if (typeof config !== "object" || config === null) return false;
|
|
490
|
+
const servers = config["mcpServers"];
|
|
491
|
+
if (typeof servers !== "object" || servers === null) return false;
|
|
492
|
+
const entry = servers[serverName];
|
|
493
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
494
|
+
const record = entry;
|
|
495
|
+
const command2 = record["command"];
|
|
496
|
+
const args2 = record["args"];
|
|
497
|
+
const env = record["env"];
|
|
498
|
+
const serializedArgs = JSON.stringify(args2 ?? []);
|
|
499
|
+
return typeof command2 === "string" && command2.length > 0 && Array.isArray(args2) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot3 && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
|
|
500
|
+
}
|
|
501
|
+
function readJsonConfig(configPath) {
|
|
502
|
+
if (!fs2.existsSync(configPath)) return null;
|
|
503
|
+
try {
|
|
504
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function getAntigravityMcpConfigPaths() {
|
|
510
|
+
const home = process.env.HOME || "";
|
|
511
|
+
return [
|
|
512
|
+
path3.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
513
|
+
path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
|
|
514
|
+
];
|
|
515
|
+
}
|
|
516
|
+
function findAntigravityRegistration(projectRoot3) {
|
|
517
|
+
const home = process.env.HOME || "";
|
|
518
|
+
for (const configPath of getAntigravityMcpConfigPaths()) {
|
|
519
|
+
const config = readJsonConfig(configPath);
|
|
520
|
+
if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot3)) {
|
|
521
|
+
return configPath.replace(home, "~");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
486
526
|
function readProjectPackageJson(projectRoot3) {
|
|
487
527
|
const packageJsonPath = path3.resolve(projectRoot3, "package.json");
|
|
488
528
|
if (!fs2.existsSync(packageJsonPath)) return null;
|
|
@@ -603,6 +643,7 @@ async function main(configOverride) {
|
|
|
603
643
|
encoding: "utf-8"
|
|
604
644
|
});
|
|
605
645
|
const hasGlobalCodexRegistration = codexGet.status === 0;
|
|
646
|
+
const antigravityRegistrationPath = findAntigravityRegistration(projectRoot3);
|
|
606
647
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
607
648
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
608
649
|
} else if (hasPluginMcp) {
|
|
@@ -639,6 +680,8 @@ async function main(configOverride) {
|
|
|
639
680
|
}
|
|
640
681
|
} else if (hasGlobalCodexRegistration) {
|
|
641
682
|
pass("MCP registered in global Codex CLI config");
|
|
683
|
+
} else if (antigravityRegistrationPath !== null) {
|
|
684
|
+
pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
|
|
642
685
|
} else {
|
|
643
686
|
const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
|
|
644
687
|
const mcpJsonPath = path3.resolve(projectRoot3, ".claude/mcp.json");
|
|
@@ -1087,6 +1130,21 @@ var init_tsserver_client = __esm({
|
|
|
1087
1130
|
this.sendNotification("open", { file: absPath2 });
|
|
1088
1131
|
await new Promise((r) => setTimeout(r, 50));
|
|
1089
1132
|
}
|
|
1133
|
+
async reloadOpenFile(file) {
|
|
1134
|
+
const absPath2 = this.resolvePath(file);
|
|
1135
|
+
if (!this.openFiles.has(absPath2)) return false;
|
|
1136
|
+
await this.sendRequest("reload", {
|
|
1137
|
+
file: absPath2,
|
|
1138
|
+
tmpfile: absPath2
|
|
1139
|
+
});
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
closeFile(file) {
|
|
1143
|
+
const absPath2 = this.resolvePath(file);
|
|
1144
|
+
if (!this.openFiles.delete(absPath2)) return false;
|
|
1145
|
+
this.sendNotification("close", { file: absPath2 });
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1090
1148
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
1091
1149
|
async definition(file, line, offset) {
|
|
1092
1150
|
const absPath2 = this.resolvePath(file);
|
|
@@ -2638,7 +2696,14 @@ async function main4() {
|
|
|
2638
2696
|
]);
|
|
2639
2697
|
moduleGraph = graphResult.graph;
|
|
2640
2698
|
moduleResolver = graphResult.resolver;
|
|
2641
|
-
startWatcher(projectRoot2, moduleGraph, graphResult.resolver
|
|
2699
|
+
startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
|
|
2700
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).catch((err) => {
|
|
2701
|
+
log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
|
|
2702
|
+
}),
|
|
2703
|
+
onFileDeleted: (filePath) => {
|
|
2704
|
+
client.closeFile(filePath);
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2642
2707
|
const transport = new StdioServerTransport();
|
|
2643
2708
|
await mcpServer.connect(transport);
|
|
2644
2709
|
log3("MCP server connected and ready");
|
|
@@ -3193,7 +3258,7 @@ Practical rule:
|
|
|
3193
3258
|
var SNIPPET_MARKER = "## TypeScript Navigation (typegraph-mcp)";
|
|
3194
3259
|
var CLAUDE_NODE_PLACEHOLDER = "__TYPEGRAPH_NODE__";
|
|
3195
3260
|
var PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
|
|
3196
|
-
var AGENT_IDS = ["claude-code", "cursor", "codex", "gemini", "copilot"];
|
|
3261
|
+
var AGENT_IDS = ["claude-code", "cursor", "codex", "gemini", "copilot", "antigravity"];
|
|
3197
3262
|
var AGENTS = {
|
|
3198
3263
|
"claude-code": {
|
|
3199
3264
|
name: "Claude Code",
|
|
@@ -3237,6 +3302,13 @@ var AGENTS = {
|
|
|
3237
3302
|
agentFile: ".github/copilot-instructions.md",
|
|
3238
3303
|
needsAgentsSkills: true,
|
|
3239
3304
|
detect: (root) => fs8.existsSync(path9.join(root, ".github/copilot-instructions.md"))
|
|
3305
|
+
},
|
|
3306
|
+
antigravity: {
|
|
3307
|
+
name: "Antigravity",
|
|
3308
|
+
pluginFiles: [],
|
|
3309
|
+
agentFile: "AGENTS.md",
|
|
3310
|
+
needsAgentsSkills: true,
|
|
3311
|
+
detect: (root) => fs8.existsSync(path9.join(root, ".gemini/antigravity"))
|
|
3240
3312
|
}
|
|
3241
3313
|
};
|
|
3242
3314
|
var CORE_FILES = [
|
|
@@ -3322,6 +3394,33 @@ function getCodexMcpServerEntry(projectRoot3) {
|
|
|
3322
3394
|
function getCodexConfigPath(projectRoot3) {
|
|
3323
3395
|
return path9.resolve(projectRoot3, ".codex/config.toml");
|
|
3324
3396
|
}
|
|
3397
|
+
function getAntigravityMcpConfigPaths2() {
|
|
3398
|
+
const home = process.env.HOME || "";
|
|
3399
|
+
return [
|
|
3400
|
+
path9.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
3401
|
+
path9.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
|
|
3402
|
+
];
|
|
3403
|
+
}
|
|
3404
|
+
function ensureAntigravityCliPlugin() {
|
|
3405
|
+
const home = process.env.HOME || "";
|
|
3406
|
+
const pluginDir = path9.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp");
|
|
3407
|
+
const pluginJsonPath = path9.join(pluginDir, "plugin.json");
|
|
3408
|
+
if (!fs8.existsSync(pluginJsonPath)) {
|
|
3409
|
+
fs8.mkdirSync(pluginDir, { recursive: true });
|
|
3410
|
+
fs8.writeFileSync(
|
|
3411
|
+
pluginJsonPath,
|
|
3412
|
+
JSON.stringify(
|
|
3413
|
+
{
|
|
3414
|
+
name: "typegraph-mcp",
|
|
3415
|
+
version: "1.0.0",
|
|
3416
|
+
description: "TypeGraph MCP server for TypeScript navigation"
|
|
3417
|
+
},
|
|
3418
|
+
null,
|
|
3419
|
+
2
|
|
3420
|
+
) + "\n"
|
|
3421
|
+
);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3325
3424
|
function isTomlSectionGroup(sectionName, prefix) {
|
|
3326
3425
|
return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
|
|
3327
3426
|
}
|
|
@@ -3495,11 +3594,15 @@ function registerMcpServers(projectRoot3, selectedAgents) {
|
|
|
3495
3594
|
if (selectedAgents.includes("copilot")) {
|
|
3496
3595
|
registerJsonMcp(projectRoot3, ".vscode/mcp.json", "servers");
|
|
3497
3596
|
}
|
|
3597
|
+
if (selectedAgents.includes("antigravity")) {
|
|
3598
|
+
registerAntigravityMcp(projectRoot3);
|
|
3599
|
+
}
|
|
3498
3600
|
}
|
|
3499
3601
|
function deregisterMcpServers(projectRoot3) {
|
|
3500
3602
|
deregisterJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
|
|
3501
3603
|
deregisterCodexMcp(projectRoot3);
|
|
3502
3604
|
deregisterJsonMcp(projectRoot3, ".vscode/mcp.json", "servers");
|
|
3605
|
+
deregisterAntigravityMcp(projectRoot3);
|
|
3503
3606
|
}
|
|
3504
3607
|
function registerJsonMcp(projectRoot3, configPath, rootKey) {
|
|
3505
3608
|
const fullPath = path9.resolve(projectRoot3, configPath);
|
|
@@ -3585,6 +3688,64 @@ function deregisterCodexMcp(projectRoot3) {
|
|
|
3585
3688
|
}
|
|
3586
3689
|
}
|
|
3587
3690
|
}
|
|
3691
|
+
function registerAntigravityMcp(projectRoot3) {
|
|
3692
|
+
const home = process.env.HOME || "";
|
|
3693
|
+
const pluginDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
|
|
3694
|
+
const tsConfigPath = path9.resolve(projectRoot3, "tsconfig.json");
|
|
3695
|
+
ensureAntigravityCliPlugin();
|
|
3696
|
+
const entry = {
|
|
3697
|
+
command: process.execPath,
|
|
3698
|
+
args: [
|
|
3699
|
+
path9.join(pluginDir, "node_modules/tsx/dist/cli.mjs"),
|
|
3700
|
+
path9.join(pluginDir, "server.ts")
|
|
3701
|
+
],
|
|
3702
|
+
env: {
|
|
3703
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot3,
|
|
3704
|
+
TYPEGRAPH_TSCONFIG: tsConfigPath
|
|
3705
|
+
}
|
|
3706
|
+
};
|
|
3707
|
+
for (const configPath of getAntigravityMcpConfigPaths2()) {
|
|
3708
|
+
let config = { mcpServers: {} };
|
|
3709
|
+
if (fs8.existsSync(configPath)) {
|
|
3710
|
+
try {
|
|
3711
|
+
config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
3712
|
+
} catch {
|
|
3713
|
+
p.log.warn(`Could not parse ~${configPath.replace(home, "")} \u2014 skipping MCP registration`);
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
3718
|
+
config.mcpServers["typegraph-mcp"] = entry;
|
|
3719
|
+
const dir = path9.dirname(configPath);
|
|
3720
|
+
if (!fs8.existsSync(dir)) {
|
|
3721
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3722
|
+
}
|
|
3723
|
+
fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3724
|
+
p.log.success(`~${configPath.replace(home, "")}: registered typegraph-mcp server`);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
function deregisterAntigravityMcp(projectRoot3) {
|
|
3728
|
+
const home = process.env.HOME || "";
|
|
3729
|
+
for (const configPath of getAntigravityMcpConfigPaths2()) {
|
|
3730
|
+
if (!fs8.existsSync(configPath)) continue;
|
|
3731
|
+
try {
|
|
3732
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
3733
|
+
if (config.mcpServers && config.mcpServers["typegraph-mcp"]) {
|
|
3734
|
+
delete config.mcpServers["typegraph-mcp"];
|
|
3735
|
+
if (Object.keys(config.mcpServers).length === 0) {
|
|
3736
|
+
delete config.mcpServers;
|
|
3737
|
+
}
|
|
3738
|
+
if (Object.keys(config).length === 0) {
|
|
3739
|
+
fs8.unlinkSync(configPath);
|
|
3740
|
+
} else {
|
|
3741
|
+
fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3742
|
+
}
|
|
3743
|
+
p.log.info(`~${configPath.replace(home, "")}: removed typegraph-mcp server`);
|
|
3744
|
+
}
|
|
3745
|
+
} catch {
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3588
3749
|
function ensureTsconfigExclude(projectRoot3) {
|
|
3589
3750
|
const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
|
|
3590
3751
|
if (!fs8.existsSync(tsconfigPath3)) return;
|
|
@@ -3807,7 +3968,7 @@ async function setup(yes2) {
|
|
|
3807
3968
|
}
|
|
3808
3969
|
}
|
|
3809
3970
|
const selectedAgents = await selectAgents(projectRoot3, yes2);
|
|
3810
|
-
const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor");
|
|
3971
|
+
const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor") || selectedAgents.includes("antigravity");
|
|
3811
3972
|
const needsAgentsSkills = selectedAgents.some((id) => AGENTS[id].needsAgentsSkills);
|
|
3812
3973
|
p.log.step(`Installing to ${PLUGIN_DIR_NAME}/...`);
|
|
3813
3974
|
const s = p.spinner();
|
package/dist/module-graph.js
CHANGED
|
@@ -297,7 +297,7 @@ function removeFile(graph, filePath) {
|
|
|
297
297
|
graph.reverse.delete(filePath);
|
|
298
298
|
graph.files.delete(filePath);
|
|
299
299
|
}
|
|
300
|
-
function startWatcher(projectRoot, graph, resolver) {
|
|
300
|
+
function startWatcher(projectRoot, graph, resolver, hooks) {
|
|
301
301
|
try {
|
|
302
302
|
const watcher = fs.watch(
|
|
303
303
|
projectRoot,
|
|
@@ -314,8 +314,10 @@ function startWatcher(projectRoot, graph, resolver) {
|
|
|
314
314
|
const absPath = path.resolve(projectRoot, filename);
|
|
315
315
|
if (fs.existsSync(absPath)) {
|
|
316
316
|
updateFile(graph, absPath, resolver, projectRoot);
|
|
317
|
+
void hooks?.onFileUpdated?.(absPath);
|
|
317
318
|
} else {
|
|
318
319
|
removeFile(graph, absPath);
|
|
320
|
+
void hooks?.onFileDeleted?.(absPath);
|
|
319
321
|
}
|
|
320
322
|
}
|
|
321
323
|
);
|
package/dist/server.js
CHANGED
|
@@ -202,6 +202,21 @@ var TsServerClient = class {
|
|
|
202
202
|
this.sendNotification("open", { file: absPath2 });
|
|
203
203
|
await new Promise((r) => setTimeout(r, 50));
|
|
204
204
|
}
|
|
205
|
+
async reloadOpenFile(file) {
|
|
206
|
+
const absPath2 = this.resolvePath(file);
|
|
207
|
+
if (!this.openFiles.has(absPath2)) return false;
|
|
208
|
+
await this.sendRequest("reload", {
|
|
209
|
+
file: absPath2,
|
|
210
|
+
tmpfile: absPath2
|
|
211
|
+
});
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
closeFile(file) {
|
|
215
|
+
const absPath2 = this.resolvePath(file);
|
|
216
|
+
if (!this.openFiles.delete(absPath2)) return false;
|
|
217
|
+
this.sendNotification("close", { file: absPath2 });
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
205
220
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
206
221
|
async definition(file, line, offset) {
|
|
207
222
|
const absPath2 = this.resolvePath(file);
|
|
@@ -568,7 +583,7 @@ function removeFile(graph, filePath) {
|
|
|
568
583
|
graph.reverse.delete(filePath);
|
|
569
584
|
graph.files.delete(filePath);
|
|
570
585
|
}
|
|
571
|
-
function startWatcher(projectRoot2, graph, resolver) {
|
|
586
|
+
function startWatcher(projectRoot2, graph, resolver, hooks) {
|
|
572
587
|
try {
|
|
573
588
|
const watcher = fs2.watch(
|
|
574
589
|
projectRoot2,
|
|
@@ -585,8 +600,10 @@ function startWatcher(projectRoot2, graph, resolver) {
|
|
|
585
600
|
const absPath2 = path2.resolve(projectRoot2, filename);
|
|
586
601
|
if (fs2.existsSync(absPath2)) {
|
|
587
602
|
updateFile(graph, absPath2, resolver, projectRoot2);
|
|
603
|
+
void hooks?.onFileUpdated?.(absPath2);
|
|
588
604
|
} else {
|
|
589
605
|
removeFile(graph, absPath2);
|
|
606
|
+
void hooks?.onFileDeleted?.(absPath2);
|
|
590
607
|
}
|
|
591
608
|
}
|
|
592
609
|
);
|
|
@@ -1677,7 +1694,14 @@ async function main() {
|
|
|
1677
1694
|
]);
|
|
1678
1695
|
moduleGraph = graphResult.graph;
|
|
1679
1696
|
moduleResolver = graphResult.resolver;
|
|
1680
|
-
startWatcher(projectRoot, moduleGraph, graphResult.resolver
|
|
1697
|
+
startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
|
|
1698
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).catch((err) => {
|
|
1699
|
+
log3(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1700
|
+
}),
|
|
1701
|
+
onFileDeleted: (filePath) => {
|
|
1702
|
+
client.closeFile(filePath);
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1681
1705
|
const transport = new StdioServerTransport();
|
|
1682
1706
|
await mcpServer.connect(transport);
|
|
1683
1707
|
log3("MCP server connected and ready");
|
package/dist/smoke-test.js
CHANGED
|
@@ -200,6 +200,21 @@ var TsServerClient = class {
|
|
|
200
200
|
this.sendNotification("open", { file: absPath });
|
|
201
201
|
await new Promise((r) => setTimeout(r, 50));
|
|
202
202
|
}
|
|
203
|
+
async reloadOpenFile(file) {
|
|
204
|
+
const absPath = this.resolvePath(file);
|
|
205
|
+
if (!this.openFiles.has(absPath)) return false;
|
|
206
|
+
await this.sendRequest("reload", {
|
|
207
|
+
file: absPath,
|
|
208
|
+
tmpfile: absPath
|
|
209
|
+
});
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
closeFile(file) {
|
|
213
|
+
const absPath = this.resolvePath(file);
|
|
214
|
+
if (!this.openFiles.delete(absPath)) return false;
|
|
215
|
+
this.sendNotification("close", { file: absPath });
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
203
218
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
204
219
|
async definition(file, line, offset) {
|
|
205
220
|
const absPath = this.resolvePath(file);
|
package/dist/tsserver-client.js
CHANGED
|
@@ -195,6 +195,21 @@ var TsServerClient = class {
|
|
|
195
195
|
this.sendNotification("open", { file: absPath });
|
|
196
196
|
await new Promise((r) => setTimeout(r, 50));
|
|
197
197
|
}
|
|
198
|
+
async reloadOpenFile(file) {
|
|
199
|
+
const absPath = this.resolvePath(file);
|
|
200
|
+
if (!this.openFiles.has(absPath)) return false;
|
|
201
|
+
await this.sendRequest("reload", {
|
|
202
|
+
file: absPath,
|
|
203
|
+
tmpfile: absPath
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
closeFile(file) {
|
|
208
|
+
const absPath = this.resolvePath(file);
|
|
209
|
+
if (!this.openFiles.delete(absPath)) return false;
|
|
210
|
+
this.sendNotification("close", { file: absPath });
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
198
213
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
199
214
|
async definition(file, line, offset) {
|
|
200
215
|
const absPath = this.resolvePath(file);
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
import * as assert from "node:assert/strict";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
9
|
+
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
type DependencyTreeResult = {
|
|
12
|
+
root: string;
|
|
13
|
+
nodes: number;
|
|
14
|
+
files: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type TypeInfoResult = {
|
|
18
|
+
type: string | null;
|
|
19
|
+
documentation: string | null;
|
|
20
|
+
kind?: string;
|
|
21
|
+
source?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ModuleExportsResult = {
|
|
25
|
+
file: string;
|
|
26
|
+
exports: Array<{
|
|
27
|
+
symbol: string;
|
|
28
|
+
type: string | null;
|
|
29
|
+
}>;
|
|
30
|
+
count: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function normalize(file: string): string {
|
|
34
|
+
return file.replaceAll("\\", "/");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sleep(ms: number): Promise<void> {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function waitFor(
|
|
42
|
+
description: string,
|
|
43
|
+
fn: () => Promise<void>,
|
|
44
|
+
timeoutMs = 5_000,
|
|
45
|
+
intervalMs = 50
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
let lastError: unknown;
|
|
49
|
+
|
|
50
|
+
while (Date.now() - start < timeoutMs) {
|
|
51
|
+
try {
|
|
52
|
+
await fn();
|
|
53
|
+
return;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
lastError = err;
|
|
56
|
+
await sleep(intervalMs);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${description} did not stabilize within ${timeoutMs}ms: ${String(lastError)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeFile(root: string, relativePath: string, content: string): void {
|
|
66
|
+
const absPath = path.join(root, relativePath);
|
|
67
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
68
|
+
fs.writeFileSync(absPath, content);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main(): Promise<void> {
|
|
72
|
+
const repoRoot = import.meta.dirname;
|
|
73
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-engine-sync-"));
|
|
74
|
+
const projectRoot = path.join(tempRoot, "project");
|
|
75
|
+
|
|
76
|
+
fs.mkdirSync(projectRoot, { recursive: true });
|
|
77
|
+
writeFile(
|
|
78
|
+
projectRoot,
|
|
79
|
+
"package.json",
|
|
80
|
+
JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
name: "typegraph-engine-sync-fixture",
|
|
83
|
+
private: true,
|
|
84
|
+
type: "module",
|
|
85
|
+
},
|
|
86
|
+
null,
|
|
87
|
+
2
|
|
88
|
+
) + "\n"
|
|
89
|
+
);
|
|
90
|
+
writeFile(
|
|
91
|
+
projectRoot,
|
|
92
|
+
"tsconfig.json",
|
|
93
|
+
JSON.stringify(
|
|
94
|
+
{
|
|
95
|
+
compilerOptions: {
|
|
96
|
+
target: "ES2022",
|
|
97
|
+
module: "ESNext",
|
|
98
|
+
moduleResolution: "Bundler",
|
|
99
|
+
strict: true,
|
|
100
|
+
},
|
|
101
|
+
include: ["src/**/*.ts"],
|
|
102
|
+
},
|
|
103
|
+
null,
|
|
104
|
+
2
|
|
105
|
+
) + "\n"
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
writeFile(projectRoot, "src/a.ts", 'export const current = "a" as const;\n');
|
|
109
|
+
writeFile(projectRoot, "src/b.ts", 'export const current = "b" as const;\n');
|
|
110
|
+
writeFile(
|
|
111
|
+
projectRoot,
|
|
112
|
+
"src/main.ts",
|
|
113
|
+
'import { current } from "./a";\nexport const value = current;\n'
|
|
114
|
+
);
|
|
115
|
+
writeFile(projectRoot, "src/test.ts", "export const oldName = 1 as const;\n");
|
|
116
|
+
writeFile(projectRoot, "src/util.ts", "export const helper = 1 as const;\n");
|
|
117
|
+
|
|
118
|
+
fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
|
|
119
|
+
fs.symlinkSync(
|
|
120
|
+
path.join(repoRoot, "node_modules/typescript"),
|
|
121
|
+
path.join(projectRoot, "node_modules/typescript"),
|
|
122
|
+
"dir"
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const client = new Client({ name: "engine-sync-test", version: "1.0.0" });
|
|
126
|
+
const transport = new StdioClientTransport({
|
|
127
|
+
command: path.join(repoRoot, "node_modules/.bin/tsx"),
|
|
128
|
+
args: [path.join(repoRoot, "server.ts")],
|
|
129
|
+
cwd: projectRoot,
|
|
130
|
+
env: {
|
|
131
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot,
|
|
132
|
+
TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
async function callTool<T>(name: string, args: Record<string, unknown>): Promise<T> {
|
|
137
|
+
const result = await client.request(
|
|
138
|
+
{
|
|
139
|
+
method: "tools/call",
|
|
140
|
+
params: {
|
|
141
|
+
name,
|
|
142
|
+
arguments: args,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
CallToolResultSchema
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const content = result.content[0];
|
|
149
|
+
assert.ok(content?.type === "text", `Expected text response from ${name}`);
|
|
150
|
+
return JSON.parse(content.text) as T;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await client.connect(transport);
|
|
155
|
+
|
|
156
|
+
const initialType = await callTool<TypeInfoResult>("ts_type_info", {
|
|
157
|
+
file: "src/main.ts",
|
|
158
|
+
line: 2,
|
|
159
|
+
column: 14,
|
|
160
|
+
});
|
|
161
|
+
assert.match(initialType.type ?? "", /"a"/);
|
|
162
|
+
|
|
163
|
+
writeFile(projectRoot, "src/main.ts", 'import { current } from "./b";\nexport const value = current;\n');
|
|
164
|
+
|
|
165
|
+
await waitFor("import swap to synchronize graph and tsserver", async () => {
|
|
166
|
+
const deps = await callTool<DependencyTreeResult>("ts_dependency_tree", {
|
|
167
|
+
file: "src/main.ts",
|
|
168
|
+
});
|
|
169
|
+
const normalizedDeps = deps.files.map(normalize);
|
|
170
|
+
assert.ok(normalizedDeps.includes("src/b.ts"), `Expected src/b.ts in ${normalizedDeps}`);
|
|
171
|
+
assert.ok(!normalizedDeps.includes("src/a.ts"), `Expected src/a.ts to be removed from ${normalizedDeps}`);
|
|
172
|
+
|
|
173
|
+
const typeInfo = await callTool<TypeInfoResult>("ts_type_info", {
|
|
174
|
+
file: "src/main.ts",
|
|
175
|
+
line: 2,
|
|
176
|
+
column: 14,
|
|
177
|
+
});
|
|
178
|
+
assert.match(typeInfo.type ?? "", /"b"/);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Open test.ts through ts_module_exports, then rename the export on disk.
|
|
182
|
+
const initialExports = await callTool<ModuleExportsResult>("ts_module_exports", {
|
|
183
|
+
file: "src/test.ts",
|
|
184
|
+
});
|
|
185
|
+
assert.ok(initialExports.exports.some((item) => item.symbol === "oldName"));
|
|
186
|
+
|
|
187
|
+
writeFile(projectRoot, "src/test.ts", "export const newName = 1 as const;\n");
|
|
188
|
+
|
|
189
|
+
await waitFor("symbol rename to refresh mixed export metadata", async () => {
|
|
190
|
+
const exportsResult = await callTool<ModuleExportsResult>("ts_module_exports", {
|
|
191
|
+
file: "src/test.ts",
|
|
192
|
+
});
|
|
193
|
+
const next = exportsResult.exports.find((item) => item.symbol === "newName");
|
|
194
|
+
assert.ok(next, `Expected newName in ${JSON.stringify(exportsResult.exports)}`);
|
|
195
|
+
assert.match(next.type ?? "", /\bnewName\b/);
|
|
196
|
+
assert.ok(!exportsResult.exports.some((item) => item.symbol === "oldName"));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Open util.ts directly so tsserver tracks it, then delete it from disk.
|
|
200
|
+
const utilInfo = await callTool<TypeInfoResult>("ts_type_info", {
|
|
201
|
+
file: "src/util.ts",
|
|
202
|
+
line: 1,
|
|
203
|
+
column: 14,
|
|
204
|
+
});
|
|
205
|
+
assert.match(utilInfo.type ?? "", /\bhelper: 1\b/);
|
|
206
|
+
fs.rmSync(path.join(projectRoot, "src/util.ts"));
|
|
207
|
+
|
|
208
|
+
await waitFor("deleted file to disappear from semantic answers", async () => {
|
|
209
|
+
const deletedInfo = await callTool<TypeInfoResult>("ts_type_info", {
|
|
210
|
+
file: "src/util.ts",
|
|
211
|
+
line: 1,
|
|
212
|
+
column: 14,
|
|
213
|
+
});
|
|
214
|
+
assert.equal(
|
|
215
|
+
deletedInfo.type,
|
|
216
|
+
null,
|
|
217
|
+
`Expected deleted file to have no type info, got ${JSON.stringify(deletedInfo)}`
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
console.log("");
|
|
222
|
+
console.log("typegraph-mcp Engine Sync Test");
|
|
223
|
+
console.log("==============================");
|
|
224
|
+
console.log(" ✓ import swaps keep dependency_tree and type_info aligned");
|
|
225
|
+
console.log(" ✓ export renames refresh ts_module_exports semantic metadata");
|
|
226
|
+
console.log(" ✓ deleted open files do not survive as tsserver ghost snapshots");
|
|
227
|
+
} finally {
|
|
228
|
+
await transport.close().catch(() => {});
|
|
229
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
main().catch((err) => {
|
|
234
|
+
console.error(err);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
package/install-oxlint-test.ts
CHANGED
|
@@ -22,13 +22,14 @@ function copyDir(src: string, dest: string): void {
|
|
|
22
22
|
function runTsx(
|
|
23
23
|
toolRoot: string,
|
|
24
24
|
args: string[],
|
|
25
|
-
cwd: string
|
|
25
|
+
cwd: string,
|
|
26
|
+
env: NodeJS.ProcessEnv = process.env
|
|
26
27
|
): string {
|
|
27
28
|
return execFileSync(path.join(toolRoot, "node_modules/.bin/tsx"), args, {
|
|
28
29
|
cwd,
|
|
29
30
|
encoding: "utf-8",
|
|
30
31
|
maxBuffer: 10 * 1024 * 1024,
|
|
31
|
-
env
|
|
32
|
+
env,
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -44,6 +45,7 @@ async function main(): Promise<void> {
|
|
|
44
45
|
const fixtureRoot = path.join(repoRoot, ".fixtures/install-oxlint");
|
|
45
46
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-install-oxlint-"));
|
|
46
47
|
const projectRoot = path.join(tempRoot, "project");
|
|
48
|
+
const homeRoot = path.join(tempRoot, "home");
|
|
47
49
|
|
|
48
50
|
copyDir(fixtureRoot, projectRoot);
|
|
49
51
|
fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
|
|
@@ -54,7 +56,14 @@ async function main(): Promise<void> {
|
|
|
54
56
|
);
|
|
55
57
|
|
|
56
58
|
try {
|
|
57
|
-
|
|
59
|
+
fs.mkdirSync(homeRoot, { recursive: true });
|
|
60
|
+
const testEnv = { ...process.env, HOME: homeRoot };
|
|
61
|
+
const setupOutput = runTsx(
|
|
62
|
+
repoRoot,
|
|
63
|
+
[path.join(repoRoot, "cli.ts"), "setup", "--yes"],
|
|
64
|
+
projectRoot,
|
|
65
|
+
testEnv
|
|
66
|
+
);
|
|
58
67
|
const pluginRoot = path.join(projectRoot, "plugins/typegraph-mcp");
|
|
59
68
|
|
|
60
69
|
const tsconfig = fs.readFileSync(path.join(projectRoot, "tsconfig.json"), "utf-8");
|
|
@@ -70,7 +79,12 @@ async function main(): Promise<void> {
|
|
|
70
79
|
assertIncludes(setupOutput, 'Added "plugins/**" to .oxlintrc.json ignorePatterns');
|
|
71
80
|
assertIncludes(setupOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
|
|
72
81
|
|
|
73
|
-
const checkOutput = runTsx(
|
|
82
|
+
const checkOutput = runTsx(
|
|
83
|
+
pluginRoot,
|
|
84
|
+
[path.join(pluginRoot, "cli.ts"), "check"],
|
|
85
|
+
projectRoot,
|
|
86
|
+
testEnv
|
|
87
|
+
);
|
|
74
88
|
assertIncludes(checkOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
|
|
75
89
|
assert.ok(
|
|
76
90
|
!checkOutput.includes("Lint config check (no ESLint or Oxlint config found)"),
|
package/module-graph.ts
CHANGED
|
@@ -460,7 +460,11 @@ export function removeFile(graph: ModuleGraph, filePath: string): void {
|
|
|
460
460
|
export function startWatcher(
|
|
461
461
|
projectRoot: string,
|
|
462
462
|
graph: ModuleGraph,
|
|
463
|
-
resolver: ResolverFactory
|
|
463
|
+
resolver: ResolverFactory,
|
|
464
|
+
hooks?: {
|
|
465
|
+
onFileUpdated?: (filePath: string) => void | Promise<void>;
|
|
466
|
+
onFileDeleted?: (filePath: string) => void | Promise<void>;
|
|
467
|
+
}
|
|
464
468
|
): void {
|
|
465
469
|
try {
|
|
466
470
|
const watcher = fs.watch(
|
|
@@ -489,9 +493,11 @@ export function startWatcher(
|
|
|
489
493
|
if (fs.existsSync(absPath)) {
|
|
490
494
|
// File created or modified
|
|
491
495
|
updateFile(graph, absPath, resolver, projectRoot);
|
|
496
|
+
void hooks?.onFileUpdated?.(absPath);
|
|
492
497
|
} else {
|
|
493
498
|
// File deleted
|
|
494
499
|
removeFile(graph, absPath);
|
|
500
|
+
void hooks?.onFileDeleted?.(absPath);
|
|
495
501
|
}
|
|
496
502
|
}
|
|
497
503
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.44",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsup",
|
|
36
36
|
"start": "tsx server.ts",
|
|
37
|
-
"test": "tsx smoke-test.ts && tsx export-surface-test.ts && tsx install-oxlint-test.ts",
|
|
37
|
+
"test": "tsx smoke-test.ts && tsx export-surface-test.ts && tsx engine-sync-test.ts && tsx install-oxlint-test.ts",
|
|
38
38
|
"check": "tsx check.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
package/server.ts
CHANGED
|
@@ -1132,7 +1132,15 @@ async function main() {
|
|
|
1132
1132
|
|
|
1133
1133
|
moduleGraph = graphResult.graph;
|
|
1134
1134
|
moduleResolver = graphResult.resolver;
|
|
1135
|
-
startWatcher(projectRoot, moduleGraph, graphResult.resolver
|
|
1135
|
+
startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
|
|
1136
|
+
onFileUpdated: (filePath) =>
|
|
1137
|
+
client.reloadOpenFile(filePath).catch((err) => {
|
|
1138
|
+
log(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1139
|
+
}),
|
|
1140
|
+
onFileDeleted: (filePath) => {
|
|
1141
|
+
client.closeFile(filePath);
|
|
1142
|
+
},
|
|
1143
|
+
});
|
|
1136
1144
|
|
|
1137
1145
|
const transport = new StdioServerTransport();
|
|
1138
1146
|
await mcpServer.connect(transport);
|
package/tsserver-client.ts
CHANGED
|
@@ -323,6 +323,23 @@ export class TsServerClient {
|
|
|
323
323
|
await new Promise((r) => setTimeout(r, 50));
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
async reloadOpenFile(file: string): Promise<boolean> {
|
|
327
|
+
const absPath = this.resolvePath(file);
|
|
328
|
+
if (!this.openFiles.has(absPath)) return false;
|
|
329
|
+
await this.sendRequest("reload", {
|
|
330
|
+
file: absPath,
|
|
331
|
+
tmpfile: absPath,
|
|
332
|
+
});
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
closeFile(file: string): boolean {
|
|
337
|
+
const absPath = this.resolvePath(file);
|
|
338
|
+
if (!this.openFiles.delete(absPath)) return false;
|
|
339
|
+
this.sendNotification("close", { file: absPath });
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
326
343
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
327
344
|
|
|
328
345
|
async definition(file: string, line: number, offset: number): Promise<DefinitionResult[]> {
|