callgraph-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +0 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +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>
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## Quick Start (VS Code Copilot)
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
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
|
|
4
3
|
`)})}de().catch(t=>{process.stderr.write(`FlowMap MCP server failed to start: ${t}
|
|
5
4
|
`),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "callgraph-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
39
39
|
"build:types": "tsc --emitDeclarationOnly",
|
|
40
|
-
"build:bundle": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node18 --minify --outfile=dist/index.js --
|
|
40
|
+
"build:bundle": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node18 --minify --outfile=dist/index.js --external:@codeflow-map/core --external:@modelcontextprotocol/sdk --external:fast-glob --external:zod",
|
|
41
41
|
"build": "pnpm run clean && pnpm run build:types && pnpm run build:bundle && node scripts/copy-grammars.cjs",
|
|
42
42
|
"start": "node dist/index.js",
|
|
43
43
|
"dev": "tsx src/index.ts"
|