Skip to main content
Glama

get_object_tree

Retrieve the hierarchical structure and screenshot of a Penpot design object to analyze its composition and relationships within design files.

Instructions

Get the object tree structure for a Penpot object ("tree" field) with rendered screenshot image of the object ("image.mcp_uri" field). Args: file_id: The ID of the Penpot file object_id: The ID of the object to retrieve fields: Specific fields to include in the tree (call "penpot_tree_schema" resource/tool for available fields) depth: How deep to traverse the object tree (-1 for full depth) format: Output format ('json' or 'yaml')

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_idYes
object_idYes
fieldsYes
depthNo
formatNojson

Implementation Reference

  • The primary handler for the 'get_object_tree' MCP tool. Retrieves cached file data, extracts subtree using helper function, generates rendered image with URI, handles JSON/YAML formatting, and manages errors.
    @self.mcp.tool() def get_object_tree( file_id: str, object_id: str, fields: List[str], depth: int = -1, format: str = "json" ) -> dict: """Get the object tree structure for a Penpot object ("tree" field) with rendered screenshot image of the object ("image.mcp_uri" field). Args: file_id: The ID of the Penpot file object_id: The ID of the object to retrieve fields: Specific fields to include in the tree (call "penpot_tree_schema" resource/tool for available fields) depth: How deep to traverse the object tree (-1 for full depth) format: Output format ('json' or 'yaml') """ try: file_data = get_cached_file(file_id) if "error" in file_data: return file_data result = get_object_subtree_with_fields( file_data, object_id, include_fields=fields, depth=depth ) if "error" in result: return result simplified_tree = result["tree"] page_id = result["page_id"] final_result = {"tree": simplified_tree} try: image = export_object( file_id=file_id, page_id=page_id, object_id=object_id ) image_id = hashlib.md5(f"{file_id}:{object_id}".encode()).hexdigest() self.rendered_components[image_id] = image # Image URI preferences: # 1. HTTP server URL if available # 2. Fallback to MCP resource URI image_uri = f"render_component://{image_id}" if hasattr(image, 'http_url'): final_result["image"] = { "uri": image.http_url, "mcp_uri": image_uri, "format": image.format if hasattr(image, 'format') else "png" } else: final_result["image"] = { "uri": image_uri, "format": image.format if hasattr(image, 'format') else "png" } except Exception as e: final_result["image_error"] = str(e) if format.lower() == "yaml": try: import yaml yaml_result = yaml.dump(final_result, default_flow_style=False, sort_keys=False) return {"yaml_result": yaml_result} except ImportError: return {"format_error": "YAML format requested but PyYAML package is not installed"} except Exception as e: return {"format_error": f"Error formatting as YAML: {str(e)}"} return final_result except Exception as e: return self._handle_api_error(e)
  • Key helper function called by the handler to construct the filtered object subtree, respecting field selection, depth limits, circular reference detection, and recursive child traversal.
    def get_object_subtree_with_fields(file_data: Dict[str, Any], object_id: str, include_fields: Optional[List[str]] = None, depth: int = -1) -> Dict[str, Any]: """ Get a filtered tree representation of an object with only specified fields. This function finds an object in the Penpot file data and returns a subtree with the object as the root, including only the specified fields and limiting the depth of the tree if requested. Args: file_data: The Penpot file data object_id: The ID of the object to get the tree for include_fields: List of field names to include in the output (None means include all) depth: Maximum depth of the tree (-1 means no limit) Returns: Dictionary containing the filtered tree or an error message """ try: # Get the content from file data content = file_data.get('data', file_data) # Find which page contains the object page_id = find_page_containing_object(content, object_id) if not page_id: return {"error": f"Object {object_id} not found in file"} # Get the page data page_data = content.get('pagesIndex', {}).get(page_id, {}) objects_dict = page_data.get('objects', {}) # Check if the object exists in this page if object_id not in objects_dict: return {"error": f"Object {object_id} not found in page {page_id}"} # Track visited nodes to prevent infinite loops visited = set() # Function to recursively build the filtered object tree def build_filtered_object_tree(obj_id: str, current_depth: int = 0): if obj_id not in objects_dict: return None # Check for circular reference if obj_id in visited: # Return a placeholder to indicate circular reference return { 'id': obj_id, 'name': objects_dict[obj_id].get('name', 'Unnamed'), 'type': objects_dict[obj_id].get('type', 'unknown'), '_circular_reference': True } # Mark this object as visited visited.add(obj_id) obj_data = objects_dict[obj_id] # Create a new dict with only the requested fields or all fields if None if include_fields is None: filtered_obj = obj_data.copy() else: filtered_obj = {field: obj_data[field] for field in include_fields if field in obj_data} # Always include the id field filtered_obj['id'] = obj_id # If depth limit reached, don't process children if depth != -1 and current_depth >= depth: # Remove from visited before returning visited.remove(obj_id) return filtered_obj # Find all children of this object children = [] for child_id, child_data in objects_dict.items(): if child_data.get('parentId') == obj_id: child_tree = build_filtered_object_tree(child_id, current_depth + 1) if child_tree: children.append(child_tree) # Add children field only if we have children if children: filtered_obj['children'] = children # Remove from visited after processing visited.remove(obj_id) return filtered_obj # Build the filtered tree starting from the requested object object_tree = build_filtered_object_tree(object_id) if not object_tree: return {"error": f"Failed to build object tree for {object_id}"} return { "tree": object_tree, "page_id": page_id } except Exception as e: return {"error": str(e)}
  • MCP resource that serves the JSON schema defining available fields in Penpot object trees, referenced in the get_object_tree tool documentation.
    @self.mcp.resource("penpot://tree-schema", mime_type="application/schema+json") def penpot_tree_schema() -> dict: """Provide the Penpot object tree schema as JSON.""" schema_path = os.path.join(config.RESOURCES_PATH, 'penpot-tree-schema.json') try: with open(schema_path, 'r') as f: return json.load(f) except Exception as e: return {"error": f"Failed to load tree schema: {str(e)}"}
  • Decorator that registers the get_object_tree function as an MCP tool.
    @self.mcp.tool() def get_object_tree(

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/montevive/penpot-mcp'

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