Skip to main content
Glama
mickm3n

Roam Research MCP Server

by mickm3n

write_to_page

Add hierarchical markdown content to existing Roam Research pages, creating structured blocks with automatic indentation detection for nested bullet points.

Instructions

Write hierarchical markdown content to a specific page in Roam Research.

Creates a new block structure on the specified page with automatic indentation
detection. Supports nested bullet points and maintains proper parent-child
relationships between blocks. Content is appended to the end of the page.

Args:
    page_name: Exact name of the target page (case-sensitive, must exist)
    content: Hierarchical markdown content using '- ' prefix and indentation
            Format: "- Main topic
- Subtopic
    - Details"
            Supports any indentation level with automatic detection
            
Returns:
    JSON string containing:
    - result: "success" if completed
    - blocks_created: Total number of blocks created (including children)
    - details: Array of individual block creation results
    
Examples:
    write_to_page("Project Notes", "- New milestone
- Task 1
- Task 2")
    write_to_page("信用卡", "- [[銀行/國泰]]
- 現金回饋 2%")

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
page_nameYes
contentYes

Implementation Reference

  • main.py:453-483 (handler)
    MCP tool handler for write_to_page. Decorated with @mcp.tool(), handles input parameters, calls Roam client method, and returns JSON-formatted result or error.
    @mcp.tool()
    async def write_to_page(page_name: str, content: str) -> str:
        """Write hierarchical markdown content to a specific page in Roam Research.
        
        Creates a new block structure on the specified page with automatic indentation
        detection. Supports nested bullet points and maintains proper parent-child
        relationships between blocks. Content is appended to the end of the page.
    
        Args:
            page_name: Exact name of the target page (case-sensitive, must exist)
            content: Hierarchical markdown content using '- ' prefix and indentation
                    Format: "- Main topic\n    - Subtopic\n        - Details"
                    Supports any indentation level with automatic detection
                    
        Returns:
            JSON string containing:
            - result: "success" if completed
            - blocks_created: Total number of blocks created (including children)
            - details: Array of individual block creation results
            
        Examples:
            write_to_page("Project Notes", "- New milestone\n    - Task 1\n    - Task 2")
            write_to_page("信用卡", "- [[銀行/國泰]]\n    - 現金回饋 2%")
        """
        try:
            client = get_roam_client()
            result = client.write_to_page(page_name, content)
            return f"Successfully wrote to page '{page_name}': {json.dumps(result, indent=2)}"
        except Exception as e:
            print(f"Error writing to page: {e}", file=sys.stderr)
            return f"Error: {str(e)}"
  • main.py:453-453 (registration)
    Tool registration via FastMCP @mcp.tool() decorator, which uses the function docstring to generate input/output schema.
    @mcp.tool()
  • Core helper method in RoamResearchMCPServer class that orchestrates page lookup, markdown parsing to blocks, and hierarchical block creation via API.
    def write_to_page(self, page_name: str, content: str) -> Dict[str, Any]:
        """Write hierarchical content to a specific page"""
        # First, get the page UID
        page_query = f"""[:find ?uid
                         :in $ ?PAGE
                         :where
                         [?e :node/title ?PAGE]
                         [?e :block/uid ?uid]
                         ]"""
    
        query_data = {"query": page_query, "args": [page_name]}
    
        endpoint = f"/api/graph/{self.graph_name}/q"
        page_result = self._make_request("POST", endpoint, query_data)
    
        # Get page UID
        if not page_result.get("result") or not page_result["result"]:
            raise ValueError(f"Page '{page_name}' not found")
    
        page_uid = page_result["result"][0][0]
    
        # Parse markdown content into hierarchical blocks
        blocks = self._parse_markdown_to_blocks(content)
        
        # Create the hierarchical structure
        results = self._create_block_hierarchy(page_uid, blocks)
        
        return {"result": "success", "blocks_created": len(results), "details": results}
  • Helper function that parses indented markdown content into Roam-compatible hierarchical block structures with children arrays.
    def _parse_markdown_to_blocks(self, content: str) -> list:
        """Parse markdown content into hierarchical block structure using dynamic indentation detection"""
        lines = [line.rstrip() for line in content.split('\n') if line.strip()]
        
        # Build indentation level mapping
        indent_map = {}  # {actual_indent: level}
        blocks = []
        stack = []  # Stack to track parent blocks at different levels
        
        for i, line in enumerate(lines):
            actual_indent = len(line) - len(line.lstrip())
            
            # Determine the level for this indentation
            if actual_indent not in indent_map:
                if actual_indent == 0:
                    indent_map[actual_indent] = 0
                else:
                    # Find the closest parent indentation level
                    parent_indents = [k for k in indent_map.keys() if k < actual_indent]
                    if parent_indents:
                        parent_indent = max(parent_indents)
                        indent_map[actual_indent] = indent_map[parent_indent] + 1
                    else:
                        indent_map[actual_indent] = 1
            
            level = indent_map[actual_indent]
            
            # Extract content (remove leading "- " if present)
            text = line.strip()
            if text.startswith('- '):
                text = text[2:]
            
            # Create block structure
            block = {
                "string": text,
                "uid": f"{datetime.now().strftime('%m-%d-%Y')}-{datetime.now().strftime('%H%M%S')}-{i}",
                "children": []
            }
            
            # Adjust stack to current level
            while len(stack) > level:
                stack.pop()
            
            if level == 0:
                # Top-level block
                blocks.append(block)
                stack = [block]
            else:
                # Child block - add to the parent at the appropriate level
                if stack and len(stack) >= level:
                    parent = stack[level - 1]
                    parent["children"].append(block)
                    # Extend stack to current level
                    while len(stack) <= level:
                        stack.append(block)
                    stack[level] = block
                else:
                    # Fallback: treat as top-level if stack is insufficient
                    blocks.append(block)
                    stack = [block]
        
        return blocks
  • Recursive helper that creates the actual blocks in Roam API using write endpoint, handling hierarchy by setting parent-uid.
    def _create_block_hierarchy(self, parent_uid: str, blocks: list) -> list:
        """Recursively create blocks with their children"""
        results = []
        
        for block in blocks:
            # Create the main block
            block_data = {
                "action": "create-block",
                "location": {"parent-uid": parent_uid, "order": "last"},
                "block": {
                    "string": block["string"],
                    "uid": block["uid"],
                },
            }
            
            write_endpoint = f"/api/graph/{self.graph_name}/write"
            result = self._make_request("POST", write_endpoint, block_data)
            results.append(result)
            
            # Create children if they exist
            if block["children"]:
                child_results = self._create_block_hierarchy(block["uid"], block["children"])
                results.extend(child_results)
        
        return results

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/mickm3n/roam-research-mcp'

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