---
name: confluence-markdown
description: Sync Confluence wiki pages with local Markdown files through an MCP server. Use it to pull a page down for editing, or push local Markdown edits back to an existing wiki page.
version: 0.2.0
---

# confluence-markdown skill

This skill teaches an MCP-capable assistant how to work with the
`confluence-markdown-mcp` server.

> 🇨🇳 中文版本见 [`skill.zh-CN.md`](skill.zh-CN.md)。

## When to use

Invoke this skill whenever the user wants to:

- **Read / fetch** a Confluence wiki page for editing, summarisation or
  quotation (use `pull_page` with `output_dir`, or `read_page` if you only
  need the content).
- **Edit and publish** local Markdown changes back to Confluence (use
  `push_page` with the exact path and `page_id`).
- Preview a page inline – access the `confluence://page/{page_id}` resource.

Do **not** use it for creating brand-new pages; that is out of scope in the
current version. Attachments referenced by the page (images and file links)
are synchronised automatically in both directions — downloaded alongside
the Markdown file on `pull_page`, and created / updated on `push_page`.

## Prerequisites

The server reads credentials from environment variables. Confirm with the
user that the following are set before the first call:

- `CONFLUENCE_BASE_URL` – e.g. `https://<tenant>.atlassian.net`
- `CONFLUENCE_EMAIL` + `CONFLUENCE_API_TOKEN` – Atlassian account email and API token for Basic auth
- `CONFLUENCE_PAT` – Personal Access Token for Bearer auth

Optional:

- `CONFLUENCE_TIMEOUT`        – HTTP timeout in seconds (default `30`)
- `CONFLUENCE_MARKDOWN_DIR`   – default root for relative `output_dir`s

Authentication is either/or: use email + API token for Basic auth, or set
`CONFLUENCE_PAT` for Bearer auth. If both are present, the PAT is used.

## Tools provided

### `pull_page(page_id: string, output_dir?: string)`

Downloads a Confluence page. When `output_dir` is provided it **must be a
directory** – the Markdown file name is generated automatically by the
server from the page title (unsafe characters are stripped), so the
caller should never pass a full file path. The resulting file contains
YAML-style front matter (`page_id`, `title`, `space_key`, `version`) and
the response includes `markdown_preview` plus the resolved `path`.
Without `output_dir`, the full Markdown body is returned in `markdown`.

### `push_page(file_path: string, page_id?: string, title?: string)`

Uploads a local Markdown file back to Confluence. The target `page_id` may
be omitted if the file carries it in its front matter (which `pull_page`
writes automatically). `title` defaults to the front-matter title or the
page's current title.

Attachment upload rules on push:

- Image references are uploaded automatically, for example
  `![image](attachments/example.png)`.
- Ordinary file links are uploaded only when the link is immediately
  followed by `<!--cm-attachment-->`, for example
  `[file](attachments/example.eml) <!--cm-attachment-->`.
- The marker must come **after** the link. `<!--cm-attachment-->[file](...)`
  is not recognised.
- `pull_page` / `read_page` URL-encode Markdown attachment targets when the
  filename contains spaces, parentheses, or non-ASCII characters so the link
  survives a round trip through Markdown parsers.
- URL-encoded local paths are decoded before matching and naming the
  attachment, so an encoded path and the decoded local filename stay in
  sync on upload and in the page body.

### `read_page(page_id: string)`

Convenience wrapper around `pull_page` that never writes to disk – returns
the Markdown body plus basic metadata.

## Recommended workflow

1. Ask the user for the Confluence page ID (and optional local path).
2. Call `pull_page` with an `output_dir`; confirm the new file location
   (the filename is produced from the page title by the server).
3. Propose Markdown edits; have the user review before uploading.
4. Call `push_page` with the same `file_path`; display the returned new
   `version`.

## Formatting guarantees

The server handles the following Confluence storage-format constructs when
converting to Markdown, and reverses the process on upload:

| Storage format | Markdown |
| --- | --- |
| `code` macro (with language + CDATA) | Fenced code block ```` ```lang ```` |
| `info` / `note` / `warning` / `tip` | `> [!INFO]` blockquote admonition |
| `<table>` with `<th>/<td>` | Pipe table (first row as header) |
| `<ul>/<ol>/<li>` (nested) | `-` / `1.` list (2-space indent) |
| `<ac:task-list>` with `<ac:task>` | `- [ ]` / `- [x]` task items |
| `<a>` / `<img>` | `[text](url)` / `![alt](src)` |
| `<span style="color: …; background-color: …">` | Same `<span>` verbatim |
| `<p style="text-align: left/right/center/justify">` | Same `<p>` verbatim |
| Inline `<u>`, `<s>`/`<del>`, `<ins>`, `<sub>`, `<sup>`, `<br>` | Same tag verbatim |
| `html` / `html-bobswift` macro (embedded `<iframe>`, e.g. drawio / diagrams.net) | Raw HTML body is unwrapped into a Markdown `<iframe …></iframe>` line; push re-wraps it in `html-bobswift` automatically |
| Any other `<ac:structured-macro>` | HTML comment token that round-trips |

Because unknown macros are preserved as comments, **do not delete them** in
an edit unless the user explicitly asks to remove that block.

### drawio / iframe embeds

Confluence renders drawio / diagrams.net diagrams through an `<iframe>`
inside an `html-bobswift` (or `html`) user macro. On pull the iframe is
unwrapped onto a single Markdown line; on push the server re-wraps it in
the same macro so Confluence can render it. Iframe `src` attributes are
restricted to `http` / `https` URLs and non-allow-listed attributes
(`onload`, `srcdoc`, `sandbox`, …) are dropped so unsafe embeds cannot
leak through a round-trip.

## Error handling

- `RuntimeError: Missing Confluence credentials...` → remind the user to
  export the required environment variables.
- `ConfluenceError: (401 Unauthorized)` → the API token or PAT is invalid, expired, or lacks permission.
- `ConfluenceError: (404 Not Found)` → double-check the `page_id`.
- `FileNotFoundError` on `push_page` → verify the absolute file path.

Always surface the returned version number after a `push_page` call so the
user can confirm the update.
