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
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| parent_id | No | ||
| note | No | ||
| layout_mode | No | ||
| position | No | top | |
| _completed | No |
Implementation Reference
- src/workflowy_mcp/server.py:77-122 (handler)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()