Markdown to Confluence
Push a local .md file to Confluence. Creates the page if it doesn't exist, updates it if it does (upsert).
Before starting - confirm 3 things
- Source file - which
.mdfile? (path) - Target space - which Confluence space key? (e.g.
BS,TECH,HR) - Parent page - which page to nest under? (title or page ID)
If any are missing, ask before doing anything.
Upsert logic
Step 1 - determine the page title
The title is the first # heading in the file:
# CRM Module - Product Specification ← this becomes the page title
If no # heading exists, use the filename without .md.
Step 2 - check if page exists
confluence_search(cql='title = "CRM Module - Product Specification" AND space = "BS" AND type = page')
- Results found → update. Show title + last modified date. Confirm: "Page exists (last updated: ). Update it?"
- No results → create. Notify: "Creating new page '' under '' in space ."
Step 3a - Update existing page
confluence_update_page(
page_id="<id from search result>",
title="<title>",
body="<converted HTML>",
version=<current_version + 1>
)
Step 3b - Create new page
First get the parent page ID if only a title was given:
confluence_search(cql='title = "<parent title>" AND space = "BS" AND type = page')
Then:
confluence_create_page(
space_key="BS",
title="<title>",
body="<converted HTML>",
parent_id="<parent page id>"
)
Markdown to Confluence HTML conversion
Confluence accepts HTML as the page body. See references/conversion.md for the full mapping.
Quick reference:
# Heading 1 → <h1>Heading 1</h1>
## Heading 2 → <h2>Heading 2</h2>
**bold** → <strong>bold</strong>
*italic* → <em>italic</em>
- item → <ul><li>item</li></ul>
1. item → <ol><li>item</li></ol>
`code` → <code>code</code>
[text](url) → <a href="url">text</a>
Code blocks:
```javascript
const x = 1;
```
Becomes:
<ac:structured-macro ac:name="code">
<ac:parameter ac:name="language">javascript</ac:parameter>
<ac:plain-text-body><![CDATA[const x = 1;]]></ac:plain-text-body>
</ac:structured-macro>
Tables: convert to <table><tbody><tr><td>...</td></tr></tbody></table>.
Numbered table rows
If a markdown table has a # or number column (first column contains sequential integers like 1, 2, 3), do NOT convert it as a regular column. Instead:
- Remove the
#/number column from the table entirely (both header and data cells). - Use Confluence's built-in numbered column by adding
isNumberColumnEnabled: trueto the table attrs in ADF format. - When using ADF format, the table node should look like:
{"type": "table", "attrs": {"layout": "default", "isNumberColumnEnabled": true}, "content": [...]} - When using storage/HTML format, add the attribute to the table tag:
<table data-number-column="true">
This ensures row numbers auto-update when rows are added, removed, or reordered in Confluence.
Strip the first # heading from the body - it becomes the page title, not content.
Running the script directly
For bulk operations or automation, use the Python script instead of MCP calls:
# Install dependencies first (one time)
pip install requests markdown python-dotenv
# Single file
python scripts/md_to_confluence.py --file spec.md --space BS --parent "Product Specs"
# Bulk push
python scripts/md_to_confluence.py --files "foundation/**/*.md" --space BS --parent "Engineering Docs"
# Dry run (check what would happen without making changes)
python scripts/md_to_confluence.py --file spec.md --space BS --parent "Product Specs" --dry-run
Credentials are read from environment variables or .env file automatically.
See scripts/md_to_confluence.py for full usage.
Token efficiency
Read the file once → convert → publish → done.
- Do not re-read the file after publishing
- Do not fetch the page back from Confluence to verify
- Trust the API response: HTTP 200/201 = success
After publishing
Report:
- Page title
- Created or updated
- Direct Confluence URL (from API response)
Nothing else. Don't fetch or summarize content back.
Bulk push
For multiple .md files:
- Confirm space and parent page applies to all, or ask per file
- Process one at a time, report status per file
- Don't stop on a single failure - complete all files, report failures at the end