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 +383 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/server.d.ts +3 -0
- package/dist/tools/analyzeFile.d.ts +2 -0
- package/dist/tools/analyzeWorkspace.d.ts +2 -0
- package/dist/tools/findOrphans.d.ts +2 -0
- package/dist/tools/getCallees.d.ts +2 -0
- package/dist/tools/getCallers.d.ts +2 -0
- package/dist/tools/getFlow.d.ts +2 -0
- package/dist/tools/listEntryPoints.d.ts +2 -0
- package/dist/utils/analysis.d.ts +6 -0
- package/dist/utils/cache.d.ts +4 -0
- package/dist/utils/fileDiscovery.d.ts +14 -0
- package/dist/utils/formatGraph.d.ts +5 -0
- package/dist/utils/toolHelper.d.ts +11 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/grammars/tree-sitter.wasm +0 -0
- package/package.json +57 -0
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
|
package/dist/index.d.ts
ADDED
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)});
|
package/dist/server.d.ts
ADDED
|
@@ -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,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,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
|
|
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
|
+
}
|