@tng-sh/mcp-server 1.0.1 → 1.0.4

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.
Files changed (43) hide show
  1. package/README.md +37 -7
  2. package/dist/index.js +307 -53
  3. package/dist/index.js.map +1 -1
  4. package/dist/init.d.ts.map +1 -1
  5. package/dist/init.js +8 -0
  6. package/dist/init.js.map +1 -1
  7. package/dist/tools/audit.d.ts +5 -1
  8. package/dist/tools/audit.d.ts.map +1 -1
  9. package/dist/tools/audit.js +29 -29
  10. package/dist/tools/audit.js.map +1 -1
  11. package/dist/tools/call_sites.d.ts +20 -0
  12. package/dist/tools/call_sites.d.ts.map +1 -0
  13. package/dist/tools/call_sites.js +108 -0
  14. package/dist/tools/call_sites.js.map +1 -0
  15. package/dist/tools/clones.d.ts +4 -1
  16. package/dist/tools/clones.d.ts.map +1 -1
  17. package/dist/tools/clones.js +25 -28
  18. package/dist/tools/clones.js.map +1 -1
  19. package/dist/tools/deadcode.d.ts +4 -1
  20. package/dist/tools/deadcode.d.ts.map +1 -1
  21. package/dist/tools/deadcode.js +33 -28
  22. package/dist/tools/deadcode.js.map +1 -1
  23. package/dist/tools/generate.d.ts +5 -1
  24. package/dist/tools/generate.d.ts.map +1 -1
  25. package/dist/tools/generate.js +28 -28
  26. package/dist/tools/generate.js.map +1 -1
  27. package/dist/tools/impact.d.ts +4 -1
  28. package/dist/tools/impact.d.ts.map +1 -1
  29. package/dist/tools/impact.js +26 -30
  30. package/dist/tools/impact.js.map +1 -1
  31. package/dist/tools/trace.d.ts +5 -1
  32. package/dist/tools/trace.d.ts.map +1 -1
  33. package/dist/tools/trace.js +29 -29
  34. package/dist/tools/trace.js.map +1 -1
  35. package/dist/tools/utils.d.ts +38 -1
  36. package/dist/tools/utils.d.ts.map +1 -1
  37. package/dist/tools/utils.js +320 -17
  38. package/dist/tools/utils.js.map +1 -1
  39. package/dist/tools/xray.d.ts +4 -1
  40. package/dist/tools/xray.d.ts.map +1 -1
  41. package/dist/tools/xray.js +26 -26
  42. package/dist/tools/xray.js.map +1 -1
  43. package/package.json +8 -2
package/README.md CHANGED
@@ -10,7 +10,7 @@ The [Model Context Protocol](https://modelcontextprotocol.io) allows AI assistan
10
10
 
11
11
  - *"Audit the `process_payment` method in `app/services/payment_processor.rb`"*
12
12
  - *"Generate comprehensive tests for the UserController#create method"*
13
- - *"Check the impact of changes to `Order#checkout`"*
13
+ - *"Run a regression check on `Order#checkout`"*
14
14
  - *"What can TNG do here?"*
15
15
 
16
16
  And the AI will use TNG automatically to provide detailed, actionable results.
@@ -21,7 +21,7 @@ And the AI will use TNG automatically to provide detailed, actionable results.
21
21
  - **Test Generation** - Automatic test creation for Ruby, Python, and JavaScript
22
22
  - **X-Ray Visualization** - Generate Mermaid flowcharts to visualize method logic
23
23
  - **Symbolic Trace** - Execution path analysis for debugging and understanding code flow
24
- - **Impact Analysis** - Blast radius check for changes to a method vs Git HEAD
24
+ - **Regression Check** - Blast radius check for changes to a method vs Git HEAD
25
25
  - **Clone Detection** - Find duplicate code with token-based, structural, and fuzzy matching
26
26
  - **Dead Code Detection** - Identify unused imports, variables, functions, and unreachable code
27
27
  - **Capabilities Listing** - List available TNG MCP tools and when to use them
@@ -310,7 +310,7 @@ AI: I'll analyze that file for dead code.
310
310
 
311
311
  ## Available Tools
312
312
 
313
- The MCP server exposes six tools:
313
+ The MCP server exposes seven tools:
314
314
 
315
315
  ### 1. `audit_method`
316
316
 
@@ -365,7 +365,21 @@ Generate symbolic trace execution path for a method.
365
365
  - Variable states at each step
366
366
  - Control flow analysis
367
367
 
368
- ### 5. `detect_clones`
368
+ ### 5. `impact_method` (Regression Check)
369
+
370
+ Analyze the impact (blast radius) of changes to a method vs its committed version (`HEAD`).
371
+
372
+ **Parameters:**
373
+ - `file_path` (required) - Path to the source file
374
+ - `method_name` (required) - Name of the method
375
+ - `class_name` (optional) - Class name (Python only)
376
+
377
+ **Returns:**
378
+ - Breaking changes detected
379
+ - Informational changes
380
+ - Impacted call sites (where this method is used)
381
+
382
+ ### 6. `detect_clones`
369
383
 
370
384
  Detect code duplication within a file.
371
385
 
@@ -378,18 +392,34 @@ Detect code duplication within a file.
378
392
  - Locations of duplicated code
379
393
  - Refactoring recommendations
380
394
 
381
- ### 6. `detect_deadcode`
395
+ ### 7. `detect_deadcode`
382
396
 
383
- Detect dead code in a file.
397
+ Detect dead code in a file or across the whole project.
384
398
 
385
399
  **Parameters:**
386
- - `file_path` (required) - Path to the source file
400
+ - `file_path` (optional) - Path to the source file (omit when using `all=true`)
401
+ - `all` (optional) - Set to true for a project-wide scan (uses CLI flag `--all`)
402
+ - `project_root` (required when `all=true`) - Project root to run the scan from
387
403
 
388
404
  **Returns:**
389
405
  - Unused imports, variables, and functions
390
406
  - Unreachable code blocks
391
407
  - Cleanup recommendations
392
408
 
409
+ ### 8. `find_call_sites`
410
+
411
+ Find in-repo call sites for a method/function.
412
+
413
+ **Parameters:**
414
+ - `file_path` (required) - Path to the source file containing the method
415
+ - `method_name` (required) - Method/function name
416
+ - `class_name` (optional) - Class/module name to disambiguate methods with the same name
417
+ - `project_root` (optional) - Project root to resolve relative paths
418
+
419
+ **Returns:**
420
+ - Files and line numbers where the method is called
421
+ - Snippet/context lines (when available)
422
+
393
423
  ## Development
394
424
 
395
425
  ### Building
package/dist/index.js CHANGED
@@ -8,11 +8,13 @@ import { xrayMethod } from './tools/xray.js';
8
8
  import { traceMethod } from './tools/trace.js';
9
9
  import { detectClones } from './tools/clones.js';
10
10
  import { detectDeadCode } from './tools/deadcode.js';
11
+ import { findCallSites } from './tools/call_sites.js';
11
12
  import { impactMethod } from './tools/impact.js';
12
13
  import { runInit } from './init.js';
14
+ import { preflightTarget, buildMcpResponse } from './tools/utils.js';
13
15
  const server = new McpServer({
14
16
  name: 'tng-mcp',
15
- version: '1.0.0',
17
+ version: '1.1.0',
16
18
  });
17
19
  if (process.argv.includes('init')) {
18
20
  const code = runInit();
@@ -21,100 +23,186 @@ if (process.argv.includes('init')) {
21
23
  const auditSchema = {
22
24
  file_path: z.string().describe('Relative or absolute path to the source file (e.g., "app/services/payment_processor.rb", "src/utils/validator.ts")'),
23
25
  method_name: z.string().describe('Name of the method/function to audit (e.g., "process_payment", "validateUser", "handleSubmit")'),
26
+ class_name: z.string().optional().describe('Class or module name to disambiguate methods with the same name'),
24
27
  test_type: z.string().optional().describe('Component type - REQUIRED for JavaScript/Python: "utility", "react_component", "express_handler", "django_view", etc. Ignored for Ruby/Rails.'),
25
28
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
26
29
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
30
+ project_root: z.string().optional().describe('Project root for deterministic relative-path resolution.'),
31
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 420).'),
32
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
33
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
27
34
  };
28
35
  const generateSchema = {
29
36
  file_path: z.string().describe('Relative or absolute path to the source file to generate tests for'),
30
37
  method_name: z.string().describe('Name of the method/function to generate tests for'),
38
+ class_name: z.string().optional().describe('Class or module name to disambiguate methods with the same name'),
31
39
  test_type: z.string().optional().describe('Component type or test framework hint - REQUIRED for JS/Python: "utility", "react_component", "express_handler", etc.'),
32
40
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
33
41
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
42
+ project_root: z.string().optional().describe('Project root for deterministic relative-path resolution.'),
43
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 420).'),
44
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
45
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
34
46
  };
35
47
  const methodSchema = {
36
48
  file_path: z.string().describe('Relative or absolute path to the source file'),
37
49
  method_name: z.string().describe('Name of the method/function'),
50
+ class_name: z.string().optional().describe('Class or module name to disambiguate methods with the same name'),
38
51
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
39
52
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
53
+ project_root: z.string().optional().describe('Project root for deterministic relative-path resolution.'),
54
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 120 for trace/xray).'),
55
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
56
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
40
57
  };
41
58
  const impactSchema = {
42
59
  file_path: z.string().describe('Relative or absolute path to the source file'),
43
60
  method_name: z.string().describe('Name of the method/function'),
44
- class_name: z.string().optional().describe('Class name (Python only, optional)'),
61
+ class_name: z.string().optional().describe('Class or module name to disambiguate methods with the same name'),
45
62
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
46
63
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
64
+ project_root: z.string().optional().describe('Project root for deterministic relative-path resolution.'),
65
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 180).'),
66
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
67
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
47
68
  };
48
69
  const clonesSchema = {
49
70
  file_path: z.string().describe('Relative or absolute path to the source file to analyze for duplication'),
50
71
  level: z.string().optional().describe('Clone detection level: "1" (exact), "2" (structural), "3" (fuzzy/near-miss), or "all" (default - detects all types)'),
51
72
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
52
73
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
74
+ project_root: z.string().optional().describe('Project root for deterministic relative-path resolution.'),
75
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 180).'),
76
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
77
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
53
78
  };
54
79
  const deadCodeSchema = {
55
80
  file_path: z.string().optional().describe('Relative or absolute path to the source file to analyze for dead code (omit when using all=true)'),
81
+ project_root: z.string().optional().describe('Project root to resolve relative paths and run project-wide scans (recommended when all=true)'),
56
82
  all: z.boolean().optional().describe('If true, analyze dead code across the entire project (passes --all).'),
57
83
  response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
58
84
  raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
85
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 600).'),
86
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
87
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
88
+ };
89
+ const callSitesSchema = {
90
+ file_path: z.string().describe('Relative or absolute path to the source file containing the method'),
91
+ method_name: z.string().describe('Name of the method/function'),
92
+ class_name: z.string().optional().describe('Class or module name to disambiguate methods with the same name'),
93
+ project_root: z.string().optional().describe('Project root to resolve relative paths'),
94
+ response_format: z.string().optional().describe('Response format: "text" (default), "json", or "both".'),
95
+ raw_json: z.boolean().optional().describe('If true, return JSON envelope with raw tool output.'),
96
+ timeout_sec: z.number().int().positive().optional().describe('Execution timeout in seconds (default 600).'),
97
+ retries: z.number().int().min(0).max(3).optional().describe('Retry attempts for transient failures (default 0).'),
98
+ debug: z.boolean().optional().describe('Return additional diagnostics in server logs/metadata.'),
59
99
  };
60
100
  const auditDescription = `Audit a specific method/function for security, bugs, performance, and code quality issues.
61
101
  Use when the user asks to audit, review, analyze, inspect, or check code.
62
102
  Returns findings with severity, line locations, and suggested fixes.
63
- Supports: Ruby, Python, JavaScript/TypeScript.`;
103
+ Supports: Ruby, Python, JavaScript/TypeScript.
104
+ Example: audit_method { file_path: "app/services/payment_processor.rb", method_name: "process_payment" }`;
64
105
  const generateDescription = `Generate tests for a specific method/function.
65
106
  Use when the user asks to write tests, create specs, or add coverage.
66
107
  Returns complete test code with suggested file path and command to run.
67
- Supports: Ruby, Python, JavaScript/TypeScript.`;
108
+ Supports: Ruby, Python, JavaScript/TypeScript.
109
+ Example: generate_test { file_path: "src/utils/validator.ts", method_name: "validateUser", test_type: "utility" }`;
68
110
  const xrayDescription = `Generate a Mermaid flowchart for a method/function (X-Ray).
69
111
  Use when the user asks for a diagram, flowchart, or visual explanation.
70
- Returns Mermaid code suitable for Markdown renderers.`;
112
+ Returns Mermaid code suitable for Markdown renderers.
113
+ Example: xray_method { file_path: "lib/order.rb", method_name: "checkout" }`;
71
114
  const traceDescription = `Generate a symbolic execution trace for a method/function.
72
115
  Use when the user asks to trace, step through, or debug execution flow.
73
- Returns step-by-step trace output.`;
74
- const impactDescription = `Analyze impact (blast radius) of changes to a method/function vs Git HEAD.
75
- Use when the user asks about impact, breaking changes, or blast radius.
76
- Returns breaking/informational changes and impacted call sites.`;
116
+ Returns step-by-step trace output.
117
+ Example: trace_method { file_path: "src/controllers/cart.ts", method_name: "addItem" }`;
118
+ const impactDescription = `Run a regression check (blast radius analysis) on changes to a method vs Git HEAD.
119
+ Use when the user asks about regressions, impact, breaking changes, or blast radius.
120
+ Returns breaking/informational changes and impacted call sites.
121
+ Example: impact_method { file_path: "app/models/user.rb", method_name: "full_name" }`;
77
122
  const clonesDescription = `Detect code duplication (clones) within a file.
78
123
  Use when the user asks to find duplicates or copy-paste code.
79
- Returns clone pairs with similarity and locations.`;
124
+ Returns clone pairs with similarity and locations.
125
+ Example: detect_clones { file_path: "src/services/order.ts", level: "2" }`;
80
126
  const deadcodeDescription = `Detect unused or unreachable code in a file or entire project.
81
127
  Use when the user asks to find dead code or unused imports/variables.
82
- Set all=true to scan the whole project.
83
- Returns unused elements with locations and cleanup suggestions.`;
128
+ Set all=true to scan the whole project (CLI flag: --all).
129
+ Returns unused elements with locations and cleanup suggestions.
130
+ Example: detect_deadcode { file_path: "src/api/auth.ts" }
131
+ Example (project-wide): detect_deadcode { all: true }`;
132
+ const callSitesDescription = `Find in-repo call sites for a method/function.
133
+ Use when the user asks "who calls this?" or "where is this used?".
134
+ Example: find_call_sites { file_path: "src/utils/auth.ts", method_name: "validateUser" }`;
135
+ async function callWithPreflight(toolName, args, handler, options = { requireMethod: false }) {
136
+ const filePath = args?.file_path ? String(args.file_path) : '';
137
+ const allMode = !!args?.all;
138
+ if (!options.allowMissingFilePath || !allMode) {
139
+ const preflight = preflightTarget({
140
+ tool: toolName,
141
+ filePath,
142
+ methodName: args?.method_name ? String(args.method_name) : undefined,
143
+ className: args?.class_name ? String(args.class_name) : undefined,
144
+ projectRoot: args?.project_root ? String(args.project_root) : undefined,
145
+ requireMethod: options.requireMethod,
146
+ });
147
+ if (!preflight.ok) {
148
+ const format = args?.response_format || (args?.raw_json ? 'json' : 'text');
149
+ return buildMcpResponse({
150
+ tool: toolName,
151
+ text: preflight.message,
152
+ isError: true,
153
+ format,
154
+ code: preflight.code,
155
+ details: preflight.details,
156
+ meta: { project_root: preflight.projectRoot },
157
+ });
158
+ }
159
+ args = {
160
+ ...args,
161
+ project_root: preflight.projectRoot,
162
+ file_path: preflight.resolvedPath || filePath,
163
+ };
164
+ }
165
+ return handler(args);
166
+ }
84
167
  // 1. Audit Method - AI-Powered Code Analysis
85
- server.registerTool('audit_method', { description: auditDescription, inputSchema: auditSchema }, async (args) => await auditMethod(args));
168
+ server.registerTool('audit_method', { description: auditDescription, inputSchema: auditSchema }, async (args) => await callWithPreflight('audit_method', args, auditMethod, { requireMethod: true }));
86
169
  // Aliases for audit_method
87
- server.registerTool('review_method', { description: 'Alias of audit_method. Use for "review this method" or "code review".', inputSchema: auditSchema }, async (args) => await auditMethod(args));
88
- server.registerTool('analyze_method', { description: 'Alias of audit_method. Use for "analyze this method".', inputSchema: auditSchema }, async (args) => await auditMethod(args));
170
+ server.registerTool('review_method', { description: 'Alias of audit_method. Use for "review this method" or "code review".', inputSchema: auditSchema }, async (args) => await callWithPreflight('audit_method', args, auditMethod, { requireMethod: true }));
171
+ server.registerTool('analyze_method', { description: 'Alias of audit_method. Use for "analyze this method".', inputSchema: auditSchema }, async (args) => await callWithPreflight('audit_method', args, auditMethod, { requireMethod: true }));
89
172
  // 2. Generate Test - AI Test Generation
90
- server.registerTool('generate_test', { description: generateDescription, inputSchema: generateSchema }, async (args) => await generateTest(args));
173
+ server.registerTool('generate_test', { description: generateDescription, inputSchema: generateSchema }, async (args) => await callWithPreflight('generate_test', args, generateTest, { requireMethod: true }));
91
174
  // Aliases for generate_test
92
- server.registerTool('generate_tests', { description: 'Alias of generate_test. Use for "generate tests".', inputSchema: generateSchema }, async (args) => await generateTest(args));
93
- server.registerTool('write_tests', { description: 'Alias of generate_test. Use for "write tests".', inputSchema: generateSchema }, async (args) => await generateTest(args));
175
+ server.registerTool('generate_tests', { description: 'Alias of generate_test. Use for "generate tests".', inputSchema: generateSchema }, async (args) => await callWithPreflight('generate_test', args, generateTest, { requireMethod: true }));
176
+ server.registerTool('write_tests', { description: 'Alias of generate_test. Use for "write tests".', inputSchema: generateSchema }, async (args) => await callWithPreflight('generate_test', args, generateTest, { requireMethod: true }));
94
177
  // 3. X-Ray Visualization - Code Flow Diagrams
95
- server.registerTool('xray_method', { description: xrayDescription, inputSchema: methodSchema }, async (args) => await xrayMethod(args));
178
+ server.registerTool('xray_method', { description: xrayDescription, inputSchema: methodSchema }, async (args) => await callWithPreflight('xray_method', args, xrayMethod, { requireMethod: true }));
96
179
  // Aliases for xray_method
97
- server.registerTool('xray_diagram', { description: 'Alias of xray_method. Use for "diagram" or "flowchart".', inputSchema: methodSchema }, async (args) => await xrayMethod(args));
98
- server.registerTool('visualize_flow', { description: 'Alias of xray_method. Use for "visualize flow".', inputSchema: methodSchema }, async (args) => await xrayMethod(args));
180
+ server.registerTool('xray_diagram', { description: 'Alias of xray_method. Use for "diagram" or "flowchart".', inputSchema: methodSchema }, async (args) => await callWithPreflight('xray_method', args, xrayMethod, { requireMethod: true }));
181
+ server.registerTool('visualize_flow', { description: 'Alias of xray_method. Use for "visualize flow".', inputSchema: methodSchema }, async (args) => await callWithPreflight('xray_method', args, xrayMethod, { requireMethod: true }));
99
182
  // 4. Trace Method - Symbolic Execution Analysis
100
- server.registerTool('trace_method', { description: traceDescription, inputSchema: methodSchema }, async (args) => await traceMethod(args));
183
+ server.registerTool('trace_method', { description: traceDescription, inputSchema: methodSchema }, async (args) => await callWithPreflight('trace_method', args, traceMethod, { requireMethod: true }));
101
184
  // 4.5 Impact Method - Blast Radius Analysis
102
- server.registerTool('impact_method', { description: impactDescription, inputSchema: impactSchema }, async (args) => await impactMethod(args));
185
+ server.registerTool('impact_method', { description: impactDescription, inputSchema: impactSchema }, async (args) => await callWithPreflight('impact_method', args, impactMethod, { requireMethod: true }));
103
186
  // Aliases for impact_method
104
- server.registerTool('impact_analysis', { description: 'Alias of impact_method. Use for "impact analysis" or "blast radius".', inputSchema: impactSchema }, async (args) => await impactMethod(args));
105
- server.registerTool('impact_audit', { description: 'Alias of impact_method. Use for "impact audit".', inputSchema: impactSchema }, async (args) => await impactMethod(args));
106
- server.registerTool('blast_radius', { description: 'Alias of impact_method. Use for "blast radius check".', inputSchema: impactSchema }, async (args) => await impactMethod(args));
187
+ server.registerTool('impact_analysis', { description: 'Alias of impact_method. Use for "impact analysis" or "blast radius".', inputSchema: impactSchema }, async (args) => await callWithPreflight('impact_method', args, impactMethod, { requireMethod: true }));
188
+ server.registerTool('impact_audit', { description: 'Alias of impact_method. Use for "regression check".', inputSchema: impactSchema }, async (args) => await callWithPreflight('impact_method', args, impactMethod, { requireMethod: true }));
189
+ server.registerTool('regression_check', { description: 'Alias of impact_method. Use for "regression check".', inputSchema: impactSchema }, async (args) => await callWithPreflight('impact_method', args, impactMethod, { requireMethod: true }));
190
+ server.registerTool('blast_radius', { description: 'Alias of impact_method. Use for "blast radius check".', inputSchema: impactSchema }, async (args) => await callWithPreflight('impact_method', args, impactMethod, { requireMethod: true }));
107
191
  // Aliases for trace_method
108
- server.registerTool('trace_execution', { description: 'Alias of trace_method. Use for "trace execution".', inputSchema: methodSchema }, async (args) => await traceMethod(args));
192
+ server.registerTool('trace_execution', { description: 'Alias of trace_method. Use for "trace execution".', inputSchema: methodSchema }, async (args) => await callWithPreflight('trace_method', args, traceMethod, { requireMethod: true }));
109
193
  // 5. Detect Clones - Code Duplication Detection
110
- server.registerTool('detect_clones', { description: clonesDescription, inputSchema: clonesSchema }, async (args) => await detectClones(args));
194
+ server.registerTool('detect_clones', { description: clonesDescription, inputSchema: clonesSchema }, async (args) => await callWithPreflight('detect_clones', args, detectClones, { requireMethod: false }));
111
195
  // Aliases for detect_clones
112
- server.registerTool('find_clones', { description: 'Alias of detect_clones. Use for "find duplicates".', inputSchema: clonesSchema }, async (args) => await detectClones(args));
196
+ server.registerTool('find_clones', { description: 'Alias of detect_clones. Use for "find duplicates".', inputSchema: clonesSchema }, async (args) => await callWithPreflight('detect_clones', args, detectClones, { requireMethod: false }));
113
197
  // 6. Detect Dead Code - Unused Code Detection
114
- server.registerTool('detect_deadcode', { description: deadcodeDescription, inputSchema: deadCodeSchema }, async (args) => await detectDeadCode(args));
198
+ server.registerTool('detect_deadcode', { description: deadcodeDescription, inputSchema: deadCodeSchema }, async (args) => await callWithPreflight('detect_deadcode', args, detectDeadCode, { requireMethod: false, allowMissingFilePath: true }));
115
199
  // Aliases for detect_deadcode
116
- server.registerTool('find_deadcode', { description: 'Alias of detect_deadcode. Use for "find unused code".', inputSchema: deadCodeSchema }, async (args) => await detectDeadCode(args));
117
- // 7. Capabilities - List Available Tools
200
+ server.registerTool('find_deadcode', { description: 'Alias of detect_deadcode. Use for "find unused code".', inputSchema: deadCodeSchema }, async (args) => await callWithPreflight('detect_deadcode', args, detectDeadCode, { requireMethod: false, allowMissingFilePath: true }));
201
+ // 7. Find Call Sites - Usage Detection
202
+ server.registerTool('find_call_sites', { description: callSitesDescription, inputSchema: callSitesSchema }, async (args) => await callWithPreflight('find_call_sites', args, findCallSites, { requireMethod: true }));
203
+ // Alias for find_call_sites
204
+ server.registerTool('call_sites', { description: 'Alias of find_call_sites. Use for "find usages" or "where is this called".', inputSchema: callSitesSchema }, async (args) => await callWithPreflight('find_call_sites', args, findCallSites, { requireMethod: true }));
205
+ // 8. Capabilities - List Available Tools
118
206
  server.registerTool('list_capabilities', {
119
207
  description: 'List available TNG MCP tools and when to use them.',
120
208
  inputSchema: {},
@@ -128,15 +216,169 @@ server.registerTool('list_capabilities', {
128
216
  '- generate_test: generate tests for a method',
129
217
  '- xray_method: Mermaid flow diagram for a method',
130
218
  '- trace_method: symbolic execution trace',
131
- '- impact_method: impact analysis (blast radius) for a method',
219
+ '- impact_method: run a regression check (blast radius) for a method',
132
220
  '- detect_clones: find duplicate code in a file',
133
- '- detect_deadcode: find unused/unreachable code in a file',
221
+ '- detect_deadcode: find unused/unreachable code in a file (or whole project with all=true)',
222
+ '- find_call_sites: locate in-repo call sites for a method/function',
223
+ '',
224
+ 'Best practices:',
225
+ '- Prefer file paths relative to the project root.',
226
+ '- If the server runs outside the repo, pass project_root or use absolute file paths.',
227
+ '- For JS/Python, include test_type when generating tests or auditing.',
228
+ '- All execution tools support timeout_sec, retries, and debug.',
229
+ '- Preflight validates file/method and ignore rules before execution.',
134
230
  '',
135
- 'Aliases: review_method, analyze_method, generate_tests, write_tests, xray_diagram, visualize_flow, trace_execution, impact_analysis, impact_audit, blast_radius, find_clones, find_deadcode.',
231
+ 'Aliases: review_method, analyze_method, generate_tests, write_tests, xray_diagram, visualize_flow, trace_execution, impact_analysis, impact_audit, regression_check, blast_radius, find_clones, find_deadcode.',
232
+ '',
233
+ `Server version: 1.1.0`,
234
+ 'Defaults: timeout (audit/generate)=420s, xray/trace=120s, impact/clones=180s, deadcode/callsites=600s; retries=0.',
136
235
  ].join('\n'),
137
236
  },
138
237
  ],
139
238
  }));
239
+ // 7.5 Validate Request - Preflight check for file/method existence
240
+ server.registerTool('validate_request', {
241
+ description: 'Validate file_path and optional method_name before running a TNG tool. Returns a checklist and recommendations.',
242
+ inputSchema: {
243
+ file_path: z.string().describe('Relative or absolute path to the source file'),
244
+ method_name: z.string().optional().describe('Name of the method/function (optional)'),
245
+ project_root: z.string().optional().describe('Project root to resolve relative paths (optional)'),
246
+ },
247
+ }, async (args) => {
248
+ const filePath = String(args?.file_path || '');
249
+ const methodName = args?.method_name ? String(args.method_name) : '';
250
+ const projectRoot = args?.project_root ? String(args.project_root) : undefined;
251
+ const preflight = preflightTarget({
252
+ tool: 'validate_request',
253
+ filePath,
254
+ methodName: methodName || undefined,
255
+ projectRoot,
256
+ requireMethod: false,
257
+ });
258
+ const response = {
259
+ ok: preflight.ok,
260
+ code: preflight.code,
261
+ message: preflight.message,
262
+ details: preflight.details,
263
+ project_root: preflight.projectRoot,
264
+ recommendations: preflight.ok ? [] : ['Fix the preflight error and retry the requested TNG tool.'],
265
+ };
266
+ return {
267
+ content: [
268
+ {
269
+ type: 'text',
270
+ text: JSON.stringify(response, null, 2),
271
+ },
272
+ ],
273
+ };
274
+ });
275
+ const PROJECT_WIDE_PHRASES = ['project', 'entire project', 'whole project', 'project-wide', 'all files', 'scan all', 'repository'];
276
+ const INTENT_DEFINITIONS = [
277
+ {
278
+ tool: 'generate_test',
279
+ strongPhrases: ['generate tests', 'generate test', 'write tests', 'write test', 'create specs', 'create spec', 'add test coverage'],
280
+ phrases: ['test coverage', 'unit test', 'integration test', 'spec file', 'test suite'],
281
+ tokens: ['test', 'tests', 'spec', 'coverage'],
282
+ negativeTokens: ['diagram', 'trace', 'impact', 'dead', 'unused', 'usage'],
283
+ },
284
+ {
285
+ tool: 'impact_method',
286
+ strongPhrases: ['blast radius', 'regression check', 'change impact', 'what breaks'],
287
+ phrases: ['regression', 'breaking change', 'breakage', 'impact analysis', 'risk analysis'],
288
+ tokens: ['impact', 'regression', 'breaks', 'breakage', 'risk'],
289
+ negativeTokens: ['diagram', 'trace'],
290
+ },
291
+ {
292
+ tool: 'audit_method',
293
+ strongPhrases: ['code review', 'review this method', 'audit this method', 'find issues'],
294
+ phrases: ['audit', 'review', 'analyze', 'inspect', 'security issues', 'code smells', 'bugs'],
295
+ tokens: ['audit', 'review', 'analyze', 'inspect', 'security', 'vulnerability', 'bug', 'issue', 'smell'],
296
+ negativeTokens: ['diagram', 'flowchart'],
297
+ },
298
+ {
299
+ tool: 'xray_method',
300
+ strongPhrases: ['xray diagram', 'x-ray diagram', 'flow chart', 'visualize flow'],
301
+ phrases: ['diagram', 'flowchart', 'mermaid', 'visual', 'xray', 'x-ray'],
302
+ tokens: ['diagram', 'flowchart', 'mermaid', 'visualize'],
303
+ negativeTokens: ['unused', 'dead'],
304
+ },
305
+ {
306
+ tool: 'trace_method',
307
+ strongPhrases: ['execution trace', 'trace method', 'step by step', 'step-by-step'],
308
+ phrases: ['trace execution', 'runtime flow', 'execution path', 'what happens when'],
309
+ tokens: ['trace', 'execution', 'runtime', 'debug'],
310
+ negativeTokens: ['diagram', 'mermaid', 'unused'],
311
+ },
312
+ {
313
+ tool: 'find_call_sites',
314
+ strongPhrases: ['where is this used', 'who calls', 'find references', 'find usages', 'call sites'],
315
+ phrases: ['callsite', 'usages', 'usage', 'references', 'where used'],
316
+ tokens: ['used', 'usage', 'usages', 'reference', 'references', 'calls', 'called'],
317
+ negativeTokens: ['unused', 'dead', 'unreachable', 'orphaned'],
318
+ },
319
+ {
320
+ tool: 'detect_clones',
321
+ strongPhrases: ['duplicate code', 'find duplicates', 'copy paste code'],
322
+ phrases: ['clone', 'clones', 'copy-paste', 'repeated code', 'similar code'],
323
+ tokens: ['clone', 'duplicate', 'duplicates', 'copy', 'paste', 'similar'],
324
+ },
325
+ {
326
+ tool: 'detect_deadcode',
327
+ strongPhrases: ['dead code', 'unused code', 'never used', 'not used', 'unreachable code'],
328
+ phrases: ['unused import', 'unused variable', 'unused function', 'orphaned code', 'stale code'],
329
+ tokens: ['dead', 'unused', 'unreachable', 'orphaned', 'stale', 'ununsed'],
330
+ negativeTokens: ['where', 'who', 'calls', 'reference', 'references'],
331
+ },
332
+ ];
333
+ function normalizeIntentText(text) {
334
+ return text
335
+ .toLowerCase()
336
+ .replace(/[`"'()[\]{}:;,.!?]/g, ' ')
337
+ .replace(/[-_/]/g, ' ')
338
+ .replace(/\s+/g, ' ')
339
+ .trim();
340
+ }
341
+ function singularizeToken(token) {
342
+ if (token.endsWith('ies') && token.length > 3)
343
+ return `${token.slice(0, -3)}y`;
344
+ if (token.endsWith('ses') && token.length > 3)
345
+ return token.slice(0, -2);
346
+ if (token.endsWith('s') && token.length > 3)
347
+ return token.slice(0, -1);
348
+ return token;
349
+ }
350
+ function scoreIntent(normalized) {
351
+ const tokens = normalized.split(' ').filter(Boolean);
352
+ const tokenSet = new Set(tokens.flatMap((token) => [token, singularizeToken(token)]));
353
+ const hasWhereUsedPattern = /\bwhere\s+is\s+.+\s+used\b/.test(normalized) || /\bwhere\s+.+\s+used\b/.test(normalized);
354
+ const hasWhoCallsPattern = /\bwho\s+calls\b/.test(normalized);
355
+ const hasUnusedPattern = /\b(unused|ununsed|dead|unreachable|never used|not used|orphaned)\b/.test(normalized);
356
+ const scores = INTENT_DEFINITIONS.map((definition) => {
357
+ let score = 0;
358
+ for (const phrase of definition.strongPhrases) {
359
+ if (normalized.includes(phrase))
360
+ score += 3.0;
361
+ }
362
+ for (const phrase of definition.phrases) {
363
+ if (normalized.includes(phrase))
364
+ score += 1.5;
365
+ }
366
+ for (const token of definition.tokens) {
367
+ if (tokenSet.has(token))
368
+ score += 0.7;
369
+ }
370
+ for (const token of definition.negativeTokens || []) {
371
+ if (tokenSet.has(token))
372
+ score -= 0.8;
373
+ }
374
+ if (definition.tool === 'find_call_sites' && (hasWhereUsedPattern || hasWhoCallsPattern))
375
+ score += 2.2;
376
+ if (definition.tool === 'detect_deadcode' && hasUnusedPattern)
377
+ score += 2.2;
378
+ return { tool: definition.tool, score };
379
+ });
380
+ return scores.sort((a, b) => b.score - a.score);
381
+ }
140
382
  // 8. Request Router - Map Natural Language to a Tool
141
383
  server.registerTool('route_tng_request', {
142
384
  description: 'Route a natural-language request to the best TNG tool and list required arguments.',
@@ -145,19 +387,15 @@ server.registerTool('route_tng_request', {
145
387
  },
146
388
  }, async (args) => {
147
389
  const rawRequest = String(args?.request || '');
148
- const request = rawRequest.toLowerCase();
149
- const matchAny = (phrases) => phrases.some((p) => request.includes(p));
150
- const toolOrder = [
151
- { tool: 'generate_test', phrases: ['write test', 'write tests', 'generate test', 'generate tests', 'create specs', 'add test coverage', 'test coverage'] },
152
- { tool: 'impact_method', phrases: ['impact', 'blast radius', 'breaking change', 'breakage', 'what breaks', 'change impact'] },
153
- { tool: 'audit_method', phrases: ['audit', 'review', 'analyze', 'inspect', 'check', 'security', 'vulnerab', 'bug', 'issue', 'code smell'] },
154
- { tool: 'xray_method', phrases: ['diagram', 'flowchart', 'visual', 'mermaid', 'xray', 'x-ray', 'flow'] },
155
- { tool: 'trace_method', phrases: ['trace', 'step through', 'step-by-step', 'execution', 'debug flow'] },
156
- { tool: 'detect_clones', phrases: ['clone', 'duplicate', 'copy-paste', 'repeated code', 'similar code'] },
157
- { tool: 'detect_deadcode', phrases: ['dead code', 'unused', 'unreachable', 'unused import', 'unused variable'] },
158
- ];
159
- const matched = toolOrder.find((entry) => matchAny(entry.phrases));
160
- const tool = matched?.tool || null;
390
+ const normalizedRequest = normalizeIntentText(rawRequest);
391
+ const ranked = scoreIntent(normalizedRequest);
392
+ const top = ranked[0];
393
+ const second = ranked[1];
394
+ const ambiguous = !!top && !!second && top.score >= 1.5 && second.score >= 1.5 && (top.score - second.score) < 1.0;
395
+ const tool = top && top.score >= 1.5 ? top.tool : null;
396
+ const confidence = !tool ? 0.0 : ambiguous ? 0.55 : Math.min(0.97, 0.6 + top.score / 10);
397
+ const matchAny = (phrases) => phrases.some((p) => normalizedRequest.includes(p));
398
+ const wantsAllDeadCode = matchAny(PROJECT_WIDE_PHRASES);
161
399
  const backticks = [...rawRequest.matchAll(/`([^`]+)`/g)].map((m) => m[1]);
162
400
  let file_path = null;
163
401
  let method_name = null;
@@ -179,31 +417,47 @@ server.registerTool('route_tng_request', {
179
417
  }
180
418
  }
181
419
  const required = [];
182
- if (tool === 'audit_method' || tool === 'generate_test' || tool === 'xray_method' || tool === 'trace_method' || tool === 'impact_method') {
420
+ if (tool === 'audit_method' || tool === 'generate_test' || tool === 'xray_method' || tool === 'trace_method' || tool === 'impact_method' || tool === 'find_call_sites') {
183
421
  required.push('file_path', 'method_name');
184
422
  }
185
423
  if (tool === 'detect_clones') {
186
424
  required.push('file_path');
187
425
  }
188
426
  if (tool === 'detect_deadcode') {
189
- const wantsAll = matchAny(['project', 'entire project', 'whole project', 'project-wide', 'all files', 'scan all', 'repository']);
190
- if (!wantsAll) {
427
+ if (!wantsAllDeadCode) {
191
428
  required.push('file_path');
192
429
  }
193
430
  }
194
431
  const missing = required.filter((key) => (key === 'file_path' ? !file_path : !method_name));
432
+ const alternatives = ranked.filter((entry) => entry.score >= 1.5).slice(0, 3);
433
+ const reason = !tool
434
+ ? 'No strong tool intent match. Ask the user to clarify the task.'
435
+ : ambiguous
436
+ ? `Ambiguous between ${top.tool} and ${second.tool}; defaulting to ${top.tool}.`
437
+ : `Matched weighted intent patterns for ${tool}.`;
195
438
  const response = {
196
439
  tool,
197
- reason: tool ? `Matched keywords for ${tool}.` : 'No matching keywords found. Ask the user to clarify the intent.',
440
+ confidence,
441
+ reason,
442
+ alternatives,
198
443
  suggested_args: {
199
444
  file_path,
200
445
  method_name,
201
- all: tool === 'detect_deadcode' && matchAny(['project', 'entire project', 'whole project', 'project-wide', 'all files', 'scan all', 'repository']),
446
+ all: tool === 'detect_deadcode' && wantsAllDeadCode,
202
447
  note: tool === 'generate_test' || tool === 'audit_method'
203
448
  ? 'For JS/Python, test_type may be required (e.g., utility, react_component, express_handler).'
204
449
  : undefined,
205
450
  },
206
451
  missing_required_args: missing,
452
+ suggested_invocation: tool
453
+ ? {
454
+ tool,
455
+ args: {
456
+ ...(file_path ? { file_path } : {}),
457
+ ...(method_name ? { method_name } : {}),
458
+ },
459
+ }
460
+ : null,
207
461
  };
208
462
  return {
209
463
  content: [