typegraph-mcp 0.9.46 → 0.9.48

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.
@@ -1,262 +0,0 @@
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
- writeFile(projectRoot, "src/closed.ts", "export const closedValue = 1 as const;\n");
118
- writeFile(
119
- projectRoot,
120
- "src/closed-consumer.ts",
121
- 'import { closedValue } from "./closed";\nexport const observed = closedValue;\n'
122
- );
123
-
124
- fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
125
- fs.symlinkSync(
126
- path.join(repoRoot, "node_modules/typescript"),
127
- path.join(projectRoot, "node_modules/typescript"),
128
- "dir"
129
- );
130
-
131
- const client = new Client({ name: "engine-sync-test", version: "1.0.0" });
132
- const transport = new StdioClientTransport({
133
- command: path.join(repoRoot, "node_modules/.bin/tsx"),
134
- args: [path.join(repoRoot, "server.ts")],
135
- cwd: projectRoot,
136
- env: {
137
- TYPEGRAPH_PROJECT_ROOT: projectRoot,
138
- TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
139
- },
140
- });
141
-
142
- async function callTool<T>(name: string, args: Record<string, unknown>): Promise<T> {
143
- const result = await client.request(
144
- {
145
- method: "tools/call",
146
- params: {
147
- name,
148
- arguments: args,
149
- },
150
- },
151
- CallToolResultSchema
152
- );
153
-
154
- const content = result.content[0];
155
- assert.ok(content?.type === "text", `Expected text response from ${name}`);
156
- return JSON.parse(content.text) as T;
157
- }
158
-
159
- try {
160
- await client.connect(transport);
161
-
162
- const initialType = await callTool<TypeInfoResult>("ts_type_info", {
163
- file: "src/main.ts",
164
- line: 2,
165
- column: 14,
166
- });
167
- assert.match(initialType.type ?? "", /"a"/);
168
-
169
- writeFile(projectRoot, "src/main.ts", 'import { current } from "./b";\nexport const value = current;\n');
170
-
171
- await waitFor("import swap to synchronize graph and tsserver", async () => {
172
- const deps = await callTool<DependencyTreeResult>("ts_dependency_tree", {
173
- file: "src/main.ts",
174
- });
175
- const normalizedDeps = deps.files.map(normalize);
176
- assert.ok(normalizedDeps.includes("src/b.ts"), `Expected src/b.ts in ${normalizedDeps}`);
177
- assert.ok(!normalizedDeps.includes("src/a.ts"), `Expected src/a.ts to be removed from ${normalizedDeps}`);
178
-
179
- const typeInfo = await callTool<TypeInfoResult>("ts_type_info", {
180
- file: "src/main.ts",
181
- line: 2,
182
- column: 14,
183
- });
184
- assert.match(typeInfo.type ?? "", /"b"/);
185
- });
186
-
187
- // Open test.ts through ts_module_exports, then rename the export on disk.
188
- const initialExports = await callTool<ModuleExportsResult>("ts_module_exports", {
189
- file: "src/test.ts",
190
- });
191
- assert.ok(initialExports.exports.some((item) => item.symbol === "oldName"));
192
-
193
- writeFile(projectRoot, "src/test.ts", "export const newName = 1 as const;\n");
194
-
195
- await waitFor("symbol rename to refresh mixed export metadata", async () => {
196
- const exportsResult = await callTool<ModuleExportsResult>("ts_module_exports", {
197
- file: "src/test.ts",
198
- });
199
- const next = exportsResult.exports.find((item) => item.symbol === "newName");
200
- assert.ok(next, `Expected newName in ${JSON.stringify(exportsResult.exports)}`);
201
- assert.match(next.type ?? "", /\bnewName\b/);
202
- assert.ok(!exportsResult.exports.some((item) => item.symbol === "oldName"));
203
- });
204
-
205
- // Change a file that tsserver has not opened. The watcher should mark
206
- // projects dirty so the next semantic query reloads closed-file contents.
207
- writeFile(projectRoot, "src/closed.ts", "export const closedValue = 2 as const;\n");
208
-
209
- await waitFor("closed file update to refresh semantic project state", async () => {
210
- const deps = await callTool<DependencyTreeResult>("ts_dependency_tree", {
211
- file: "src/closed-consumer.ts",
212
- });
213
- const normalizedDeps = deps.files.map(normalize);
214
- assert.ok(normalizedDeps.includes("src/closed.ts"), `Expected src/closed.ts in ${normalizedDeps}`);
215
-
216
- const typeInfo = await callTool<TypeInfoResult>("ts_type_info", {
217
- file: "src/closed-consumer.ts",
218
- line: 2,
219
- column: 14,
220
- });
221
- assert.match(typeInfo.type ?? "", /\b2\b/);
222
- });
223
-
224
- // Open util.ts directly so tsserver tracks it, then delete it from disk.
225
- const utilInfo = await callTool<TypeInfoResult>("ts_type_info", {
226
- file: "src/util.ts",
227
- line: 1,
228
- column: 14,
229
- });
230
- assert.match(utilInfo.type ?? "", /\bhelper: 1\b/);
231
- fs.rmSync(path.join(projectRoot, "src/util.ts"));
232
-
233
- await waitFor("deleted file to disappear from semantic answers", async () => {
234
- const deletedInfo = await callTool<TypeInfoResult>("ts_type_info", {
235
- file: "src/util.ts",
236
- line: 1,
237
- column: 14,
238
- });
239
- assert.equal(
240
- deletedInfo.type,
241
- null,
242
- `Expected deleted file to have no type info, got ${JSON.stringify(deletedInfo)}`
243
- );
244
- });
245
-
246
- console.log("");
247
- console.log("typegraph-mcp Engine Sync Test");
248
- console.log("==============================");
249
- console.log(" ✓ import swaps keep dependency_tree and type_info aligned");
250
- console.log(" ✓ export renames refresh ts_module_exports semantic metadata");
251
- console.log(" ✓ closed-file edits refresh tsserver project state");
252
- console.log(" ✓ deleted open files do not survive as tsserver ghost snapshots");
253
- } finally {
254
- await transport.close().catch(() => {});
255
- fs.rmSync(tempRoot, { recursive: true, force: true });
256
- }
257
- }
258
-
259
- main().catch((err) => {
260
- console.error(err);
261
- process.exit(1);
262
- });
@@ -1,202 +0,0 @@
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 ModuleExportRecord = {
12
- symbol: string;
13
- kind: string;
14
- line: number;
15
- type: string | null;
16
- exportKind: "value" | "type";
17
- isTypeOnly: boolean;
18
- isNamespace: boolean;
19
- source: "local" | "re-export" | "star-re-export";
20
- from: string | null;
21
- definedIn: string;
22
- definedLine: number | null;
23
- };
24
-
25
- type ModuleExportsResult = {
26
- file: string;
27
- exports: ModuleExportRecord[];
28
- count: number;
29
- localCount: number;
30
- reExportCount: number;
31
- typeOnlyCount: number;
32
- valueCount: number;
33
- namespaceExportCount: number;
34
- hasLocalRuntimeExports: boolean;
35
- isPrimarilyBarrel: boolean;
36
- };
37
-
38
- function copyDir(src: string, dest: string): void {
39
- fs.mkdirSync(dest, { recursive: true });
40
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
41
- const srcPath = path.join(src, entry.name);
42
- const destPath = path.join(dest, entry.name);
43
- if (entry.isDirectory()) {
44
- copyDir(srcPath, destPath);
45
- } else {
46
- fs.copyFileSync(srcPath, destPath);
47
- }
48
- }
49
- }
50
-
51
- function findExport(
52
- result: ModuleExportsResult,
53
- symbol: string,
54
- exportKind: "value" | "type"
55
- ): ModuleExportRecord {
56
- const found = result.exports.find(
57
- (item) => item.symbol === symbol && item.exportKind === exportKind
58
- );
59
- assert.ok(found, `Expected ${symbol}:${exportKind} in ${result.file}`);
60
- return found;
61
- }
62
-
63
- function assertProjectPath(actual: string | null, expected: string): void {
64
- assert.ok(actual, `Expected path ${expected}`);
65
- const normalized = actual.replaceAll("\\", "/");
66
- assert.equal(normalized, expected, `Expected ${normalized} to equal ${expected}`);
67
- }
68
-
69
- async function main(): Promise<void> {
70
- const repoRoot = import.meta.dirname;
71
- const fixtureRoot = path.join(repoRoot, ".fixtures/export-surface");
72
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-export-surface-"));
73
- const projectRoot = path.join(tempRoot, "project");
74
-
75
- copyDir(fixtureRoot, projectRoot);
76
- fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
77
- fs.symlinkSync(
78
- path.join(repoRoot, "node_modules/typescript"),
79
- path.join(projectRoot, "node_modules/typescript"),
80
- "dir"
81
- );
82
-
83
- const client = new Client({ name: "export-surface-test", version: "1.0.0" });
84
- const transport = new StdioClientTransport({
85
- command: path.join(repoRoot, "node_modules/.bin/tsx"),
86
- args: [path.join(repoRoot, "server.ts")],
87
- cwd: projectRoot,
88
- env: {
89
- TYPEGRAPH_PROJECT_ROOT: projectRoot,
90
- TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
91
- },
92
- });
93
-
94
- try {
95
- await client.connect(transport);
96
-
97
- async function moduleExports(file: string): Promise<ModuleExportsResult> {
98
- const result = await client.request(
99
- {
100
- method: "tools/call",
101
- params: {
102
- name: "ts_module_exports",
103
- arguments: { file },
104
- },
105
- },
106
- CallToolResultSchema
107
- );
108
-
109
- const content = result.content[0];
110
- assert.ok(content?.type === "text", `Expected text response for ${file}`);
111
- return JSON.parse(content.text) as ModuleExportsResult;
112
- }
113
-
114
- const source = await moduleExports("src/source.ts");
115
- const defaultExport = findExport(source, "default", "value");
116
- assert.equal(defaultExport.source, "local");
117
- assert.equal(defaultExport.kind, "function");
118
- assert.equal(defaultExport.definedIn, "src/source.ts");
119
- assert.equal(defaultExport.definedLine, 1);
120
-
121
- const defaultExpression = await moduleExports("src/default-expression.ts");
122
- const expressionDefault = findExport(defaultExpression, "default", "value");
123
- assert.equal(expressionDefault.source, "local");
124
- assert.equal(expressionDefault.definedIn, "src/default-expression.ts");
125
-
126
- const barrel = await moduleExports("src/barrel.ts");
127
- const barrelValue = findExport(barrel, "value", "value");
128
- const barrelUserShape = findExport(barrel, "UserShape", "type");
129
- assert.equal(barrelValue.source, "star-re-export");
130
- assert.equal(barrelUserShape.source, "star-re-export");
131
- assert.equal(barrel.exports.some((item) => item.symbol === "default"), false);
132
- assert.equal(barrel.exports.some((item) => item.symbol === "buildUser"), false);
133
- assert.equal(barrelUserShape.isTypeOnly, true);
134
- assert.equal(barrel.typeOnlyCount, 2);
135
- assert.equal(barrel.isPrimarilyBarrel, true);
136
- assert.equal(barrel.hasLocalRuntimeExports, false);
137
-
138
- const typeReExport = await moduleExports("src/reexport-type.ts");
139
- const userShape = findExport(typeReExport, "UserShape", "type");
140
- assert.equal(userShape.source, "re-export");
141
- assert.equal(userShape.isTypeOnly, true);
142
- assert.equal(userShape.exportKind, "type");
143
- assert.equal(typeReExport.typeOnlyCount, 1);
144
- assert.equal(typeReExport.valueCount, 0);
145
-
146
- const namedReExport = await moduleExports("src/named-reexport.ts");
147
- const aliasedValue = findExport(namedReExport, "aliasedValue", "value");
148
- assert.equal(aliasedValue.source, "re-export");
149
- assertProjectPath(aliasedValue.from, "src/source.ts");
150
- assert.equal(aliasedValue.definedIn, "src/source.ts");
151
- assert.equal(namedReExport.namespaceExportCount, 0);
152
-
153
- const namespaceReExport = await moduleExports("src/namespace-reexport.ts");
154
- const models = findExport(namespaceReExport, "Models", "value");
155
- assert.equal(models.source, "re-export");
156
- assert.equal(models.isNamespace, true);
157
- assert.equal(namespaceReExport.namespaceExportCount, 1);
158
-
159
- const mixed = await moduleExports("src/mixed.ts");
160
- const localValue = findExport(mixed, "SessionId", "value");
161
- const localType = findExport(mixed, "SessionId", "type");
162
- const externalValue = findExport(mixed, "externalValue", "value");
163
- const externalUserShape = findExport(mixed, "ExternalUserShape", "type");
164
- assert.equal(localValue.source, "local");
165
- assert.equal(localType.source, "local");
166
- assert.equal(localType.kind, "type");
167
- assert.equal(localType.definedLine, 2);
168
- assert.equal(externalValue.source, "re-export");
169
- assert.equal(externalUserShape.source, "re-export");
170
- assert.equal(externalUserShape.isTypeOnly, true);
171
- assert.equal(mixed.localCount, 2);
172
- assert.equal(mixed.reExportCount, 2);
173
- assert.equal(mixed.typeOnlyCount, 2);
174
- assert.equal(mixed.hasLocalRuntimeExports, true);
175
- assert.equal(mixed.isPrimarilyBarrel, false);
176
-
177
- const collision = await moduleExports("src/collision-barrel.ts");
178
- assert.equal(collision.exports.some((item) => item.symbol === "dup"), false);
179
- assert.equal(collision.count, 0);
180
-
181
- console.log("");
182
- console.log("typegraph-mcp Export Surface Test");
183
- console.log("=================================");
184
- console.log(" ✓ local default exports are reported as default");
185
- console.log(" ✓ anonymous default exports stay visible");
186
- console.log(" ✓ barrel star re-exports");
187
- console.log(" ✓ barrel star re-exports exclude default exports");
188
- console.log(" ✓ type-only named re-exports");
189
- console.log(" ✓ named alias re-exports");
190
- console.log(" ✓ namespace re-exports");
191
- console.log(" ✓ mixed local + re-export modules");
192
- console.log(" ✓ conflicting star re-exports stay hidden");
193
- } finally {
194
- await transport.close().catch(() => {});
195
- fs.rmSync(tempRoot, { recursive: true, force: true });
196
- }
197
- }
198
-
199
- main().catch((err) => {
200
- console.error(err);
201
- process.exit(1);
202
- });
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
-
3
- import * as assert from "node:assert/strict";
4
- import { execFileSync } from "node:child_process";
5
- import * as fs from "node:fs";
6
- import * as os from "node:os";
7
- import * as path from "node:path";
8
-
9
- function copyDir(src: string, dest: string): void {
10
- fs.mkdirSync(dest, { recursive: true });
11
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
12
- const srcPath = path.join(src, entry.name);
13
- const destPath = path.join(dest, entry.name);
14
- if (entry.isDirectory()) {
15
- copyDir(srcPath, destPath);
16
- } else {
17
- fs.copyFileSync(srcPath, destPath);
18
- }
19
- }
20
- }
21
-
22
- function runTsx(
23
- toolRoot: string,
24
- args: string[],
25
- cwd: string,
26
- env: NodeJS.ProcessEnv = process.env
27
- ): string {
28
- return execFileSync(path.join(toolRoot, "node_modules/.bin/tsx"), args, {
29
- cwd,
30
- encoding: "utf-8",
31
- maxBuffer: 10 * 1024 * 1024,
32
- env,
33
- });
34
- }
35
-
36
- function assertIncludes(text: string, expected: string): void {
37
- assert.ok(
38
- text.includes(expected),
39
- `Expected output to include:\n${expected}\n\nActual output:\n${text}`
40
- );
41
- }
42
-
43
- async function main(): Promise<void> {
44
- const repoRoot = import.meta.dirname;
45
- const fixtureRoot = path.join(repoRoot, ".fixtures/install-oxlint");
46
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-install-oxlint-"));
47
- const projectRoot = path.join(tempRoot, "project");
48
- const homeRoot = path.join(tempRoot, "home");
49
-
50
- copyDir(fixtureRoot, projectRoot);
51
- fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
52
- fs.symlinkSync(
53
- path.join(repoRoot, "node_modules/typescript"),
54
- path.join(projectRoot, "node_modules/typescript"),
55
- "dir"
56
- );
57
-
58
- try {
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
- );
67
- const pluginRoot = path.join(projectRoot, "plugins/typegraph-mcp");
68
-
69
- const tsconfig = fs.readFileSync(path.join(projectRoot, "tsconfig.json"), "utf-8");
70
- const oxlint = fs.readFileSync(path.join(projectRoot, ".oxlintrc.json"), "utf-8");
71
- const eslint = fs.readFileSync(path.join(projectRoot, "eslint.config.js"), "utf-8");
72
-
73
- assertIncludes(tsconfig, '"$schema": "http://json.schemastore.org/tsconfig"');
74
- assertIncludes(tsconfig, '"exclude": ["plugins/**"]');
75
- assertIncludes(oxlint, '"ignorePatterns": [');
76
- assertIncludes(oxlint, '"plugins/**"');
77
- assertIncludes(eslint, 'const config = [\n { ignores: ["plugins/**"] },');
78
- assert.ok(fs.existsSync(path.join(pluginRoot, "cli.ts")), "Expected installed plugin CLI");
79
-
80
- assertIncludes(setupOutput, 'Added "plugins/**" to tsconfig.json exclude');
81
- assertIncludes(setupOutput, 'Added "plugins/**" to .oxlintrc.json ignorePatterns');
82
- assertIncludes(setupOutput, 'Added "plugins/**" to eslint.config.js ignores');
83
- assertIncludes(setupOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
84
- assertIncludes(setupOutput, "ESLint ignores plugins/ (eslint.config.js)");
85
-
86
- const checkOutput = runTsx(
87
- pluginRoot,
88
- [path.join(pluginRoot, "cli.ts"), "check"],
89
- projectRoot,
90
- testEnv
91
- );
92
- assertIncludes(checkOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
93
- assertIncludes(checkOutput, "ESLint ignores plugins/ (eslint.config.js)");
94
- assert.ok(
95
- !checkOutput.includes("Lint config check (no ESLint or Oxlint config found)"),
96
- `Did not expect lint config detection to be skipped:\n${checkOutput}`
97
- );
98
-
99
- console.log("");
100
- console.log("typegraph-mcp Install Oxlint Test");
101
- console.log("=================================");
102
- console.log(" ✓ tsconfig schema URL preserved during exclude patch");
103
- console.log(" ✓ tsconfig exclude patch ignores unrelated plugins text");
104
- console.log(" ✓ .oxlintrc.json patched with plugins ignore");
105
- console.log(" ✓ eslint.config.js named flat-config array patched with plugins ignore");
106
- console.log(" ✓ installed plugin health check recognizes Oxlint config");
107
- console.log(" ✓ installed plugin health check recognizes ESLint config");
108
- } finally {
109
- fs.rmSync(tempRoot, { recursive: true, force: true });
110
- }
111
- }
112
-
113
- main().catch((err) => {
114
- console.error(err);
115
- process.exit(1);
116
- });