@de-otio/epimethian-mcp
v6.8.0
Published
Confluence Cloud tools for AI assistants via the Model Context Protocol
Maintainers
Readme
Epimethian MCP
A security-focused MCP server that gives AI agents safe, multi-tenant access to Confluence Cloud. It provides some features not available in the official MCP server, like support for draw.io diagrams, macros, etc.
What's new (v6.8.0): authorise_destructive_writes + batch_token field on every destructive tool — pre-authorise a batch of destructive writes with a single user prompt, then fan out the actual writes (sub-agent fan-out, bulk doc refreshes) without each one going through its own elicitation. Page-id-scoped (no wildcards), TTL-bounded, operation-bounded; validation failures fall through transparently to the per-call gate. v6.7.2 flipped EPIMETHIAN_TOKEN_IN_TEXT to default-on (opt out via EPIMETHIAN_HIDE_TOKEN_IN_TEXT=true), closing the under-configured-install gap for Claude Code users. v6.6.0–6.6.3 introduced soft-confirmation token round-trip for clients without working elicitation: single-use, diff-bound tokens; fast-decline auto-detection (the Claude Code "fakes elicitation" bug now Just Works without EPIMETHIAN_BYPASS_ELICITATION); outputSchema so spec-compliant clients forward the structured payload to the agent; SDK-compat hotfix for z.object output schemas. v6.5 added per-client setup CLI snippets (epimethian-mcp setup --client …). v6.4.1 added atomic multi-section updates and find-replace mode. See CHANGELOG.md for full details.
Why use this?
The official Atlassian MCP server covers basic Confluence and Jira access. Epimethian targets gaps that matter for consultants, power users, and teams with strict security requirements:
- OS keychain credential storage — API tokens are stored in macOS Keychain or Linux libsecret, never in plaintext config files. Setup uses masked input so tokens don't leak into terminal scrollback.
- Multi-tenant profile isolation — Each Atlassian tenant gets its own named profile with fully separate credentials and keychain entries. No risk of cross-tenant writes when switching between clients.
- Tenant-aware write safety — Write operations echo the target tenant so the AI agent (and you) always see where changes are going.
- draw.io diagram support — Create and embed draw.io diagrams directly in Confluence pages (assuming the tenant has draw.io installed), something the official server doesn't expose.
- Attribution tracking — Edited pages are labelled
epimethian-editedfor easy discovery. Confluence version messages include the MCP client name (e.g. "Updated by Claude Code (via Epimethian v5.2.0)") so you can trace which AI-assisted edits touched which content.
If you don't need any of the above, the official Atlassian server is a fine choice.
How it works
Epimethian runs as a local MCP server that your AI agent (Claude Code, Cursor, etc.) talks to over stdio. On startup it reads a profile name from the environment, pulls the matching credentials from your OS keychain, validates the connection against Confluence Cloud, and then exposes a set of tools the agent can call. All Confluence API calls go directly from your machine to Atlassian — there is no intermediate service.
Quick Start
Tell your AI agent:
Install and configure the Epimethian MCP server. See https://github.com/de-otio/epimethian-mcp
For a detailed agent-facing guide (installation, configuration, profile management, uninstallation), see install-agent.md or run epimethian-mcp agent-guide after installation.
Or install manually:
npm install -g @de-otio/epimethian-mcp
epimethian-mcp setup --profile <name>The setup command prompts for your Confluence URL, email, and API token (masked input), tests the connection, and stores all credentials securely in your OS keychain under the named profile.
MCP Configuration
Add to your .mcp.json (or equivalent MCP client config):
{
"mcpServers": {
"confluence": {
"command": "epimethian-mcp",
"env": {
"CONFLUENCE_PROFILE": "my-profile"
}
}
}
}All credentials (URL, email, token) are read from the OS keychain at startup. Only the profile name goes in config files.
For IDE-hosted agents, use the absolute path from which epimethian-mcp as the command value.
Multi-Tenant Support
Consultants and developers working across multiple Atlassian tenants can create a profile per tenant:
epimethian-mcp setup --profile globex
epimethian-mcp setup --profile acme-corpEach project's .mcp.json specifies which profile to use. Profiles are fully isolated — separate keychain entries, separate Confluence instances, separate MCP server names (confluence-globex, confluence-acme-corp).
Manage profiles:
epimethian-mcp profiles # list all (shows read-only status)
epimethian-mcp profiles --verbose # show URLs, emails, and read-only status
CONFLUENCE_PROFILE=globex epimethian-mcp status # test connection
epimethian-mcp profiles --remove <name> # delete profile and credentialsThe --remove command deletes the profile's keychain entry and registry record after interactive confirmation. For non-interactive environments (CI, agent shell sessions), pass --force to skip the prompt.
Per-Profile Read-Only Mode
Protect client tenants from accidental writes:
epimethian-mcp profiles --set-read-only acme-corp
epimethian-mcp profiles --set-read-write globexNew profiles default to read-only. The setup command prompts "Enable writes for this profile? [y/N]" or accepts --read-write for non-interactive use.
When a profile is read-only, write tools are not registered at all — the agent's tool list is truthful and contains only read tools plus check_permissions. The posture is resolved at server startup — restart running servers after changing it.
Read-Only Mode
Read-only is the recommended posture for most users. It provides defense-in-depth: even if the underlying API token has write access, the MCP will not expose write tools to the agent. This protects against prompt-injection attacks, accidental mutations during exploratory sessions, and misconfigured agents.
Configuring posture
Set posture in the profile settings (preferred):
// ~/.config/epimethian-mcp/profiles.json
{
"profiles": {
"my-profile": {
"posture": "read-only"
}
}
}Or use the legacy environment variable (still supported):
CONFLUENCE_READ_ONLY=trueThe posture setting is a tri-state:
| Value | Behavior |
|---|---|
| "read-only" | Write tools are never registered, regardless of what the token can do. |
| "read-write" | Write tools are always registered. Writes that fail at the API level return remediation messages. |
| "detect" (default) | A startup probe infers the effective posture from the token's actual permissions. |
The legacy readOnly: boolean profile key remains supported as an alias and is resolved to posture: "read-only" / "read-write". Users should migrate to posture directly.
MCP posture is independent of token capability
You can pin the MCP to read-only even when the API token has full write access. This is the "belt and suspenders" case: the Confluence permission boundary enforces one layer, and the MCP profile enforces another. The check_permissions tool makes the distinction visible — it reports both the configured posture and what the probe determined the token can actually do.
Startup probe (posture: "detect")
When posture is "detect", Epimethian runs a lightweight probe at startup to determine whether the token can write. The probe queries the Confluence permissions endpoint for the first available space and, if that is unavailable, falls back to a dry-run write attempt. The result is logged as a startup banner and drives which tools are registered for the session.
Probe outcomes:
- Token can write → effective posture is
read-write; all tools registered. - Token is read-only → effective posture is
read-only; write tools not registered. - Probe inconclusive → effective posture defaults to
read-writewith a visible warning.
Checking permissions
The check_permissions tool is always registered, in every posture. Call it from the agent to inspect the configured posture, effective posture, probe result, and token capability. For operator diagnostics from the CLI:
CONFLUENCE_PROFILE=my-profile epimethian-mcp permissions my-profileFor the full posture resolution matrix and error remediation design, see doc/design/14-api-permission-handling.md.
Provenance: AI-Edited Badge
Any page created or modified by this MCP is automatically tagged with a yellow "AI-edited" content-status badge. The badge appears as a colored pill in the Confluence page view and space index, signaling that the page has been touched by an AI agent and has not yet been reviewed by a human. A human can clear it in one click after review.
The badge is re-applied on every body-modifying tool call (idempotent: skipped when the page already carries an equivalent badge in any supported locale, to avoid version spam). If a subsequent AI edit is made after the human clears the badge, it reappears.
Default badge
- Label:
"AI-edited"(English default) - Color:
#FFC400(yellow — reads as attention needed)
Configuration
| Setting | Default | Purpose |
|---|---|---|
| unverifiedStatus | true | Master toggle. Set to false to disable the badge entirely. |
| unverifiedStatusLocale | Confluence site default → en | Language for the badge label. |
| unverifiedStatusName | (unset) | Full label override (bypasses locale lookup). Must be ≤20 chars. |
| unverifiedStatusColor | #FFC400 | Color override. One of five Confluence-allowed values: #FFC400, #2684FF, #57D9A3, #FF7452, #8777D9. |
Environment variable equivalents: CONFLUENCE_UNVERIFIED_STATUS=false, CONFLUENCE_UNVERIFIED_STATUS_LOCALE=fr.
Disable the badge for a profile:
{ "posture": "read-write", "unverifiedStatus": false }Custom label (e.g., for compliance workflows):
{ "unverifiedStatusName": "Needs legal review", "unverifiedStatusColor": "#FF7452" }Supported locales
| Locale | Label |
|---|---|
| en (default) | AI-edited |
| fr | Modifié par IA |
| de | KI-bearbeitet |
| es | Editado por IA |
| pt | Editado por IA |
| it | Modificato da IA |
| nl | AI-bewerkt |
| ja | AI編集済み |
| zh | AI已编辑 |
| ko | AI 편집됨 |
The locale is resolved from (in order): unverifiedStatusLocale profile setting → CONFLUENCE_UNVERIFIED_STATUS_LOCALE env var → Confluence site default language (probed once per tenant via GET /wiki/rest/api/settings/systemInfo) → "en". The MCP process's own OS locale is intentionally NOT consulted — the badge is a server-stored string shown to every viewer of the page, so it must follow the tenant, not whoever happens to run the agent.
When the badge cannot be applied (e.g., the token lacks content-state permission), the tool call still succeeds and a warning is surfaced in the tool response instead of failing silently.
For the full design — idempotency behavior, version-bump math, lifecycle diagram, and security considerations — see doc/design/13-unverified-status.md.
Token Efficiency
Confluence pages are verbose — storage format HTML with macro markup can easily reach 50,000+ tokens. Epimethian reduces token usage through several strategies, all lossless with respect to Confluence data:
- Drill-down pattern — Use
headings_onlyto get a page outline (~500 tokens), thensectionto read just the part you need in storage format. No need to fetch the full page body. - Section-level editing —
update_page_sectionreplaces content under a single heading. The rest of the page is never touched, eliminating the need to send the full body on updates. - Multi-section atomic updates —
update_page_sections(v6.4.0+) updates multiple sections in one version bump, eliminating version conflicts and intermediate reads during tree-building workflows. - Find-replace mode —
update_page_sectionandupdate_page_sectionsaccept optionalfind_replace: [{find, replace}, ...](v6.4.0+) for literal-string substitutions without resending section bodies. Macro-safe: substitutions cannot match inside macro boundaries. - Skip-read shortcut —
update_page,update_page_section, andupdate_page_sectionsacceptversion: "current"to skip the read of the latest version when the next operation will be an update (v6.3.0+). - Page cache — An in-memory, version-keyed cache eliminates redundant API calls during iterative editing. After updating a page, subsequent reads serve from cache (~90% fewer tokens on repeated reads).
- Search excerpts — Search results include content previews so the agent can triage results without calling
get_pageon each one. - Markdown view —
format: "markdown"returns a compact read-only rendering where macros become[macro: name]placeholders. The server rejects any attempt to write markdown back — storage format is the only accepted write format. - Truncation —
max_lengthcuts the body at an element boundary with a[truncated at N of M characters]marker.
Tools
| Tool | Description |
| --------------------- | ---------------------------------------------------------------------- |
| create_page | Create a new page |
| get_page | Read a page by ID (headings_only, section, max_length, format) |
| get_page_by_title | Look up a page by title (same options as get_page) |
| update_page | Update an existing page |
| update_page_section | Update a single section by heading name |
| update_page_sections | Update multiple sections atomically in one version bump |
| delete_page | Delete a page |
| list_pages | List pages in a space |
| get_page_children | Get child pages |
| search_pages | Search via CQL (includes content excerpts) |
| get_spaces | List available spaces |
| add_attachment | Upload a file attachment |
| get_attachments | List attachments on a page |
| add_drawio_diagram | Add a draw.io diagram |
| get_labels | Get all labels on a page |
| add_label | Add one or more labels to a page |
| remove_label | Remove a label from a page |
| get_comments | Read page comments (footer and inline) |
| create_comment | Add a comment to a page |
| resolve_comment | Resolve or reopen an inline comment |
| delete_comment | Delete a comment |
| get_page_status | Get the content status badge on a page |
| set_page_status | Set the content status badge on a page |
| remove_page_status | Remove the content status badge from a page |
| get_page_versions | List version history for a page |
| get_page_version | Get page content at a specific historical version (read-only markdown) |
| diff_page_versions | Compare two versions of a page |
| prepend_to_page | Insert content at the beginning of a page (additive, safe) |
| append_to_page | Insert content at the end of a page (additive, safe) |
| revert_page | Revert a page to a previous version (lossless) |
| lookup_user | Search for Atlassian users by name or email |
| resolve_page_link | Resolve a page title + space key to a stable page ID and URL |
| get_version | Return the server version |
Environment Variables
Configuration via environment variables (all optional; sensible defaults provided):
| Variable | Default | Purpose |
|---|---|---|
| CONFLUENCE_PROFILE | (required) | Active profile name (e.g., my-profile). Credentials are loaded from OS keychain. |
| EPIMETHIAN_WRITE_BUDGET_ROLLING | 75 writes per 15 min | Rolling-window write limit (per-scope: session, profile, global). Set to 0 to disable. Replaces deprecated EPIMETHIAN_WRITE_BUDGET_HOURLY (removed in v7.0); the old name still works as an alias. |
| EPIMETHIAN_WRITE_BUDGET_SESSION | 250 writes | Session-scoped write limit. |
| EPIMETHIAN_SUPPRESS_EQUIVALENT_DELETIONS | false | Opt-in feature flag. When true, suppress confirm_deletions for macro byte-equivalent round-trips (e.g. re-rendered <ac:link> with reordered attributes). |
| EPIMETHIAN_BYPASS_ELICITATION | false | Escape hatch for MCP clients that advertise elicitation support but never honour it. When true, skips the in-protocol confirmation prompt. The harness's permission allow-list still gates writes. |
| EPIMETHIAN_MUTATION_LOG | false | Opt-in logging. Write JSONL records to ~/.epimethian/logs/ for every write operation. |
| EPIMETHIAN_AUTO_UPGRADE | check-only | Set to patches for automatic patch-version installs (same npm provenance verification). |
| CONFLUENCE_READ_ONLY | (deprecated) | Legacy alias for posture: "read-only" in profile settings. Use the profile config instead. |
| CONFLUENCE_UNVERIFIED_STATUS | true | Master toggle for AI-edited badge. Set to false to disable. |
| CONFLUENCE_UNVERIFIED_STATUS_LOCALE | Confluence site default → en | Language for the badge label (10 locales: en/fr/de/es/pt/it/nl/ja/zh/ko). |
For CI/headless environments without OS keychain, set all three: CONFLUENCE_URL, CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN.
Content Safety
Write operations are protected by layered safety guards to prevent accidental content loss:
- Shrinkage guard —
update_pagerejects writes that reduce the body by more than 50%. Passconfirm_shrinkage: trueto override. - Structural integrity — rejects writes that drop more than 50% of headings. Pass
confirm_structure_loss: trueto override. - Empty-body rejection — hard guard, no opt-out. Rejects writes that produce near-empty pages.
- Additive tools —
prepend_to_pageandappend_to_pageavoid full-body replacement entirely. - Lossless revert —
revert_pageuses raw storage format, avoiding lossy markdown conversion. - Mutation log — opt-in via
EPIMETHIAN_MUTATION_LOG=true. Writes JSONL records to~/.epimethian/logs/for every write operation.
Credential Security
- Credentials are stored per-profile in the OS keychain (macOS Keychain / Linux libsecret)
- URL, email, and API token are stored as an atomic unit — no mixing across profiles
- Tokens are never written to disk in plaintext
- The
setupcommand uses masked input so tokens don't appear in terminal scrollback - Startup validation verifies credentials, tenant identity (email), and tenant seal (cloudId) before accepting tool calls. Sealed profiles fail closed if the tenant-id endpoint is unreachable.
- Write operations include a tenant echo so the target is always visible
- For CI/headless environments, set all three env vars (
CONFLUENCE_URL,CONFLUENCE_EMAIL,CONFLUENCE_API_TOKEN) — partial combinations are rejected - Updates are check-and-notify by default. Run
epimethian-mcp upgradeto install — the CLI verifies the npm provenance attestation first and refuses to install without it. SetEPIMETHIAN_AUTO_UPGRADE=patchesto opt in to automatic patch installs (same integrity check).
For a full security & safety evaluation — threat model, defence-in-depth mechanisms, known limitations — see doc/design/security/.
Development
git clone https://github.com/de-otio/epimethian-mcp.git
cd epimethian-mcp
npm install
npm run build
npm testArchitecture
flowchart LR
Agent["AI Agent<br/>(Claude Code, Cursor, ...)"]
subgraph Local["Local machine"]
MCP["Epimethian MCP Server"]
Keychain["OS Keychain<br/>(macOS Keychain / libsecret)"]
Registry["Tenant Profile Registry<br/>(~/.config/epimethian-mcp/profiles.json)"]
end
subgraph Atlassian["Atlassian Cloud"]
TenantA["Tenant A<br/>(e.g. globex)"]
TenantB["Tenant B<br/>(e.g. acme-corp)"]
end
Agent -- "stdio (MCP)" --> MCP
MCP -- "list / select profile" --> Registry
MCP -- "read credentials for profile" --> Keychain
MCP -- "HTTPS + API token<br/>(tenant A)" --> TenantA
MCP -- "HTTPS + API token<br/>(tenant B)" --> TenantB
Registry -. "names + read-only flags<br/>(no secrets)" .- KeychainThe MCP server resolves the active profile from CONFLUENCE_PROFILE, loads its URL/email/token from the keychain, and talks directly to the matching Atlassian tenant. The profile registry stores only non-secret metadata (profile names, read-only flags); tokens never leave the keychain in plaintext.
