ripple
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@rippleWho calls calculate_total and which tests reach it?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ripple
Know what breaks before you change it. ripple is an MCP server that gives a coding agent (Claude Code, Cursor, anything that speaks MCP) the call graph of a Python repository, so the agent can ask "who calls this, and which tests reach it?" before it edits, and run just those tests after.
Why
A coding agent dropped into unfamiliar code edits blind. It greps for a function name, sees a few
hits, and changes the signature. The callers that reach it through an import alias, a self. call in
a subclass or a re-export in a package __init__ don't match the grep, and the first sign of trouble
is a failing test, or a failure in production. ripple answers the structural question directly and
says plainly where static analysis runs out.
Related MCP server: repo-atlas-mcp
Quickstart
Requires Python 3.10+ and uv.
uv tool install git+https://github.com/xtelos/ripple # puts `ripple` on your PATHClaude Code, from your project directory:
claude mcp add ripple -- ripple serve --repo "$PWD"Cursor, in .cursor/mcp.json:
{
"mcpServers": {
"ripple": { "command": "ripple", "args": ["serve", "--repo", "${workspaceFolder}"] }
}
}Or from a terminal: ripple index ., then ripple impact price_order.
The five tools
Tool | What it answers | Read-only |
| Who calls this directly, with the file:line of each call site | yes |
| What this calls, plus the calls ripple could not resolve and why | yes |
| Every transitive caller grouped by file, the tests that reach it listed separately as pytest node ids, and same-named call sites ripple could not rule out | yes |
| The shortest call chain from one symbol to another | yes |
| Runs the test command (default | no, it runs code |
A symbol can be a full dotted path (shop.pricing.price_order), a suffix (OrderService.place_order)
or a bare name (price_order). An ambiguous name returns the candidates rather than picking one.
The four graph tools advertise readOnlyHint: true, which lets a client run them without prompting.
check is marked destructive instead, because a test suite can do anything its tests do.
impact lists at most limit callers and limit tests (100 by default, up to 1000 over MCP). When
it found more, it says so: truncated is true and omitted_callers / omitted_tests give the
counts (the CLI prints "... and N more callers not listed"). Each test comes with its pytest node id
(tests/test_x.py::test_y, or tests/test_x.py::TestZ::test_y for a method), found by pytest's
default collection rules: test* functions at module level in test_*.py and *_test.py files,
test* methods of Test* classes that have no __init__, and every test* method of a
unittest.TestCase subclass whatever its name. A class runs the test methods it inherits, so a
method defined on a base is listed once for each class that runs it, under that class's node id
(TestChild::test_shared). Pass those node ids to check to run just those tests. Helpers in test
files that are not tests themselves are listed with the other callers, since they may need updating.
check(tests=[...]) accepts only test file paths and node ids inside the repository. Anything that
starts with - or @ (pytest reads options from an @file) or points outside the repo is refused,
so a caller cannot slip in a pytest option (--basetemp=DIR deletes DIR). Output is read from a temporary file and the timeout covers the
whole run, so a test that leaves a process running in the background cannot hold up the result.
The summary and each failure or output line check returns are cut to 240 characters, ending in
[truncated N chars] when something was cut.
Example, trimmed:
$ ripple impact tax_for --repo bench/fixtures/shop
impact of shop.tax.tax_for (depth 5): 6 callers
shop/pricing.py
shop.pricing.price_order line 8 (1 hop)
shop/service.py
shop.service.OrderService.place_order line 17 (2 hops)
shop.service.OrderService.reprice line 26 (2 hops)
tests that reach it (3):
tests/test_pricing.py::test_percent_discount_and_tax
...
$ ripple check tests/test_pricing.py::test_percent_discount_and_tax --repo bench/fixtures/shop
{
"passed": true,
"summary": "1 passed in 0.01s",
...How it works
Two passes over the standard library ast module, no other parser.
Collect (
ripple/indexer.py): each file is read on its own. For every module, class and function it records what names are bound (defs, imports, assignments and what they suggest about a value's class) and every call site.Resolve (
ripple/resolver.py): with the whole repository in view, each call site either becomes an edge, because the source proves its target, or is kept as unresolved with a reason. ripple never guesses. "There is only one method namedsave, so it must be that one" is exactly the kind of answer that looks right and is wrong in the cases an agent cannot check.
What counts as proof: names defined or imported in scope; import x, from x import y, aliases,
relative imports and package re-exports; self.method() through base classes; super(); calling a
class (edge to its __init__); and method calls on values whose class is known from a constructor
call, a type annotation, a return annotation or a self.attr assigned in the class. When a method is
called on self or a typed value, subclass overrides are linked too (class hierarchy analysis).
The index is cached outside your repo (RIPPLE_CACHE_DIR if set, else $XDG_CACHE_HOME/ripple or
~/.cache/ripple) and keyed on every file's mtime and size, so each query re-checks the files and
rebuilds only when something changed.
On a 105-file package (pydantic) a cold index took 0.8 s and a cached load 0.02 s on my machine.
Benchmark
python -m bench scores ripple against hand-written ground truth: 39 caller, callee and impact
queries over three small fixture apps in bench/fixtures (an order and pricing service, a notes CLI,
a web handler layer). CI fails if any number drops below bench/baseline.json.
Case | Queries | Exact | Precision | Recall |
Imports, aliases, re-exports | 11 | 11 | 1.000 | 1.000 |
Methods, | 6 | 6 | 1.000 | 1.000 |
Typed receivers | 5 | 5 | 1.000 | 1.000 |
Callees | 3 | 2 | 1.000 | 0.750 |
Decorators | 4 | 2 | 0.667 | 0.500 |
Dynamic (getattr, dispatch tables, untyped values) | 6 | 0 | 1.000 | 0.125 |
Impact (transitive) | 4 | 1 | 1.000 | 0.459 |
Overall | 39 | 27 | 0.985 | 0.670 |
How to read this honestly:
The truth was written before the code. The fixtures and every expected answer were committed before any indexer code existed (see the first commit), and the dynamic cases were included on purpose to show where static analysis stops.
The truth is checked against runtime.
bench/trace.pyruns each fixture's own test suite under a profiler, records the calls that actually happen, andtests/test_ground_truth.pyfails if any expected answer disagrees with that trace. The tracer shares no code with ripple.It is small. 39 queries over three fixtures I wrote, each query aimed at one resolution rule or one known limit. It shows which rules work, not what recall looks like on your codebase, where more of the code is likely to be untyped and dynamic.
The one false positive is a design choice. ripple treats a decorated function as called directly, so it reports a test that calls
hello()as a caller ofhello. At runtime the test calls the decorator's wrapper, which callshello.
Agent eval
The benchmark checks that ripple's answers are right. The eval checks whether a coding agent does better with them: ten refactoring tasks, each run by Claude Code in headless mode, once with a ripple MCP server and once without, three times each.
The tasks (
eval/tasks/) are renames and signature changes where grep is noisy or incomplete: a method namedtotalon four classes, a function reached only through a package re-export alias, an override in another package, agetattrlookup that neither grep nor ripple can see. Seven run overeval/fixtures/ledger, a small invoicing app written for the eval, and three reuse the benchmark fixtures. Each task'swhyfield says what makes it hard.Grading. An edit counts as a success when the fixture's own tests pass and still number at least the task's minimum (
min_tests), and a hidden test the agent never sees passes too. The hidden test checks that the change was made and that every caller still works.tests/test_eval.pyshows each check fails on the untouched fixture and passes on a reference solution, and CI runs it.Same conditions. Both modes get the same prompt, model, tools and permissions (edit files, run pytest). The MCP server is the only difference. Each run starts from a fresh copy under
/tmp, in a directory whose name says nothing about the task or mode, and leaves out the user's own settings, hooks and auto-memory. A with-ripple run whose ripple server fails to connect is marked invalid and left out of the numbers.
.venv/bin/python -m eval.run # 10 tasks x 2 modes x 3 repeats, into eval/results/
.venv/bin/python -m eval.run --tasks 04-ledger-rename-invoice-total --repeats 1 --out /tmp/smoke.jsonEach batch writes eval/results/<date>-<time>-<id>.json (unless --out says otherwise), a .md
table and a -diffs/ directory beside it, and refuses to overwrite an earlier batch's files.
It calls the claude CLI on whatever account it is logged in to, so it never runs in CI.
Results
First full run, 2026-09-26, Claude Sonnet 5, 60 runs (eval/results/2026-09-26.json):
Mode | Success | Median input tokens | Median output tokens | Median turns | Median wall time |
with ripple | 30/30 | 145,461 | 3,372 | 19.5 | 25.2 s |
without | 30/30 | 129,108 | 3,211 | 15.0 | 22.9 s |
ripple did not help on these tasks. Both modes solved every task every time, so there is no difference in success to measure, and with ripple the agent spent about 13% more input tokens and four or five more turns, mostly on loading and calling the tools (78 ripple calls across the 30 runs).
The likely reason is the tasks, not the tool: the fixtures are a few hundred lines, small enough that the agent can grep and read everything it needs, which puts both modes at the ceiling. The cases where a call graph should matter (a large unfamiliar codebase, callers the agent never opens) are exactly the ones this first task set does not have. The next version of the eval needs tasks over bigger, real repositories, where the without-ripple agent has a real chance to miss a caller.
In this run each arm's working directory name included its condition label (such as
--with-ripple--), which the agent can see; that is fixed now, and the result is unlikely to depend
on it since both arms scored 30/30.
Known limits
Static analysis of a dynamic language misses things. ripple does not follow:
method calls on values of unknown type: unannotated parameters, loop variables, results of functions without a return annotation (reported as unresolved, with the reason)
getattr, dict or registry dispatch, callbacks passed as arguments,functools.partialdecorator wrappers (the decorated function is treated as called directly)
calls on the result of a call (
make().run()), property access,__call__, metaclasses, monkeypatching,importlib,execcode imported under a name that differs from its path in the repo (modules are named by their path from the repo root, with a leading
src/dropped). Two files that get the same name (tests/test_x.pyandsrc/tests/test_x.py) share one set of symbol ids, so a name defined in both is one symbol;ripple indexwarns when that happens and names the files.directories that hold dependencies or build output: hidden directories,
__pycache__,node_modules,site-packages,venvand any directory with apyvenv.cfganywhere, andbuild,distandenvat the repo root only.ripple indexlists the skipped directories, except hidden ones and__pycache__.custom pytest collection settings (
python_files,python_functions,python_classes): test node ids follow pytest's defaults. ripple sees only the repo's own classes, so a class counts as aunittest.TestCasesubclass when it has a base from outside the repo whose name ends inTestCase(unittest.TestCase,django.test.TestCase), and an__init__inherited from outside the repo is not seen.
Override edges can over-approximate: a subclass override is linked even if that subclass never reaches the call site. Method lookup walks bases depth-first, which matches Python's C3 order except in diamond-shaped hierarchies.
To keep the blind spots visible, callees lists every unresolved call with its reason, and impact
lists unresolved call sites that use the same name as the target under possible_missed_callers.
A call counts as "code outside the repo" only when it must be: a method missing from a class's repo
bases is inherited from an external base such as Exception, unless a base could not be resolved
(class Cached(make_base())) or a subclass defines the method, and then it is unresolved too.
Subscripted bases such as Repo[int] or Generic[T] resolve to the class they subscript.
Next
TypeScript, the other language agents spend most of their time in.
Development
uv venv && uv pip install -e ".[dev]"
.venv/bin/python -m pytest -q # unit tests, plus the ground truth against its runtime trace
.venv/bin/python -m bench # the benchmark; --write-baseline after a deliberate changeMIT licensed. Copyright (c) 2026 Dylan Fodor.
Available Tools
5 toolscalleesARead-onlyIdempotent
List what a function or method calls: resolved callees with call sites, plus the calls ripple could not resolve statically and why.
| Name | Required | Description | Default |
|---|---|---|---|
| symbol | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds genuine context beyond that by disclosing that results include call sites and that unresolved calls are returned with reasons, which shapes how an agent interprets the output. It says nothing about result volume or pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with zero waste, front-loading the core action and then the secondary payoff of unresolved-call diagnostics.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With one parameter and full annotation coverage the tool is simple enough that the description is nearly sufficient, and it does explain what comes back despite having no output schema. The unresolved gap is symbol formatting and whether results are recursive or single-level, which an agent needs before calling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter exists and schema description coverage is 0%, so the schema gives no help. The description partially compensates by establishing that the symbol is a function or method, but it never states the expected format (bare name vs qualified 'Class.method') which matters greatly for a code-analysis lookup.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: list what a function or method calls, returning resolved callees with call sites plus unresolved calls. That directionality makes it distinguishable from the sibling 'callers' without opening a schema, though no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: the outgoing-call framing tells an agent this is the inverse of 'callers', so the direction choice is inferable. There is no explicit when-to-use, when-not-to-use, or named alternative such as 'callers' or 'impact'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
callersARead-onlyIdempotent
List the functions and methods that directly call a symbol, with the file:line of each call site. symbol: dotted path, Class.method suffix, or bare name.
| Name | Required | Description | Default |
|---|---|---|---|
| symbol | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and closed-world scope, so safety needs no restating. The description adds real behavioral context by disclosing the return content (file:line of each call site) and the scope limit ('directly', not transitive), which is not in any structured field.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler, with the core action and return shape front-loaded and the parameter guidance following. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read-only tool with no output schema, the description covers the action, the accepted symbol syntax, and the returned data shape. It stops short of clarifying how nested or indirect calls are excluded, but nothing essential to a correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the sole parameter has no schema-level description, so the description must compensate — and it does, spelling out accepted forms: dotted path, Class.method suffix, or bare name. That is meaningful semantics beyond the bare 'string' type.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (list) and resource (functions/methods that directly call a symbol) plus what is returned (file:line per call site). The word 'directly' implicitly separates it from transitive-analysis siblings like impact, but no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the incoming-reference query versus callees (outgoing), but the description never states when to choose this over callees, impact, or path. No exclusions or prerequisites are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
checkADestructive
Run the repository's test command (default: python -m pytest -q) and return pass or fail, the summary line and the first failing tests, never the full log. tests: optional pytest node ids or test paths inside the repo to run instead of the whole suite, e.g. the node_id of each test impact listed. Options (anything starting with '-') are refused.
| Name | Required | Description | Default |
|---|---|---|---|
| tests | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare that this is not read-only and is destructive, so the description does not need to repeat that safety profile. It adds useful behavioral detail beyond annotations: the default command, that output is limited to pass/fail plus summary and first failures, never the full log, and that options starting with '-' are refused. It still does not explain possible test side effects, but the major behavioral contract is present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the primary action and output contract, then explains the only parameter. Every sentence adds necessary information: default command, return shape, subset behavior, and option restriction.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With one optional parameter, no output schema, and annotations covering safety, the description is complete enough to invoke the tool correctly. It explains the default command, how to run a subset, what is returned, and what input is refused.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry parameter meaning. It does this well: `tests` is optional, accepts pytest node ids or test paths inside the repo, and runs those instead of the whole suite, with an example linking to impact-listed tests. It also clarifies that CLI-style options are refused.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: run the repository's test command. It clearly distinguishes this tool from the static-analysis siblings (callers, callees, impact, path) by specifying test execution and pass/fail reporting.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explains the optional `tests` argument as a way to run a subset instead of the full suite, and gives the concrete use case of passing node ids from `impact`. It does not explicitly state when not to use the tool or compare it against all alternatives, but the context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
impactARead-onlyIdempotent
Answer 'what breaks if I change this?': every transitive caller of a symbol up to depth hops, grouped by file, with the tests that reach it listed separately (each with the pytest node_id to pass to check). At most limit callers and limit tests are listed; truncated and omitted_callers / omitted_tests say how many more were found. Also lists unresolved call sites with the same name, which static analysis cannot rule out.
| Name | Required | Description | Default |
|---|---|---|---|
| depth | No | ||
| limit | No | ||
| symbol | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint/idempotent annotations, it discloses substantial behavior: transitive traversal, grouping by file, tests listed separately, limit/truncation semantics with omitted_callers/omitted_tests counts, and unresolved call sites static analysis cannot rule out.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the motivating question, then dense but informative clauses. The single run-on sentence is slightly long, but nearly every clause carries operational meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description covers the return shape thoroughly: grouped-by-file callers, separate test list with node_ids, truncation fields, and unresolved call site listings. An agent knows what it will get back.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema coverage the description must compensate, and it does for all three: 'depth hops', 'at most limit callers and limit tests', and the symbol being queried. It stops short of format guidance for symbol (e.g., qualified name) and valid ranges/defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific question ('what breaks if I change this?'), resource (transitive callers of a symbol), and scope (up to depth hops), plus tests reached. 'Transitive' implicitly separates it from the direct-callers sibling, but it doesn't name 'callers' as the alternative for shallow queries.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'what breaks if I change this?' framing gives clear context for when to reach for it, and it points at 'check' via the pytest node_id follow-up. No explicit exclusions or 'use X instead for Y' guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pathARead-onlyIdempotent
Find the shortest call chain from one symbol to another, e.g. from a test or entry point to the function you are changing. Returns each hop with the file:line it is called from.
| Name | Required | Description | Default |
|---|---|---|---|
| source | Yes | ||
| target | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds return-shape context ('each hop with the file:line it is called from'), which is genuine value, but it omits failure behavior when no chain exists and whether traversal follows calls or references — gaps annotations do not fill.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler: the operation and its scope come first, the return format second. Every clause carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Read-only, two-parameter query with no output schema, so the description bears moderate load. It explains the result shape but not the no-path case, symbol format, or which direction the chain is walked — the last being essential for interpreting the result correctly among graph siblings.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the schema contributes only the names 'source' and 'target'. The description compensates partially by framing them as symbols at the ends of a call chain, but leaves symbol syntax (fully-qualified name vs file:line) and directionality (which end is traversed from) unspecified.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'find the shortest call chain from one symbol to another' — which is meaningfully distinct from the one-directional siblings (callers/callees) and the blast-radius-oriented 'impact'. It stops short of naming those siblings explicitly, so the differentiation is inferential rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The example 'from a test or entry point to the function you are changing' supplies a concrete usage scenario, which is real guidance. However, it never states when to prefer callers/callees/impact instead, nor any precondition (e.g. symbols must already exist in the index).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
callees - First observed
callers - First observed
check - First observed
impact - First observed
path
TDQS
Scored across 5 tools
Each tool answers a distinct query: direct callers, direct callees, transitive impact, shortest call chain, and test execution. The only near-overlap is callers vs. impact, but the descriptions explicitly distinguish direct vs. depth-limited transitive results, and direction (callers vs. callees) is spelled out.
All five names are short, single-token lowercase words with no mixed casing or prefix noise, which reads as a deliberate convention. Minor deviation: callers/callees/path/impact are nouns while check is a verb, but the set is still highly predictable.
Five tools is tightly scoped for a call-graph analysis server, and each maps to a real workflow step (navigate down, navigate up, assess blast radius, trace a route, verify with tests). Nothing feels redundant or padded.
The forward/reverse/path/verify lifecycle is covered, including unresolved call sites and test node_ids, which is unusually thorough. The main gap is symbol discovery: there is no search, list_symbols, or definition tool, so an agent must already know the exact symbol path or rely on external grep.
Maintenance
Related MCP Connectors
Codebase intelligence for AI agents — dead code, blast radius, ownership.
Code intelligence platform for AI agents. 20 tools for architecture, security & impact analysis.
Search indexed code, trace dependencies, assess change impact, and recall repository memory.
Deterministic context layer for your codebase: change impact, blast radius, answers with receipts.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceProvides a dependency graph of any local repository with tools for change impact, transitive dependents, health audits, and more, enabling AI coding agents to see structure and refactor safely.4,912 npm4MIT
- AlicenseAqualityDmaintenanceProvides a graph-based code analysis to coding agents, enabling tools to trace call dependencies, assess change impact, and identify hotspots using tree-sitter and Neo4j.103 npmMIT
- AlicenseBqualityBmaintenanceEnables AI agents to index local Python repositories and retrieve code intelligence through semantic search, caller lookups, hybrid context expansion, and visualization.5MIT
- FlicenseNot gradedqualityCmaintenanceEnables coding agents to query a local-first code intelligence graph of Python repositories—covering functions, classes, modules, and their relationships—via MCP, supporting subgraph retrieval, caller lookup, and impact analysis without re-reading the codebase.-