Skip to content

benwelker/callscope

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CallScope

A zero-dependency, local, interactive call-graph navigator for Python codebases. One script in, one self-contained HTML file out — plus an MCP server so AI agents can walk your call graph too.

python3 callscope.py /path/to/repo -o map.html
open map.html

No install, no server, no index database, no API keys. Python 3.9+ stdlib only.

CallScope navigating pallets/click: open a function, click a highlighted call, follow the chain across panes

Quick start

git clone https://github.com/benwelker/callscope
cd callscope

# generate an interactive map of any Python codebase
python3 callscope.py ~/src/myproject -o myproject.html
open myproject.html

Or install it as a command (optional):

pipx install git+https://github.com/benwelker/callscope
callscope ~/src/myproject -o myproject.html

In the map:

  • Pick a function in the sidebar (or press / and search). Its source opens in a pane with every call site highlighted.
  • Click a call → the callee opens in the next pane. The chain of panes is the call path you're exploring.
  • ← callers on any pane walks the graph backwards.
  • Link styling is honest about confidence: solid underline = statically resolved, dashed = best-effort match, dotted grey = defined outside the codebase (builtin/stdlib/third-party).

The problem this solves

Understanding an unfamiliar codebase means chasing function calls across many files. The tool developers keep asking for — most recently as the top request in Hacker News' "Ask HN: What developer tool do you wish existed in 2026?" — is: open a function in a pane showing only that function with its calls highlighted, click a call, and keep walking.

Sourcetrail, the beloved tool closest to this, was discontinued in 2021 and never replaced. Sourcegraph is cloud-oriented code search; Understand and Source Insight are commercial; IDE "peek definition" is one hop at a time with no persistent trail. CallScope is the missing piece: local, instant, deterministic, and shareable — the output is one HTML file with zero external requests, so you can email it, commit it, or open it on a plane.

CLI

callscope ROOT [-o map.html] [--name LABEL] [--json PATH] [--no-html]
Flag What it does
-o PATH Output HTML file (default callscope.html)
--name Project label shown in the UI (default: directory name)
--json Also write the raw call-graph index as JSON (for scripts and agents)
--no-html Skip the HTML map (use with --json for data-only runs)

The JSON index contains every function (id, module, file:line, docstring, source) with its resolved call edges and inverted caller edges — everything a script or agent needs to query the graph without re-parsing the codebase.

MCP server: give your AI agent the call graph

Coding agents have the same problem humans do — tracing call paths across files burns time and context. callscope_mcp.py is a Model Context Protocol server (stdio, stdlib-only, no packages to install) that lets an agent query the graph directly.

Register it with Claude Code:

claude mcp add callscope -- python3 /path/to/callscope/callscope_mcp.py

Or in any MCP client's config:

{
  "mcpServers": {
    "callscope": {
      "command": "python3",
      "args": ["/path/to/callscope/callscope_mcp.py"]
    }
  }
}

Then ask your agent things like "use callscope to map this repo and show me how cli() ends up calling Context.invoke". Tools exposed:

Tool What the agent gets
analyze_project Scan a codebase, build + cache the graph; stats and module inventory
search_functions Find function ids by name / Class.method / module substring
get_function One function: source, calls (with resolution confidence), callers
callers_of Reverse edges: everything that calls a function
call_path Shortest call chain from A to B ("how does A end up invoking B?")
generate_map Write the interactive HTML map for the human to open

All tools take a path argument and auto-analyze on first use; pass refresh: true to analyze_project after the code changes.

How it works

callscope.py does a two-pass static analysis with stdlib ast:

  1. Collect — walks every .py file; indexes functions, methods, classes (with bases), per-module imports (including relative imports and aliases), and raw call sites with exact source positions. @overload stubs are skipped.
  2. Resolve — links each call site to a definition using, in order: same-module definitions, imported names (module- and function-level), self/cls dispatch with base-class walking, local-variable type inference from var = ClassName() assignments, module-alias attribute calls, class instantiation → __init__, and finally a globally-unique-name fallback flagged as approximate.

The resolved graph plus source snippets are embedded as JSON in an HTML template whose JS renders the panes, tokenizes Python for syntax highlighting, and overlays clickable call marks by exact line/column.

Scale check: pallets/click → 76 files, 1,679 functions, 2,292 resolved call edges, ~1.8 MB output, under two seconds.

Tests

python3 tests/test_resolution.py   # each call-resolution rule, via --json
python3 tests/test_mcp.py          # MCP handshake + all 6 tools over stdio
cd tests/e2e && npm install && npm test   # drives the real UI headlessly (jsdom)

Limitations

Static analysis, not a type checker: duck-typed attribute calls on parameters, decorators that rewrite functions, and dynamic dispatch (getattr) resolve only via the flagged approximate fallback or not at all. Python-only for now — the pane UI is language-agnostic, so other language front-ends only need to emit the same JSON shape.

Roadmap: opt-in LLM-assisted resolution of the unresolved tail (--ai-resolve), more language front-ends.

License

MIT

About

A zero-dependency, local, interactive call-graph navigator for Python codebases. One script in, one self-contained HTML file out

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors