callgraph-mcp 1.0.2 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +4 -193
  2. package/dist/index.js +2 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,29 +8,8 @@ Powered by [`@codeflow-map/core`](https://www.npmjs.com/package/@codeflow-map/co
8
8
 
9
9
  **Supports:** TypeScript · JavaScript · TSX · JSX · Python · Go
10
10
 
11
- <p><strong><span style="color:red;">Disclaimer: Grammars for TypeScript, JavaScript, TSX, JSX, Python, and Go are already bundled. After install, they are available at <code>callgraph-mcp/grammars</code>.</span></strong></p>
11
+ > **Bundled grammars:** TypeScript, JavaScript, TSX, JSX, Python, and Go grammars are included. After install, they are available in `callgraph-mcp/grammars`.
12
12
 
13
- ---
14
-
15
- ## Quick Start (VS Code Copilot)
16
-
17
- Add this to `.vscode/mcp.json`:
18
-
19
- ```json
20
- {
21
- "servers": {
22
- "flowmap": {
23
- "type": "stdio",
24
- "command": "npx",
25
- "args": ["-y", "callgraph-mcp"]
26
- }
27
- }
28
- }
29
- ```
30
-
31
- VS Code starts and stops the server automatically.
32
-
33
- ---
34
13
 
35
14
  ## Setup
36
15
 
@@ -53,85 +32,11 @@ Add to your project's `.vscode/mcp.json`:
53
32
  }
54
33
  ```
55
34
 
56
- VS Code starts and stops the server automatically. WASM grammars are bundled — no environment variables needed.
57
-
58
- > **Tip:** Create `.vscode/mcp.json` via the Command Palette → **MCP: Add Server** → **stdio**.
59
-
60
- ### Option 2 — Global install
61
-
62
- ```bash
63
- npm install -g callgraph-mcp
64
- ```
65
-
66
- ```json
67
- {
68
- "servers": {
69
- "flowmap": {
70
- "type": "stdio",
71
- "command": "callgraph-mcp",
72
- "env": {
73
- "FLOWMAP_TRANSPORT": "stdio"
74
- }
75
- }
76
- }
77
- }
78
- ```
79
-
80
- ### Option 3 — Claude Desktop
81
-
82
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
83
-
84
- ```json
85
- {
86
- "mcpServers": {
87
- "flowmap": {
88
- "command": "npx",
89
- "args": ["-y", "callgraph-mcp"],
90
- "env": {
91
- "FLOWMAP_TRANSPORT": "stdio"
92
- }
93
- }
94
- }
95
- }
96
- ```
97
-
98
- ### Option 4 — Cursor
99
-
100
- Add to your project's `.cursor/mcp.json`:
35
+ Start the server in your editor. WASM grammars are bundled — no environment variables needed.
101
36
 
102
- ```json
103
- {
104
- "mcpServers": {
105
- "flowmap": {
106
- "command": "npx",
107
- "args": ["-y", "callgraph-mcp"],
108
- "env": {
109
- "FLOWMAP_TRANSPORT": "stdio"
110
- }
111
- }
112
- }
113
- }
114
- ```
115
-
116
- ### Option 5 — Cline
117
-
118
- Add to your Cline MCP settings (commonly `cline_mcp_settings.json`):
119
-
120
- ```json
121
- {
122
- "mcpServers": {
123
- "flowmap": {
124
- "command": "npx",
125
- "args": ["-y", "callgraph-mcp"],
126
- "env": {
127
- "FLOWMAP_TRANSPORT": "stdio"
128
- }
129
- }
130
- }
131
- }
132
- ```
37
+ > **Tip:** Create `.vscode/mcp.json` via the Command Palette -> **MCP: Add Server** -> **stdio**.
133
38
 
134
- ### Option 6 — HTTP-SSE (shared or remote server)
39
+ ### Option 2 — HTTP-SSE (shared or remote server)
135
40
 
136
41
  Use `FLOWMAP_TRANSPORT=http` for HTTP-SSE compatible clients.
137
42
 
@@ -156,100 +61,6 @@ Then point your client at it:
156
61
  }
157
62
  ```
158
63
 
159
- ---
160
-
161
- ## Configure Environment Variables
162
-
163
- Use one of the following approaches depending on your client.
164
-
165
- ### In VS Code `.vscode/mcp.json`
166
-
167
- ```json
168
- {
169
- "servers": {
170
- "flowmap": {
171
- "type": "stdio",
172
- "command": "npx",
173
- "args": ["-y", "callgraph-mcp"],
174
- "env": {
175
- "FLOWMAP_TRANSPORT": "http",
176
- "FLOWMAP_PORT": "3100",
177
- "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
178
- }
179
- }
180
- }
181
- }
182
- ```
183
-
184
- ### In Claude Desktop config
185
-
186
- ```json
187
- {
188
- "mcpServers": {
189
- "flowmap": {
190
- "command": "npx",
191
- "args": ["-y", "callgraph-mcp"],
192
- "env": {
193
- "FLOWMAP_TRANSPORT": "stdio",
194
- "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
195
- }
196
- }
197
- }
198
- }
199
- ```
200
-
201
- ### In Cursor `.cursor/mcp.json`
202
-
203
- ```json
204
- {
205
- "mcpServers": {
206
- "flowmap": {
207
- "command": "npx",
208
- "args": ["-y", "callgraph-mcp"],
209
- "env": {
210
- "FLOWMAP_TRANSPORT": "stdio",
211
- "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
212
- }
213
- }
214
- }
215
- }
216
- ```
217
-
218
- ### In Cline MCP settings
219
-
220
- ```json
221
- {
222
- "mcpServers": {
223
- "flowmap": {
224
- "command": "npx",
225
- "args": ["-y", "callgraph-mcp"],
226
- "env": {
227
- "FLOWMAP_TRANSPORT": "stdio",
228
- "FLOWMAP_GRAMMARS": "/absolute/path/to/grammars"
229
- }
230
- }
231
- }
232
- }
233
- ```
234
-
235
- ### In your shell for one-off runs
236
-
237
- macOS / Linux:
238
-
239
- ```bash
240
- FLOWMAP_TRANSPORT=http FLOWMAP_PORT=3100 npx callgraph-mcp
241
- ```
242
-
243
- Windows PowerShell:
244
-
245
- ```powershell
246
- $env:FLOWMAP_TRANSPORT="http"
247
- $env:FLOWMAP_PORT="3100"
248
- npx callgraph-mcp
249
- ```
250
-
251
- ---
252
-
253
64
  ## Tools Reference
254
65
 
255
66
  | Tool | Required params | Optional | What it returns |
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- "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
3
- `)})}de().catch(t=>{process.stderr.write(`FlowMap MCP server failed to start: ${t}
2
+ "use strict";var me=Object.create;var P=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var ue=Object.getOwnPropertyNames;var ye=Object.getPrototypeOf,he=Object.prototype.hasOwnProperty;var xe=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ue(t))!he.call(e,n)&&n!==r&&P(e,n,{get:()=>t[n],enumerable:!(o=ge(t,n))||o.enumerable});return e};var g=(e,t,r)=>(r=e!=null?me(ye(e)):{},xe(t||!e||!e.__esModule?P(r,"default",{value:e,enumerable:!0}):r,e));var ie=require("http"),ae=require("@modelcontextprotocol/sdk/server/mcp.js"),ce=require("@modelcontextprotocol/sdk/server/stdio.js"),le=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),pe=require("crypto");var b=require("zod"),$=g(require("fs"));var v=g(require("path")),M=g(require("fs")),h=require("@codeflow-map/core");var D=new Map,Se=3e4;function U(e){let t=D.get(e);return t?Date.now()-t.cachedAt>Se?(D.delete(e),null):t.graph:null}function z(e,t){D.set(e,{graph:t,cachedAt:Date.now(),workspacePath:e})}var w=g(require("path")),I=g(require("fast-glob")),E=require("@codeflow-map/core"),_e=["**/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 J(e,t={}){let{exclude:r=[],language:o}=t,n;if(o){let c=Object.entries(E.FILE_EXTENSION_MAP).filter(([,f])=>f===o).map(([f])=>f.replace(".",""));n=c.length>0?c:[]}else n=Object.keys(E.FILE_EXTENSION_MAP).map(c=>c.replace(".",""));if(n.length===0)return[];let s=n.length===1?`**/*.${n[0]}`:`**/*.{${n.join(",")}}`,a=[..._e,...r],l=e.replace(/\\/g,"/"),p=await(0,I.default)(s,{cwd:l,ignore:a,absolute:!1,dot:!1,onlyFiles:!0}),i=[];for(let c of p){let f=w.extname(c),x=E.FILE_EXTENSION_MAP[f];x&&i.push({filePath:c.replace(/\\/g,"/"),absPath:w.resolve(e,c),languageId:x})}return i}var G=50,W=!1;function R(){if(process.env.FLOWMAP_GRAMMARS)return process.env.FLOWMAP_GRAMMARS;let e=[v.resolve(__dirname,"..","grammars"),v.resolve(__dirname,"..","..","grammars")];for(let t of e)if(M.existsSync(v.join(t,"tree-sitter.wasm")))return t;return e[0]}async function ve(){if(!W){let e=R(),t=v.join(e,"tree-sitter.wasm"),r=M.existsSync(t);console.error(`[flowmap] Grammar directory: ${e} (tree-sitter.wasm ${r?"found":"missing"})`),await(0,h.initTreeSitter)(e),W=!0}}async function u(e,t={}){let r=U(e);if(r)return r;await ve();let o=R(),n=Date.now(),s=await J(e,t),a=[],l=[],p=0;for(let S=0;S<s.length;S+=G){let y=s.slice(S,S+G),_=await Promise.all(y.map(m=>(0,h.parseFile)(m.filePath,m.absPath,o,m.languageId).catch(()=>null)));for(let m of _)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:f}=(0,h.partitionFlows)(a,i),x={nodes:a,edges:i,flows:c,orphans:f,scannedFiles:p,durationMs:Date.now()-n};return z(e,x),x}function d(e,t,r,o,n){e.tool(t,r,o,n)}var Oe=["typescript","javascript","python","java","go","rust","tsx","jsx"],Ne=["node_modules","dist",".git","__pycache__","*.test.*","*.spec.*"];function j(e){d(e,"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:t,exclude:r,language:o})=>{try{if(!$.existsSync(t))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${t}`,workspacePath:t})}]};let n=r?r.split(",").map(l=>l.trim()).filter(Boolean):Ne,s=o&&Oe.includes(o)?o:void 0,a=await u(t,{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:t})}]}}})}var H=require("zod"),X=g(require("fs")),T=g(require("path")),O=require("@codeflow-map/core");var K=!1;function k(e){d(e,"flowmap_analyze_file","Scan a single file and return all functions defined in it, their parameters, and calls made within the file.",{filePath:H.z.string().describe("Absolute path to the file to analyse")},async({filePath:t})=>{try{if(!X.existsSync(t))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FILE_NOT_FOUND",message:`File does not exist: ${t}`})}]};let r=T.extname(t),o=O.FILE_EXTENSION_MAP[r];if(!o)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"UNSUPPORTED_LANGUAGE",message:`Unsupported file extension: ${r}`})}]};let n=R();K||(await(0,O.initTreeSitter)(n),K=!0);let s=Date.now(),a=T.basename(t),l=await(0,O.parseFile)(a,t,n,o);return{content:[{type:"text",text:JSON.stringify({filePath:a,functions:l.functions,calls:l.calls,durationMs:Date.now()-s})}]}}catch(r){let o=r instanceof Error?r.message:String(r);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o})}]}}})}var C=require("zod"),B=g(require("fs"));function Z(e){d(e,"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:C.z.string().describe("The function name to find callers of"),workspacePath:C.z.string().describe("Absolute path to the repository root")},async({functionName:t,workspacePath:r})=>{try{if(!B.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let o=await u(r),n=o.nodes.filter(i=>i.name===t);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${t}" found in the codebase.`,workspacePath:r})}]};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(f=>f.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:t,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:r})}]}}})}var L=require("zod"),V=g(require("fs"));function Y(e){d(e,"flowmap_get_callees","Return all functions directly called by the named function. Use this to understand what a function depends on.",{functionName:L.z.string().describe("The function name to find callees of"),workspacePath:L.z.string().describe("Absolute path to the repository root")},async({functionName:t,workspacePath:r})=>{try{if(!V.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let o=await u(r),n=o.nodes.filter(i=>i.name===t);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${t}" found in the codebase.`,workspacePath:r})}]};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(f=>f.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:t,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:r})}]}}})}var F=require("zod"),q=g(require("fs"));function Q(e){d(e,"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:F.z.string().describe("The starting function name"),workspacePath:F.z.string().describe("Absolute path to the repository root"),maxDepth:F.z.number().optional().describe("Maximum recursion depth. Default 10.")},async({functionName:t,workspacePath:r,maxDepth:o})=>{let n=o??10;try{if(!q.existsSync(r))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${r}`,workspacePath:r})}]};let s=await u(r),a=s.nodes.filter(y=>y.name===t);if(a.length===0)return{content:[{type:"text",text:JSON.stringify({error:!0,code:"FUNCTION_NOT_FOUND",message:`No function named "${t}" found in the codebase.`,workspacePath:r})}]};let l=a[0],p=new Map;for(let y of s.edges){let _=p.get(y.from)||[],m=s.nodes.find(A=>A.id===y.to);m&&(_.push({edge:y,node:m}),p.set(y.from,_))}let i=new Set,c=[],f=[],x=0,S=[l.id];for(i.add(l.id),c.push(l);S.length>0&&x<n;){let y=[];for(let _ of S){let m=p.get(_)||[];for(let{edge:A,node:N}of m)f.push(A),i.has(N.id)||(i.add(N.id),c.push(N),y.push(N.id))}S=y,x++}return{content:[{type:"text",text:JSON.stringify({entryFunction:t,nodes:c,edges:f,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:r})}]}}})}var ee=require("zod"),te=g(require("fs"));function re(e){d(e,"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:ee.z.string().describe("Absolute path to the repository root")},async({workspacePath:t})=>{try{if(!te.existsSync(t))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${t}`,workspacePath:t})}]};let r=await u(t),n=r.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:r.durationMs})}]}}catch(r){let o=r instanceof Error?r.message:String(r);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o,workspacePath:t})}]}}})}var ne=require("zod"),oe=g(require("fs"));function se(e){d(e,"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:ne.z.string().describe("Absolute path to the repository root")},async({workspacePath:t})=>{try{if(!oe.existsSync(t))return{content:[{type:"text",text:JSON.stringify({error:!0,code:"WORKSPACE_NOT_FOUND",message:`Directory does not exist: ${t}`,workspacePath:t})}]};let r=await u(t),o=r.orphans.map(n=>{let s=r.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:r.durationMs,note:"Exported functions may be used by external consumers \u2014 verify before deleting."})}]}}catch(r){let o=r instanceof Error?r.message:String(r);return{content:[{type:"text",text:JSON.stringify({error:!0,code:"PARSE_ERROR",message:o,workspacePath:t})}]}}})}function fe(){let e=new ae.McpServer({name:"callgraph-mcp",version:"1.0.0"});return Ee(e),e}function Ee(e){j(e),k(e),Z(e),Y(e),Q(e),re(e),se(e)}async function de(){let e=(process.env.FLOWMAP_TRANSPORT||"stdio").toLowerCase();e==="http"||e==="sse"?await Re():await we()}async function we(){let e=fe(),t=new ce.StdioServerTransport;await e.connect(t)}async function Re(){let e=parseInt(process.env.FLOWMAP_PORT||"3100",10),t=fe(),r=new le.StreamableHTTPServerTransport({sessionIdGenerator:()=>(0,pe.randomUUID)()}),o=(0,ie.createServer)(async(n,s)=>{let a=n.url||"/";a==="/mcp"||a==="/"?await r.handleRequest(n,s):s.writeHead(404).end("Not Found")});await t.connect(r),o.listen(e,()=>{process.stderr.write(`FlowMap MCP server listening on http://localhost:${e}/mcp
3
+ `)})}de().catch(e=>{process.stderr.write(`FlowMap MCP server failed to start: ${e}
4
4
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "callgraph-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.1.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",