callgraph-mcp 1.0.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.
package/README.md ADDED
@@ -0,0 +1,383 @@
1
+ # callgraph-mcp
2
+
3
+ **CallGraph MCP Server** — exposes deterministic call-graph analysis for any codebase as [Model Context Protocol](https://modelcontextprotocol.io/) tools. Give your AI agent a precise, structural map of your code — no hallucination, no guessing.
4
+
5
+ Fully local. No cloud. No LLM. No telemetry.
6
+
7
+ Powered by [`@codeflow-map/core`](https://www.npmjs.com/package/@codeflow-map/core) and Tree-sitter WASM parsers.
8
+
9
+ **Supports:** TypeScript · JavaScript · TSX · JSX · Python · Go
10
+
11
+ ---
12
+
13
+ ## Quick Start (VS Code Copilot)
14
+
15
+ Add this to `.vscode/mcp.json`:
16
+
17
+ ```json
18
+ {
19
+ "servers": {
20
+ "flowmap": {
21
+ "type": "stdio",
22
+ "command": "npx",
23
+ "args": ["-y", "callgraph-mcp"]
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ VS Code starts and stops the server automatically.
30
+
31
+ ---
32
+
33
+ ## Setup
34
+
35
+ ### Option 1 — VS Code Copilot via `npx` (no install required)
36
+
37
+ Add to your project's `.vscode/mcp.json`:
38
+
39
+ ```json
40
+ {
41
+ "servers": {
42
+ "flowmap": {
43
+ "type": "stdio",
44
+ "command": "npx",
45
+ "args": ["-y", "callgraph-mcp"],
46
+ "env": {
47
+ "FLOWMAP_TRANSPORT": "stdio"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ VS Code starts and stops the server automatically. WASM grammars are bundled — no environment variables needed.
55
+
56
+ > **Tip:** Create `.vscode/mcp.json` via the Command Palette → **MCP: Add Server** → **stdio**.
57
+
58
+ ### Option 2 — Global install
59
+
60
+ ```bash
61
+ npm install -g callgraph-mcp
62
+ ```
63
+
64
+ ```json
65
+ {
66
+ "servers": {
67
+ "flowmap": {
68
+ "type": "stdio",
69
+ "command": "callgraph-mcp",
70
+ "env": {
71
+ "FLOWMAP_TRANSPORT": "stdio"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### Option 3 — Claude Desktop
79
+
80
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "flowmap": {
86
+ "command": "npx",
87
+ "args": ["-y", "callgraph-mcp"],
88
+ "env": {
89
+ "FLOWMAP_TRANSPORT": "stdio"
90
+ }
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Option 4 — Cursor
97
+
98
+ Add to your project's `.cursor/mcp.json`:
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "flowmap": {
104
+ "command": "npx",
105
+ "args": ["-y", "callgraph-mcp"],
106
+ "env": {
107
+ "FLOWMAP_TRANSPORT": "stdio"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Option 5 — Cline
115
+
116
+ Add to your Cline MCP settings (commonly `cline_mcp_settings.json`):
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "flowmap": {
122
+ "command": "npx",
123
+ "args": ["-y", "callgraph-mcp"],
124
+ "env": {
125
+ "FLOWMAP_TRANSPORT": "stdio"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Option 6 — HTTP-SSE (shared or remote server)
133
+
134
+ Use `FLOWMAP_TRANSPORT=http` for HTTP-SSE compatible clients.
135
+
136
+ Start the server:
137
+
138
+ ```bash
139
+ FLOWMAP_TRANSPORT=http FLOWMAP_PORT=3100 npx callgraph-mcp
140
+ # Windows PowerShell:
141
+ # $env:FLOWMAP_TRANSPORT="http"; $env:FLOWMAP_PORT="3100"; npx callgraph-mcp
142
+ ```
143
+
144
+ Then point your client at it:
145
+
146
+ ```json
147
+ {
148
+ "servers": {
149
+ "flowmap": {
150
+ "type": "http",
151
+ "url": "http://localhost:3100/mcp"
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Configure Environment Variables
160
+
161
+ Use one of the following approaches depending on your client.
162
+
163
+ ### In VS Code `.vscode/mcp.json`
164
+
165
+ ```json
166
+ {
167
+ "servers": {
168
+ "flowmap": {
169
+ "type": "stdio",
170
+ "command": "npx",
171
+ "args": ["-y", "callgraph-mcp"],
172
+ "env": {
173
+ "FLOWMAP_TRANSPORT": "http",
174
+ "FLOWMAP_PORT": "3100",
175
+ "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### In Claude Desktop config
183
+
184
+ ```json
185
+ {
186
+ "mcpServers": {
187
+ "flowmap": {
188
+ "command": "npx",
189
+ "args": ["-y", "callgraph-mcp"],
190
+ "env": {
191
+ "FLOWMAP_TRANSPORT": "stdio",
192
+ "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### In Cursor `.cursor/mcp.json`
200
+
201
+ ```json
202
+ {
203
+ "mcpServers": {
204
+ "flowmap": {
205
+ "command": "npx",
206
+ "args": ["-y", "callgraph-mcp"],
207
+ "env": {
208
+ "FLOWMAP_TRANSPORT": "stdio",
209
+ "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
210
+ }
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### In Cline MCP settings
217
+
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "flowmap": {
222
+ "command": "npx",
223
+ "args": ["-y", "callgraph-mcp"],
224
+ "env": {
225
+ "FLOWMAP_TRANSPORT": "stdio",
226
+ "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### In your shell for one-off runs
234
+
235
+ macOS / Linux:
236
+
237
+ ```bash
238
+ FLOWMAP_TRANSPORT=http FLOWMAP_PORT=3100 npx callgraph-mcp
239
+ ```
240
+
241
+ Windows PowerShell:
242
+
243
+ ```powershell
244
+ $env:FLOWMAP_TRANSPORT="http"
245
+ $env:FLOWMAP_PORT="3100"
246
+ npx callgraph-mcp
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Tools Reference
252
+
253
+ | Tool | Required params | Optional | What it returns |
254
+ |------|----------------|----------|-----------------|
255
+ | `flowmap_analyze_workspace` | `workspacePath` | `exclude`, `language` | Full call graph: all nodes, edges, flows, orphans |
256
+ | `flowmap_analyze_file` | `filePath` | — | Functions and call sites in a single file |
257
+ | `flowmap_get_callers` | `functionName`, `workspacePath` | — | Every function across the workspace that directly calls the named function |
258
+ | `flowmap_get_callees` | `functionName`, `workspacePath` | — | Every function the named function directly calls |
259
+ | `flowmap_get_flow` | `functionName`, `workspacePath` | `maxDepth` (default 10) | Full BFS subgraph reachable from a function — the complete execution path |
260
+ | `flowmap_list_entry_points` | `workspacePath` | — | All entry points: mains, route handlers, CLI commands, React roots |
261
+ | `flowmap_find_orphans` | `workspacePath` | — | Functions unreachable from any entry point — potential dead code |
262
+
263
+ **`workspacePath`** is the absolute path to the repository root (e.g. `/home/user/my-project` or `C:\projects\my-app`).
264
+
265
+ ---
266
+
267
+ ## Environment Variable Reference
268
+
269
+ | Variable | Default | Description |
270
+ |----------|---------|-------------|
271
+ | `FLOWMAP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` (`http` is used for HTTP-SSE clients) |
272
+ | `FLOWMAP_PORT` | `3100` | HTTP server port (only used for `http` transport) |
273
+ | `FLOWMAP_GRAMMARS` | *(bundled)* | Override path to Tree-sitter WASM grammar files |
274
+
275
+ ---
276
+
277
+ ## Example Use Cases
278
+
279
+ ### Explore an unfamiliar codebase
280
+
281
+ > *"I just cloned this repo. Walk me through where execution starts and what the main flows are."*
282
+
283
+ The agent calls `flowmap_list_entry_points` to find where code begins, then `flowmap_get_flow` on each entry point to trace the full execution paths. It can describe the architecture without reading every file.
284
+
285
+ ---
286
+
287
+ ### Understand the impact of a change before making it
288
+
289
+ > *"I need to change the signature of `processPayment`. What will break?"*
290
+
291
+ The agent calls `flowmap_get_callers("processPayment", workspacePath)` to get every call site across the entire codebase — with file paths and line numbers — so it knows exactly what needs updating before touching anything.
292
+
293
+ ---
294
+
295
+ ### Safe refactoring — find what to clean up
296
+
297
+ > *"We're doing a big cleanup. What functions are safe to delete?"*
298
+
299
+ The agent calls `flowmap_find_orphans(workspacePath)`. Functions with zero reachability from entry points and not exported are strong deletion candidates. Combined with `flowmap_get_callers` for verification, this gives a confident dead-code list.
300
+
301
+ ---
302
+
303
+ ### Trace a bug through the call chain
304
+
305
+ > *"The `submitOrder` function is failing. What does it call, and what does each of those call?"*
306
+
307
+ The agent calls `flowmap_get_flow("submitOrder", workspacePath, maxDepth: 5)` to get the full downstream call tree — showing exactly which functions are in the execution path and which files they live in.
308
+
309
+ ---
310
+
311
+ ### PR review — understand what changed
312
+
313
+ > *"This PR modifies `validateUser`. What's the blast radius?"*
314
+
315
+ The agent calls `flowmap_get_callers("validateUser", workspacePath)` to enumerate every caller, then `flowmap_get_flow("validateUser", workspacePath)` to show all downstream dependency. It can summarise the risk surface of the change deterministically.
316
+
317
+ ---
318
+
319
+ ### Understand a single file before editing it
320
+
321
+ > *"What does `src/auth/middleware.ts` export and what does it call?"*
322
+
323
+ The agent calls `flowmap_analyze_file("/abs/path/to/src/auth/middleware.ts")` to get a precise list of every function, its parameters, return type, and all outgoing calls — without needing to read the file itself.
324
+
325
+ ---
326
+
327
+ ### Agentic code generation with structural guardrails
328
+
329
+ When an agent is generating new code, it can call `flowmap_analyze_workspace` before and after to verify:
330
+ - New functions are connected to the call graph (not accidentally orphaned)
331
+ - No existing entry points were broken
332
+ - The intended call relationships were actually created
333
+
334
+ ---
335
+
336
+ ## Example Prompts for VS Code Copilot
337
+
338
+ ```
339
+ List all entry points in this workspace
340
+ ```
341
+ ```
342
+ What functions call `buildCallGraph` anywhere in the codebase?
343
+ ```
344
+ ```
345
+ Show me the full execution path starting from `startServer`, up to 6 levels deep
346
+ ```
347
+ ```
348
+ Find all dead code — functions that are never reached from any entry point
349
+ ```
350
+ ```
351
+ What does `parseFile` directly depend on?
352
+ ```
353
+ ```
354
+ I'm changing `connectDb`. Who calls it? Give me file paths and line numbers.
355
+ ```
356
+ ```
357
+ Analyze just src/api/routes.ts and tell me what it exports and what it calls
358
+ ```
359
+
360
+ ---
361
+
362
+ ## How It Works
363
+
364
+ 1. Tree-sitter WASM grammars parse each source file into an AST — no runtime execution, no imports
365
+ 2. Functions and call sites are extracted from the AST
366
+ 3. A call graph is built by resolving call site names to function definitions (by name, suffix, and file)
367
+ 4. Entry points are detected: functions that are never called but call others
368
+ 5. The graph is partitioned into independent execution flows via BFS from each entry point
369
+
370
+ Files are parsed in parallel batches of 50. Results are cached for 30 seconds — repeated calls within a session are instant.
371
+
372
+ ---
373
+
374
+ ## Links
375
+
376
+ - [VS Code Extension (CallSight)](https://marketplace.visualstudio.com/items?itemName=devricky-codes.callsight)
377
+ - [Core Package (@codeflow-map/core)](https://www.npmjs.com/package/@codeflow-map/core)
378
+ - [Source Code](https://github.com/devricky-codes/callgraph-mcp)
379
+ - [Report Issues](https://github.com/devricky-codes/callgraph-mcp)
380
+
381
+ ## License
382
+
383
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";var fe=Object.create;var C=Object.defineProperty;var me=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var ue=Object.getPrototypeOf,ye=Object.prototype.hasOwnProperty;var he=(t,r,e,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of ge(r))!ye.call(t,n)&&n!==e&&C(t,n,{get:()=>r[n],enumerable:!(o=me(r,n))||o.enumerable});return t};var y=(t,r,e)=>(e=t!=null?fe(ue(t)):{},he(r||!t||!t.__esModule?C(e,"default",{value:t,enumerable:!0}):e,t));var se=require("http"),ie=require("@modelcontextprotocol/sdk/server/mcp.js"),ae=require("@modelcontextprotocol/sdk/server/stdio.js"),ce=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),le=require("crypto");var b=require("zod"),W=y(require("fs"));var G=y(require("path")),h=require("@codeflow-map/core");var A=new Map,xe=3e4;function L(t){let r=A.get(t);return r?Date.now()-r.cachedAt>xe?(A.delete(t),null):r.graph:null}function P(t,r){A.set(t,{graph:r,cachedAt:Date.now(),workspacePath:t})}var E=y(require("path")),U=y(require("fast-glob")),N=require("@codeflow-map/core"),Se=["**/node_modules/**","**/venv/**","**/.venv/**","**/__pycache__/**","**/vendor/**","**/target/**","**/.git/**","**/dist/**","**/build/**","**/.next/**","**/.turbo/**","**/coverage/**","**/.gradle/**","**/.cache/**","**/site-packages/**","**/.mypy_cache/**","**/.pytest_cache/**","**/out/**","**/bin/**","**/obj/**","**/tests/**","**/__tests__/**","**/spec/**","**/__specs__/**","**/test/**"];async function z(t,r={}){let{exclude:e=[],language:o}=r,n;if(o){let c=Object.entries(N.FILE_EXTENSION_MAP).filter(([,d])=>d===o).map(([d])=>d.replace(".",""));n=c.length>0?c:[]}else n=Object.keys(N.FILE_EXTENSION_MAP).map(c=>c.replace(".",""));if(n.length===0)return[];let s=n.length===1?`**/*.${n[0]}`:`**/*.{${n.join(",")}}`,a=[...Se,...e],l=t.replace(/\\/g,"/"),p=await(0,U.default)(s,{cwd:l,ignore:a,absolute:!1,dot:!1,onlyFiles:!0}),i=[];for(let c of p){let d=E.extname(c),x=N.FILE_EXTENSION_MAP[d];x&&i.push({filePath:c.replace(/\\/g,"/"),absPath:E.resolve(t,c),languageId:x})}return i}var I=50,J=!1;function R(){return process.env.FLOWMAP_GRAMMARS?process.env.FLOWMAP_GRAMMARS:G.resolve(__dirname,"..","..","grammars")}async function ve(){J||(await(0,h.initTreeSitter)(R()),J=!0)}async function g(t,r={}){let e=L(t);if(e)return e;await ve();let o=R(),n=Date.now(),s=await z(t,r),a=[],l=[],p=0;for(let S=0;S<s.length;S+=I){let u=s.slice(S,S+I),v=await Promise.all(u.map(m=>(0,h.parseFile)(m.filePath,m.absPath,o,m.languageId).catch(()=>null)));for(let m of v)m&&(a.push(...m.functions),l.push(...m.calls),p++)}let i=(0,h.buildCallGraph)(a,l);(0,h.detectEntryPoints)(a,i);let{flows:c,orphans:d}=(0,h.partitionFlows)(a,i),x={nodes:a,edges:i,flows:c,orphans:d,scannedFiles:p,durationMs:Date.now()-n};return P(t,x),x}function f(t,r,e,o,n){t.tool(r,e,o,n)}var _e=["typescript","javascript","python","java","go","rust","tsx","jsx"],Oe=["node_modules","dist",".git","__pycache__","*.test.*","*.spec.*"];function $(t){f(t,"flowmap_analyze_workspace","Scan an entire codebase and return a full call graph \u2014 all functions, their parameters, and all call relationships between them. Use this first when exploring an unfamiliar codebase.",{workspacePath:b.z.string().describe("Absolute path to the repository root"),exclude:b.z.string().optional().describe("Comma-separated glob patterns to exclude. Defaults: node_modules,dist,.git,__pycache__,*.test.*,*.spec.*"),language:b.z.string().optional().describe("Filter to a single language: typescript, javascript, python, java, go, rust, tsx, jsx. Omit to scan all.")},async({workspacePath:r,exclude:e,language:o})=>{try{if(!W.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let n=e?e.split(",").map(l=>l.trim()).filter(Boolean):Oe,s=o&&_e.includes(o)?o:void 0,a=await g(r,{exclude:n,language:s});return{content:[{type:"text",text:JSON.stringify(a)}]}}catch(n){let s=n instanceof Error?n.message:String(n);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:s,workspacePath:r})}]}}})}var K=require("zod"),H=y(require("fs")),T=y(require("path")),_=require("@codeflow-map/core");var j=!1;function X(t){f(t,"flowmap_analyze_file","Scan a single file and return all functions defined in it, their parameters, and calls made within the file.",{filePath:K.z.string().describe("Absolute path to the file to analyse")},async({filePath:r})=>{try{if(!H.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FILE_NOT_FOUND",message:`File does not exist: ${r}`})}]};let e=T.extname(r),o=_.FILE_EXTENSION_MAP[e];if(!o)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"UNSUPPORTED_LANGUAGE",message:`Unsupported file extension: ${e}`})}]};let n=R();j||(await(0,_.initTreeSitter)(n),j=!0);let s=Date.now(),a=T.basename(r),l=await(0,_.parseFile)(a,r,n,o);return{content:[{type:"text",text:JSON.stringify({filePath:a,functions:l.functions,calls:l.calls,durationMs:Date.now()-s})}]}}catch(e){let o=e instanceof Error?e.message:String(e);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o})}]}}})}var D=require("zod"),k=y(require("fs"));function B(t){f(t,"flowmap_get_callers","Return all functions that directly call the named function. Use this for impact analysis \u2014 to understand what breaks if you change a function's signature.",{functionName:D.z.string().describe("The function name to find callers of"),workspacePath:D.z.string().describe("Absolute path to the repository root")},async({functionName:r,workspacePath:e})=>{try{if(!k.existsSync(e))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${e}`,workspacePath:e})}]};let o=await g(e),n=o.nodes.filter(i=>i.name===r);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${r}" found in the codebase.`,workspacePath:e})}]};let s=n[0],a=new Set(n.map(i=>i.id)),p=o.edges.filter(i=>a.has(i.to)).map(i=>{let c=o.nodes.find(d=>d.id===i.from);return{id:i.from,name:c?.name??"unknown",filePath:c?.filePath??"unknown",startLine:c?.startLine??0,callLine:i.line}});return{content:[{type:"text",text:JSON.stringify({target:r,targetId:s.id,callers:p,count:p.length})}]}}catch(o){let n=o instanceof Error?o.message:String(o);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:n,workspacePath:e})}]}}})}var M=require("zod"),Z=y(require("fs"));function V(t){f(t,"flowmap_get_callees","Return all functions directly called by the named function. Use this to understand what a function depends on.",{functionName:M.z.string().describe("The function name to find callees of"),workspacePath:M.z.string().describe("Absolute path to the repository root")},async({functionName:r,workspacePath:e})=>{try{if(!Z.existsSync(e))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${e}`,workspacePath:e})}]};let o=await g(e),n=o.nodes.filter(i=>i.name===r);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${r}" found in the codebase.`,workspacePath:e})}]};let s=n[0],a=new Set(n.map(i=>i.id)),p=o.edges.filter(i=>a.has(i.from)).map(i=>{let c=o.nodes.find(d=>d.id===i.to);return{id:i.to,name:c?.name??"unknown",filePath:c?.filePath??"unknown",startLine:c?.startLine??0,callLine:i.line}});return{content:[{type:"text",text:JSON.stringify({target:r,targetId:s.id,callees:p,count:p.length})}]}}catch(o){let n=o instanceof Error?o.message:String(o);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:n,workspacePath:e})}]}}})}var w=require("zod"),Y=y(require("fs"));function q(t){f(t,"flowmap_get_flow","Return the complete sub-graph reachable from a given function \u2014 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.",{functionName:w.z.string().describe("The starting function name"),workspacePath:w.z.string().describe("Absolute path to the repository root"),maxDepth:w.z.number().optional().describe("Maximum recursion depth. Default 10.")},async({functionName:r,workspacePath:e,maxDepth:o})=>{let n=o??10;try{if(!Y.existsSync(e))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${e}`,workspacePath:e})}]};let s=await g(e),a=s.nodes.filter(u=>u.name===r);if(a.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${r}" found in the codebase.`,workspacePath:e})}]};let l=a[0],p=new Map;for(let u of s.edges){let v=p.get(u.from)||[],m=s.nodes.find(F=>F.id===u.to);m&&(v.push({edge:u,node:m}),p.set(u.from,v))}let i=new Set,c=[],d=[],x=0,S=[l.id];for(i.add(l.id),c.push(l);S.length>0&&x<n;){let u=[];for(let v of S){let m=p.get(v)||[];for(let{edge:F,node:O}of m)d.push(F),i.has(O.id)||(i.add(O.id),c.push(O),u.push(O.id))}S=u,x++}return{content:[{type:"text",text:JSON.stringify({entryFunction:r,nodes:c,edges:d,depth:x,totalFunctions:c.length})}]}}catch(s){let a=s instanceof Error?s.message:String(s);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:a,workspacePath:e})}]}}})}var Q=require("zod"),ee=y(require("fs"));function te(t){f(t,"flowmap_list_entry_points","Return all detected entry points in the codebase \u2014 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.",{workspacePath:Q.z.string().describe("Absolute path to the repository root")},async({workspacePath:r})=>{try{if(!ee.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let e=await g(r),n=e.nodes.filter(s=>s.isEntryPoint).map(s=>({id:s.id,name:s.name,filePath:s.filePath,startLine:s.startLine,language:s.language,isExported:s.isExported,isAsync:s.isAsync}));return{content:[{type:"text",text:JSON.stringify({entryPoints:n,count:n.length,durationMs:e.durationMs})}]}}catch(e){let o=e instanceof Error?e.message:String(e);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o,workspacePath:r})}]}}})}var re=require("zod"),ne=y(require("fs"));function oe(t){f(t,"flowmap_find_orphans","Return all functions that are never called from any entry point \u2014 potential dead code. Use this during refactoring to identify code that can safely be removed.",{workspacePath:re.z.string().describe("Absolute path to the repository root")},async({workspacePath:r})=>{try{if(!ne.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let e=await g(r),o=e.orphans.map(n=>{let s=e.nodes.find(a=>a.id===n);return s?{id:s.id,name:s.name,filePath:s.filePath,startLine:s.startLine,language:s.language,isExported:s.isExported}:{id:n,name:"unknown",filePath:"unknown",startLine:0}});return{content:[{type:"text",text:JSON.stringify({orphans:o,count:o.length,durationMs:e.durationMs,note:"Exported functions may be used by external consumers \u2014 verify before deleting."})}]}}catch(e){let o=e instanceof Error?e.message:String(e);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o,workspacePath:r})}]}}})}function pe(){let t=new ie.McpServer({name:"callgraph-mcp",version:"1.0.0"});return Ne(t),t}function Ne(t){$(t),X(t),B(t),V(t),q(t),te(t),oe(t)}async function de(){let t=(process.env.FLOWMAP_TRANSPORT||"stdio").toLowerCase();t==="http"||t==="sse"?await Re():await Ee()}async function Ee(){let t=pe(),r=new ae.StdioServerTransport;await t.connect(r)}async function Re(){let t=parseInt(process.env.FLOWMAP_PORT||"3100",10),r=pe(),e=new ce.StreamableHTTPServerTransport({sessionIdGenerator:()=>(0,le.randomUUID)()}),o=(0,se.createServer)(async(n,s)=>{let a=n.url||"/";a==="/mcp"||a==="/"?await e.handleRequest(n,s):s.writeHead(404).end("Not Found")});await r.connect(e),o.listen(t,()=>{process.stderr.write(`FlowMap MCP server listening on http://localhost:${t}/mcp
4
+ `)})}de().catch(t=>{process.stderr.write(`FlowMap MCP server failed to start: ${t}
5
+ `),process.exit(1)});
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createMcpServer(): McpServer;
3
+ export declare function startServer(): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAnalyzeFile(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAnalyzeWorkspace(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerFindOrphans(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetCallees(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetCallers(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetFlow(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerListEntryPoints(server: McpServer): void;
@@ -0,0 +1,6 @@
1
+ import { Graph } from '@codeflow-map/core';
2
+ import { DiscoveryOptions } from './fileDiscovery';
3
+ export declare function resolveWasmDir(): string;
4
+ export interface AnalyzeOptions extends DiscoveryOptions {
5
+ }
6
+ export declare function analyzeWorkspace(workspacePath: string, options?: AnalyzeOptions): Promise<Graph>;
@@ -0,0 +1,4 @@
1
+ import { Graph } from '@codeflow-map/core';
2
+ export declare function getCached(workspacePath: string): Graph | null;
3
+ export declare function setCached(workspacePath: string, graph: Graph): void;
4
+ export declare function clearCache(workspacePath?: string): void;
@@ -0,0 +1,14 @@
1
+ import { SupportedLanguage } from '@codeflow-map/core';
2
+ export interface DiscoveredFile {
3
+ /** Relative path (forward slashes) from workspace root */
4
+ filePath: string;
5
+ /** Absolute path on disk */
6
+ absPath: string;
7
+ /** Language identified from file extension */
8
+ languageId: SupportedLanguage;
9
+ }
10
+ export interface DiscoveryOptions {
11
+ exclude?: string[];
12
+ language?: SupportedLanguage;
13
+ }
14
+ export declare function discoverFiles(workspacePath: string, options?: DiscoveryOptions): Promise<DiscoveredFile[]>;
@@ -0,0 +1,5 @@
1
+ import { Graph } from '@codeflow-map/core';
2
+ /**
3
+ * Produce an LLM-friendly text summary of a Graph for use alongside JSON responses.
4
+ */
5
+ export declare function formatGraphSummary(graph: Graph): string;
@@ -0,0 +1,11 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
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
+ export declare function registerTool<T extends Record<string, z.ZodTypeAny>>(server: McpServer, name: string, description: string, schema: T, handler: (args: {
10
+ [K in keyof T]: z.infer<T[K]>;
11
+ }) => Promise<CallToolResult>): void;
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "callgraph-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for codebase call-flow analysis. Local, deterministic, language-agnostic. Powered by @codeflow-map/core.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "call-graph",
9
+ "static-analysis",
10
+ "code-analysis",
11
+ "flowmap"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "devricky-codes",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/devricky-codes/callgraph-mcp.git",
18
+ "directory": "packages/mcp-server"
19
+ },
20
+ "homepage": "https://github.com/devricky-codes/callgraph-mcp#readme",
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "bin": {
24
+ "callgraph-mcp": "./dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist/",
28
+ "grammars/",
29
+ "README.md"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "scripts": {
38
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
39
+ "build:types": "tsc --emitDeclarationOnly",
40
+ "build:bundle": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node18 --minify --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" --external:@codeflow-map/core --external:@modelcontextprotocol/sdk --external:fast-glob --external:zod",
41
+ "build": "pnpm run clean && pnpm run build:types && pnpm run build:bundle && node scripts/copy-grammars.cjs",
42
+ "start": "node dist/index.js",
43
+ "dev": "tsx src/index.ts"
44
+ },
45
+ "dependencies": {
46
+ "@codeflow-map/core": "^0.2.5",
47
+ "@modelcontextprotocol/sdk": "^1.27.0",
48
+ "fast-glob": "^3.3.0",
49
+ "zod": "^3.25.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.10.6",
53
+ "esbuild": "^0.27.4",
54
+ "typescript": "^5.3.3",
55
+ "tsx": "^4.0.0"
56
+ }
57
+ }