Skip to main content
Glama

workflowy_list_nodes

Retrieve child nodes from a WorkFlowy outline to navigate hierarchical task structures. This tool helps organize and access nested information within your outlines.

Instructions

DEPRECATED: Use workflowy_glimpse (GLIMPSE) instead

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
parent_idNo

Implementation Reference

  • Registration and deprecated handler stub for 'workflowy_list_nodes' tool. Raises ValueError with deprecation notice recommending workflowy_glimpse.
    @mcp.tool(name="workflowy_list_nodes", description="DEPRECATED: Use workflowy_glimpse (GLIMPSE) instead")
    async def list_nodes_base(parent_id: str | None = None) -> dict:
        """Deprecated - use GLIMPSE instead."""
        raise ValueError("""⚠️ FUNCTION RENAMED
    
    The function 'workflowy_list_nodes' has been renamed to 'workflowy_list_nodes__WARNING__prefer_glimpse'.
    
    BUT MORE IMPORTANTLY: Use workflowy_glimpse (GLIMPSE command) instead!
    
    ✅ RECOMMENDED:
      workflowy_glimpse(node_id="...")
      
    Returns: {"root": {...}, "children": [...]} with complete tree structure.
    
    GLIMPSE is better:
    - Gets full nested tree (not just direct children)
    - Gets root node metadata
    - More efficient
    
    📚 Build the GLIMPSE habit!
    """)
  • Full handler logic (under warning suffixed name). Validates secret_code, creates NodeListRequest, calls client.list_nodes, returns nodes list.
    @mcp.tool(name="workflowy_list_nodes__WARNING__prefer_glimpse", description="⚠️ WARNING: Prefer workflowy_glimpse (GLIMPSE) for reading trees. List WorkFlowy nodes (omit parent_id for root)")
    async def list_nodes(
        parent_id: str | None = None,
        secret_code: str | None = None,
    ) -> dict:
        """List WorkFlowy nodes.
    
        Args:
            parent_id: ID of parent node to list children for
                       (omit or pass None to list root nodes - parameter won't be sent to API)
            secret_code: Authorization code from Dan (required for WARNING functions)
    
        Returns:
            Dictionary with 'nodes' list and 'total' count
        """
        # 🔐 SECRET CODE VALIDATION
        is_valid, error = validate_secret_code(secret_code, "workflowy_list_nodes__WARNING__prefer_glimpse")
        if not is_valid:
            raise ValueError(error)
        
        client = get_client()
    
        request = NodeListRequest(  # type: ignore[call-arg]
            parentId=parent_id,
        )
    
        if _rate_limiter:
            await _rate_limiter.acquire()
    
        try:
            nodes, total = await client.list_nodes(request)
            if _rate_limiter:
                _rate_limiter.on_success()
            return {
                "nodes": [node.model_dump() for node in nodes],
                "total": total,
                "_warning": "⚠️ For reading multiple nodes or full trees, use workflowy_glimpse (GLIMPSE) instead for efficiency"
            }
        except Exception as e:
            if _rate_limiter and hasattr(e, "__class__") and e.__class__.__name__ == "RateLimitError":
                _rate_limiter.on_rate_limit(getattr(e, "retry_after", None))
            raise
  • Pydantic input schema model NodeListRequest used by list_nodes handlers.
    class NodeListRequest(BaseModel):
        """Request parameters for listing nodes."""
    
        parentId: str | None = Field(None, description="Parent node ID to list children for")
  • Core client-side implementation: HTTP GET /nodes with parent_id param, response parsing to WorkFlowyNode list, full retry/rate-limit handling.
    async def list_nodes(self, request: NodeListRequest, max_retries: int = 10) -> tuple[list[WorkFlowyNode], int]:
        """List nodes with optional filtering and exponential backoff retry.
        
        Args:
            request: Node list request
            max_retries: Maximum retry attempts (default 10)
        """
        import asyncio
    
        logger = _ClientLogger()
        retry_count = 0
        base_delay = 1.0
        
        while retry_count < max_retries:
            # Force delay at START of each iteration (rate limit protection)
            await asyncio.sleep(API_RATE_LIMIT_DELAY)
            
            try:
                # exclude_none=True ensures parent_id is omitted entirely for root nodes
                # (API requires absence of parameter, not null value)
                # Build params manually to ensure snake_case (API expects parent_id not parentId)
                params = {}
                if request.parentId is not None:
                    params['parent_id'] = request.parentId
                response = await self.client.get("/nodes", params=params)
                response_data: list[Any] | dict[str, Any] = await self._handle_response(response)
    
                # Assuming API returns an array of nodes directly
                # (Need to verify actual response structure)
                nodes: list[WorkFlowyNode] = []
                if isinstance(response_data, dict):
                    if "nodes" in response_data:
                        nodes = [WorkFlowyNode(**node_data) for node_data in response_data["nodes"]]
                elif isinstance(response_data, list):
                    nodes = [WorkFlowyNode(**node_data) for node_data in response_data]
    
                total = len(nodes)  # API doesn't provide a total count
                return nodes, total
                
            except RateLimitError as e:
                retry_count += 1
                retry_after = getattr(e, 'retry_after', None) or (base_delay * (2 ** retry_count))
                logger.warning(
                    f"Rate limited on list_nodes. Retry after {retry_after}s. "
                    f"Attempt {retry_count}/{max_retries}"
                )
                
                if retry_count < max_retries:
                    await asyncio.sleep(retry_after)
                else:
                    raise
                    
            except NetworkError as e:
                retry_count += 1
                logger.warning(
                    f"Network error on list_nodes: {e}. Retry {retry_count}/{max_retries}"
                )
                
                if retry_count < max_retries:
                    await asyncio.sleep(base_delay * (2 ** retry_count))
                else:
                    raise
                    
            except httpx.TimeoutException as err:
                retry_count += 1
                
                logger.warning(
                    f"Timeout error: {err}. Retry {retry_count}/{max_retries}"
                )
                
                if retry_count < max_retries:
                    await asyncio.sleep(base_delay * (2 ** retry_count))
                else:
                    raise TimeoutError("list_nodes") from err
        
        raise NetworkError("list_nodes failed after maximum retries")

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/daniel347x/workflowy-mcp-fixed'

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