@supermodeltools/mcp-server 0.7.0 → 0.7.2

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.
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * Tool for agents to submit bug reports as GitHub issues.
4
+ * Creates issues on the supermodeltools/mcp repository.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.handler = exports.tool = exports.metadata = void 0;
8
+ exports.formatBugReportBody = formatBugReportBody;
9
+ const types_1 = require("../types");
10
+ const github_1 = require("../utils/github");
11
+ exports.metadata = {
12
+ resource: 'issues',
13
+ operation: 'write',
14
+ tags: ['bug-report', 'github'],
15
+ httpMethod: 'post',
16
+ httpPath: '/repos/supermodeltools/mcp/issues',
17
+ operationId: 'createBugReport',
18
+ };
19
+ exports.tool = {
20
+ name: 'report_bug',
21
+ description: `Submit a bug report to the Supermodel MCP server GitHub repository (${github_1.GITHUB_REPO}).
22
+
23
+ Creates a GitHub issue with structured fields (steps to reproduce, expected vs actual behavior) formatted into a clear bug report. The Supermodel team actively monitors and responds to all submitted issues.
24
+
25
+ Use this tool whenever you encounter a bug, error, or unexpected behavior with any Supermodel MCP tool or with the underlying Supermodel API. Examples include:
26
+ - A tool returned an error that seems incorrect or unexpected
27
+ - A tool produced results that don't match its description or documentation
28
+ - You encountered a crash, hang, timeout, or other failure that appears to be a server-side issue
29
+ - The API returned malformed, incomplete, or nonsensical data
30
+ - An edge case or specific input caused a tool to break
31
+ - You received an error with "reportable: true" in the structured error response
32
+
33
+ Providing steps_to_reproduce, expected_behavior, and actual_behavior helps the team fix bugs faster, but only title and description are required.
34
+
35
+ This tool requires a GITHUB_TOKEN environment variable. To set it up:
36
+ 1. Create a GitHub personal access token at https://github.com/settings/tokens with the "public_repo" scope
37
+ 2. Set it in your environment: export GITHUB_TOKEN=ghp_your_token_here
38
+ 3. Restart the MCP server`,
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ title: {
43
+ type: 'string',
44
+ description: 'Short, descriptive title for the bug (e.g. "get_call_graph fails on monorepo with symlinks").',
45
+ },
46
+ description: {
47
+ type: 'string',
48
+ description: 'What happened? Describe the bug clearly.',
49
+ },
50
+ steps_to_reproduce: {
51
+ type: 'string',
52
+ description: 'Step-by-step instructions to reproduce the bug.',
53
+ },
54
+ expected_behavior: {
55
+ type: 'string',
56
+ description: 'What you expected to happen.',
57
+ },
58
+ actual_behavior: {
59
+ type: 'string',
60
+ description: 'What actually happened instead.',
61
+ },
62
+ labels: {
63
+ type: 'array',
64
+ items: { type: 'string' },
65
+ description: 'Optional labels to categorize the issue (e.g. ["bug", "crash"]).',
66
+ },
67
+ },
68
+ required: ['title', 'description'],
69
+ },
70
+ };
71
+ /**
72
+ * Format structured bug report fields into a markdown issue body.
73
+ */
74
+ function formatBugReportBody(fields) {
75
+ const sections = [];
76
+ sections.push('## Description\n\n' + fields.description);
77
+ if (fields.steps_to_reproduce) {
78
+ sections.push('## Steps to Reproduce\n\n' + fields.steps_to_reproduce);
79
+ }
80
+ if (fields.expected_behavior) {
81
+ sections.push('## Expected Behavior\n\n' + fields.expected_behavior);
82
+ }
83
+ if (fields.actual_behavior) {
84
+ sections.push('## Actual Behavior\n\n' + fields.actual_behavior);
85
+ }
86
+ return sections.join('\n\n');
87
+ }
88
+ const handler = async (_client, args) => {
89
+ const tokenError = (0, github_1.validateGitHubToken)();
90
+ if (tokenError)
91
+ return (0, types_1.asErrorResult)(tokenError);
92
+ if (!args) {
93
+ return (0, types_1.asErrorResult)({
94
+ type: 'validation_error',
95
+ message: 'Missing required parameters: title and description.',
96
+ code: 'MISSING_PARAMETERS',
97
+ recoverable: false,
98
+ suggestion: 'Provide both "title" and "description" parameters.',
99
+ });
100
+ }
101
+ const { title, description, steps_to_reproduce, expected_behavior, actual_behavior, labels, } = args;
102
+ const titleError = (0, github_1.validateRequiredString)(title, 'title', 'INVALID_TITLE', 'Provide a short, descriptive title for the bug.');
103
+ if (titleError)
104
+ return (0, types_1.asErrorResult)(titleError);
105
+ const descError = (0, github_1.validateRequiredString)(description, 'description', 'INVALID_DESCRIPTION', 'Describe the bug clearly.');
106
+ if (descError)
107
+ return (0, types_1.asErrorResult)(descError);
108
+ const stepsError = (0, github_1.validateOptionalString)(steps_to_reproduce, 'steps_to_reproduce', 'INVALID_STEPS', 'Provide steps to reproduce as a string.');
109
+ if (stepsError)
110
+ return (0, types_1.asErrorResult)(stepsError);
111
+ const expectedError = (0, github_1.validateOptionalString)(expected_behavior, 'expected_behavior', 'INVALID_EXPECTED_BEHAVIOR', 'Describe expected behavior as a string.');
112
+ if (expectedError)
113
+ return (0, types_1.asErrorResult)(expectedError);
114
+ const actualError = (0, github_1.validateOptionalString)(actual_behavior, 'actual_behavior', 'INVALID_ACTUAL_BEHAVIOR', 'Describe actual behavior as a string.');
115
+ if (actualError)
116
+ return (0, types_1.asErrorResult)(actualError);
117
+ const labelsError = (0, github_1.validateLabels)(labels);
118
+ if (labelsError)
119
+ return (0, types_1.asErrorResult)(labelsError);
120
+ const body = formatBugReportBody({
121
+ description: description,
122
+ steps_to_reproduce: steps_to_reproduce,
123
+ expected_behavior: expected_behavior,
124
+ actual_behavior: actual_behavior,
125
+ });
126
+ return (0, github_1.createGitHubIssue)('report_bug', {
127
+ title: title,
128
+ body,
129
+ labels: labels,
130
+ }, 'Bug report created successfully.');
131
+ };
132
+ exports.handler = handler;
133
+ exports.default = { metadata: exports.metadata, tool: exports.tool, handler: exports.handler };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Task-specific query tools for focused codebase queries.
4
+ * These tools provide fast, targeted answers to specific code navigation questions.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.taskQueryTools = exports.traceDataFlowToolDef = exports.findDefinitionToolDef = exports.traceCallChainToolDef = exports.findCallSitesToolDef = void 0;
8
+ const types_1 = require("../types");
9
+ const find_call_sites_1 = require("./find-call-sites");
10
+ const trace_call_chain_1 = require("./trace-call-chain");
11
+ const find_definition_1 = require("./find-definition");
12
+ const trace_data_flow_1 = require("./trace-data-flow");
13
+ /**
14
+ * Create a handler wrapper that calls the tool function and formats the result
15
+ */
16
+ function createTaskQueryHandler(toolFunction, toolName) {
17
+ return async (client, args, defaultWorkdir) => {
18
+ if (!args) {
19
+ args = {};
20
+ }
21
+ // Inject default workdir if directory not provided
22
+ if (!args.path && defaultWorkdir) {
23
+ args.path = defaultWorkdir;
24
+ }
25
+ try {
26
+ // Call the tool function
27
+ const result = await toolFunction(args);
28
+ // Return formatted JSON response
29
+ return (0, types_1.asTextContentResult)(JSON.stringify(result, null, 2));
30
+ }
31
+ catch (error) {
32
+ const message = typeof error?.message === 'string' ? error.message : String(error);
33
+ // Handle common error cases
34
+ if (message.includes('Graph not cached')) {
35
+ return (0, types_1.asErrorResult)({
36
+ type: 'validation_error',
37
+ message: 'Graph not cached. Run explore_codebase or get_call_graph first to analyze the repository.',
38
+ code: 'GRAPH_NOT_CACHED',
39
+ recoverable: true,
40
+ suggestion: 'Call explore_codebase or one of the get_*_graph tools first to analyze and cache the repository graph.',
41
+ });
42
+ }
43
+ return (0, types_1.asErrorResult)({
44
+ type: 'internal_error',
45
+ message: `${toolName} failed: ${message}`,
46
+ code: 'TOOL_EXECUTION_FAILED',
47
+ recoverable: false,
48
+ reportable: true,
49
+ });
50
+ }
51
+ };
52
+ }
53
+ /**
54
+ * Create tool metadata for each task-specific query tool
55
+ */
56
+ function createTaskQueryTool(config) {
57
+ const metadata = {
58
+ resource: 'queries',
59
+ operation: 'read',
60
+ tags: ['task-specific', 'query'],
61
+ };
62
+ const tool = {
63
+ name: config.name,
64
+ description: config.description,
65
+ inputSchema: config.inputSchema,
66
+ };
67
+ const handler = createTaskQueryHandler(config.handler, config.name);
68
+ return { metadata, tool, handler };
69
+ }
70
+ // Create individual tool definitions
71
+ exports.findCallSitesToolDef = createTaskQueryTool(find_call_sites_1.findCallSitesTool);
72
+ exports.traceCallChainToolDef = createTaskQueryTool(trace_call_chain_1.traceCallChainTool);
73
+ exports.findDefinitionToolDef = createTaskQueryTool(find_definition_1.findDefinitionTool);
74
+ exports.traceDataFlowToolDef = createTaskQueryTool(trace_data_flow_1.traceDataFlowTool);
75
+ // Export all task query tools as an array for easy registration
76
+ exports.taskQueryTools = [
77
+ exports.findCallSitesToolDef,
78
+ exports.traceCallChainToolDef,
79
+ exports.findDefinitionToolDef,
80
+ exports.traceDataFlowToolDef,
81
+ ];
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ /**
3
+ * Task-specific tool: Trace call chain between two functions
4
+ * Find shortest path showing how control flows from one function to another
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.traceCallChainTool = void 0;
8
+ exports.traceCallChain = traceCallChain;
9
+ const zod_1 = require("zod");
10
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
11
+ const cache_1 = require("../cache");
12
+ const TraceCallChainArgsSchema = zod_1.z.object({
13
+ path: zod_1.z.string().describe('Repository path'),
14
+ from_function: zod_1.z.string().describe('Starting function name'),
15
+ to_function: zod_1.z.string().describe('Target function name'),
16
+ max_depth: zod_1.z.number().optional().describe('Maximum call chain depth to search'),
17
+ });
18
+ /**
19
+ * Trace call chain from one function to another using BFS
20
+ */
21
+ async function traceCallChain(args) {
22
+ const { path, from_function, to_function, max_depth = 10 } = args;
23
+ // Get cached graph
24
+ const cacheKey = getCacheKey(path);
25
+ const graph = cache_1.graphCache.get(cacheKey);
26
+ if (!graph) {
27
+ throw new Error('Graph not cached. Run explore_codebase first to analyze the repository.');
28
+ }
29
+ // Find source function
30
+ const fromNodes = findFunctionsByName(graph, from_function);
31
+ if (fromNodes.length === 0) {
32
+ return {
33
+ from_function,
34
+ to_function,
35
+ path_exists: false,
36
+ call_chain: [],
37
+ summary: `Source function "${from_function}" not found in codebase.`,
38
+ };
39
+ }
40
+ // Find target function
41
+ const toNodes = findFunctionsByName(graph, to_function);
42
+ if (toNodes.length === 0) {
43
+ return {
44
+ from_function,
45
+ to_function,
46
+ path_exists: false,
47
+ call_chain: [],
48
+ summary: `Target function "${to_function}" not found in codebase.`,
49
+ };
50
+ }
51
+ // Use first match if multiple functions with same name
52
+ const fromNode = fromNodes[0];
53
+ const toNode = toNodes[0];
54
+ // BFS to find shortest path
55
+ const path_result = findShortestCallPath(graph, fromNode.id, toNode.id, max_depth);
56
+ if (!path_result) {
57
+ return {
58
+ from_function,
59
+ to_function,
60
+ path_exists: false,
61
+ call_chain: [],
62
+ summary: `No call chain found from "${from_function}" to "${to_function}" within depth ${max_depth}.`,
63
+ };
64
+ }
65
+ // Build call chain steps
66
+ const callChain = [];
67
+ for (let i = 0; i < path_result.length; i++) {
68
+ const nodeId = path_result[i];
69
+ const node = graph.nodeById.get(nodeId);
70
+ if (!node)
71
+ continue;
72
+ const step = {
73
+ function_name: node.properties?.name || 'unknown',
74
+ file: node.properties?.filePath || 'unknown',
75
+ line: node.properties?.startLine || 0,
76
+ };
77
+ // Add call site info if there's a next function
78
+ if (i < path_result.length - 1) {
79
+ const nextNodeId = path_result[i + 1];
80
+ const edge = findCallEdge(graph, nodeId, nextNodeId);
81
+ if (edge) {
82
+ step.call_to_next = {
83
+ line: edge.properties?.lineNumber || 0,
84
+ column: edge.properties?.columnNumber,
85
+ };
86
+ }
87
+ }
88
+ callChain.push(step);
89
+ }
90
+ // Generate summary
91
+ const summary = generateSummary(from_function, to_function, callChain);
92
+ return {
93
+ from_function,
94
+ to_function,
95
+ path_exists: true,
96
+ call_chain: callChain,
97
+ summary,
98
+ chain_length: callChain.length,
99
+ };
100
+ }
101
+ /**
102
+ * BFS to find shortest path between two functions
103
+ */
104
+ function findShortestCallPath(graph, fromId, toId, maxDepth) {
105
+ if (fromId === toId) {
106
+ return [fromId];
107
+ }
108
+ const queue = [{ id: fromId, path: [fromId] }];
109
+ const visited = new Set([fromId]);
110
+ while (queue.length > 0) {
111
+ const { id, path } = queue.shift();
112
+ // Check depth limit
113
+ if (path.length > maxDepth) {
114
+ continue;
115
+ }
116
+ // Get callees
117
+ const adj = graph.callAdj.get(id);
118
+ if (!adj)
119
+ continue;
120
+ for (const calleeId of adj.out) {
121
+ if (calleeId === toId) {
122
+ // Found target
123
+ return [...path, calleeId];
124
+ }
125
+ if (!visited.has(calleeId)) {
126
+ visited.add(calleeId);
127
+ queue.push({ id: calleeId, path: [...path, calleeId] });
128
+ }
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Helper: Find function nodes by name
135
+ */
136
+ function findFunctionsByName(graph, name) {
137
+ const lowerName = name.toLowerCase();
138
+ const nodeIds = graph.nameIndex.get(lowerName) || [];
139
+ return nodeIds
140
+ .map((id) => graph.nodeById.get(id))
141
+ .filter((node) => node && node.labels?.[0] === 'Function');
142
+ }
143
+ /**
144
+ * Helper: Find specific call edge between two functions
145
+ */
146
+ function findCallEdge(graph, fromId, toId) {
147
+ const relationships = graph.raw?.graph?.relationships || [];
148
+ return relationships.find((rel) => rel.type === 'calls' && rel.startNode === fromId && rel.endNode === toId);
149
+ }
150
+ /**
151
+ * Helper: Generate natural language summary
152
+ */
153
+ function generateSummary(fromName, toName, chain) {
154
+ if (chain.length === 0) {
155
+ return `No call chain found from "${fromName}" to "${toName}".`;
156
+ }
157
+ if (chain.length === 2) {
158
+ return `"${fromName}" directly calls "${toName}".`;
159
+ }
160
+ const pathNames = chain.map(step => step.function_name);
161
+ const arrow = ' → ';
162
+ const pathStr = pathNames.join(arrow);
163
+ return `Call chain (${chain.length} steps): ${pathStr}`;
164
+ }
165
+ /**
166
+ * Helper: Generate cache key
167
+ */
168
+ function getCacheKey(path) {
169
+ return `cache_${path}`;
170
+ }
171
+ /**
172
+ * Tool metadata for MCP registration
173
+ */
174
+ exports.traceCallChainTool = {
175
+ name: 'trace_call_chain',
176
+ description: 'Trace the call chain from one function to another, showing the shortest path of function calls',
177
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(TraceCallChainArgsSchema),
178
+ handler: traceCallChain,
179
+ };
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ /**
3
+ * Task-specific tool: Trace data flow for a variable/parameter
4
+ * Follow how data flows through function parameters and variables
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.traceDataFlowTool = void 0;
8
+ exports.traceDataFlow = traceDataFlow;
9
+ const zod_1 = require("zod");
10
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
11
+ const cache_1 = require("../cache");
12
+ const TraceDataFlowArgsSchema = zod_1.z.object({
13
+ path: zod_1.z.string().describe('Repository path'),
14
+ variable: zod_1.z.string().describe('Variable or parameter name to trace'),
15
+ function_name: zod_1.z.string().optional().describe('Function context (optional, helps narrow scope)'),
16
+ max_depth: zod_1.z.number().optional().describe('Maximum depth to trace'),
17
+ });
18
+ /**
19
+ * Trace data flow for a variable/parameter
20
+ */
21
+ async function traceDataFlow(args) {
22
+ const { path, variable, function_name, max_depth = 5 } = args;
23
+ // Get cached graph
24
+ const cacheKey = getCacheKey(path);
25
+ const graph = cache_1.graphCache.get(cacheKey);
26
+ if (!graph) {
27
+ throw new Error('Graph not cached. Run explore_codebase first to analyze the repository.');
28
+ }
29
+ // Find variable/parameter nodes
30
+ const variableNodes = findVariablesByName(graph, variable, function_name);
31
+ if (variableNodes.length === 0) {
32
+ return {
33
+ variable,
34
+ function_context: function_name,
35
+ found: false,
36
+ flow_steps: [],
37
+ summary: function_name
38
+ ? `Variable "${variable}" not found in function "${function_name}".`
39
+ : `Variable "${variable}" not found in codebase.`,
40
+ };
41
+ }
42
+ // Build flow steps from the variable node
43
+ const flowSteps = [];
44
+ const visited = new Set();
45
+ // Start with the first matching variable
46
+ const startNode = variableNodes[0];
47
+ traceFromNode(graph, startNode, flowSteps, visited, 0, max_depth);
48
+ // If no flow found, at least report the definition
49
+ if (flowSteps.length === 0) {
50
+ const props = startNode.properties || {};
51
+ flowSteps.push({
52
+ step_type: 'definition',
53
+ location: {
54
+ function: props.scope || 'unknown',
55
+ file: props.filePath || 'unknown',
56
+ line: props.startLine || 0,
57
+ },
58
+ description: `Variable "${variable}" is defined here`,
59
+ variable_name: variable,
60
+ });
61
+ }
62
+ // Generate summary
63
+ const summary = generateSummary(variable, function_name, flowSteps);
64
+ return {
65
+ variable,
66
+ function_context: function_name,
67
+ found: true,
68
+ flow_steps: flowSteps,
69
+ summary,
70
+ };
71
+ }
72
+ /**
73
+ * Recursively trace data flow from a node
74
+ */
75
+ function traceFromNode(graph, node, steps, visited, depth, maxDepth) {
76
+ if (depth >= maxDepth || visited.has(node.id)) {
77
+ return;
78
+ }
79
+ visited.add(node.id);
80
+ const props = node.properties || {};
81
+ // Add definition step
82
+ if (depth === 0) {
83
+ steps.push({
84
+ step_type: 'definition',
85
+ location: {
86
+ function: props.scope || 'unknown',
87
+ file: props.filePath || 'unknown',
88
+ line: props.startLine || 0,
89
+ },
90
+ description: `Variable "${props.name}" is defined`,
91
+ variable_name: props.name,
92
+ });
93
+ }
94
+ // Find relationships from this node
95
+ const relationships = graph.raw?.graph?.relationships || [];
96
+ for (const rel of relationships) {
97
+ if (rel.startNode !== node.id)
98
+ continue;
99
+ const targetNode = graph.nodeById.get(rel.endNode);
100
+ if (!targetNode)
101
+ continue;
102
+ const targetProps = targetNode.properties || {};
103
+ // Handle different relationship types
104
+ if (rel.type === 'USES' || rel.type === 'reads') {
105
+ steps.push({
106
+ step_type: 'usage',
107
+ location: {
108
+ function: targetProps.name || 'unknown',
109
+ file: targetProps.filePath || props.filePath || 'unknown',
110
+ line: targetProps.startLine || 0,
111
+ },
112
+ description: `Used in ${targetProps.name || 'expression'}`,
113
+ variable_name: props.name,
114
+ });
115
+ }
116
+ else if (rel.type === 'ASSIGNS' || rel.type === 'writes') {
117
+ steps.push({
118
+ step_type: 'assignment',
119
+ location: {
120
+ function: props.scope || 'unknown',
121
+ file: props.filePath || 'unknown',
122
+ line: rel.properties?.lineNumber || 0,
123
+ },
124
+ description: `Assigned value`,
125
+ variable_name: props.name,
126
+ });
127
+ }
128
+ else if (rel.type === 'PASSED_TO' || rel.type === 'argument_to') {
129
+ steps.push({
130
+ step_type: 'passed_to',
131
+ location: {
132
+ function: targetProps.name || 'unknown',
133
+ file: targetProps.filePath || 'unknown',
134
+ line: targetProps.startLine || 0,
135
+ },
136
+ description: `Passed as argument to ${targetProps.name}`,
137
+ variable_name: props.name,
138
+ });
139
+ // Continue tracing in the called function
140
+ traceFromNode(graph, targetNode, steps, visited, depth + 1, maxDepth);
141
+ }
142
+ else if (rel.type === 'RETURNS') {
143
+ steps.push({
144
+ step_type: 'returned_from',
145
+ location: {
146
+ function: props.scope || 'unknown',
147
+ file: props.filePath || 'unknown',
148
+ line: rel.properties?.lineNumber || 0,
149
+ },
150
+ description: `Returned from function`,
151
+ variable_name: props.name,
152
+ });
153
+ }
154
+ else if (rel.type === 'transforms_to' || rel.type === 'TRANSFORMS') {
155
+ steps.push({
156
+ step_type: 'transformation',
157
+ location: {
158
+ function: props.scope || 'unknown',
159
+ file: props.filePath || 'unknown',
160
+ line: rel.properties?.lineNumber || 0,
161
+ },
162
+ description: `Transformed to ${targetProps.name}`,
163
+ variable_name: props.name,
164
+ transformed_to: targetProps.name,
165
+ });
166
+ // Continue tracing the transformed variable
167
+ traceFromNode(graph, targetNode, steps, visited, depth + 1, maxDepth);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Find variable/parameter nodes by name
173
+ */
174
+ function findVariablesByName(graph, name, functionContext) {
175
+ const lowerName = name.toLowerCase();
176
+ const nodeIds = graph.nameIndex.get(lowerName) || [];
177
+ let nodes = nodeIds
178
+ .map((id) => graph.nodeById.get(id))
179
+ .filter((node) => {
180
+ if (!node)
181
+ return false;
182
+ const label = node.labels?.[0] || '';
183
+ return ['Variable', 'Parameter', 'Field', 'Constant'].includes(label);
184
+ });
185
+ // Filter by function context if provided
186
+ if (functionContext) {
187
+ const lowerContext = functionContext.toLowerCase();
188
+ nodes = nodes.filter((node) => {
189
+ const scope = (node.properties?.scope || '').toLowerCase();
190
+ return scope.includes(lowerContext);
191
+ });
192
+ }
193
+ return nodes;
194
+ }
195
+ /**
196
+ * Generate natural language summary
197
+ */
198
+ function generateSummary(variable, functionContext, steps) {
199
+ if (steps.length === 0) {
200
+ return `No data flow found for "${variable}".`;
201
+ }
202
+ if (steps.length === 1) {
203
+ return `Variable "${variable}" is defined but not used in tracked data flows.`;
204
+ }
205
+ // Count step types
206
+ const usages = steps.filter(s => s.step_type === 'usage').length;
207
+ const passedTo = steps.filter(s => s.step_type === 'passed_to').length;
208
+ const transforms = steps.filter(s => s.step_type === 'transformation').length;
209
+ const parts = [];
210
+ if (usages > 0)
211
+ parts.push(`${usages} usage(s)`);
212
+ if (passedTo > 0)
213
+ parts.push(`passed to ${passedTo} function(s)`);
214
+ if (transforms > 0)
215
+ parts.push(`${transforms} transformation(s)`);
216
+ const context = functionContext ? ` in "${functionContext}"` : '';
217
+ return `Data flow for "${variable}"${context}: ${parts.join(', ')} (${steps.length} total steps)`;
218
+ }
219
+ /**
220
+ * Helper: Generate cache key
221
+ */
222
+ function getCacheKey(path) {
223
+ return `cache_${path}`;
224
+ }
225
+ /**
226
+ * Tool metadata for MCP registration
227
+ */
228
+ exports.traceDataFlowTool = {
229
+ name: 'trace_data_flow',
230
+ description: 'Trace how data flows through a variable or parameter, showing usage, transformations, and passing between functions',
231
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(TraceDataFlowArgsSchema),
232
+ handler: traceDataFlow,
233
+ };