Skip to main content
Glama
akhilthomas236

MCP Jira & Confluence Server

create-confluence-page

Create a Confluence page in a specified space by providing title and content, with optional parent page for hierarchical placement.

Instructions

Create a new Confluence page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
space_keyYes
titleYes
contentYes
parent_idNo

Implementation Reference

  • Tool registration for 'create-confluence-page' in the list_tools handler, defining its name, description, and input schema (space_key, title, content required; parent_id optional).
    types.Tool(
        name="create-confluence-page",
        description="Create a new Confluence page",
        inputSchema={
            "type": "object",
            "properties": {
                "space_key": {"type": "string"},
                "title": {"type": "string"},
                "content": {"type": "string"},
                "parent_id": {"type": "string"},
            },
            "required": ["space_key", "title", "content"],
        },
    ),
  • Handler for 'create-confluence-page' tool execution. Extracts arguments, detects markdown content and converts to Confluence storage format, calls confluence_client.create_page(), and returns a success response with an embedded resource URI.
    elif name == "create-confluence-page":
        space_key = arguments.get("space_key")
        title = arguments.get("title")
        content = arguments.get("content")
        parent_id = arguments.get("parent_id")
        
        if not space_key or not title or not content:
            raise ValueError("Missing required arguments: space_key, title, and content")
        
        # Convert content from markdown to Confluence storage format if needed
        # Improved markdown detection
        markdown_patterns = [
            r'^#{1,6}\s+',           # Headers
            r'\*\*(.*?)\*\*',        # Bold
            r'\*(.*?)\*',            # Italic/emphasis  
            r'`([^`]+)`',            # Inline code
            r'```',                  # Code blocks
            r'^[\s]*[-*]\s+',        # Unordered lists
            r'^[\s]*\d+\.\s+',       # Ordered lists
            r'\[.*?\]\(.*?\)',       # Links
            r'!\[.*?\]\(.*?\)',      # Images
        ]
        
        is_markdown = any(re.search(pattern, content, re.MULTILINE) for pattern in markdown_patterns)
        
        if is_markdown:
            try:
                formatted_content = ConfluenceFormatter.markdown_to_confluence(content)
                logger.info("Successfully converted markdown content to Confluence storage format")
            except Exception as e:
                logger.warning(f"Failed to convert markdown, using as plain HTML: {e}")
                # Fallback: wrap in simple paragraph tags with line breaks
                lines = content.split('\n')
                formatted_lines = [f"<p>{line}</p>" if line.strip() else "" for line in lines]
                formatted_content = '\n'.join(formatted_lines)
        else:
            # Check if it's already HTML/XML format
            if content.strip().startswith('<') and content.strip().endswith('>'):
                formatted_content = content
                logger.info("Using content as-is (appears to be HTML/storage format)")
            else:
                # Plain text - wrap in paragraph tags
                formatted_content = f"<p>{content}</p>"
                logger.info("Plain text detected - wrapped in paragraph tags")
            
        result = await confluence_client.create_page(
            space_key=space_key,
            title=title,
            content=formatted_content,
            parent_id=parent_id
        )
        
        page_id = result.get("id")
        if not page_id:
            raise ValueError("Failed to create Confluence page, no page id returned")
            
        return [
            types.TextContent(
                type="text",
                text=f"Created Confluence page: {title}",
            ),
            types.EmbeddedResource(
                type="resource",
                resource=types.TextResourceContents(
                    uri=AnyUrl(build_confluence_uri(page_id, space_key)),
                    text=f"Created Confluence page: {title}",
                    mimeType="text/markdown"
                )
            )
        ]
  • ConfluenceClient.create_page() - the underlying API client method that builds the Confluence REST API request payload and POSTs to /rest/api/content to create the page.
    async def create_page(self, space_key: str, title: str, content: str, parent_id: Optional[str] = None) -> Dict[str, Any]:
        """Create a new Confluence page."""
        data = {
            "type": "page",
            "title": title,
            "space": {"key": space_key},
            "body": {
                "storage": {
                    "value": content,
                    "representation": "storage"
                }
            }
        }
        
        if parent_id:
            data["ancestors"] = [{"id": parent_id}]
            
        return await self.post("content", data)
  • Detailed markdown-to-Confluence-storage-format conversion used by the handler when content is detected as markdown.
            elif line.strip().startswith('#### '):
                processed_lines.append(f'<h4>{line.strip()[5:]}</h4>')
            else:
                processed_lines.append(line)
        
        output = '\n'.join(processed_lines)
        
        # Convert basic formatting - be more careful with regex
        # Bold - prioritize ** over *
        output = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', output)
        
        # Italic - only match single * that are not part of **
        output = re.sub(r'(?<!\*)\*([^*\n]+?)\*(?!\*)', r'<em>\1</em>', output)
        
        # Convert inline code first (to protect it from other conversions)
        output = re.sub(r'`([^`\n]+)`', r'<code>\1</code>', output)
        
        # Convert code blocks to simple preformatted text (safer than macros)
        output = re.sub(r'```[\w]*\n(.*?)\n```', r'<pre>\1</pre>', output, flags=re.DOTALL)
        
        # Convert simple links
        output = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', output)
        
        # Convert images to simple format  
        output = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', r'<img src="\2" alt="\1" />', output)
        
        # Process lists more carefully
        lines = output.split('\n')
        final_lines = []
        in_ul = False
        in_ol = False
        
        for line in lines:
            stripped = line.strip()
            
            # Handle unordered list items
            if re.match(r'^[-*]\s+', stripped):
                if not in_ul:
                    if in_ol:
                        final_lines.append('</ol>')
                        in_ol = False
                    final_lines.append('<ul>')
                    in_ul = True
                
                item_content = re.sub(r'^[-*]\s+', '', stripped)
                final_lines.append(f'<li>{item_content}</li>')
                
            # Handle ordered list items
            elif re.match(r'^\d+\.\s+', stripped):
                if not in_ol:
                    if in_ul:
                        final_lines.append('</ul>')
                        in_ul = False
                    final_lines.append('<ol>')
                    in_ol = True
                
                item_content = re.sub(r'^\d+\.\s+', '', stripped)
                final_lines.append(f'<li>{item_content}</li>')
                
            else:
                # Close any open lists
                if in_ul:
                    final_lines.append('</ul>')
                    in_ul = False
                if in_ol:
                    final_lines.append('</ol>')
                    in_ol = False
                
                # Add the line
                if stripped:
                    # Only wrap in <p> if it's not already an HTML element
                    if not (stripped.startswith('<') and stripped.endswith('>')):
                        final_lines.append(f'<p>{stripped}</p>')
                    else:
                        final_lines.append(stripped)
                else:
                    final_lines.append('')
        
        # Close any remaining lists
        if in_ul:
            final_lines.append('</ul>')
        if in_ol:
            final_lines.append('</ol>')
        
        return '\n'.join(final_lines)
    
    @staticmethod
    def _detailed_markdown_to_confluence(input_text: str) -> str:
  • ConfluencePage model representing the data structure of a Confluence page.
    @dataclass
    class ConfluencePage:
        """Representation of a Confluence page."""
        id: str
        title: str
        content: str
        space_key: str
        version: int
        created: Optional[str] = None
        updated: Optional[str] = None
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden but only says 'Create a new Confluence page', not disclosing behavioral traits like side effects, authentication needs, or rate limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence is concise but lacks necessary detail; under-specification outweighs brevity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema, no annotations, and no guidance on return values, error handling, or relationships to sibling tools. Incomplete for a tool with four parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description does not explain any parameter meanings or usage. Users must infer from parameter names alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states it creates a Confluence page, distinguishing it from sibling tools like update-confluence-page and comment-confluence-page. However, it lacks specificity about the creation process.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives. Does not mention prerequisites (e.g., needing a space key) or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/akhilthomas236/mcp-jira-confluence-sse'

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