@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.
- package/README.md +37 -7
- package/dist/index.js +307 -53
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +8 -0
- package/dist/init.js.map +1 -1
- package/dist/tools/audit.d.ts +5 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +29 -29
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/call_sites.d.ts +20 -0
- package/dist/tools/call_sites.d.ts.map +1 -0
- package/dist/tools/call_sites.js +108 -0
- package/dist/tools/call_sites.js.map +1 -0
- package/dist/tools/clones.d.ts +4 -1
- package/dist/tools/clones.d.ts.map +1 -1
- package/dist/tools/clones.js +25 -28
- package/dist/tools/clones.js.map +1 -1
- package/dist/tools/deadcode.d.ts +4 -1
- package/dist/tools/deadcode.d.ts.map +1 -1
- package/dist/tools/deadcode.js +33 -28
- package/dist/tools/deadcode.js.map +1 -1
- package/dist/tools/generate.d.ts +5 -1
- package/dist/tools/generate.d.ts.map +1 -1
- package/dist/tools/generate.js +28 -28
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/impact.d.ts +4 -1
- package/dist/tools/impact.d.ts.map +1 -1
- package/dist/tools/impact.js +26 -30
- package/dist/tools/impact.js.map +1 -1
- package/dist/tools/trace.d.ts +5 -1
- package/dist/tools/trace.d.ts.map +1 -1
- package/dist/tools/trace.js +29 -29
- package/dist/tools/trace.js.map +1 -1
- package/dist/tools/utils.d.ts +38 -1
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +320 -17
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/xray.d.ts +4 -1
- package/dist/tools/xray.d.ts.map +1 -1
- package/dist/tools/xray.js +26 -26
- package/dist/tools/xray.js.map +1 -1
- 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
|
-
- *"
|
|
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
|
-
- **
|
|
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
|
|
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. `
|
|
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
|
-
###
|
|
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` (
|
|
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.
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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
|
|
88
|
-
server.registerTool('analyze_method', { description: 'Alias of audit_method. Use for "analyze this method".', inputSchema: auditSchema }, async (args) => await
|
|
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
|
|
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
|
|
93
|
-
server.registerTool('write_tests', { description: 'Alias of generate_test. Use for "write tests".', inputSchema: generateSchema }, async (args) => await
|
|
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
|
|
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
|
|
98
|
-
server.registerTool('visualize_flow', { description: 'Alias of xray_method. Use for "visualize flow".', inputSchema: methodSchema }, async (args) => await
|
|
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
|
|
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
|
|
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
|
|
105
|
-
server.registerTool('impact_audit', { description: 'Alias of impact_method. Use for "
|
|
106
|
-
server.registerTool('
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
117
|
-
// 7.
|
|
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:
|
|
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
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
+
confidence,
|
|
441
|
+
reason,
|
|
442
|
+
alternatives,
|
|
198
443
|
suggested_args: {
|
|
199
444
|
file_path,
|
|
200
445
|
method_name,
|
|
201
|
-
all: tool === 'detect_deadcode' &&
|
|
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: [
|