callgraph-mcp 1.3.0 → 1.4.0

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,140 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerGetFlow = registerGetFlow;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("fs"));
39
+ const analysis_1 = require("../utils/analysis");
40
+ const toolHelper_1 = require("../utils/toolHelper");
41
+ function registerGetFlow(server) {
42
+ (0, toolHelper_1.registerTool)(server, 'flowmap_get_flow', 'Return the complete sub-graph reachable from a given function — every function it calls, every function those call, and so on recursively. Use this to understand the full execution path of a feature or entry point.', {
43
+ functionName: zod_1.z.string().describe('The starting function name'),
44
+ workspacePath: zod_1.z.string().describe('Absolute path to the repository root'),
45
+ maxDepth: zod_1.z.number().optional().describe('Maximum recursion depth. Default 10.'),
46
+ }, async ({ functionName, workspacePath, maxDepth: rawMaxDepth }) => {
47
+ const maxDepth = rawMaxDepth ?? 10;
48
+ try {
49
+ if (!fs.existsSync(workspacePath)) {
50
+ return {
51
+ content: [{
52
+ type: 'text',
53
+ text: JSON.stringify({
54
+ error: true,
55
+ code: 'WORKSPACE_NOT_FOUND',
56
+ message: `Directory does not exist: ${workspacePath}`,
57
+ workspacePath,
58
+ }),
59
+ }],
60
+ };
61
+ }
62
+ const graph = await (0, analysis_1.analyzeWorkspace)(workspacePath);
63
+ const targets = graph.nodes.filter(n => n.name === functionName);
64
+ if (targets.length === 0) {
65
+ return {
66
+ content: [{
67
+ type: 'text',
68
+ text: JSON.stringify({
69
+ error: true,
70
+ code: 'FUNCTION_NOT_FOUND',
71
+ message: `No function named "${functionName}" found in the codebase.`,
72
+ workspacePath,
73
+ }),
74
+ }],
75
+ };
76
+ }
77
+ const target = targets[0];
78
+ // Build adjacency map for BFS
79
+ const outgoing = new Map();
80
+ for (const edge of graph.edges) {
81
+ const list = outgoing.get(edge.from) || [];
82
+ const toNode = graph.nodes.find(n => n.id === edge.to);
83
+ if (toNode) {
84
+ list.push({ edge, node: toNode });
85
+ outgoing.set(edge.from, list);
86
+ }
87
+ }
88
+ // BFS from target, depth-limited
89
+ const visitedIds = new Set();
90
+ const reachableNodes = [];
91
+ const reachableEdges = [];
92
+ let currentDepth = 0;
93
+ let frontier = [target.id];
94
+ visitedIds.add(target.id);
95
+ reachableNodes.push(target);
96
+ while (frontier.length > 0 && currentDepth < maxDepth) {
97
+ const nextFrontier = [];
98
+ for (const nodeId of frontier) {
99
+ const neighbours = outgoing.get(nodeId) || [];
100
+ for (const { edge, node } of neighbours) {
101
+ reachableEdges.push(edge);
102
+ if (!visitedIds.has(node.id)) {
103
+ visitedIds.add(node.id);
104
+ reachableNodes.push(node);
105
+ nextFrontier.push(node.id);
106
+ }
107
+ }
108
+ }
109
+ frontier = nextFrontier;
110
+ currentDepth++;
111
+ }
112
+ return {
113
+ content: [{
114
+ type: 'text',
115
+ text: JSON.stringify({
116
+ entryFunction: functionName,
117
+ nodes: reachableNodes,
118
+ edges: reachableEdges,
119
+ depth: currentDepth,
120
+ totalFunctions: reachableNodes.length,
121
+ }),
122
+ }],
123
+ };
124
+ }
125
+ catch (err) {
126
+ const message = err instanceof Error ? err.message : String(err);
127
+ return {
128
+ content: [{
129
+ type: 'text',
130
+ text: JSON.stringify({
131
+ error: true,
132
+ code: 'PARSE_ERROR',
133
+ message,
134
+ workspacePath,
135
+ }),
136
+ }],
137
+ };
138
+ }
139
+ });
140
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerListEntryPoints = registerListEntryPoints;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("fs"));
39
+ const analysis_1 = require("../utils/analysis");
40
+ const toolHelper_1 = require("../utils/toolHelper");
41
+ function registerListEntryPoints(server) {
42
+ (0, toolHelper_1.registerTool)(server, 'flowmap_list_entry_points', 'Return all detected entry points in the codebase — main functions, HTTP route handlers, React root renders, CLI commands, etc. Always call this first when exploring a new codebase to understand where execution begins.', {
43
+ workspacePath: zod_1.z.string().describe('Absolute path to the repository root'),
44
+ }, async ({ workspacePath }) => {
45
+ try {
46
+ if (!fs.existsSync(workspacePath)) {
47
+ return {
48
+ content: [{
49
+ type: 'text',
50
+ text: JSON.stringify({
51
+ error: true,
52
+ code: 'WORKSPACE_NOT_FOUND',
53
+ message: `Directory does not exist: ${workspacePath}`,
54
+ workspacePath,
55
+ }),
56
+ }],
57
+ };
58
+ }
59
+ const graph = await (0, analysis_1.analyzeWorkspace)(workspacePath);
60
+ const entryNodes = graph.nodes.filter(n => n.isEntryPoint);
61
+ const entryPoints = entryNodes.map(n => ({
62
+ id: n.id,
63
+ name: n.name,
64
+ filePath: n.filePath,
65
+ startLine: n.startLine,
66
+ language: n.language,
67
+ isExported: n.isExported,
68
+ isAsync: n.isAsync,
69
+ }));
70
+ return {
71
+ content: [{
72
+ type: 'text',
73
+ text: JSON.stringify({
74
+ entryPoints,
75
+ count: entryPoints.length,
76
+ durationMs: graph.durationMs,
77
+ }),
78
+ }],
79
+ };
80
+ }
81
+ catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ return {
84
+ content: [{
85
+ type: 'text',
86
+ text: JSON.stringify({
87
+ error: true,
88
+ code: 'PARSE_ERROR',
89
+ message,
90
+ workspacePath,
91
+ }),
92
+ }],
93
+ };
94
+ }
95
+ });
96
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveWasmDir = resolveWasmDir;
37
+ exports.analyzeWorkspace = analyzeWorkspace;
38
+ const path = __importStar(require("path"));
39
+ const fs = __importStar(require("fs"));
40
+ const core_1 = require("@codeflow-map/core");
41
+ const cache_1 = require("./cache");
42
+ const fileDiscovery_1 = require("./fileDiscovery");
43
+ const BATCH_SIZE = 50;
44
+ let treeSitterInitialized = false;
45
+ function resolveWasmDir() {
46
+ // 1. Explicit env var
47
+ if (process.env.FLOWMAP_GRAMMARS) {
48
+ return process.env.FLOWMAP_GRAMMARS;
49
+ }
50
+ const candidates = [
51
+ // Bundled runtime: dist/index.js => <package-root>/grammars
52
+ path.resolve(__dirname, '..', 'grammars'),
53
+ // Dev/tsc layout: src/utils or dist/utils => <package-root>/grammars
54
+ path.resolve(__dirname, '..', '..', 'grammars'),
55
+ ];
56
+ for (const candidate of candidates) {
57
+ if (fs.existsSync(path.join(candidate, 'tree-sitter.wasm'))) {
58
+ return candidate;
59
+ }
60
+ }
61
+ // Last-resort fallback keeps previous behavior for unexpected layouts.
62
+ return candidates[0];
63
+ }
64
+ async function ensureTreeSitter() {
65
+ if (!treeSitterInitialized) {
66
+ const wasmDir = resolveWasmDir();
67
+ const wasmPath = path.join(wasmDir, 'tree-sitter.wasm');
68
+ const wasmExists = fs.existsSync(wasmPath);
69
+ console.error(`[flowmap] Grammar directory: ${wasmDir} (tree-sitter.wasm ${wasmExists ? 'found' : 'missing'})`);
70
+ await (0, core_1.initTreeSitter)(wasmDir);
71
+ treeSitterInitialized = true;
72
+ }
73
+ }
74
+ async function analyzeWorkspace(workspacePath, options = {}) {
75
+ // Check cache
76
+ const cached = (0, cache_1.getCached)(workspacePath);
77
+ if (cached)
78
+ return cached;
79
+ await ensureTreeSitter();
80
+ const wasmDir = resolveWasmDir();
81
+ const startTime = Date.now();
82
+ // Discover files
83
+ const files = await (0, fileDiscovery_1.discoverFiles)(workspacePath, options);
84
+ const allFunctions = [];
85
+ const allCalls = [];
86
+ let scannedFiles = 0;
87
+ // Batch-parse to avoid EMFILE
88
+ for (let i = 0; i < files.length; i += BATCH_SIZE) {
89
+ const batch = files.slice(i, i + BATCH_SIZE);
90
+ const results = await Promise.all(batch.map(f => (0, core_1.parseFile)(f.filePath, f.absPath, wasmDir, f.languageId).catch(() => null)));
91
+ for (const result of results) {
92
+ if (result) {
93
+ allFunctions.push(...result.functions);
94
+ allCalls.push(...result.calls);
95
+ scannedFiles++;
96
+ }
97
+ }
98
+ }
99
+ // Build graph
100
+ const edges = (0, core_1.buildCallGraph)(allFunctions, allCalls);
101
+ (0, core_1.detectEntryPoints)(allFunctions, edges);
102
+ const { flows, orphans } = (0, core_1.partitionFlows)(allFunctions, edges);
103
+ const graph = {
104
+ nodes: allFunctions,
105
+ edges,
106
+ flows,
107
+ orphans,
108
+ scannedFiles,
109
+ durationMs: Date.now() - startTime,
110
+ };
111
+ (0, cache_1.setCached)(workspacePath, graph);
112
+ return graph;
113
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCached = getCached;
4
+ exports.setCached = setCached;
5
+ exports.clearCache = clearCache;
6
+ const cache = new Map();
7
+ const TTL_MS = 30_000; // 30 seconds
8
+ function getCached(workspacePath) {
9
+ const entry = cache.get(workspacePath);
10
+ if (!entry)
11
+ return null;
12
+ if (Date.now() - entry.cachedAt > TTL_MS) {
13
+ cache.delete(workspacePath);
14
+ return null;
15
+ }
16
+ return entry.graph;
17
+ }
18
+ function setCached(workspacePath, graph) {
19
+ cache.set(workspacePath, { graph, cachedAt: Date.now(), workspacePath });
20
+ }
21
+ function clearCache(workspacePath) {
22
+ if (workspacePath) {
23
+ cache.delete(workspacePath);
24
+ }
25
+ else {
26
+ cache.clear();
27
+ }
28
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.discoverFiles = discoverFiles;
40
+ const path = __importStar(require("path"));
41
+ const fast_glob_1 = __importDefault(require("fast-glob"));
42
+ const core_1 = require("@codeflow-map/core");
43
+ const DISCOVERY_EXCLUDES = [
44
+ '**/node_modules/**', '**/venv/**', '**/.venv/**',
45
+ '**/__pycache__/**', '**/vendor/**', '**/target/**',
46
+ '**/.git/**', '**/dist/**', '**/build/**',
47
+ '**/.next/**', '**/.turbo/**', '**/coverage/**',
48
+ '**/.gradle/**', '**/.cache/**', '**/site-packages/**',
49
+ '**/.mypy_cache/**', '**/.pytest_cache/**',
50
+ '**/out/**', '**/bin/**', '**/obj/**', '**/tests/**', '**/__tests__/**',
51
+ '**/spec/**', '**/__specs__/**', '**/test/**',
52
+ ];
53
+ async function discoverFiles(workspacePath, options = {}) {
54
+ const { exclude = [], language } = options;
55
+ // Build extension list — either filtered to one language or all supported
56
+ let extensions;
57
+ if (language) {
58
+ const matching = Object.entries(core_1.FILE_EXTENSION_MAP)
59
+ .filter(([, lang]) => lang === language)
60
+ .map(([ext]) => ext.replace('.', ''));
61
+ extensions = matching.length > 0 ? matching : [];
62
+ }
63
+ else {
64
+ extensions = Object.keys(core_1.FILE_EXTENSION_MAP).map(e => e.replace('.', ''));
65
+ }
66
+ if (extensions.length === 0)
67
+ return [];
68
+ const pattern = extensions.length === 1
69
+ ? `**/*.${extensions[0]}`
70
+ : `**/*.{${extensions.join(',')}}`;
71
+ const allExcludes = [...DISCOVERY_EXCLUDES, ...exclude];
72
+ // fast-glob expects forward-slash paths
73
+ const cwd = workspacePath.replace(/\\/g, '/');
74
+ const relativePaths = await (0, fast_glob_1.default)(pattern, {
75
+ cwd,
76
+ ignore: allExcludes,
77
+ absolute: false,
78
+ dot: false,
79
+ onlyFiles: true,
80
+ });
81
+ const results = [];
82
+ for (const rel of relativePaths) {
83
+ const ext = path.extname(rel);
84
+ const lang = core_1.FILE_EXTENSION_MAP[ext];
85
+ if (!lang)
86
+ continue;
87
+ results.push({
88
+ filePath: rel.replace(/\\/g, '/'),
89
+ absPath: path.resolve(workspacePath, rel),
90
+ languageId: lang,
91
+ });
92
+ }
93
+ return results;
94
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatGraphSummary = formatGraphSummary;
4
+ /**
5
+ * Produce an LLM-friendly text summary of a Graph for use alongside JSON responses.
6
+ */
7
+ function formatGraphSummary(graph) {
8
+ const entryPoints = graph.nodes.filter(n => n.isEntryPoint);
9
+ const lines = [
10
+ `Scanned ${graph.scannedFiles} files in ${graph.durationMs}ms.`,
11
+ `Found ${graph.nodes.length} functions, ${graph.edges.length} call edges.`,
12
+ `${graph.flows.length} execution flows, ${graph.orphans.length} orphan functions.`,
13
+ ];
14
+ if (entryPoints.length > 0) {
15
+ lines.push('');
16
+ lines.push(`Entry points (${entryPoints.length}):`);
17
+ for (const ep of entryPoints.slice(0, 20)) {
18
+ lines.push(` - ${ep.name} (${ep.filePath}:${ep.startLine})`);
19
+ }
20
+ if (entryPoints.length > 20) {
21
+ lines.push(` ... and ${entryPoints.length - 20} more`);
22
+ }
23
+ }
24
+ if (graph.orphans.length > 0) {
25
+ lines.push('');
26
+ lines.push(`Orphan functions (${graph.orphans.length}):`);
27
+ const orphanNodes = graph.orphans
28
+ .map(id => graph.nodes.find(n => n.id === id))
29
+ .filter(Boolean);
30
+ for (const n of orphanNodes.slice(0, 10)) {
31
+ lines.push(` - ${n.name} (${n.filePath}:${n.startLine})`);
32
+ }
33
+ if (graph.orphans.length > 10) {
34
+ lines.push(` ... and ${graph.orphans.length - 10} more`);
35
+ }
36
+ }
37
+ return lines.join('\n');
38
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTool = registerTool;
4
+ /**
5
+ * Registers a tool on the McpServer with explicit type handling to avoid
6
+ * TS2589 "Type instantiation is excessively deep" errors that occur with
7
+ * the MCP SDK 1.27.x + Zod 3.25.x combination.
8
+ */
9
+ function registerTool(server, name, description, schema, handler) {
10
+ server.tool(name, description, schema, handler);
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "callgraph-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "MCP server for codebase call-flow analysis. Local, deterministic, language-agnostic. Powered by @codeflow-map/core.",
5
5
  "keywords": [
6
6
  "mcp",