Skip to main content
Glama

workflowy_create_node

Add a new bullet point, task, or heading to your WorkFlowy outline. Specify the content, parent location, and format to organize your thoughts and tasks.

Instructions

Create a new node in WorkFlowy

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYes
parent_idNo
noteNo
layout_modeNo
positionNotop
_completedNo

Implementation Reference

  • The primary handler function for the 'workflowy_create_node' tool, decorated with @mcp.tool for registration. It constructs a NodeCreateRequest, handles rate limiting, calls the WorkFlowyClient to create the node, and returns the resulting WorkFlowyNode.
    @mcp.tool(name="workflowy_create_node", description="Create a new node in WorkFlowy")
    async def create_node(
        name: str,
        parent_id: str | None = None,
        note: str | None = None,
        layout_mode: Literal["bullets", "todo", "h1", "h2", "h3"] | None = None,
        position: Literal["top", "bottom"] = "top",
        _completed: bool = False,
    ) -> WorkFlowyNode:
        """Create a new node in WorkFlowy.
    
        Args:
            name: The text content of the node
            parent_id: ID of the parent node (optional)
            note: Additional note/description for the node
            layout_mode: Layout mode for the node (bullets, todo, h1, h2, h3) (optional)
            position: Where to place the new node - "top" (default) or "bottom"
            _completed: Whether the node should be marked as completed (not used)
    
        Returns:
            The created WorkFlowy node
        """
        client = get_client()
    
        request = NodeCreateRequest(  # type: ignore[call-arg]
            name=name,
            parent_id=parent_id,
            note=note,
            layoutMode=layout_mode,
            position=position,
        )
    
        if _rate_limiter:
            await _rate_limiter.acquire()
    
        try:
            node = await client.create_node(request)
            if _rate_limiter:
                _rate_limiter.on_success()
            return node
        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 BaseModel defining the input schema (NodeCreateRequest) for the workflowy_create_node tool, including validation for required fields like name and optional parameters matching the handler arguments.
    class NodeCreateRequest(BaseModel):
        """Request payload for creating a new node."""
    
        parent_id: str | None = Field(None, description="Parent node ID ('None' for root level)")
        name: str = Field(..., description="Text content (required)")
        note: str | None = Field(None, description="Note content (optional)")
        layoutMode: Literal["bullets", "todo", "h1", "h2", "h3"] | None = Field(
            None, description="Display mode (bullets, todo, h1, h2, h3)"
        )
        position: Literal["top", "bottom"] | None = Field(
            "top", description="Position: 'top' or 'bottom' (default: 'top')"
        )
    
        @field_validator("name")
        @classmethod
        def validate_name(cls, v: str) -> str:
            """Ensure name is non-empty."""
            if not v or not v.strip():
                raise ValueError("Node name must be non-empty")
            return v
    
        @field_validator("parent_id")
        @classmethod
        def validate_parent_id(cls, v: str | None) -> str | None:
            """Keep parent_id as-is - None means root level."""
            return v
  • Pydantic BaseModel WorkFlowyNode used as the return type for the workflowy_create_node tool, defining the structure of the created node with recursive children support, properties, and custom serialization.
    class WorkFlowyNode(BaseModel):
        """Represents a single node in the WorkFlowy outline hierarchy."""
    
        # API fields (what the API actually returns)
        id: str = Field(..., description="Unique identifier for the node")
        name: str | None = Field(
            None, validation_alias=AliasChoices("name", "nm"), description="Text content of the node"
        )
        note: str | None = Field(
            None,
            validation_alias=AliasChoices("note", "no"),
            description="Note content attached to the node",
        )
        priority: int | None = Field(None, description="Sort order")
        data: dict[str, Any] | None = Field(None, description="Node data including layoutMode")
        createdAt: int | None = Field(
            None,
            validation_alias=AliasChoices("createdAt", "created"),
            description="Creation timestamp (Unix timestamp)",
        )
        modifiedAt: int | None = Field(
            None,
            validation_alias=AliasChoices("modifiedAt", "modified"),
            description="Last modification timestamp",
        )
        completedAt: int | None = Field(
            None, description="Completion timestamp (null if not completed)"
        )
    
        # Nested structure fields
        children: list["WorkFlowyNode"] | None = Field(None, alias="ch", description="Child nodes")
        parent_id: str | None = Field(None, alias="parentId", description="Parent node ID")
    
        # Handle 'cp' field for backward compatibility - we'll compute from completedAt
        completed_flag: bool | None = Field(
            None, alias="cp", description="Completion status (for tests)"
        )
    
        @property
        def layoutMode(self) -> str | None:
            """Extract layoutMode from data field."""
            if self.data and isinstance(self.data, dict):
                return self.data.get("layoutMode")
            return None
    
        # Backward compatibility aliases for tests
        @property
        def nm(self) -> str | None:
            """Backward compatibility for name field."""
            return self.name
    
        @property
        def no(self) -> str | None:
            """Backward compatibility for note field."""
            return self.note
    
        @property
        def cp(self) -> bool:
            """Backward compatibility for completed status."""
            # Use completed_flag if it was set (from tests), otherwise check completedAt
            if self.completed_flag is not None:
                return self.completed_flag
            return self.completedAt is not None
    
        @property
        def ch(self) -> list["WorkFlowyNode"] | None:
            """Backward compatibility for children field."""
            return self.children
    
        @property
        def created(self) -> int:
            """Backward compatibility for created timestamp."""
            return self.createdAt or 0
    
        @property
        def modified(self) -> int:
            """Backward compatibility for modified timestamp."""
            return self.modifiedAt or 0
    
        @field_validator("id")
        @classmethod
        def validate_id(cls, v: str) -> str:
            """Ensure ID is non-empty."""
            if not v or not v.strip():
                raise ValueError("Node ID must be non-empty")
            return v
    
        @field_validator("createdAt", "modifiedAt", "completedAt")
        @classmethod
        def validate_timestamp(cls, v: int | None) -> int | None:
            """Ensure timestamps are positive."""
            if v is not None and v <= 0:
                raise ValueError("Timestamp must be positive")
            return v
    
        def model_dump(self, **kwargs: Any) -> dict[str, Any]:
            """Custom serialization to include backward compatibility fields."""
            data: dict[str, Any] = super().model_dump(**kwargs)
    
            # Add backward compatibility fields for tests
            data["nm"] = self.name
            data["no"] = self.note
            data["cp"] = self.cp
            data["ch"] = self.children
            data["created"] = self.createdAt or 0
            data["modified"] = self.modifiedAt or 0
    
            return data
    
        class Config:
            """Pydantic model configuration."""
    
            populate_by_name = True  # Allow both field names and aliases
            json_schema_extra = {
                "example": {
                    "id": "node-123",
                    "name": "Example Node",
                    "note": "This is a note",
                    "priority": 1,
                    "layoutMode": "bullets",
                    "createdAt": 1704067200,
                    "modifiedAt": 1704067200,
                    "completedAt": None,
                    "children": [],
                }
            }
    
    
    # Enable forward references for recursive model
    WorkFlowyNode.model_rebuild()

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

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