An MCP server for Confluence with safe handling of Confluence Storage Format (XHTML).
When AI assistants interact with Confluence via MCP servers, they often corrupt pages—especially tables—because:
- LLMs generate Markdown or HTML5 instead of Confluence Storage XHTML
- Tables require specific structure (
<tbody>, no<thead>) - Macros need
ac:namespaces - Round-tripping through incorrect formats causes data loss
This library provides:
- Structured IR (Intermediate Representation): Work with Go types instead of raw XHTML
- Safe Rendering: IR → valid Storage XHTML with proper structure
- Validation: Catch forbidden tags, missing
<tbody>, etc. before API calls - MCP Server: Tools that accept structured JSON, never raw XHTML
| Package | Description |
|---|---|
storage |
IR types, render, parse, validate for Confluence Storage Format |
confluence |
REST API client with IR integration |
mcpserver |
MCP server implementation with structured tools |
go install github.com/agentplexus/mcp-confluence/cmd/mcp-confluence@latestgo get github.com/agentplexus/mcp-confluenceimport "github.com/agentplexus/mcp-confluence/storage"
// Create a page with structured blocks
page := &storage.Page{
Blocks: []storage.Block{
&storage.Heading{Level: 1, Text: "Welcome"},
&storage.Paragraph{Text: "This is a test page."},
&storage.Table{
Headers: []string{"Name", "Status"},
Rows: []storage.Row{
{Cells: []storage.Cell{
{Text: "Service A"},
{Macro: &storage.Macro{
Name: "status",
Params: map[string]string{"colour": "Green", "title": "OK"},
}},
}},
},
},
},
}
// Render to Storage XHTML
xhtml, err := storage.Render(page)
if err != nil {
log.Fatal(err)
}
// Validate before sending to Confluence
if err := storage.Validate(xhtml); err != nil {
log.Fatal(err)
}import "github.com/agentplexus/mcp-confluence/confluence"
// Create client
auth := confluence.BasicAuth{
Username: "user@example.com",
Token: "your-api-token",
}
client := confluence.NewClient("https://example.atlassian.net/wiki", auth)
// Get a page as structured IR
page, info, err := client.GetPageStorage(ctx, "12345")
if err != nil {
log.Fatal(err)
}
// Modify the page
page.Blocks = append(page.Blocks, &storage.Paragraph{Text: "New content"})
// Update the page
err = client.UpdatePageStorage(ctx, info.ID, page, info.Version, info.Title)# Install from GitHub
go install github.com/agentplexus/mcp-confluence/cmd/mcp-confluence@latest
# Or build from source
go build -o mcp-confluence ./cmd/mcp-confluenceClaude Code supports three configuration scopes. See Claude Code MCP docs for details.
User scope (~/.claude.json):
{
"mcpServers": {
"confluence": {
"command": "/path/to/mcp-confluence",
"env": {
"CONFLUENCE_BASE_URL": "https://example.atlassian.net/wiki",
"CONFLUENCE_USERNAME": "user@example.com",
"CONFLUENCE_API_TOKEN": "your-api-token"
}
}
}
}Project scope (.mcp.json in project root, can be checked into source control):
{
"mcpServers": {
"confluence": {
"command": "/path/to/mcp-confluence",
"env": {
"CONFLUENCE_BASE_URL": "https://example.atlassian.net/wiki",
"CONFLUENCE_USERNAME": "user@example.com",
"CONFLUENCE_API_TOKEN": "your-api-token"
}
}
}
}Enterprise managed (managed-mcp.json in system directories):
See Enterprise MCP configuration for details.
Add to your Claude Desktop settings (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"confluence": {
"command": "/path/to/mcp-confluence",
"env": {
"CONFLUENCE_BASE_URL": "https://example.atlassian.net/wiki",
"CONFLUENCE_USERNAME": "user@example.com",
"CONFLUENCE_API_TOKEN": "your-api-token"
}
}
}
}| Variable | Description |
|---|---|
CONFLUENCE_BASE_URL |
Your Confluence instance URL (e.g., https://example.atlassian.net/wiki) |
CONFLUENCE_USERNAME |
Your Confluence username (usually your email) |
CONFLUENCE_API_TOKEN |
API token from Atlassian Account Settings |
export CONFLUENCE_BASE_URL=https://example.atlassian.net/wiki
export CONFLUENCE_USERNAME=user@example.com
export CONFLUENCE_API_TOKEN=your-api-token
./mcp-confluenceThe MCP server exposes these tools:
| Tool | Description |
|---|---|
confluence_read_page |
Read a page as structured blocks |
confluence_read_page_xhtml |
Read a page as raw Storage Format XHTML |
confluence_update_page |
Update a page with structured blocks |
confluence_update_page_xhtml |
Update a page with raw Storage Format XHTML |
confluence_create_page |
Create a new page with structured blocks |
confluence_create_table |
Create a table block from structured data |
confluence_delete_page |
Delete a page |
confluence_search_pages |
Search pages using CQL |
The structured block tools (confluence_read_page, confluence_update_page) are safer and recommended for most use cases. However, the XHTML tools are useful when:
- Debugging: See the raw XHTML to understand parsing issues
- Complex content: Tables with column widths, nested lists, or custom macros that the block parser doesn't fully support
- Preserving formatting: When you need to make small edits without losing inline styles or attributes
{
"name": "confluence_read_page",
"arguments": {
"page_id": "12345"
}
}{
"name": "confluence_read_page_xhtml",
"arguments": {
"page_id": "12345"
}
}Returns the raw Storage Format XHTML in the xhtml field, along with page metadata.
{
"name": "confluence_create_page",
"arguments": {
"space_key": "TEAM",
"title": "Meeting Notes 2025-01-15",
"parent_id": "11111",
"blocks": [
{"type": "heading", "level": 1, "text": "Meeting Notes"},
{"type": "paragraph", "text": "Attendees: Alice, Bob, Carol"},
{"type": "heading", "level": 2, "text": "Action Items"},
{"type": "bullet_list", "items": ["Review PR #123", "Update documentation", "Schedule follow-up"]}
]
}
}{
"name": "confluence_update_page",
"arguments": {
"page_id": "12345",
"title": "Updated Page Title",
"blocks": [
{"type": "heading", "level": 1, "text": "Updated Content"},
{"type": "paragraph", "text": "This page has been updated."},
{"type": "table", "headers": ["Name", "Role"], "rows": [["Alice", "Lead"], ["Bob", "Developer"]]}
]
}
}{
"name": "confluence_update_page_xhtml",
"arguments": {
"page_id": "12345",
"title": "Updated Page Title",
"xhtml": "<h1>Updated Content</h1><p>This page has been updated with raw XHTML.</p><table><tbody><tr><th>Name</th><th>Role</th></tr><tr><td>Alice</td><td>Lead</td></tr></tbody></table>"
}
}Use this when you need to preserve complex formatting that would be lost with structured blocks.
{
"name": "confluence_create_table",
"arguments": {
"headers": ["Service", "Owner", "Status"],
"rows": [
["Auth", "Platform", {"macro": {"name": "status", "params": {"colour": "Green", "title": "OK"}}}],
["API", "Backend", {"macro": {"name": "status", "params": {"colour": "Yellow", "title": "Degraded"}}}]
]
}
}{
"name": "confluence_delete_page",
"arguments": {
"page_id": "12345"
}
}{
"name": "confluence_search_pages",
"arguments": {
"cql": "space=TEAM and type=page and title~\"Meeting\"",
"limit": 10
}
}| Type | Description |
|---|---|
Paragraph |
Text paragraph |
Heading |
H1-H6 headings |
Table |
Tables with headers, rows, and optional macros in cells |
BulletList |
Unordered list |
NumberedList |
Ordered list |
Macro |
Confluence macros (status, info, code, etc.) |
CodeBlock |
Code blocks with language |
HorizontalRule |
Horizontal divider |
- LLMs produce structured JSON (not XHTML) → fewer errors
- Rendering is deterministic Go code → guaranteed valid output
- Validation catches issues before API calls
- Round-trip safe: Parse → Modify → Render preserves structure
MIT
- ROADMAP.md - Planned features
- Confluence Storage Format Documentation