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
| Name | Required | Description | Default |
|---|---|---|---|
| space_key | Yes | ||
| title | Yes | ||
| content | Yes | ||
| parent_id | No |
Implementation Reference
- src/mcp_jira_confluence/server.py:704-717 (registration)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