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 to call
Section titled “When to call”- 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.
Parameters
Section titled “Parameters”| 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. |
Returns
Section titled “Returns”| 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. |
Examples
Section titled “Examples”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": []}Things worth knowing
Section titled “Things worth knowing”- A scope miss looks exactly like a legitimately empty package, and
scopeonly ever matches apackagefield prefix — never a file path.num_public_entities: 0andpackages: []carry noerrorornoteflag distinguishing “nothing matched” from “this package is genuinely empty” — and sincescopefilters on astartswithof the entity’spackagefield, a directory-shaped guess like"click.exceptions"never matches on a flat-package repo such as click (parse’snum_packages: 2—""and"click", so"click"is the only useful prefix). Confirm the scope string against the graph’s actualpackagevalues (or just call withscope=Nonefirst) before trusting a0. depended_onis a structural signal scoped to the file, not the component — it’struewhen an entity has an incoming edge from outside its own file.diff_impact’sbroken_contractsuses the same kind of signal but against the changed-files boundary, andexplain_component’sapi_surfacefield 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 anymemberslist. dep_graphis resolved from the session store but nothing new is stored back. The MCP wrapper looks up thedep_graphsession ID once, then returns a plain dict with nosession_id/type— there’s no session to chain into a follow-up call the wayingest/parse/recoverchain into each other.- This tool’s return dict carries no
graph/architecturekey, so a tightmax_tokensruns through the genericenforce_budgetdegrade path (drop the largest top-level key repeatedly, flag_budget_truncated: true) — on a largepackageslist that likely means losing the entire surface rather than a partial one.