@de-otio/epimethian-mcp 6.6.3 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +44 -21
- package/dist/cli/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A security-focused [MCP](https://modelcontextprotocol.io/) 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.
|
|
8
8
|
|
|
9
|
-
**What's new (v6.
|
|
9
|
+
**What's new (v6.6.3):** Soft-confirmation token round-trip for MCP clients without (or with broken) elicitation. v6.6.0 introduced single-use, diff-bound tokens; v6.6.1 added fast-decline auto-detection (the Claude Code "fakes elicitation" bug now Just Works without `EPIMETHIAN_BYPASS_ELICITATION`) plus `versionField` coercion for string-encoded ints; v6.6.2 declared `outputSchema` so spec-compliant clients forward the structured payload to the agent, plus an `EPIMETHIAN_TOKEN_IN_TEXT=true` fallback for clients that drop content blocks; v6.6.3 hotfixed an SDK incompatibility with `z.discriminatedUnion` outputSchemas. 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](CHANGELOG.md) for full details.
|
|
10
10
|
|
|
11
11
|
## Why use this?
|
|
12
12
|
|
package/dist/cli/index.js
CHANGED
|
@@ -35481,7 +35481,7 @@ async function getPage(pageId, includeBody) {
|
|
|
35481
35481
|
async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
|
|
35482
35482
|
const cfg = await getConfig();
|
|
35483
35483
|
const pageBody = normalizeBodyForSubmit(body);
|
|
35484
|
-
const epimethianTag = `Epimethian v${"6.
|
|
35484
|
+
const epimethianTag = `Epimethian v${"6.7.0"}`;
|
|
35485
35485
|
const versionMsg = cfg.attribution && clientLabel ? `Created by ${clientLabel} (via ${epimethianTag})` : `Created by ${epimethianTag}`;
|
|
35486
35486
|
const payload = {
|
|
35487
35487
|
title,
|
|
@@ -35502,7 +35502,7 @@ async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
|
|
|
35502
35502
|
async function _rawUpdatePage(pageId, opts) {
|
|
35503
35503
|
const cfg = await getConfig();
|
|
35504
35504
|
const newVersion = opts.version + 1;
|
|
35505
|
-
const epimethianTag = `Epimethian v${"6.
|
|
35505
|
+
const epimethianTag = `Epimethian v${"6.7.0"}`;
|
|
35506
35506
|
const effectiveClient = cfg.attribution ? opts.clientLabel : void 0;
|
|
35507
35507
|
let versionMessage;
|
|
35508
35508
|
if (opts.versionMessage && effectiveClient)
|
|
@@ -46715,15 +46715,16 @@ function parseDirectiveAttrs(attrs) {
|
|
|
46715
46715
|
}
|
|
46716
46716
|
return result;
|
|
46717
46717
|
}
|
|
46718
|
-
function extractInlineDirectives(md) {
|
|
46718
|
+
function extractInlineDirectives(md, mdi) {
|
|
46719
46719
|
const directives = /* @__PURE__ */ new Map();
|
|
46720
46720
|
let idx = 0;
|
|
46721
|
-
const directiveRe = /:(status|mention|date|emoji|jira|anchor)\[([^\]]*)\](?:\{([^}]*)\})?/g;
|
|
46721
|
+
const directiveRe = /:(status|mention|date|emoji|jira|anchor|info|note|warning|tip|success)\[((?:[^\[\]]|\[[^\]]*\])*)\](?:\{([^}]*)\})?/g;
|
|
46722
46722
|
const processed = md.replace(
|
|
46723
46723
|
directiveRe,
|
|
46724
46724
|
(_match, name, label, rawAttrs) => {
|
|
46725
46725
|
const attrs = rawAttrs ? parseDirectiveAttrs(rawAttrs) : {};
|
|
46726
46726
|
let xml;
|
|
46727
|
+
let isBlock = false;
|
|
46727
46728
|
switch (name) {
|
|
46728
46729
|
case "status": {
|
|
46729
46730
|
const colour = attrs["colour"] ?? attrs["color"] ?? "";
|
|
@@ -46791,12 +46792,28 @@ function extractInlineDirectives(md) {
|
|
|
46791
46792
|
xml = `<ac:structured-macro ac:name="anchor" ac:schema-version="1"><ac:parameter ac:name="">${escapeXmlText(label)}</ac:parameter></ac:structured-macro>`;
|
|
46792
46793
|
break;
|
|
46793
46794
|
}
|
|
46795
|
+
case "info":
|
|
46796
|
+
case "note":
|
|
46797
|
+
case "warning":
|
|
46798
|
+
case "tip":
|
|
46799
|
+
case "success": {
|
|
46800
|
+
const title = attrs["title"] ?? "";
|
|
46801
|
+
const body = mdi.renderInline(label, {});
|
|
46802
|
+
xml = `<ac:structured-macro ac:name="${name}" ac:schema-version="1">`;
|
|
46803
|
+
if (title) {
|
|
46804
|
+
xml += `<ac:parameter ac:name="title">${escapeXmlAttr(title)}</ac:parameter>`;
|
|
46805
|
+
}
|
|
46806
|
+
xml += `<ac:rich-text-body><p>${body}</p></ac:rich-text-body>`;
|
|
46807
|
+
xml += `</ac:structured-macro>`;
|
|
46808
|
+
isBlock = true;
|
|
46809
|
+
break;
|
|
46810
|
+
}
|
|
46794
46811
|
default: {
|
|
46795
46812
|
throw new ConverterError(`Unknown directive ':${name}'.`, "UNKNOWN_DIRECTIVE");
|
|
46796
46813
|
}
|
|
46797
46814
|
}
|
|
46798
46815
|
const placeholder = `@@@EPIDIRECTIVE${idx++}@@@`;
|
|
46799
|
-
directives.set(placeholder, xml);
|
|
46816
|
+
directives.set(placeholder, { xml, isBlock });
|
|
46800
46817
|
return placeholder;
|
|
46801
46818
|
}
|
|
46802
46819
|
);
|
|
@@ -47032,7 +47049,7 @@ function markdownToStorage(md, opts) {
|
|
|
47032
47049
|
const slugify = createHeadingSlugger();
|
|
47033
47050
|
mdi.renderer.rules.heading_open = buildHeadingRenderer(slugify, headingOffset);
|
|
47034
47051
|
mdi.renderer.rules.heading_close = buildHeadingRenderer(slugify, headingOffset);
|
|
47035
|
-
const { processed: mdAfterDirectives, directives } = extractInlineDirectives(bodyMd);
|
|
47052
|
+
const { processed: mdAfterDirectives, directives } = extractInlineDirectives(bodyMd, mdi);
|
|
47036
47053
|
const { processed: mdWithoutColumns, blocks: columnsBlocks } = extractColumnsBlocks(
|
|
47037
47054
|
mdAfterDirectives,
|
|
47038
47055
|
mdi
|
|
@@ -47059,8 +47076,14 @@ function markdownToStorage(md, opts) {
|
|
|
47059
47076
|
html = html.replace(new RegExp(`<p>\\s*${escapeRegex2(placeholder)}\\s*</p>`, "g"), raw);
|
|
47060
47077
|
html = html.replace(placeholder, raw);
|
|
47061
47078
|
}
|
|
47062
|
-
for (const [placeholder,
|
|
47063
|
-
|
|
47079
|
+
for (const [placeholder, { xml, isBlock }] of directives) {
|
|
47080
|
+
if (isBlock) {
|
|
47081
|
+
html = html.replace(
|
|
47082
|
+
new RegExp(`<p>\\s*${escapeRegex2(placeholder)}\\s*</p>`, "g"),
|
|
47083
|
+
xml
|
|
47084
|
+
);
|
|
47085
|
+
}
|
|
47086
|
+
html = html.replace(new RegExp(escapeRegex2(placeholder), "g"), xml);
|
|
47064
47087
|
}
|
|
47065
47088
|
for (const [placeholder, columnsXml] of columnsBlocks) {
|
|
47066
47089
|
html = html.replace(new RegExp(`<p>\\s*${escapeRegex2(placeholder)}\\s*</p>`, "g"), columnsXml);
|
|
@@ -49925,7 +49948,7 @@ Informational:
|
|
|
49925
49948
|
var install_agent_default;
|
|
49926
49949
|
var init_install_agent = __esm({
|
|
49927
49950
|
"install-agent.md"() {
|
|
49928
|
-
install_agent_default = '# Epimethian MCP - Agent Installation Guide\n\n> This document is for AI agents installing and configuring the Epimethian MCP server on behalf of a user.\n\n## Verification\n\nThe official package is **`@de-otio/epimethian-mcp`** (scoped under `@de-otio`).\n\nDo NOT install unscoped alternatives like `epimethian-mcp` \u2014 these are unofficial and potentially unsafe.\n\n## Prerequisites\n\n- Node.js 18 or later\n- npm on PATH\n\n## Step 1: Install\n\n```bash\nnpm install -g @de-otio/epimethian-mcp\n```\n\nVerify the installation:\n\n```bash\nepimethian-mcp --version 2>/dev/null || which epimethian-mcp\n```\n\n## Step 2: Resolve the absolute path\n\nIDE-hosted agents may not resolve PATH correctly. Always use the absolute path in MCP configuration:\n\n```bash\nwhich epimethian-mcp\n```\n\nUse the output as the `command` value in the MCP config below.\n\n## Step 3: Collect configuration\n\nAsk the user for:\n1. **Profile name** \u2014 a short identifier for this Confluence instance (e.g., `globex`, `acme-corp`). Lowercase alphanumeric and hyphens only.\n2. **Confluence Cloud URL** \u2014 e.g., `https://yoursite.atlassian.net`\n3. **Email address** \u2014 the email associated with their Atlassian account\n\n## Step 4: Write MCP configuration\n\nRun `epimethian-mcp setup --profile <name> --client <client-id>` after Step 5 (credential setup) \u2014 it prints the exact config snippet for your MCP host. Supported clients: `claude-code`, `claude-desktop`, `claude-code-vscode`, `cursor`, `windsurf`, `zed`, `opencode`. Keep the fallback hand-typed examples below for cases where the CLI is unavailable.\n\nAdd the server to the user\'s MCP client config. The exact file and shape depend on the client:\n\n**Claude Code, Claude Desktop, Cursor, Windsurf, Zed** \u2014 `.mcp.json` (or the\nequivalent client-specific config). The standard `mcpServers` shape:\n\n```json\n{\n "mcpServers": {\n "confluence": {\n "command": "<absolute path from Step 2>",\n "env": {\n "CONFLUENCE_PROFILE": "<profile name from Step 3>"\n }\n }\n }\n}\n```\n\n**OpenCode** \u2014 `opencode.json` at the project root or\n`~/.config/opencode/opencode.json`. Different shape (`mcp` block, `type:\n"local"`, `command` is an array, `environment` not `env`):\n\n```jsonc\n{\n "$schema": "https://opencode.ai/config.json",\n "mcp": {\n "confluence": {\n "type": "local",\n "command": ["<absolute path from Step 2>"],\n "enabled": true,\n "environment": {\n "CONFLUENCE_PROFILE": "<profile name from Step 3>",\n "EPIMETHIAN_ALLOW_UNGATED_WRITES": "true"\n }\n }\n }\n}\n```\n\nOpenCode does not support MCP elicitation (the in-protocol confirmation\nprompts), so write tools that fire the elicitation gate fail unless\n`EPIMETHIAN_ALLOW_UNGATED_WRITES=true` is set. See "MCP client\ncompatibility" below for the trade-off.\n\n**IMPORTANT:** The only required env var is `CONFLUENCE_PROFILE`. The URL,\nemail, and API token are stored securely in the OS keychain \u2014 they should\nNOT appear in config files.\n\n## Step 5: Credential setup\n\nTell the user to run this command in their terminal:\n\n```\nepimethian-mcp setup --profile <profile name from Step 3>\n```\n\nThis interactive command will:\n1. Prompt for the Confluence URL, email, and API token (masked input)\n2. Test the connection\n3. Store all credentials securely in the OS keychain under the named profile\n\nThe API token is generated at: https://id.atlassian.com/manage-profile/security/api-tokens\n\n**Do NOT ask the user for the API token yourself.** The token must go directly from the user into the interactive setup command to avoid appearing in conversation logs.\n\n## Step 6: User must restart the MCP client\n\n**IMPORTANT:** The user must restart their MCP client (e.g., restart Claude Code, reload VS Code, restart Claude Desktop) for the new server configuration to take effect. The MCP client reads `.mcp.json` at startup and does not detect changes while running.\n\nTell the user:\n> Please restart your MCP client now to activate the Confluence tools.\n\n## Step 7: Validation\n\nAfter the user restarts, verify the server is working by listing available Confluence tools or running a simple operation like listing spaces.\n\n## Adding Additional Tenants\n\nTo add a second Confluence instance (e.g., for a different customer):\n\n1. Run `epimethian-mcp setup --profile <new-profile-name>` with the new credentials\n2. In the project that uses the new tenant, update `.mcp.json` to set `CONFLUENCE_PROFILE` to the new profile name\n3. Restart the MCP client\n\nEach VS Code window / Claude Code session uses the profile specified in its `.mcp.json`. Profiles are fully isolated \u2014 different OS keychain entries, different Confluence instances.\n\n## Managing Profiles\n\n- List all profiles: `epimethian-mcp profiles`\n- Show details: `epimethian-mcp profiles --verbose`\n- Check connection: `CONFLUENCE_PROFILE=<name> epimethian-mcp status`\n- Set read-only: `epimethian-mcp profiles --set-read-only <name>`\n- Set read-write: `epimethian-mcp profiles --set-read-write <name>`\n\n### Read-Only Mode\n\nNew profiles default to **read-only**. When read-only, all write tools are blocked and return an error. To enable writes for a profile:\n\n```bash\nepimethian-mcp profiles --set-read-write <name>\n```\n\nOr during setup: `epimethian-mcp setup --profile <name> --read-write`\n\n**Important:** Restart any running MCP servers after changing the read-only flag.\n\n### Removing a Profile\n\nTo delete a profile and its credentials, run:\n\n```bash\nepimethian-mcp profiles --remove <name> --force\n```\n\n**Agents must pass `--force`** because the command normally prompts for interactive confirmation (`Remove profile "<name>" and delete its credentials? [y/N]`), which will fail in non-TTY environments like agent shell sessions. The `--force` flag skips the confirmation prompt when stdin is not a TTY.\n\nThis command:\n1. Deletes the credential entry (URL, email, API token) from the OS keychain\n2. Removes the profile from the registry at `~/.config/epimethian-mcp/profiles.json`\n3. Writes an entry to the audit log at `~/.config/epimethian-mcp/audit.log`\n\nAfter removing a profile, also remove or update any `.mcp.json` files that reference it \u2014 otherwise the MCP server will fail to start with a missing-profile error.\n\n**Errors:**\n- If the profile name is invalid (not matching lowercase alphanumeric/hyphens, 1\u201363 chars), the command exits with code 1\n- If the profile does not exist in the keychain, the keychain deletion is silently skipped \u2014 the registry entry is still removed\n\n## Accessing This Guide Post-Install\n\nOnce installed, this guide is available locally via:\n\n```bash\nepimethian-mcp agent-guide\n```\n\nThis prints the full agent guide to stdout \u2014 no web fetch required.\n\n## Uninstallation\n\nWhen a user asks to uninstall Epimethian MCP, follow these steps:\n\n### Step 1: Check for existing profiles\n\n```bash\nepimethian-mcp profiles\n```\n\n### Step 2: Ask the user about credential cleanup\n\nIf profiles exist, ask the user:\n\n> You have Epimethian profiles configured: [list the profile names]. Would you like to delete all stored credentials before uninstalling? (This removes API tokens from your OS keychain.)\n\n### Step 3: Delete credentials (if the user agrees)\n\nFor each profile the user wants removed:\n\n```bash\nepimethian-mcp profiles --remove <name> --force\n```\n\nOr to remove all profiles:\n\n```bash\nfor name in $(epimethian-mcp profiles | grep \'^ \'); do epimethian-mcp profiles --remove "$name" --force; done\n```\n\n### Step 4: Remove MCP configuration\n\nDelete the `confluence` entry (or the tenant-specific entry like `confluence-globex`) from the project\'s `.mcp.json`.\n\n### Step 5: Uninstall the package\n\n```bash\nnpm uninstall -g @de-otio/epimethian-mcp\n```\n\n### Step 6: Restart the MCP client\n\nTell the user to restart their MCP client so it stops trying to launch the removed server.\n\n## CI/CD (No Keychain)\n\nFor environments where the OS keychain is unavailable (Docker, CI), set all three env vars directly:\n\n```json\n{\n "mcpServers": {\n "confluence": {\n "command": "<absolute path>",\n "env": {\n "CONFLUENCE_URL": "<url>",\n "CONFLUENCE_EMAIL": "<email>",\n "CONFLUENCE_API_TOKEN": "<token>"\n }\n }\n }\n}\n```\n\n**Warning:** This exposes the API token in the process environment. Use profile-based auth whenever possible.\n\n## Troubleshooting\n\nIf **npm install fails**:\n- Verify Node.js 18+ is installed: `node --version`\n- Verify npm is on PATH: `npm --version`\n- If permission errors occur, the user may need to fix their npm prefix or use a Node version manager (nvm, fnm)\n\nIf **`epimethian-mcp setup` fails**:\n- "Connection failed": Verify the Confluence URL is correct and accessible\n- "Token is invalid or expired": The user needs to generate a new API token at https://id.atlassian.com/manage-profile/security/api-tokens\n- Keychain errors on Linux: The user may need to install `libsecret` / `gnome-keyring` (`apt install libsecret-tools` or equivalent)\n\nIf **the server doesn\'t appear after restart**:\n- Verify the `.mcp.json` path is correct for the user\'s MCP client\n- Verify the `command` value is an absolute path (run `which epimethian-mcp` to confirm)\n- Check that `.mcp.json` contains valid JSON (no trailing commas, correct quoting)\n\n## Write budget (safety cap on writes)\n\nepimethian-mcp enforces two write-rate caps per server process:\n\n- **Session cap** (default 250): total writes since the server started.\n- **Rolling cap** (default 75 per 15-minute window): catches bursts.\n\nThese are local safety nets, not Confluence limits. They exist because an\nautonomous agent in a retry loop or with a bad plan can issue hundreds of writes\nvery quickly, and most users would rather have a brief pause to confirm than\ndiscover the result an hour later.\n\n### What to do when you (the agent) hit `WRITE_BUDGET_EXCEEDED`\n\n1. **Stop and check.** Was the in-progress work user-requested and going as\n planned? If unsure, ask the user before continuing.\n2. **Explain to the user, in your own words:**\n - The safety budget has been hit (which scope, current vs. limit).\n - What the budget is for: a guard against runaway agents.\n - Whether the work-in-progress is legitimate (your judgement).\n - The two ways forward: wait for the rolling window to reopen, or raise the cap.\n3. **If the user wants to raise the cap**, give them this snippet to add to the\n `env` block of the epimethian-mcp entry in their MCP config (`.mcp.json` or\n equivalent \u2014 see Step 4 above for the layout):\n\n ```json\n "EPIMETHIAN_WRITE_BUDGET_ROLLING": "200",\n "EPIMETHIAN_WRITE_BUDGET_SESSION": "1000"\n ```\n\n Set either value to `"0"` to disable that scope. **Confirm with the user\n before recommending a raise** \u2014 the budget exists precisely to create a\n pause-and-check moment. The user must restart the MCP server (re-open the\n MCP client) for changes to take effect.\n4. **If the user gets a deprecation warning** about `EPIMETHIAN_WRITE_BUDGET_HOURLY`,\n tell them to rename it to `EPIMETHIAN_WRITE_BUDGET_ROLLING` in the same\n config file. The old name still works but will be removed in version 7.\n\n### Operator-side defaults\n\n- **`EPIMETHIAN_WRITE_BUDGET_SESSION`** \u2014 default 250; set to "0" to disable.\n- **`EPIMETHIAN_WRITE_BUDGET_ROLLING`** \u2014 default 75 per 15-minute window; set to "0" to disable.\n- **`EPIMETHIAN_WRITE_BUDGET_HOURLY`** \u2014 deprecated alias for `EPIMETHIAN_WRITE_BUDGET_ROLLING`; will be removed in version 7.\n\n## Soft confirmation (clients without elicitation)\n\nSome MCP clients (currently OpenCode, plus others) don\'t implement the in-protocol\nconfirmation prompt. Starting in v6.6.0, epimethian-mcp routes those confirmations\nthrough your agent\'s normal chat surface instead.\n\n### What you (the agent) see\n\nWhen a destructive write is requested against a client without elicitation, the\ntool returns an error with a confirmation token:\n\n```\nisError: true\nstructuredContent:\n {\n "confirm_token": "<opaque token>",\n "audit_id": "<UUID for correlation>",\n "expires_at": "<ISO timestamp>",\n "page_id": "<pageId>",\n ...\n }\ncontent[0].text:\n \u26A0\uFE0F Confirmation required (SOFT_CONFIRMATION_REQUIRED)\n\n {humanSummary}\n\n Please ask the user before retrying. If approved, re-call with:\n "confirm_token": from structuredContent.\n\n Expires at {timestamp}; invalidated by competing writes.\n```\n\n### What to do\n\n1. STOP. Don\'t retry blindly.\n2. Show the user, in their language, what\'s about to happen (use the\n `humanSummary` field from the result).\n3. Ask the user explicitly. Wait for their answer.\n4. If approved: re-call the tool with the SAME parameters plus\n `confirm_token` from the structuredContent.\n5. If denied: tell the user the operation has been cancelled.\n\n### Token semantics\n\n- Single-use: a successful retry consumes the token. Replays fail.\n- 5-minute TTL by default.\n- Invalidated by any competing write to the same page (stale).\n- Bound to the specific diff and tenant: changing the body, page version, or\n tenant invalidates the token.\n\n### Operator opt-outs\n\nThese environment variables control soft confirmation behavior:\n\n- **`EPIMETHIAN_ALLOW_UNGATED_WRITES=true`** \u2014 bypasses soft confirmation\n entirely (no prompt; useful for headless / CI).\n- **`EPIMETHIAN_DISABLE_SOFT_CONFIRM=true`** \u2014 keeps the legacy\n `ELICITATION_REQUIRED_BUT_UNAVAILABLE` failure mode for clients without\n elicitation support.\n- **`EPIMETHIAN_SOFT_CONFIRM_TTL_MS=300000`** \u2014 override the default 5-minute\n TTL (clamped to 60 seconds minimum, 15 minutes maximum).\n- **`EPIMETHIAN_SOFT_CONFIRM_MINT_LIMIT=100`** \u2014 override the per-15-minute\n mint cap (default 100; "0" disables the cap entirely).\n\n### Multi-process deployments\n\nTokens are process-local in-memory. If you\'re running multiple MCP server\nprocesses for one tenant (e.g. a load-balanced fleet or separate processes\nper IDE window), a soft confirmation minted by process P1 will fail validation\nin process P2 (the load balancer routes the retry to a different process).\nThis is not a bug \u2014 it\'s the safe failure mode \u2014 but it means the user needs\nto mint a new token if the retry lands on a different process.\n\n**Recommendation:** Pin a single MCP server process per agent or IDE window.\nPre-seal profiles upgraded from versions before v5.5.0 must run `epimethian-mcp\nsetup` once to acquire a sealed cloudId before soft confirmation is available.\n\n## MCP client compatibility\n\nepimethian-mcp uses MCP **elicitation** (the in-protocol confirmation\nprompt added to MCP in 2025) as the human-in-the-loop gate for destructive\noperations. Different MCP clients support elicitation differently \u2014 some\nfully, some not at all, and some advertise the capability without honouring\nit. The compatibility matrix below tells you which env-var workaround to\nrecommend, if any.\n\n| Client | Elicitation? | What to do |\n|---|---|---|\n| **Claude Code (CLI)** | Yes \u2014 full support | No special config needed. |\n| **Claude Desktop** | Yes \u2014 full support | No special config needed. |\n| **Claude Code VS Code extension \u2264 2.1.123** | Fakes it | Set `EPIMETHIAN_BYPASS_ELICITATION=true` (see below). |\n| **Claude Code VS Code extension \u2265 2.1.124** | Likely fixed (verify) | If write tools fail with `NO_USER_RESPONSE`, fall back to `EPIMETHIAN_BYPASS_ELICITATION=true`. |\n| **OpenCode** | No \u2014 capability not advertised | Set `EPIMETHIAN_ALLOW_UNGATED_WRITES=true` or use only read tools / additive writes that don\'t trigger the gate. No tracking issue at sst/opencode yet (as of v6.4.1); a feature request would be needed for real elicitation support. |\n| **Cursor / Windsurf / Zed / others** | Varies | If write tools fail with `ELICITATION_REQUIRED_BUT_UNAVAILABLE`, the client doesn\'t advertise the capability \u2014 use `EPIMETHIAN_ALLOW_UNGATED_WRITES=true`. If write tools fail with `NO_USER_RESPONSE` despite the client claiming support, the client fakes it \u2014 use `EPIMETHIAN_BYPASS_ELICITATION=true`. |\n\n### Difference between the two bypass env vars\n\nThese are **not** interchangeable. Pick the one that matches the failure mode:\n\n- **`EPIMETHIAN_ALLOW_UNGATED_WRITES=true`** \u2014 for clients that *don\'t\n advertise* elicitation during the MCP handshake. The server detects the\n absence and (with this flag) lets writes proceed. OpenCode falls in this\n category.\n- **`EPIMETHIAN_BYPASS_ELICITATION=true`** \u2014 for clients that *advertise*\n elicitation but never actually honour the request (the SDK transport\n silently returns `{action: "decline"}`). The Claude Code VS Code\n extension \u2264 2.1.123 falls in this category. This flag is unconditional \u2014\n it bypasses elicitation even when the client claims to support it.\n\n### Trade-off: what you give up by setting either flag\n\nBoth flags **disable the in-protocol confirmation gate**. Writes still go\nthrough the harness\'s tool allow-list (so users can still block the tool\nin their permission settings) and through every server-side guard\n(provenance, source-policy, write-budget, byte-equivalence) \u2014 but the user\nno longer gets a UI prompt before each destructive operation. Recommend\nthis only when:\n\n1. The user is aware of and accepts the trade-off, AND\n2. The user\'s MCP client provides some other interaction model where they\n can intervene (e.g. they review tool calls before approval), OR\n3. The work is read-mostly and only occasional, additive writes happen.\n\n**Do NOT set either flag silently.** If you (the agent) need to recommend\none, explain to the user what the gate is for, why their client can\'t\nhonour it, and what alternative protections remain.\n\n## Other operator-side environment variables\n\nThese are off by default and only relevant in specific scenarios:\n\n- **`EPIMETHIAN_SUPPRESS_EQUIVALENT_DELETIONS`** \u2014 opt-in (default OFF).\n When set to `true`, suppresses the `confirm_deletions` gate for token\n deletion+creation pairs that canonicalise to byte-equivalent XML\n (e.g. re-rendering the same `<ac:link>` macros with different attribute\n order, or regenerating an `<ac:structured-macro>` whose parameters and\n CDATA body are identical after sort). Genuine semantic deletions still\n fire the gate. Every suppressed pair is recorded in the mutation log\n for postmortem. Useful for spaces with lots of cross-link rewrites\n where the gate fires repeatedly on no-op churn.\n- **`EPIMETHIAN_REQUIRE_SOURCE`** \u2014 opt-in (default OFF). When `true`,\n every write tool call must include a `source` parameter (one of\n `user_request` / `file_or_cli_input` / `chained_tool_output` /\n `elicitation_response`). Calls without an explicit source are rejected\n with `SOURCE_POLICY_BLOCKED`. Useful in audit-heavy environments where\n every write must declare provenance.\n- **`EPIMETHIAN_AUTO_UPGRADE`** \u2014 opt-in (default OFF). When `true`, the\n server checks for and applies updates on startup. Useful for managed\n fleets; usually you want explicit `epimethian-mcp upgrade` runs instead.\n- **`CONFLUENCE_READ_ONLY`** \u2014 opt-in (default OFF). When `true`, all\n write tools are disabled regardless of MCP client config. Useful for\n read-only profiles or sandbox environments.\n\n## Available Tools (35)\n\n| Tool | Description |\n|------|-------------|\n| `check_permissions` | Report the current profile\'s MCP access mode and the token\'s capabilities |\n| `create_page` | Create a new Confluence page |\n| `get_page` | Read a page by ID (use `headings_only` to preview structure first) |\n| `get_page_by_title` | Look up a page by title (use `headings_only` to preview structure first) |\n| `update_page` | Update an existing page |\n| `update_page_section` | Update a single section by heading name (supports `body` replacement OR `find_replace` literal substitutions) |\n| `update_page_sections` | Atomically update multiple sections in one version bump (all-or-nothing) |\n| `prepend_to_page` | Insert content at the beginning of an existing page (additive, safe) |\n| `append_to_page` | Insert content at the end of an existing page (additive, safe) |\n| `delete_page` | Delete a page |\n| `revert_page` | Revert a page to a previous version |\n| `list_pages` | List pages in a space |\n| `get_page_children` | Get child pages of a page |\n| `search_pages` | Search pages using CQL (Confluence Query Language) |\n| `get_spaces` | List available Confluence spaces |\n| `add_attachment` | Upload a file attachment to a page |\n| `get_attachments` | List attachments on a page |\n| `add_drawio_diagram` | Add a draw.io diagram to a page |\n| `get_labels` | Get all labels on a Confluence page |\n| `add_label` | Add one or more labels to a Confluence page |\n| `remove_label` | Remove a label from a Confluence page |\n| `get_page_status` | Get the content status badge on a page |\n| `set_page_status` | Set the content status badge on a page |\n| `remove_page_status` | Remove the content status badge from a page |\n| `get_comments` | Get footer and/or inline comments on a page |\n| `create_comment` | Create a footer or inline comment on a page |\n| `resolve_comment` | Resolve or reopen an inline comment |\n| `delete_comment` | Permanently delete a comment |\n| `get_page_versions` | List version history for a page |\n| `get_page_version` | Get page content at a specific historical version |\n| `diff_page_versions` | Compare two versions of a page |\n| `lookup_user` | Search for Atlassian users by name or email to resolve accountId for inline mentions |\n| `resolve_page_link` | Resolve a page title + space key to a stable contentId and URL for page links |\n| `get_version` | Return the epimethian-mcp server version and report available updates |\n| `upgrade` | Upgrade epimethian-mcp to the latest available version (restart required after) |\n';
|
|
49951
|
+
install_agent_default = '# Epimethian MCP - Agent Installation Guide\n\n> This document is for AI agents installing and configuring the Epimethian MCP server on behalf of a user.\n\n## Verification\n\nThe official package is **`@de-otio/epimethian-mcp`** (scoped under `@de-otio`).\n\nDo NOT install unscoped alternatives like `epimethian-mcp` \u2014 these are unofficial and potentially unsafe.\n\n## Prerequisites\n\n- Node.js 18 or later\n- npm on PATH\n\n## Step 1: Install\n\n```bash\nnpm install -g @de-otio/epimethian-mcp\n```\n\nVerify the installation:\n\n```bash\nepimethian-mcp --version 2>/dev/null || which epimethian-mcp\n```\n\n## Step 2: Resolve the absolute path\n\nIDE-hosted agents may not resolve PATH correctly. Always use the absolute path in MCP configuration:\n\n```bash\nwhich epimethian-mcp\n```\n\nUse the output as the `command` value in the MCP config below.\n\n## Step 3: Collect configuration\n\nAsk the user for:\n1. **Profile name** \u2014 a short identifier for this Confluence instance (e.g., `globex`, `acme-corp`). Lowercase alphanumeric and hyphens only.\n2. **Confluence Cloud URL** \u2014 e.g., `https://yoursite.atlassian.net`\n3. **Email address** \u2014 the email associated with their Atlassian account\n\n## Step 4: Write MCP configuration\n\nRun `epimethian-mcp setup --profile <name> --client <client-id>` after Step 5 (credential setup) \u2014 it prints the exact config snippet for your MCP host. Supported clients: `claude-code`, `claude-desktop`, `claude-code-vscode`, `cursor`, `windsurf`, `zed`, `opencode`. Keep the fallback hand-typed examples below for cases where the CLI is unavailable.\n\nAdd the server to the user\'s MCP client config. The exact file and shape depend on the client:\n\n**Claude Code, Claude Desktop, Cursor, Windsurf, Zed** \u2014 `.mcp.json` (or the\nequivalent client-specific config). The standard `mcpServers` shape:\n\n```json\n{\n "mcpServers": {\n "confluence": {\n "command": "<absolute path from Step 2>",\n "env": {\n "CONFLUENCE_PROFILE": "<profile name from Step 3>"\n }\n }\n }\n}\n```\n\n**OpenCode** \u2014 `opencode.json` at the project root or\n`~/.config/opencode/opencode.json`. Different shape (`mcp` block, `type:\n"local"`, `command` is an array, `environment` not `env`):\n\n```jsonc\n{\n "$schema": "https://opencode.ai/config.json",\n "mcp": {\n "confluence": {\n "type": "local",\n "command": ["<absolute path from Step 2>"],\n "enabled": true,\n "environment": {\n "CONFLUENCE_PROFILE": "<profile name from Step 3>",\n "EPIMETHIAN_ALLOW_UNGATED_WRITES": "true"\n }\n }\n }\n}\n```\n\nOpenCode does not support MCP elicitation (the in-protocol confirmation\nprompts), so write tools that fire the elicitation gate fail unless\n`EPIMETHIAN_ALLOW_UNGATED_WRITES=true` is set. See "MCP client\ncompatibility" below for the trade-off.\n\n**IMPORTANT:** The only required env var is `CONFLUENCE_PROFILE`. The URL,\nemail, and API token are stored securely in the OS keychain \u2014 they should\nNOT appear in config files.\n\n## Step 5: Credential setup\n\nTell the user to run this command in their terminal:\n\n```\nepimethian-mcp setup --profile <profile name from Step 3>\n```\n\nThis interactive command will:\n1. Prompt for the Confluence URL, email, and API token (masked input)\n2. Test the connection\n3. Store all credentials securely in the OS keychain under the named profile\n\nThe API token is generated at: https://id.atlassian.com/manage-profile/security/api-tokens\n\n**Do NOT ask the user for the API token yourself.** The token must go directly from the user into the interactive setup command to avoid appearing in conversation logs.\n\n## Step 6: User must restart the MCP client\n\n**IMPORTANT:** The user must restart their MCP client (e.g., restart Claude Code, reload VS Code, restart Claude Desktop) for the new server configuration to take effect. The MCP client reads `.mcp.json` at startup and does not detect changes while running.\n\nTell the user:\n> Please restart your MCP client now to activate the Confluence tools.\n\n## Step 7: Validation\n\nAfter the user restarts, verify the server is working by listing available Confluence tools or running a simple operation like listing spaces.\n\n## Adding Additional Tenants\n\nTo add a second Confluence instance (e.g., for a different customer):\n\n1. Run `epimethian-mcp setup --profile <new-profile-name>` with the new credentials\n2. In the project that uses the new tenant, update `.mcp.json` to set `CONFLUENCE_PROFILE` to the new profile name\n3. Restart the MCP client\n\nEach VS Code window / Claude Code session uses the profile specified in its `.mcp.json`. Profiles are fully isolated \u2014 different OS keychain entries, different Confluence instances.\n\n## Managing Profiles\n\n- List all profiles: `epimethian-mcp profiles`\n- Show details: `epimethian-mcp profiles --verbose`\n- Check connection: `CONFLUENCE_PROFILE=<name> epimethian-mcp status`\n- Set read-only: `epimethian-mcp profiles --set-read-only <name>`\n- Set read-write: `epimethian-mcp profiles --set-read-write <name>`\n\n### Read-Only Mode\n\nNew profiles default to **read-only**. When read-only, all write tools are blocked and return an error. To enable writes for a profile:\n\n```bash\nepimethian-mcp profiles --set-read-write <name>\n```\n\nOr during setup: `epimethian-mcp setup --profile <name> --read-write`\n\n**Important:** Restart any running MCP servers after changing the read-only flag.\n\n### Removing a Profile\n\nTo delete a profile and its credentials, run:\n\n```bash\nepimethian-mcp profiles --remove <name> --force\n```\n\n**Agents must pass `--force`** because the command normally prompts for interactive confirmation (`Remove profile "<name>" and delete its credentials? [y/N]`), which will fail in non-TTY environments like agent shell sessions. The `--force` flag skips the confirmation prompt when stdin is not a TTY.\n\nThis command:\n1. Deletes the credential entry (URL, email, API token) from the OS keychain\n2. Removes the profile from the registry at `~/.config/epimethian-mcp/profiles.json`\n3. Writes an entry to the audit log at `~/.config/epimethian-mcp/audit.log`\n\nAfter removing a profile, also remove or update any `.mcp.json` files that reference it \u2014 otherwise the MCP server will fail to start with a missing-profile error.\n\n**Errors:**\n- If the profile name is invalid (not matching lowercase alphanumeric/hyphens, 1\u201363 chars), the command exits with code 1\n- If the profile does not exist in the keychain, the keychain deletion is silently skipped \u2014 the registry entry is still removed\n\n## Accessing This Guide Post-Install\n\nOnce installed, this guide is available locally via:\n\n```bash\nepimethian-mcp agent-guide\n```\n\nThis prints the full agent guide to stdout \u2014 no web fetch required.\n\n## Uninstallation\n\nWhen a user asks to uninstall Epimethian MCP, follow these steps:\n\n### Step 1: Check for existing profiles\n\n```bash\nepimethian-mcp profiles\n```\n\n### Step 2: Ask the user about credential cleanup\n\nIf profiles exist, ask the user:\n\n> You have Epimethian profiles configured: [list the profile names]. Would you like to delete all stored credentials before uninstalling? (This removes API tokens from your OS keychain.)\n\n### Step 3: Delete credentials (if the user agrees)\n\nFor each profile the user wants removed:\n\n```bash\nepimethian-mcp profiles --remove <name> --force\n```\n\nOr to remove all profiles:\n\n```bash\nfor name in $(epimethian-mcp profiles | grep \'^ \'); do epimethian-mcp profiles --remove "$name" --force; done\n```\n\n### Step 4: Remove MCP configuration\n\nDelete the `confluence` entry (or the tenant-specific entry like `confluence-globex`) from the project\'s `.mcp.json`.\n\n### Step 5: Uninstall the package\n\n```bash\nnpm uninstall -g @de-otio/epimethian-mcp\n```\n\n### Step 6: Restart the MCP client\n\nTell the user to restart their MCP client so it stops trying to launch the removed server.\n\n## CI/CD (No Keychain)\n\nFor environments where the OS keychain is unavailable (Docker, CI), set all three env vars directly:\n\n```json\n{\n "mcpServers": {\n "confluence": {\n "command": "<absolute path>",\n "env": {\n "CONFLUENCE_URL": "<url>",\n "CONFLUENCE_EMAIL": "<email>",\n "CONFLUENCE_API_TOKEN": "<token>"\n }\n }\n }\n}\n```\n\n**Warning:** This exposes the API token in the process environment. Use profile-based auth whenever possible.\n\n## Troubleshooting\n\nIf **npm install fails**:\n- Verify Node.js 18+ is installed: `node --version`\n- Verify npm is on PATH: `npm --version`\n- If permission errors occur, the user may need to fix their npm prefix or use a Node version manager (nvm, fnm)\n\nIf **`epimethian-mcp setup` fails**:\n- "Connection failed": Verify the Confluence URL is correct and accessible\n- "Token is invalid or expired": The user needs to generate a new API token at https://id.atlassian.com/manage-profile/security/api-tokens\n- Keychain errors on Linux: The user may need to install `libsecret` / `gnome-keyring` (`apt install libsecret-tools` or equivalent)\n\nIf **the server doesn\'t appear after restart**:\n- Verify the `.mcp.json` path is correct for the user\'s MCP client\n- Verify the `command` value is an absolute path (run `which epimethian-mcp` to confirm)\n- Check that `.mcp.json` contains valid JSON (no trailing commas, correct quoting)\n\n## Write budget (safety cap on writes)\n\nepimethian-mcp enforces two write-rate caps per server process:\n\n- **Session cap** (default 250): total writes since the server started.\n- **Rolling cap** (default 75 per 15-minute window): catches bursts.\n\nThese are local safety nets, not Confluence limits. They exist because an\nautonomous agent in a retry loop or with a bad plan can issue hundreds of writes\nvery quickly, and most users would rather have a brief pause to confirm than\ndiscover the result an hour later.\n\n### What to do when you (the agent) hit `WRITE_BUDGET_EXCEEDED`\n\n1. **Stop and check.** Was the in-progress work user-requested and going as\n planned? If unsure, ask the user before continuing.\n2. **Explain to the user, in your own words:**\n - The safety budget has been hit (which scope, current vs. limit).\n - What the budget is for: a guard against runaway agents.\n - Whether the work-in-progress is legitimate (your judgement).\n - The two ways forward: wait for the rolling window to reopen, or raise the cap.\n3. **If the user wants to raise the cap**, give them this snippet to add to the\n `env` block of the epimethian-mcp entry in their MCP config (`.mcp.json` or\n equivalent \u2014 see Step 4 above for the layout):\n\n ```json\n "EPIMETHIAN_WRITE_BUDGET_ROLLING": "200",\n "EPIMETHIAN_WRITE_BUDGET_SESSION": "1000"\n ```\n\n Set either value to `"0"` to disable that scope. **Confirm with the user\n before recommending a raise** \u2014 the budget exists precisely to create a\n pause-and-check moment. The user must restart the MCP server (re-open the\n MCP client) for changes to take effect.\n4. **If the user gets a deprecation warning** about `EPIMETHIAN_WRITE_BUDGET_HOURLY`,\n tell them to rename it to `EPIMETHIAN_WRITE_BUDGET_ROLLING` in the same\n config file. The old name still works but will be removed in version 7.\n\n### Operator-side defaults\n\n- **`EPIMETHIAN_WRITE_BUDGET_SESSION`** \u2014 default 250; set to "0" to disable.\n- **`EPIMETHIAN_WRITE_BUDGET_ROLLING`** \u2014 default 75 per 15-minute window; set to "0" to disable.\n- **`EPIMETHIAN_WRITE_BUDGET_HOURLY`** \u2014 deprecated alias for `EPIMETHIAN_WRITE_BUDGET_ROLLING`; will be removed in version 7.\n\n## Soft confirmation (clients without working elicitation)\n\nSome MCP clients (OpenCode, the Claude Code VS Code extension, and\nothers) don\'t implement the in-protocol confirmation prompt \u2014 either\nthey don\'t advertise the capability, or they advertise it and never\nhonour the request (the SDK transport silently returns\n`{action: "decline"}` without showing the user a UI). Starting in\nv6.6.0, epimethian-mcp routes those confirmations through your\nagent\'s normal chat surface instead.\n\nThe implementation evolved across v6.6.0 \u2192 v6.6.3:\n\n- **v6.6.0** introduced soft elicitation for clients that don\'t\n *advertise* the capability \u2014 token-bound, single-use, diff-bound.\n- **v6.6.1** added **fast-decline auto-detection**: if the client\n advertises elicitation but the decline arrives in <50 ms (well below\n human reaction time), the session is flagged as fake and the call\n is re-routed through the soft-confirm path automatically. Threshold\n is overridable via `EPIMETHIAN_FAST_DECLINE_THRESHOLD_MS=<10..5000>`.\n No env-var configuration needed for the Claude Code VS Code\n extension\'s "fakes elicitation" bug \u2014 it Just Works.\n- **v6.6.2** declared `outputSchema` on every mutating tool so\n spec-compliant clients are obliged to forward `structuredContent`\n (where the token lives) to the agent. Added an opt-in\n `EPIMETHIAN_TOKEN_IN_TEXT=true` fallback that appends the full\n token to `content[0].text` for clients that drop `content` blocks\n on `isError: true` results (Claude Code issues #15412 / #9962 /\n #39976).\n- **v6.6.3** swapped the `outputSchema` from `z.discriminatedUnion`\n to `z.object` so the MCP SDK\'s `normalizeObjectSchema` (which only\n accepts schemas with `.shape`) can route the structured payload\n through `validateToolOutput` without throwing `_zod` undefined\n after the write commits. Hotfix; data-integrity-critical.\n\n### What you (the agent) see\n\nWhen a destructive write is requested against a client without working\nelicitation, the tool returns an error with a confirmation token:\n\n```\nisError: true\nstructuredContent:\n {\n "kind": "confirmation_required",\n "confirm_token": "<opaque token>",\n "audit_id": "<UUID for correlation>",\n "expires_at": "<ISO timestamp>",\n "page_id": "<pageId>",\n "human_summary": "<one-line description for the user>",\n "deletion_summary": { ... numeric counts only ... } // optional\n }\ncontent[0].text:\n \u26A0\uFE0F Confirmation required (SOFT_CONFIRMATION_REQUIRED)\n\n {human_summary}\n\n Please ask the user before retrying. If approved, re-call with the\n same parameters plus "confirm_token" from structuredContent.\n\n Token tail: ...<last 8 chars> Expires: <timestamp> Audit ID: <uuid>\n\n [FALLBACK] Full token (EPIMETHIAN_TOKEN_IN_TEXT=true): <full token>\n \u2190 only present when EPIMETHIAN_TOKEN_IN_TEXT=true is set\n```\n\nThe `kind` discriminator distinguishes this `"confirmation_required"`\narm from the success arm (`"written"` or `"deleted"`) on the same\ntool. Successful writes return:\n\n```\nstructuredContent:\n { "kind": "written", "page_id": "...", "new_version": 12, ... }\n```\n\n\u2026or `"kind": "deleted"` for `delete_page`.\n\n### What to do\n\n1. STOP. Don\'t retry blindly.\n2. Show the user, in their language, what\'s about to happen \u2014 use the\n `human_summary` field from `structuredContent`, or the\n human-readable text in `content[0].text` if your client doesn\'t\n forward `structuredContent`. **Never echo the token bytes to the\n user** \u2014 the token is meant to flow agent \u2192 server, not user \u2192 eye.\n3. Ask the user explicitly. Wait for their answer.\n4. If approved: re-call the tool with the SAME parameters plus\n `confirm_token`. Read the token from `structuredContent.confirm_token`\n when available; if your client doesn\'t surface that, the token\'s\n full bytes are in the `[FALLBACK] Full token: \u2026` line of\n `content[0].text` whenever `EPIMETHIAN_TOKEN_IN_TEXT=true` is set\n on the server. The 8-character "Token tail: \u2026" line in the prose\n is for human inspection only \u2014 it is **not** the token.\n5. If denied: tell the user the operation has been cancelled.\n\n### Token semantics\n\n- Single-use: a successful retry consumes the token. Replays fail.\n- 5-minute TTL by default.\n- Invalidated by any competing write to the same page (stale).\n- Bound to the specific diff and tenant: changing the body, page version, or\n tenant invalidates the token.\n\n### Operator opt-outs\n\nThese environment variables control soft confirmation behavior:\n\n- **`EPIMETHIAN_ALLOW_UNGATED_WRITES=true`** \u2014 bypasses soft confirmation\n entirely (no prompt; useful for headless / CI). Removes the\n human-in-the-loop gate; the harness\'s tool allow-list still applies.\n- **`EPIMETHIAN_DISABLE_SOFT_CONFIRM=true`** \u2014 keeps the legacy\n `ELICITATION_REQUIRED_BUT_UNAVAILABLE` failure mode for clients without\n elicitation support.\n- **`EPIMETHIAN_SOFT_CONFIRM_TTL_MS=300000`** \u2014 override the default 5-minute\n TTL (clamped to 60 seconds minimum, 15 minutes maximum).\n- **`EPIMETHIAN_SOFT_CONFIRM_MINT_LIMIT=100`** \u2014 override the per-15-minute\n mint cap (default 100; "0" disables the cap entirely).\n\n#### v6.6.1+ fast-decline auto-detection (Claude Code VS Code et al.)\n\n- **`EPIMETHIAN_TREAT_ELICITATION_AS_UNSUPPORTED=true`** \u2014 deterministic\n counterpart to fast-decline auto-detection. Use when your client is\n known to advertise elicitation but never honour it (e.g. the Claude\n Code VS Code extension \u2264 2.1.123) and you want to skip the timing\n probe on the first call. Distinct from `EPIMETHIAN_BYPASS_ELICITATION`:\n this routes through the soft-confirmation gate; bypass removes the\n gate entirely.\n- **`EPIMETHIAN_FAST_DECLINE_THRESHOLD_MS=<10..5000>`** \u2014 override the\n fast-decline threshold (default 50 ms). Raise this if a slow MCP\n transport is mis-classifying real declines as fake.\n- **`EPIMETHIAN_DISABLE_FAST_DECLINE_DETECTION=true`** \u2014 total\n off-switch for the auto-detection; restores exactly v6.6.0 behaviour.\n\n#### v6.6.2+ structured-content fallback\n\n- **`EPIMETHIAN_TOKEN_IN_TEXT=true`** \u2014 opt-in fallback for clients that\n drop `content` blocks on `isError: true` results, or that ignore the\n `outputSchema` declaration and never surface `structuredContent`\n to the agent. When set, the soft-confirm result text appends a\n `[FALLBACK] Full token (EPIMETHIAN_TOKEN_IN_TEXT=true): <token>`\n line so the agent can still extract the token. The structured\n payload is unchanged. Trade-off: the token is visible in the agent\n transcript (the security choice v6.6.0 explicitly avoided), so use\n only when needed. Today this is required for Claude Code (the VS\n Code extension and possibly the CLI) \u2014 see the per-client matrix\n below.\n\n### Multi-process deployments\n\nTokens are process-local in-memory. If you\'re running multiple MCP server\nprocesses for one tenant (e.g. a load-balanced fleet or separate processes\nper IDE window), a soft confirmation minted by process P1 will fail validation\nin process P2 (the load balancer routes the retry to a different process).\nThis is not a bug \u2014 it\'s the safe failure mode \u2014 but it means the user needs\nto mint a new token if the retry lands on a different process.\n\n**Recommendation:** Pin a single MCP server process per agent or IDE window.\nPre-seal profiles upgraded from versions before v5.5.0 must run `epimethian-mcp\nsetup` once to acquire a sealed cloudId before soft confirmation is available.\n\n## MCP client compatibility\n\nepimethian-mcp uses MCP **elicitation** (the in-protocol confirmation\nprompt added to MCP in 2025) as the human-in-the-loop gate for destructive\noperations. Different MCP clients support elicitation differently \u2014 some\nfully, some not at all, and some advertise the capability without honouring\nit. The compatibility matrix below tells you which env-var workaround to\nrecommend, if any.\n\n| Client | Elicitation? | What to do (v6.6.3+) |\n|---|---|---|\n| **Claude Code (CLI)** | Yes \u2014 full support | No special config needed. |\n| **Claude Desktop** | Yes \u2014 full support | No special config needed. |\n| **Claude Code VS Code extension** (all versions tested through 2.1.x) | Fakes it (advertises capability, never honours) | v6.6.1\'s fast-decline auto-detection routes the call through soft-confirm automatically. **Plus** set `EPIMETHIAN_TOKEN_IN_TEXT=true` in the server\'s env so the agent can read the full token from `content[0].text` \u2014 Claude Code does not currently surface `structuredContent` on `isError: true` responses (issue #15412). No `EPIMETHIAN_BYPASS_ELICITATION` needed; the gate works through the soft-confirm token flow. |\n| **OpenCode** | No \u2014 capability not advertised | v6.6.0+ soft-confirmation token flow is automatic when the client lacks elicitation. The Vercel AI SDK forwards `structuredContent` to the model when `outputSchema` is declared (which v6.6.2 added) \u2014 the agent should be able to read `confirm_token` directly. If your build of OpenCode/AI-SDK doesn\'t honour `outputSchema`, set `EPIMETHIAN_TOKEN_IN_TEXT=true` as a fallback. `EPIMETHIAN_ALLOW_UNGATED_WRITES=true` remains an escape hatch for headless / CI runs where no human is in the loop. |\n| **Cursor / Windsurf / Zed / others** | Varies | If write tools fail with `ELICITATION_REQUIRED_BUT_UNAVAILABLE`, the client doesn\'t advertise the capability \u2014 soft-confirm should kick in automatically; use `EPIMETHIAN_ALLOW_UNGATED_WRITES=true` only if no human is in the loop. If write tools succeed at the gate but the agent can\'t see the token in the response, set `EPIMETHIAN_TOKEN_IN_TEXT=true`. If the client *advertises* elicitation but always fails fast (decline arrives in <50 ms), v6.6.1\'s auto-detection re-routes through soft-confirm. Set `EPIMETHIAN_TREAT_ELICITATION_AS_UNSUPPORTED=true` to skip the timing probe on the first call when the client is known to fake it. |\n\n### Three configuration paths \u2014 pick the one that matches your client\n\nThese flags are **not interchangeable**. The newer (v6.6.x) flags\npreserve the human-in-the-loop gate; the older bypass flags remove it.\nDefault to preserving the gate.\n\n- **(Recommended) Soft-confirm token flow.** No env-var bypass needed.\n v6.6.0 + v6.6.1 + v6.6.3 give you a working soft-confirm round-trip\n for any client whose host can put a "do you approve?" prompt in front\n of the user (chat surface, tool-result UI, etc.). For clients with\n rendering quirks, layer on `EPIMETHIAN_TOKEN_IN_TEXT=true`\n (additive \u2014 keeps the gate, just exposes the token in\n `content[0].text` too).\n- **`EPIMETHIAN_ALLOW_UNGATED_WRITES=true`** \u2014 for clients that *don\'t\n advertise* elicitation AND have no other way to surface a\n confirmation prompt (e.g. headless / CI runs). Removes the gate\n entirely; only the harness allow-list and server-side guards remain.\n Pre-v6.6.0 escape hatch; rarely needed today.\n- **`EPIMETHIAN_BYPASS_ELICITATION=true`** \u2014 unconditional bypass,\n regardless of whether the client advertises elicitation. Original\n v6.4.1 escape hatch for Claude Code VS Code\'s "fakes elicitation"\n bug. **In v6.6.1+ this is no longer needed** for that bug \u2014 the\n fast-decline detector routes around it. Use only if you specifically\n want to remove the gate.\n\n### Trade-off: what you give up by setting `ALLOW_UNGATED_WRITES` or `BYPASS_ELICITATION`\n\nThese two flags **disable the human-in-the-loop confirmation gate\nentirely**. Writes still go through the harness\'s tool allow-list (so\nusers can still block the tool in their permission settings) and\nthrough every server-side guard (provenance, source-policy,\nwrite-budget, byte-equivalence) \u2014 but the user no longer gets a\nprompt before each destructive operation. Recommend this only when:\n\n1. The user is aware of and accepts the trade-off, AND\n2. The user\'s MCP client provides some other interaction model where they\n can intervene (e.g. they review tool calls before approval), OR\n3. The work is read-mostly and only occasional, additive writes happen.\n\n**Do NOT set either flag silently.** If you (the agent) need to recommend\none, explain to the user what the gate is for, why their client can\'t\nhonour it, and what alternative protections remain.\n\n`EPIMETHIAN_TREAT_ELICITATION_AS_UNSUPPORTED` and\n`EPIMETHIAN_TOKEN_IN_TEXT` (v6.6.1+ / v6.6.2+) are different \u2014 they\n**preserve** the gate by routing through the soft-confirm token flow.\nThey affect how the prompt reaches the user, not whether one happens.\nPrefer these to the bypass flags whenever the client supports any\nform of agent \u2194 user dialogue.\n\n## Other operator-side environment variables\n\nThese are off by default and only relevant in specific scenarios:\n\n- **`EPIMETHIAN_SUPPRESS_EQUIVALENT_DELETIONS`** \u2014 opt-in (default OFF).\n When set to `true`, suppresses the `confirm_deletions` gate for token\n deletion+creation pairs that canonicalise to byte-equivalent XML\n (e.g. re-rendering the same `<ac:link>` macros with different attribute\n order, or regenerating an `<ac:structured-macro>` whose parameters and\n CDATA body are identical after sort). Genuine semantic deletions still\n fire the gate. Every suppressed pair is recorded in the mutation log\n for postmortem. Useful for spaces with lots of cross-link rewrites\n where the gate fires repeatedly on no-op churn.\n- **`EPIMETHIAN_REQUIRE_SOURCE`** \u2014 opt-in (default OFF). When `true`,\n every write tool call must include a `source` parameter (one of\n `user_request` / `file_or_cli_input` / `chained_tool_output` /\n `elicitation_response`). Calls without an explicit source are rejected\n with `SOURCE_POLICY_BLOCKED`. Useful in audit-heavy environments where\n every write must declare provenance.\n- **`EPIMETHIAN_AUTO_UPGRADE`** \u2014 opt-in (default OFF). When `true`, the\n server checks for and applies updates on startup. Useful for managed\n fleets; usually you want explicit `epimethian-mcp upgrade` runs instead.\n- **`CONFLUENCE_READ_ONLY`** \u2014 opt-in (default OFF). When `true`, all\n write tools are disabled regardless of MCP client config. Useful for\n read-only profiles or sandbox environments.\n\n## Available Tools (35)\n\n| Tool | Description |\n|------|-------------|\n| `check_permissions` | Report the current profile\'s MCP access mode and the token\'s capabilities |\n| `create_page` | Create a new Confluence page |\n| `get_page` | Read a page by ID (use `headings_only` to preview structure first) |\n| `get_page_by_title` | Look up a page by title (use `headings_only` to preview structure first) |\n| `update_page` | Update an existing page |\n| `update_page_section` | Update a single section by heading name (supports `body` replacement OR `find_replace` literal substitutions) |\n| `update_page_sections` | Atomically update multiple sections in one version bump (all-or-nothing) |\n| `prepend_to_page` | Insert content at the beginning of an existing page (additive, safe) |\n| `append_to_page` | Insert content at the end of an existing page (additive, safe) |\n| `delete_page` | Delete a page |\n| `revert_page` | Revert a page to a previous version |\n| `list_pages` | List pages in a space |\n| `get_page_children` | Get child pages of a page |\n| `search_pages` | Search pages using CQL (Confluence Query Language) |\n| `get_spaces` | List available Confluence spaces |\n| `add_attachment` | Upload a file attachment to a page |\n| `get_attachments` | List attachments on a page |\n| `add_drawio_diagram` | Add a draw.io diagram to a page |\n| `get_labels` | Get all labels on a Confluence page |\n| `add_label` | Add one or more labels to a Confluence page |\n| `remove_label` | Remove a label from a Confluence page |\n| `get_page_status` | Get the content status badge on a page |\n| `set_page_status` | Set the content status badge on a page |\n| `remove_page_status` | Remove the content status badge from a page |\n| `get_comments` | Get footer and/or inline comments on a page |\n| `create_comment` | Create a footer or inline comment on a page |\n| `resolve_comment` | Resolve or reopen an inline comment |\n| `delete_comment` | Permanently delete a comment |\n| `get_page_versions` | List version history for a page |\n| `get_page_version` | Get page content at a specific historical version |\n| `diff_page_versions` | Compare two versions of a page |\n| `lookup_user` | Search for Atlassian users by name or email to resolve accountId for inline mentions |\n| `resolve_page_link` | Resolve a page title + space key to a stable contentId and URL for page links |\n| `get_version` | Return the epimethian-mcp server version and report available updates |\n| `upgrade` | Upgrade epimethian-mcp to the latest available version (restart required after) |\n';
|
|
49929
49952
|
}
|
|
49930
49953
|
});
|
|
49931
49954
|
|
|
@@ -49950,7 +49973,7 @@ __export(upgrade_exports, {
|
|
|
49950
49973
|
runUpgrade: () => runUpgrade
|
|
49951
49974
|
});
|
|
49952
49975
|
async function runUpgrade() {
|
|
49953
|
-
const currentVersion = "6.
|
|
49976
|
+
const currentVersion = "6.7.0";
|
|
49954
49977
|
console.log(`epimethian-mcp upgrade: current version v${currentVersion}`);
|
|
49955
49978
|
let pending = await getPendingUpdate();
|
|
49956
49979
|
if (!pending) {
|
|
@@ -61749,7 +61772,7 @@ async function registerTools(server, config3) {
|
|
|
61749
61772
|
{
|
|
61750
61773
|
description: describeWithLock(
|
|
61751
61774
|
withDestructiveWarning(
|
|
61752
|
-
'Create a new page in Confluence. Accepts either Confluence storage format (XHTML) or GFM markdown \u2014 markdown is automatically converted to storage format before submission. Do NOT mix the two: a body that contains both <ac:.../> storage tags AND markdown structural patterns (## headings, lists, fenced code blocks) is rejected with MIXED_INPUT_DETECTED. To inject a TOC macro from markdown, use YAML frontmatter at the top of the body: `---\\ntoc:\\n maxLevel: 3\\n minLevel: 1\\n---`. For other macros from markdown, use directive syntax: `:info[
|
|
61775
|
+
'Create a new page in Confluence. Accepts either Confluence storage format (XHTML) or GFM markdown \u2014 markdown is automatically converted to storage format before submission. Do NOT mix the two: a body that contains both <ac:.../> storage tags AND markdown structural patterns (## headings, lists, fenced code blocks) is rejected with MIXED_INPUT_DETECTED. To inject a TOC macro from markdown, use YAML frontmatter at the top of the body: `---\\ntoc:\\n maxLevel: 3\\n minLevel: 1\\n---`. For other macros from markdown, use directive syntax: panel macros `:info[...]`/`:note[...]`/`:warning[...]`/`:tip[...]`/`:success[...]` (panel body accepts inline markdown; optional `{title="..."}`), plus `:status[...]{colour=...}`, `:mention[Name]{accountId=...}`, `:date[2026-04-23]`, `:emoji[smile]`, `:jira[PROJ-1]`, `:anchor[name]`. Use allow_raw_html: true to permit raw HTML inside markdown (disabled by default for security). Use confluence_base_url to override the base URL used by the link rewriter (defaults to the configured Confluence URL). If the space has auto-numbering, the page version may advance silently after creation while post-processing renders the TOC and number prefixes. Re-read the page before subsequent updates. Set wait_for_post_processing=true to poll until the version stabilises (recommended when the next operation will be an update \u2014 addresses post-processing churn without resorting to version="current").'
|
|
61753
61776
|
),
|
|
61754
61777
|
config3
|
|
61755
61778
|
),
|
|
@@ -61757,7 +61780,7 @@ async function registerTools(server, config3) {
|
|
|
61757
61780
|
title: external_exports.string().describe("Page title"),
|
|
61758
61781
|
space_key: external_exports.string().describe("Confluence space key, e.g. 'DEV' or 'TEAM'"),
|
|
61759
61782
|
body: external_exports.string().describe(
|
|
61760
|
-
|
|
61783
|
+
'Page content \u2014 GFM markdown or Confluence storage format (XHTML). Markdown is auto-detected and converted. Do not mix the two: inlining <ac:.../> macros inside a markdown body is rejected. For a TOC use YAML frontmatter (toc: { maxLevel, minLevel }); for other macros use directive syntax \u2014 panels (:info[...] / :note[...] / :warning[...] / :tip[...] / :success[...] with optional {title="..."}), plus :status[...]{colour=...}, :mention[...]{accountId=...}, :date[YYYY-MM-DD], :emoji[name], :jira[KEY-123], :anchor[name].'
|
|
61761
61784
|
),
|
|
61762
61785
|
parent_id: external_exports.string().optional().describe("Optional parent page ID"),
|
|
61763
61786
|
allow_raw_html: external_exports.boolean().default(false).describe("Allow raw HTML passthrough inside markdown bodies (disabled by default; only enable for trusted content)."),
|
|
@@ -61925,7 +61948,7 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
61925
61948
|
{
|
|
61926
61949
|
description: describeWithLock(
|
|
61927
61950
|
withDestructiveWarning(
|
|
61928
|
-
"Update an existing Confluence page. Accepts GFM markdown or Confluence storage format \u2014 markdown is automatically converted via the token-aware write path, which preserves all existing macros and rich elements. Do NOT mix the two: a body that contains both <ac:.../> storage tags AND markdown structural patterns (## headings, lists, fenced code blocks) is rejected with MIXED_INPUT_DETECTED. To inject a TOC macro from markdown, use YAML frontmatter at the top of the body: `---\\ntoc:\\n maxLevel: 3\\n minLevel: 1\\n---`. For other macros from markdown, use directive syntax: `:info[
|
|
61951
|
+
"Update an existing Confluence page. Accepts GFM markdown or Confluence storage format \u2014 markdown is automatically converted via the token-aware write path, which preserves all existing macros and rich elements. Do NOT mix the two: a body that contains both <ac:.../> storage tags AND markdown structural patterns (## headings, lists, fenced code blocks) is rejected with MIXED_INPUT_DETECTED. To inject a TOC macro from markdown, use YAML frontmatter at the top of the body: `---\\ntoc:\\n maxLevel: 3\\n minLevel: 1\\n---`. For other macros from markdown, use directive syntax: panel macros `:info[...]`/`:note[...]`/`:warning[...]`/`:tip[...]`/`:success[...]` (panel body accepts inline markdown; optional `{title=\"...\"}`), plus `:status[...]{colour=...}`, `:mention[Name]{accountId=...}`, `:date[2026-04-23]`, `:emoji[smile]`, `:jira[PROJ-1]`, `:anchor[name]`. You must provide the version number from your most recent get_page call. If the page was modified by someone else since then, this will return a conflict error \u2014 re-read the page and retry.\n\nFor narrow changes to a single section, prefer update_page_section \u2014 it leaves the rest of the page untouched and is safer for targeted edits.\n\nMarkdown update flags:\n- confirm_deletions: set to true to acknowledge removing preserved macros/elements (default false \u2014 any deletion errors until confirmed).\n- replace_body: set to true for a wholesale rewrite that skips preservation (default false).\n- confirm_shrinkage: set to true to acknowledge a >50% body size reduction (default false).\n- confirm_structure_loss: set to true to acknowledge a >50% heading count drop (default false).\n- allow_raw_html: allow raw HTML inside markdown bodies (default false).\n- confluence_base_url: override the URL used by the link rewriter.\n\nreplace_body skips all safety nets (token preservation, deletion confirmation). When delegating update_page to a subagent, ensure the agent includes the full existing body \u2014 replace_body replaces ALL content with only what you provide.\n\nIf your MCP client does not support in-protocol confirmation, this tool returns `SOFT_CONFIRMATION_REQUIRED` on the first call when destructive flags are set. STOP and ask the user before retrying. If the user approves, re-call this tool with the same parameters plus `confirm_token` from the first response. The token expires after 5 minutes and is invalidated by competing writes. See the 'Soft confirmation' section of `install-agent.md` for the full protocol."
|
|
61929
61952
|
),
|
|
61930
61953
|
config3
|
|
61931
61954
|
),
|
|
@@ -61935,7 +61958,7 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
61935
61958
|
version: versionField.describe(
|
|
61936
61959
|
`The page version number from your most recent get_page call. Pass the literal string "current" to skip the read and apply this update on top of whatever the latest version is right now. WARNING: "current" deliberately bypasses optimistic concurrency \u2014 it is NOT a conflict-resolution strategy. If a coworker (or another agent) writes between our read and submit, the API will still 409 and we propagate the conflict. Use a numeric version when you want the "don't overwrite my coworker's changes" guard. Use "current" only as a shortcut to skip the get_page round-trip when concurrent writes are not a concern (e.g. immediately after create_page).`
|
|
61937
61960
|
),
|
|
61938
|
-
body: external_exports.string().optional().describe(
|
|
61961
|
+
body: external_exports.string().optional().describe('New body content \u2014 GFM markdown or Confluence storage format (XHTML). Markdown is auto-detected and converted via the token-aware write path. Do not mix the two: inlining <ac:.../> macros inside a markdown body is rejected. For a TOC use YAML frontmatter (toc: { maxLevel, minLevel }); for other macros use directive syntax \u2014 panels (:info[...] / :note[...] / :warning[...] / :tip[...] / :success[...] with optional {title="..."}), plus :status[...]{colour=...}, :mention[...]{accountId=...}, :date[YYYY-MM-DD], :emoji[name], :jira[KEY-123], :anchor[name].'),
|
|
61939
61962
|
version_message: external_exports.string().optional().describe("Optional version comment"),
|
|
61940
61963
|
confirm_deletions: external_exports.boolean().default(false).describe("Set to true to acknowledge that your markdown removes preserved macros or rich elements. Required when any preserved element would be deleted."),
|
|
61941
61964
|
replace_body: external_exports.boolean().default(false).describe("Set to true for a wholesale page rewrite that skips token preservation. All existing macros will be lost. Use only when intentionally replacing the full body."),
|
|
@@ -62204,7 +62227,7 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
62204
62227
|
page_id: external_exports.string().describe("The Confluence page ID"),
|
|
62205
62228
|
section: external_exports.string().describe("Heading text identifying the section to replace (case-insensitive)"),
|
|
62206
62229
|
body: external_exports.string().optional().describe(
|
|
62207
|
-
|
|
62230
|
+
'New content for this section \u2014 GFM markdown or Confluence storage format. Markdown is auto-detected and converted via the token-aware write path, which preserves existing macros and emoticons within the section. The heading itself is preserved; only content under it is replaced. Do not mix the two: inlining <ac:.../> macros inside a markdown body is rejected with MIXED_INPUT_DETECTED. For macros from markdown use directive syntax \u2014 panels :info[...] / :note[...] / :warning[...] / :tip[...] / :success[...] (with optional {title="..."}), plus :status[...]{colour=...}, :mention[...]{accountId=...}, :date[YYYY-MM-DD], :emoji[name], :jira[KEY-123], :anchor[name]. Exactly one of `body` or `find_replace` must be provided.'
|
|
62208
62231
|
),
|
|
62209
62232
|
find_replace: external_exports.array(
|
|
62210
62233
|
external_exports.object({
|
|
@@ -63844,7 +63867,7 @@ ${titleFenced}${echo2}`
|
|
|
63844
63867
|
inputSchema: {}
|
|
63845
63868
|
},
|
|
63846
63869
|
async () => {
|
|
63847
|
-
let text2 = `epimethian-mcp v${"6.
|
|
63870
|
+
let text2 = `epimethian-mcp v${"6.7.0"}`;
|
|
63848
63871
|
try {
|
|
63849
63872
|
const pending = await getPendingUpdate();
|
|
63850
63873
|
if (pending) {
|
|
@@ -63875,7 +63898,7 @@ ${label} update available: v${pending.current} \u2192 v${pending.latest}. Run \`
|
|
|
63875
63898
|
const pending = await getPendingUpdate();
|
|
63876
63899
|
if (!pending) {
|
|
63877
63900
|
return toolResult(
|
|
63878
|
-
`epimethian-mcp v${"6.
|
|
63901
|
+
`epimethian-mcp v${"6.7.0"} is already up to date.`
|
|
63879
63902
|
);
|
|
63880
63903
|
}
|
|
63881
63904
|
const output = await performUpgrade(pending.latest);
|
|
@@ -63897,7 +63920,7 @@ async function startRecoveryServer(profile) {
|
|
|
63897
63920
|
const server = new McpServer(
|
|
63898
63921
|
{
|
|
63899
63922
|
name: `confluence-${profile}-setup-needed`,
|
|
63900
|
-
version: "6.
|
|
63923
|
+
version: "6.7.0"
|
|
63901
63924
|
},
|
|
63902
63925
|
{
|
|
63903
63926
|
instructions: `The Confluence profile "${profile}" referenced by CONFLUENCE_PROFILE has no keychain entry, so no Confluence tools are available. Call the setup_profile tool for instructions to create it.`
|
|
@@ -63948,21 +63971,21 @@ async function main() {
|
|
|
63948
63971
|
const serverName = config3.profile ? `confluence-${config3.profile}` : "confluence";
|
|
63949
63972
|
const server = new McpServer({
|
|
63950
63973
|
name: serverName,
|
|
63951
|
-
version: "6.
|
|
63974
|
+
version: "6.7.0"
|
|
63952
63975
|
});
|
|
63953
63976
|
await registerTools(server, config3);
|
|
63954
63977
|
const transport = new StdioServerTransport();
|
|
63955
63978
|
await server.connect(transport);
|
|
63956
63979
|
try {
|
|
63957
63980
|
const pending = await getPendingUpdate();
|
|
63958
|
-
if (pending && pending.current === "6.
|
|
63981
|
+
if (pending && pending.current === "6.7.0") {
|
|
63959
63982
|
console.error(
|
|
63960
63983
|
`epimethian-mcp: update available: v${pending.current} \u2192 v${pending.latest} (${pending.type}). Run \`epimethian-mcp upgrade\` to install.`
|
|
63961
63984
|
);
|
|
63962
63985
|
}
|
|
63963
63986
|
} catch {
|
|
63964
63987
|
}
|
|
63965
|
-
checkForUpdates("6.
|
|
63988
|
+
checkForUpdates("6.7.0").catch(() => {
|
|
63966
63989
|
});
|
|
63967
63990
|
}
|
|
63968
63991
|
|