Skip to content

api_surface

Extracts only the public top-level types and functions of a codebase — grouped by package, with public members nested under their owner type — so an agent wiring into unfamiliar code sees what it can call without reading implementation bodies.

  • When wiring into a component or package you haven’t read, to see what’s actually callable.
  • Before extracting or refactoring a module, to know which entities are part of its public contract.
  • When writing integration code and you’re unsure whether a name is internal or public.
Name Type Default Description
dep_graph DependencyGraph required Dependency graph to extract the public surface from.
scope str | None None Optional package-prefix filter — only entities whose recorded package attribute starts with this string are included.
include_members bool True Nest public methods/fields under their owner type when True.
Field Type Meaning
scope str | None Echoes the input scope.
note str Fixed disclaimer explaining what “public” and “signature” mean here: visibility is derived from the underscore-prefix naming convention (never read from a field), and a “signature” is name + kind (+ superclass/interfaces for types) — no parameter or return types, since parsers don’t store them.
num_public_entities int Count of top-level public entities across all packages (nested members aren’t counted separately).
packages list[dict] {package, entities}, entities sorted by fqn. Each entity is {fqn, name, kind, depended_on, superclass?, interfaces?, members?}depended_on is true when something outside the entity’s own file has an edge into it; members (when include_members=True) is {fqn, name, kind} for each public method/field owned by that type.

Both calls run against the same parse session as the other star-tool pages (dep_graph="fe538dededcc", click’s src/click/ tree). click has a flat package layout — every module’s recorded package is the bare string "click" (or ""), never a per-module dotted path like click.exceptions.

→ api_surface(dep_graph="fe538dededcc", scope="click")
{
"scope": "click",
"note": "Public API surface only: implementation bodies, private members, and parameter/return types are omitted …",
"num_public_entities": 125,
"packages": [
{
"package": "click",
"entities": [
{ "fqn": "click._compat.get_best_encoding", "name": "get_best_encoding", "kind": "function", "depended_on": false },
{
"fqn": "click._termui_impl.MaybeStripAnsi", "name": "MaybeStripAnsi", "kind": "class",
"depended_on": false, "superclass": "io.TextIOWrapper",
"members": [ { "fqn": "click._termui_impl.MaybeStripAnsi.write", "name": "write", "kind": "method" } ]
}
// … 123 more entities across click/*.py
]
}
]
}

The second call uses a scope that looks intuitive from the file layout (click/exceptions.py) but doesn’t match anything, because package is recorded per-module, not per-directory:

→ api_surface(dep_graph="fe538dededcc", scope="click.exceptions")
{
"scope": "click.exceptions",
"note": "Public API surface only: implementation bodies, private members, and parameter/return types are omitted …",
"num_public_entities": 0,
"packages": []
}
  • A scope miss looks exactly like a legitimately empty package, and scope only ever matches a package field prefix — never a file path. num_public_entities: 0 and packages: [] carry no error or note flag distinguishing “nothing matched” from “this package is genuinely empty” — and since scope filters on a startswith of the entity’s package field, a directory-shaped guess like "click.exceptions" never matches on a flat-package repo such as click (parse’s num_packages: 2"" and "click", so "click" is the only useful prefix). Confirm the scope string against the graph’s actual package values (or just call with scope=None first) before trusting a 0.
  • depended_on is a structural signal scoped to the file, not the component — it’s true when an entity has an incoming edge from outside its own file. diff_impact’s broken_contracts uses the same kind of signal but against the changed-files boundary, and explain_component’s api_surface field uses it against a recovered-component boundary — three different boundaries for the same underlying “who depends on this from outside” check.
  • Members without a resolvable owner are silently skipped, not surfaced as an error — if a method’s properties["owner"] FQN isn’t itself in the graph, that method just doesn’t appear under any members list.
  • dep_graph is resolved from the session store but nothing new is stored back. The MCP wrapper looks up the dep_graph session ID once, then returns a plain dict with no session_id/type — there’s no session to chain into a follow-up call the way ingest/parse/recover chain into each other.
  • This tool’s return dict carries no graph/architecture key, so a tight max_tokens runs through the generic enforce_budget degrade path (drop the largest top-level key repeatedly, flag _budget_truncated: true) — on a large packages list that likely means losing the entire surface rather than a partial one.