Skip to main content
Glama
okeefeco

PyEye Server

by okeefeco

expand

Traverse one outbound edge from a resolved handle to retrieve adjacent symbols as stubs. Supports edges like members, callees, imports, subclasses, and enclosing_scope.

Instructions

Python: Expand one outbound edge from a canonical handle (single hop).

The traversal primitive that walks ONE edge from a source handle and returns adjacent symbols as lightweight Stubs. Use resolve()/inspect() first to obtain a canonical handle, then call expand() to traverse.

Supported edges (the complete static/outbound set):

  • members — class/module → direct members (attributes, methods, nested classes). stubs: [] means the class/module was found but has no members; that is NOT the same as unsupported. Static-surface ceiling: members are read from source; runtime-injected members (metaclass / setattr / __getattr__ / type() / __init_subclass__) are NOT captured — e.g. a Django Model shows none of its metaclass-injected _meta / objects / DoesNotExist.

  • callees — function/method → forward static call targets. Includes project symbols and stdlib/external symbols reachable via Jedi's goto. Dynamic calls (un-inferable parameters, getattr, lambdas, etc.) are counted in unresolved_call_sites rather than invented.

  • imported_by — module → the project modules that import it (module Stubs), computed by static AST import-graph reversal (no reverse symbol search). Covers importers anywhere in the project including tests and standalone scripts. Non-module handles return the unsupported branch with reason: "not_yet_implemented" (symbol-level imported_by is not yet implemented). Ceiling: runtime-dynamic imports (importlib/__import__ with computed targets) are not detected.

  • subclasses — class → the project classes that directly subclass it (class Stubs), computed by an AST class-graph walk + forward goto (no reverse symbol search). Returns the DIRECT (depth-1) subclasses only (#422) — one hop, symmetric with superclasses; the full transitive closure is served by trace(follow=["subclasses"], max_depth=k, max_nodes=N), which carries the cap + truncated contract. A class result includes a static transitive_hint field pointing to that trace route. subclasses is an expand-only edge: inspect does NOT measure it (dropped in #392); a cheap direct count is gated on the Pyright reference backend / class-graph cache (#333/#397), because even the direct count is a reverse query needing the same project-wide scan as callers/references. stubs: [] means the class has no project subclasses (measured-none). A non-class handle also returns the supported branch with stubs: [] (and no transitive_hint) — only a class CAN be subclassed, so [] is true by definition, not an absence-vs-zero lie. Static-surface ceiling: the result is complete only over literal class B(A): subclassing; dynamically-created subclasses (type('B', (A,), {}), factory-built classes, __init_subclass__ registration) are NOT captured.

  • superclasses — class → its base classes (class Stubs), resolved by Jedi from the class definition (no reverse search). A non-class handle returns stubs: [] ([] true by definition, as with subclasses).

  • imports — module → the symbols/modules it imports (Stubs), computed by static AST + forward goto. stubs: [] is measured-none. A non-module handle returns the unsupported branch with reason: "not_yet_implemented" (mirrors imported_by).

  • enclosing_scope — symbol → its immediate lexical enclosing scope (the inverse of members), resolved by Jedi parent(): a method → its class, a nested def/class → its enclosing def/class, a top-level def/class/variable → its module. At most ONE Stub. A module returns stubs: [] (a module has no enclosing lexical scope — packages are not lexical scopes); [] is therefore measured-empty, never unsupported.

Unsupported edges return the unsupported branch (never raise):

  • Inbound/reference edges (callers, references, overrides, …) require the Pyright reference backend (#333) and return unsupported: true, reason: "deferred_reference_backend".

  • Wrong-kind handles (e.g. imported_by on a non-module) return the unsupported branch with reason: "not_yet_implemented".

  • Unrecognised edge names return reason: "unknown_edge".

Response shape — discriminated union:

Supported branch ("unsupported" key absent): ::

{ "source": str,                 # canonical source handle
  "edge":   str,
  "stubs":  [Stub, ...],         # [] == measured-empty (NOT unsupported)
  "unresolved_call_sites": int   # callees ONLY; absent for members }

Unsupported branch ("stubs" key absent): ::

{ "source": str,
  "edge":   str,
  "unsupported": True,
  "reason":  str,               # deferred_reference_backend |
                                # not_yet_implemented | unknown_edge
  "detail":  str }              # human-readable explanation

Each Stub carries: handle, kind, scope, line_start, line_end, and signature when Jedi yields one (always for class/function/method; also any name whose inferred type is callable).

Relationship to deprecated tools: members supersedes the deprecated find_subclasses/find_symbol pattern for enumerating class members. callees supersedes manual get_call_hierarchy usage for forward edges. Both deprecated tools remain registered until Phase B migration.

Args: handle: Canonical Python dotted-name string (from resolve/inspect). edge: The outbound edge to expand (e.g. "members", "callees"). project_path: Project root path (default: current directory).

Returns: ExpandResult dict — supported branch or unsupported branch (see above). Never raises; unresolvable source handles yield graceful supported-empty results consistent with inspect()'s minimal-node contract.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
handleYes
edgeYes
project_pathNo.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite no annotations, the description comprehensively details behavioral traits for each edge: static-surface ceilings, response shape, error handling (never raises), and exact conditions for unsupported branches. It discloses limitations like runtime-injected members and dynamic imports.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is very lengthy and could be more concise. While it is well-structured with bullet points and code blocks, the verbosity may hinder quick comprehension. However, it front-loads the one-sentence summary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity and many sibling tools (22 listed), the description covers all necessary context: when to use, inputs, outputs for each branch, edge-specific details, behavioral ceilings, and relationship to deprecated tools. It is exceptionally complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage, but the description adds meaning by explaining that handle is a canonical dotted name, edge is a specific outbound edge with enumerated possibilities, and project_path defaults to current directory. Though not all parameters are explicitly described in a structured way, the context provided is sufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it expands one outbound edge from a canonical handle, lists all supported edges with examples, and distinguishes from sibling tools like resolve(), inspect(), and trace(). It also mentions superseding deprecated tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs to use resolve()/inspect() first, then expand(). Explains when to use trace() for transitive closure. Lists unsupported edges and why they are unsupported, providing clear guidance on when not to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/okeefeco/pyeye-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server