Skip to main content
Glama

workflowy_create_node

Create new outline nodes in WorkFlowy with customizable properties like name, parent hierarchy, notes, layout modes, and completion status for organizing tasks and information.

Instructions

Create a new node in WorkFlowy

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYes
parent_idNo
noteNo
layout_modeNo
positionNotop
_completedNo

Implementation Reference

  • Handler function for creating a single node. Validates secret code, constructs NodeCreateRequest, calls WorkFlowyClient.create_node, handles rate limiting and returns the created node with a warning.
    @mcp.tool(name="workflowy_create_single_node__WARNING__prefer_ETCH", description="⚠️ WARNING: Prefer workflowy_etch (ETCH) instead. This creates ONE node only.") 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"] = "bottom", _completed: bool = False, secret_code: str | None = None, ) -> dict: """Create a SINGLE node in WorkFlowy. ⚠️ WARNING: Prefer workflowy_etch (ETCH) for creating 2+ nodes. This tool is ONLY for: - Adding one VYRTHEX to existing log (real-time work) - One quick update to a known node - Live work in progress 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 - "bottom" (default) or "top" _completed: Whether the node should be marked as completed (not used) secret_code: Authorization code from Dan (required for WARNING functions) Returns: Dictionary with node data and warning message """ # 🔐 SECRET CODE VALIDATION is_valid, error = validate_secret_code(secret_code, "workflowy_create_single_node__WARNING__prefer_ETCH") if not is_valid: raise ValueError(error) 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 data with warning message return { **node.model_dump(), "_warning": "⚠️ WARNING: You just created a SINGLE node. For 2+ nodes, use workflowy_etch instead (same performance, more capability)." } 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
  • Registers the create_node function as an MCP tool named 'workflowy_create_single_node__WARNING__prefer_ETCH' (referred to as 'workflowy_create_node' in tests).
    @mcp.tool(name="workflowy_create_single_node__WARNING__prefer_ETCH", description="⚠️ WARNING: Prefer workflowy_etch (ETCH) instead. This creates ONE node only.")
  • Pydantic model defining the input parameters for node creation: name (required), parent_id, note, layoutMode, position with validation.
    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 model for WorkFlowyNode used as output type for the created node, with aliases for API compatibility 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/daniel347x/workflowy-mcp-fixed'

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