search_tree
Search the entire passive skill tree for nodes matching a keyword, including unallocated nodes. Filter by class to focus on reachable nodes near specific starting locations.
Instructions
Search the full passive skill tree for nodes matching a keyword.
Unlike search_passives, this searches ALL nodes in the tree — not just those allocated in the loaded build. Use this to discover nodes you haven't taken yet.
Each result includes:
allocated: whether the node is already taken in the current build
ascendancy: the ascendancy class that unlocks this node, or "" for generic nodes
distance_from_build: minimum passive points needed to reach this node from the current build (null if no build is loaded or the node is unreachable)
Use classes to restrict results to one or more class regions (Voronoi partition). This eliminates nodes from the opposite side of the tree that are never reachable in practice. Valid class names: Druid, Huntress, Mercenary, Monk, Sorceress, Warrior.
A build does not need to be loaded. Tree data must be present.
Examples: search_tree("life") → all life notables across the full tree search_tree("lightning", classes=["Monk"]) → lightning nodes near the Monk start search_tree("life", classes=["Monk", "Sorceress"]) → life nodes in two regions search_tree("Invoker") → all Invoker ascendancy nodes
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| include_small_nodes | No | ||
| classes | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/poe2_mcp/server.py:205-258 (handler)The search_tree tool handler function decorated with @mcp.tool(). It searches the full passive skill tree for nodes matching a keyword (case-insensitive), with optional filtering by class region (Voronoi partition) and whether to include small nodes. Returns nodes with id, name, type, stats, ascendancy, allocation status, and distance from the current build.
@mcp.tool() def search_tree( query: str, include_small_nodes: bool = False, classes: list[str] | None = None, ) -> list[dict]: """ Search the full passive skill tree for nodes matching a keyword. Unlike search_passives, this searches ALL nodes in the tree — not just those allocated in the loaded build. Use this to discover nodes you haven't taken yet. Each result includes: - allocated: whether the node is already taken in the current build - ascendancy: the ascendancy class that unlocks this node, or "" for generic nodes - distance_from_build: minimum passive points needed to reach this node from the current build (null if no build is loaded or the node is unreachable) Use classes to restrict results to one or more class regions (Voronoi partition). This eliminates nodes from the opposite side of the tree that are never reachable in practice. Valid class names: Druid, Huntress, Mercenary, Monk, Sorceress, Warrior. A build does not need to be loaded. Tree data must be present. Examples: search_tree("life") → all life notables across the full tree search_tree("lightning", classes=["Monk"]) → lightning nodes near the Monk start search_tree("life", classes=["Monk", "Sorceress"]) → life nodes in two regions search_tree("Invoker") → all Invoker ascendancy nodes """ if _tree is None: return [{"error": "Tree data not loaded — passive tree search unavailable."}] allocated_ids: set[int] = set(_build.allocated_node_ids) if _build is not None else set() distance_map = _build_distance_map(allocated_ids) if allocated_ids else {} matches = _tree.search(query, classes=classes) if not include_small_nodes: matches = [n for n in matches if n.is_keystone or n.is_notable or n.is_mastery] return [ { "id": n.id, "name": n.name, "type": _node_type(n), "stats": n.stats, "ascendancy": n.ascendancy_name, "allocated": n.id in allocated_ids, "distance_from_build": distance_map.get(n.id), } for n in matches ] - src/poe2_mcp/server.py:1-14 (registration)The search_tree tool is registered via the @mcp.tool() decorator on line 205. The MCP server is instantiated on line 20 as FastMCP('poe2-mcp'), and the decorator pattern registers the function as a tool automatically.
""" PoE2 MCP server. Tools: load_build — decode a PoB export code or pobb.in URL get_stats — computed character stats (life, mana, damage, resists, …) get_passives — allocated passive nodes (with names/stats if tree data is loaded) get_items — equipped items and their mods get_skills — skill socket groups and gem links search_passives — find allocated passives matching a keyword search_tree — find any node in the full passive tree (allocated or not) """ from mcp.server.fastmcp import FastMCP - src/poe2_mcp/tree/__init__.py:1-3 (schema)Exports the PassiveTree class and TreeNode dataclass which define the schema used by search_tree. TreeNode includes id, name, stats, is_keystone, is_notable, is_mastery, ascendancy_name, and neighbors fields.
from .loader import PassiveTree, TreeNode, load_tree, load_default_tree __all__ = ["PassiveTree", "TreeNode", "load_tree", "load_default_tree"] - src/poe2_mcp/tree/loader.py:57-70 (helper)The PassiveTree.search() method is the core search helper called by the search_tree handler. It performs a case-insensitive search across all node names and stats, with optional class region filtering using Voronoi partition data.
def search(self, query: str, classes: list[str] | None = None) -> list[TreeNode]: """Return all nodes whose name or stats contain the query string (case-insensitive). If classes is provided, only nodes belonging to those Voronoi regions are returned. """ q = query.lower() results = [ node for node in self._nodes.values() if q in node.name.lower() or any(q in s.lower() for s in node.stats) ] if classes: class_set = set(classes) results = [n for n in results if self._voronoi.get(n.id, "") in class_set] return results - src/poe2_mcp/tree/loader.py:20-30 (schema)The TreeNode dataclass defines the structure of tree nodes returned by search_tree queries.
@dataclass class TreeNode: id: int name: str stats: list[str] = field(default_factory=list) is_keystone: bool = False is_notable: bool = False is_mastery: bool = False ascendancy_name: str = "" neighbors: list[int] = field(default_factory=list)