@de-otio/epimethian-mcp 4.2.1 → 4.3.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/dist/cli/index.js CHANGED
@@ -30591,6 +30591,26 @@ async function runProfiles() {
30591
30591
  await setReadOnlyFlag(name, false);
30592
30592
  return;
30593
30593
  }
30594
+ const disableAttrIdx = args.indexOf("--disable-attribution");
30595
+ if (disableAttrIdx > -1) {
30596
+ const name = args[disableAttrIdx + 1];
30597
+ if (!name || !PROFILE_NAME_RE.test(name)) {
30598
+ console.error("Error: --disable-attribution requires a valid profile name.");
30599
+ process.exit(1);
30600
+ }
30601
+ await setAttributionFlag(name, false);
30602
+ return;
30603
+ }
30604
+ const enableAttrIdx = args.indexOf("--enable-attribution");
30605
+ if (enableAttrIdx > -1) {
30606
+ const name = args[enableAttrIdx + 1];
30607
+ if (!name || !PROFILE_NAME_RE.test(name)) {
30608
+ console.error("Error: --enable-attribution requires a valid profile name.");
30609
+ process.exit(1);
30610
+ }
30611
+ await setAttributionFlag(name, true);
30612
+ return;
30613
+ }
30594
30614
  const verbose = args.includes("--verbose");
30595
30615
  const profiles = await readProfileRegistry();
30596
30616
  if (profiles.length === 0) {
@@ -30599,19 +30619,20 @@ async function runProfiles() {
30599
30619
  }
30600
30620
  if (verbose) {
30601
30621
  console.log(
30602
- ` ${"Profile".padEnd(20)} ${"URL".padEnd(40)} ${"Read-Only".padEnd(12)} Email`
30622
+ ` ${"Profile".padEnd(20)} ${"URL".padEnd(40)} ${"Read-Only".padEnd(12)} ${"Attribution".padEnd(14)} Email`
30603
30623
  );
30604
30624
  console.log(
30605
- ` ${"\u2500".repeat(20)} ${"\u2500".repeat(40)} ${"\u2500".repeat(12)} ${"\u2500".repeat(30)}`
30625
+ ` ${"\u2500".repeat(20)} ${"\u2500".repeat(40)} ${"\u2500".repeat(12)} ${"\u2500".repeat(14)} ${"\u2500".repeat(30)}`
30606
30626
  );
30607
30627
  for (const name of profiles) {
30608
30628
  try {
30609
30629
  const creds = await readFromKeychain(name);
30610
30630
  const settings = await getProfileSettings(name);
30611
30631
  const roLabel = settings?.readOnly ? "YES" : "no";
30632
+ const attrLabel = settings?.attribution === false ? "disabled" : "enabled";
30612
30633
  if (creds) {
30613
30634
  console.log(
30614
- ` ${name.padEnd(20)} ${creds.url.padEnd(40)} ${roLabel.padEnd(12)} ${creds.email}`
30635
+ ` ${name.padEnd(20)} ${creds.url.padEnd(40)} ${roLabel.padEnd(12)} ${attrLabel.padEnd(14)} ${creds.email}`
30615
30636
  );
30616
30637
  } else {
30617
30638
  console.log(
@@ -30629,7 +30650,8 @@ async function runProfiles() {
30629
30650
  for (const name of profiles) {
30630
30651
  const settings = await getProfileSettings(name);
30631
30652
  const roSuffix = settings?.readOnly ? " (read-only)" : "";
30632
- console.log(` ${name}${roSuffix}`);
30653
+ const attrSuffix = settings?.attribution === false ? " (no attribution)" : "";
30654
+ console.log(` ${name}${roSuffix}${attrSuffix}`);
30633
30655
  }
30634
30656
  console.log("\nUse --verbose to show URLs and emails.");
30635
30657
  }
@@ -30645,6 +30667,17 @@ async function setReadOnlyFlag(name, readOnly) {
30645
30667
  console.log(`Profile "${name}" is now ${label}.`);
30646
30668
  console.log("Note: Restart any running MCP servers for this change to take effect.");
30647
30669
  }
30670
+ async function setAttributionFlag(name, attribution) {
30671
+ const profiles = await readProfileRegistry();
30672
+ if (!profiles.includes(name)) {
30673
+ console.error(`Error: Profile "${name}" does not exist.`);
30674
+ process.exit(1);
30675
+ }
30676
+ await setProfileSettings(name, { attribution });
30677
+ const label = attribution ? "enabled" : "disabled";
30678
+ console.log(`Attribution footer for profile "${name}" is now ${label}.`);
30679
+ console.log("Note: Restart any running MCP servers for this change to take effect.");
30680
+ }
30648
30681
  async function removeProfile(name, force) {
30649
30682
  if (!force || import_node_process3.stdin.isTTY) {
30650
30683
  if (!import_node_process3.stdin.isTTY) {
@@ -30749,7 +30782,7 @@ var init_status = __esm({
30749
30782
  var install_agent_default;
30750
30783
  var init_install_agent = __esm({
30751
30784
  "install-agent.md"() {
30752
- 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., `jambit`, `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\nAdd the server to `.mcp.json` (or the equivalent config file for the user\'s MCP client):\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**IMPORTANT:** The only env var needed is `CONFLUENCE_PROFILE`. The URL, email, and API token are stored securely in the OS keychain \u2014 they should NOT 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-jambit`) 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## Available Tools (26)\n\n| Tool | Description |\n|------|-------------|\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 |\n| `delete_page` | Delete a page |\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';
30785
+ 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., `jambit`, `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\nAdd the server to `.mcp.json` (or the equivalent config file for the user\'s MCP client):\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**IMPORTANT:** The only env var needed is `CONFLUENCE_PROFILE`. The URL, email, and API token are stored securely in the OS keychain \u2014 they should NOT 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-jambit`) 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## Available Tools (27)\n\n| Tool | Description |\n|------|-------------|\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 |\n| `delete_page` | Delete a page |\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| `get_version` | Return the epimethian-mcp server version |\n';
30753
30786
  }
30754
30787
  });
30755
30788
 
@@ -45120,12 +45153,14 @@ async function getConfig() {
45120
45153
  const { url, email: email2, apiToken, profile } = await resolveCredentials();
45121
45154
  const registrySettings = profile ? await getProfileSettings(profile) : void 0;
45122
45155
  const readOnly = registrySettings?.readOnly === true || process.env.CONFLUENCE_READ_ONLY === "true";
45156
+ const attribution = registrySettings?.attribution !== false && process.env.CONFLUENCE_ATTRIBUTION !== "false";
45123
45157
  const authHeader = "Basic " + Buffer.from(`${email2}:${apiToken}`).toString("base64");
45124
45158
  _config = Object.freeze({
45125
45159
  url,
45126
45160
  email: email2,
45127
45161
  profile,
45128
45162
  readOnly,
45163
+ attribution,
45129
45164
  apiV2: `${url}/wiki/api/v2`,
45130
45165
  apiV1: `${url}/wiki/rest/api`,
45131
45166
  authHeader,
@@ -45167,8 +45202,9 @@ Expected user: ${email2}
45167
45202
  }
45168
45203
  const profileLabel = profile ? `profile: ${profile}` : "env-var mode";
45169
45204
  const readOnlyLabel = config2.readOnly ? ", READ-ONLY" : "";
45205
+ const attributionLabel = config2.attribution ? "" : ", NO-ATTRIBUTION";
45170
45206
  console.error(
45171
- `epimethian-mcp: connected to ${url} as ${email2} (${profileLabel}${readOnlyLabel})`
45207
+ `epimethian-mcp: connected to ${url} as ${email2} (${profileLabel}${readOnlyLabel}${attributionLabel})`
45172
45208
  );
45173
45209
  }
45174
45210
  var PageSchema = external_exports.object({
@@ -45367,21 +45403,23 @@ async function getPage(pageId, includeBody) {
45367
45403
  return page;
45368
45404
  }
45369
45405
  async function createPage(spaceId, title, body, parentId) {
45406
+ const cfg = await getConfig();
45370
45407
  const cleanBody = stripAttributionFooter(toStorageFormat(body));
45408
+ const pageBody = cfg.attribution ? cleanBody + "\n" + buildAttributionFooter("created") : cleanBody;
45371
45409
  const payload = {
45372
45410
  title,
45373
45411
  spaceId,
45374
45412
  status: "current",
45375
45413
  body: {
45376
45414
  representation: "storage",
45377
- value: cleanBody + "\n" + buildAttributionFooter("created")
45415
+ value: pageBody
45378
45416
  },
45379
- version: { message: "Created by Epimethian" }
45417
+ version: { message: `Created by Epimethian v${"4.3.0"}` }
45380
45418
  };
45381
45419
  if (parentId) payload.parentId = parentId;
45382
45420
  const raw = await v2Post("/pages", payload);
45383
45421
  const page = PageSchema.parse(raw);
45384
- pageCache.set(page.id, page.version?.number ?? 1, cleanBody + "\n" + buildAttributionFooter("created"));
45422
+ pageCache.set(page.id, page.version?.number ?? 1, pageBody);
45385
45423
  try {
45386
45424
  await addLabels(page.id, [ATTRIBUTION_LABEL]);
45387
45425
  } catch {
@@ -45389,8 +45427,9 @@ async function createPage(spaceId, title, body, parentId) {
45389
45427
  return page;
45390
45428
  }
45391
45429
  async function updatePage(pageId, opts) {
45430
+ const cfg = await getConfig();
45392
45431
  const newVersion = opts.version + 1;
45393
- const versionMessage = opts.versionMessage ? `${opts.versionMessage} (via Epimethian)` : "Updated by Epimethian";
45432
+ const versionMessage = opts.versionMessage ? `${opts.versionMessage} (via Epimethian v${"4.3.0"})` : `Updated by Epimethian v${"4.3.0"}`;
45394
45433
  const payload = {
45395
45434
  id: pageId,
45396
45435
  status: "current",
@@ -45399,9 +45438,10 @@ async function updatePage(pageId, opts) {
45399
45438
  };
45400
45439
  if (opts.body) {
45401
45440
  const cleanBody = stripAttributionFooter(toStorageFormat(opts.body));
45441
+ const pageBody = cfg.attribution ? cleanBody + "\n" + buildAttributionFooter("updated") : cleanBody;
45402
45442
  payload.body = {
45403
45443
  representation: "storage",
45404
- value: cleanBody + "\n" + buildAttributionFooter("updated")
45444
+ value: pageBody
45405
45445
  };
45406
45446
  }
45407
45447
  let raw;
@@ -45415,8 +45455,9 @@ async function updatePage(pageId, opts) {
45415
45455
  }
45416
45456
  const page = PageSchema.parse(raw);
45417
45457
  if (opts.body) {
45418
- const cachedBody = stripAttributionFooter(toStorageFormat(opts.body));
45419
- pageCache.set(pageId, newVersion, cachedBody + "\n" + buildAttributionFooter("updated"));
45458
+ const cleanBody = stripAttributionFooter(toStorageFormat(opts.body));
45459
+ const pageBody = cfg.attribution ? cleanBody + "\n" + buildAttributionFooter("updated") : cleanBody;
45460
+ pageCache.set(pageId, newVersion, pageBody);
45420
45461
  }
45421
45462
  try {
45422
45463
  await addLabels(page.id, [ATTRIBUTION_LABEL]);
@@ -45541,12 +45582,18 @@ var ATTRIBUTION_LABEL = "epimethian-managed";
45541
45582
  var ATTRIBUTION_START = "<!-- epimethian-attribution-start -->";
45542
45583
  var ATTRIBUTION_END = "<!-- epimethian-attribution-end -->";
45543
45584
  function buildAttributionFooter(action) {
45544
- return ATTRIBUTION_START + `<p style="font-size:11px;color:#999;margin-top:2em;"><em>This page was ${action} with <a href="${GITHUB_URL}">Epimethian</a>.</em></p>` + ATTRIBUTION_END;
45585
+ return ATTRIBUTION_START + `<p style="font-size:9px;color:#999;margin-top:2em;"><em>This page was ${action} with <a href="${GITHUB_URL}">Epimethian</a> v${"4.3.0"}.</em></p>` + ATTRIBUTION_END;
45545
45586
  }
45546
45587
  function stripAttributionFooter(body) {
45547
45588
  return body.replace(
45548
45589
  /<!--\s*epimethian-attribution-start\s*-->[\s\S]*?<!--\s*epimethian-attribution-end\s*-->/g,
45549
45590
  ""
45591
+ ).replace(
45592
+ // Also strip bare (unmarked) attribution paragraphs — these appear
45593
+ // when an agent copies page content from get_page and passes it
45594
+ // back to update_page without removing the footer.
45595
+ /<p[^>]*>[\s\S]*?<a\s[^>]*href="https:\/\/github\.com\/de-otio\/epimethian-mcp"[^>]*>Epimethian<\/a>[\s\S]*?<\/p>/gi,
45596
+ ""
45550
45597
  ).trimEnd();
45551
45598
  }
45552
45599
  async function getLabels(pageId) {
@@ -46497,7 +46544,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
46497
46544
  "get_page_status",
46498
46545
  "get_page_versions",
46499
46546
  "get_page_version",
46500
- "diff_page_versions"
46547
+ "diff_page_versions",
46548
+ "get_version"
46501
46549
  ]);
46502
46550
  function writeGuard(toolName, config2) {
46503
46551
  if (!config2.readOnly) return null;
@@ -47549,6 +47597,14 @@ ${result.diff}${truncNote}` + echo
47549
47597
  }
47550
47598
  }
47551
47599
  );
47600
+ server.registerTool(
47601
+ "get_version",
47602
+ {
47603
+ description: "Return the epimethian-mcp server version.",
47604
+ inputSchema: {}
47605
+ },
47606
+ async () => toolResult(`epimethian-mcp v${"4.3.0"}`)
47607
+ );
47552
47608
  }
47553
47609
  async function main() {
47554
47610
  const config2 = await getConfig();
@@ -47556,7 +47612,7 @@ async function main() {
47556
47612
  const serverName = config2.profile ? `confluence-${config2.profile}` : "confluence";
47557
47613
  const server = new McpServer({
47558
47614
  name: serverName,
47559
- version: "1.0.0"
47615
+ version: "4.3.0"
47560
47616
  });
47561
47617
  registerTools(server, config2);
47562
47618
  const transport = new StdioServerTransport();