@de-otio/epimethian-mcp 6.0.0 → 6.1.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 +115 -1
- package/dist/cli/index.js +691 -83
- package/dist/cli/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -28993,6 +28993,75 @@ var init_page_cache = __esm({
|
|
|
28993
28993
|
}
|
|
28994
28994
|
});
|
|
28995
28995
|
|
|
28996
|
+
// src/server/config.ts
|
|
28997
|
+
function resolvePosture(settings) {
|
|
28998
|
+
if (settings?.posture) return settings.posture;
|
|
28999
|
+
if (settings?.readOnly === true) return "read-only";
|
|
29000
|
+
if (settings?.readOnly === false) return "read-write";
|
|
29001
|
+
const envVal = process.env.CONFLUENCE_READ_ONLY;
|
|
29002
|
+
if (envVal === "true") return "read-only";
|
|
29003
|
+
if (envVal === "false") return "read-write";
|
|
29004
|
+
return "detect";
|
|
29005
|
+
}
|
|
29006
|
+
function resolveUnverifiedStatusFlag(settings) {
|
|
29007
|
+
if (settings?.unverifiedStatus !== void 0) return settings.unverifiedStatus;
|
|
29008
|
+
if (process.env.CONFLUENCE_UNVERIFIED_STATUS === "false") return false;
|
|
29009
|
+
if (process.env.CONFLUENCE_UNVERIFIED_STATUS === "true") return true;
|
|
29010
|
+
return true;
|
|
29011
|
+
}
|
|
29012
|
+
function resolveEffectivePosture(configured, probed) {
|
|
29013
|
+
if (configured === "read-only") {
|
|
29014
|
+
return { effective: "read-only", source: "profile" };
|
|
29015
|
+
}
|
|
29016
|
+
if (configured === "read-write") {
|
|
29017
|
+
if (probed === "read-only") {
|
|
29018
|
+
return {
|
|
29019
|
+
effective: "read-write",
|
|
29020
|
+
source: "profile",
|
|
29021
|
+
warning: "configured read-write but probe indicates token is read-only; writes will likely fail"
|
|
29022
|
+
};
|
|
29023
|
+
}
|
|
29024
|
+
return { effective: "read-write", source: "profile" };
|
|
29025
|
+
}
|
|
29026
|
+
if (probed === "write") {
|
|
29027
|
+
return { effective: "read-write", source: "probe" };
|
|
29028
|
+
}
|
|
29029
|
+
if (probed === "read-only") {
|
|
29030
|
+
return { effective: "read-only", source: "probe" };
|
|
29031
|
+
}
|
|
29032
|
+
return {
|
|
29033
|
+
effective: "read-write",
|
|
29034
|
+
source: "default",
|
|
29035
|
+
warning: "capability probe was inconclusive \u2014 defaulting to read-write"
|
|
29036
|
+
};
|
|
29037
|
+
}
|
|
29038
|
+
function resolveUnverifiedStatusLocale(settings) {
|
|
29039
|
+
if (settings?.unverifiedStatusLocale) return settings.unverifiedStatusLocale;
|
|
29040
|
+
if (process.env.CONFLUENCE_UNVERIFIED_STATUS_LOCALE) {
|
|
29041
|
+
return process.env.CONFLUENCE_UNVERIFIED_STATUS_LOCALE;
|
|
29042
|
+
}
|
|
29043
|
+
return void 0;
|
|
29044
|
+
}
|
|
29045
|
+
var ProfileSettingsValidator;
|
|
29046
|
+
var init_config = __esm({
|
|
29047
|
+
"src/server/config.ts"() {
|
|
29048
|
+
"use strict";
|
|
29049
|
+
init_zod();
|
|
29050
|
+
ProfileSettingsValidator = external_exports.object({
|
|
29051
|
+
readOnly: external_exports.boolean().optional(),
|
|
29052
|
+
posture: external_exports.enum(["read-only", "read-write", "detect"]).optional(),
|
|
29053
|
+
attribution: external_exports.boolean().optional(),
|
|
29054
|
+
unverifiedStatus: external_exports.boolean().optional(),
|
|
29055
|
+
unverifiedStatusLocale: external_exports.string().optional(),
|
|
29056
|
+
unverifiedStatusName: external_exports.string().max(20).optional(),
|
|
29057
|
+
unverifiedStatusColor: external_exports.enum(["#FFC400", "#2684FF", "#57D9A3", "#FF7452", "#8777D9"]).optional(),
|
|
29058
|
+
allowed_tools: external_exports.array(external_exports.string()).optional(),
|
|
29059
|
+
denied_tools: external_exports.array(external_exports.string()).optional(),
|
|
29060
|
+
spaces: external_exports.array(external_exports.string()).optional()
|
|
29061
|
+
}).strict();
|
|
29062
|
+
}
|
|
29063
|
+
});
|
|
29064
|
+
|
|
28996
29065
|
// node_modules/he/he.js
|
|
28997
29066
|
var require_he = __commonJS({
|
|
28998
29067
|
"node_modules/he/he.js"(exports2, module2) {
|
|
@@ -34945,6 +35014,67 @@ var require_dist2 = __commonJS({
|
|
|
34945
35014
|
});
|
|
34946
35015
|
|
|
34947
35016
|
// src/server/confluence-client.ts
|
|
35017
|
+
var confluence_client_exports = {};
|
|
35018
|
+
__export(confluence_client_exports, {
|
|
35019
|
+
CommentSchema: () => CommentSchema,
|
|
35020
|
+
ConfluenceApiError: () => ConfluenceApiError,
|
|
35021
|
+
ConfluenceAuthError: () => ConfluenceAuthError,
|
|
35022
|
+
ConfluenceConflictError: () => ConfluenceConflictError,
|
|
35023
|
+
ConfluenceNotFoundError: () => ConfluenceNotFoundError,
|
|
35024
|
+
ConfluencePermissionError: () => ConfluencePermissionError,
|
|
35025
|
+
ContentStateSchema: () => ContentStateSchema,
|
|
35026
|
+
LabelSchema: () => LabelSchema,
|
|
35027
|
+
PageSchema: () => PageSchema,
|
|
35028
|
+
ProfileNotConfiguredError: () => ProfileNotConfiguredError,
|
|
35029
|
+
_rawCreatePage: () => _rawCreatePage,
|
|
35030
|
+
_rawUpdatePage: () => _rawUpdatePage,
|
|
35031
|
+
addLabels: () => addLabels,
|
|
35032
|
+
createFooterComment: () => createFooterComment,
|
|
35033
|
+
createInlineComment: () => createInlineComment,
|
|
35034
|
+
deleteFooterComment: () => deleteFooterComment,
|
|
35035
|
+
deleteInlineComment: () => deleteInlineComment,
|
|
35036
|
+
deletePage: () => deletePage,
|
|
35037
|
+
ensureAttributionLabel: () => ensureAttributionLabel,
|
|
35038
|
+
extractHeadings: () => extractHeadings,
|
|
35039
|
+
extractSection: () => extractSection,
|
|
35040
|
+
extractSectionBody: () => extractSectionBody,
|
|
35041
|
+
formatPage: () => formatPage,
|
|
35042
|
+
getAttachments: () => getAttachments,
|
|
35043
|
+
getCommentReplies: () => getCommentReplies,
|
|
35044
|
+
getConfig: () => getConfig,
|
|
35045
|
+
getContentState: () => getContentState,
|
|
35046
|
+
getFooterComments: () => getFooterComments,
|
|
35047
|
+
getInlineComments: () => getInlineComments,
|
|
35048
|
+
getLabels: () => getLabels,
|
|
35049
|
+
getPage: () => getPage,
|
|
35050
|
+
getPageByTitle: () => getPageByTitle,
|
|
35051
|
+
getPageChildren: () => getPageChildren,
|
|
35052
|
+
getPageVersionBody: () => getPageVersionBody,
|
|
35053
|
+
getPageVersions: () => getPageVersions,
|
|
35054
|
+
getSpaces: () => getSpaces,
|
|
35055
|
+
listPages: () => listPages,
|
|
35056
|
+
looksLikeMarkdown: () => looksLikeMarkdown,
|
|
35057
|
+
normalizeBodyForSubmit: () => normalizeBodyForSubmit,
|
|
35058
|
+
probeWriteCapability: () => probeWriteCapability,
|
|
35059
|
+
removeContentState: () => removeContentState,
|
|
35060
|
+
removeLabel: () => removeLabel,
|
|
35061
|
+
replaceSection: () => replaceSection,
|
|
35062
|
+
resolveComment: () => resolveComment,
|
|
35063
|
+
resolveCredentials: () => resolveCredentials,
|
|
35064
|
+
resolveSpaceId: () => resolveSpaceId,
|
|
35065
|
+
sanitizeCommentBody: () => sanitizeCommentBody,
|
|
35066
|
+
sanitizeError: () => sanitizeError,
|
|
35067
|
+
searchPages: () => searchPages,
|
|
35068
|
+
searchPagesByTitle: () => searchPagesByTitle,
|
|
35069
|
+
searchUsers: () => searchUsers,
|
|
35070
|
+
setClientLabel: () => setClientLabel,
|
|
35071
|
+
setContentState: () => setContentState,
|
|
35072
|
+
toMarkdownView: () => toMarkdownView,
|
|
35073
|
+
toStorageFormat: () => toStorageFormat,
|
|
35074
|
+
truncateStorageFormat: () => truncateStorageFormat,
|
|
35075
|
+
uploadAttachment: () => uploadAttachment,
|
|
35076
|
+
validateStartup: () => validateStartup
|
|
35077
|
+
});
|
|
34948
35078
|
function setClientLabel(label) {
|
|
34949
35079
|
if (!label) {
|
|
34950
35080
|
_clientLabel = void 0;
|
|
@@ -35004,8 +35134,13 @@ async function getConfig() {
|
|
|
35004
35134
|
if (_config) return _config;
|
|
35005
35135
|
const { url, email: email2, apiToken, profile, sealedCloudId, sealedDisplayName } = await resolveCredentials();
|
|
35006
35136
|
const registrySettings = profile ? await getProfileSettings(profile) : void 0;
|
|
35007
|
-
const
|
|
35137
|
+
const posture = resolvePosture(registrySettings);
|
|
35138
|
+
const readOnly = posture === "read-only";
|
|
35008
35139
|
const attribution = registrySettings?.attribution !== false && process.env.CONFLUENCE_ATTRIBUTION !== "false";
|
|
35140
|
+
const unverifiedStatus = resolveUnverifiedStatusFlag(registrySettings);
|
|
35141
|
+
const unverifiedStatusLocale = resolveUnverifiedStatusLocale(registrySettings);
|
|
35142
|
+
const unverifiedStatusName = registrySettings?.unverifiedStatusName;
|
|
35143
|
+
const unverifiedStatusColor = registrySettings?.unverifiedStatusColor;
|
|
35009
35144
|
const authHeader = "Basic " + Buffer.from(`${email2}:${apiToken}`).toString("base64");
|
|
35010
35145
|
const jsonHeaders = Object.freeze({
|
|
35011
35146
|
Authorization: authHeader,
|
|
@@ -35016,16 +35151,83 @@ async function getConfig() {
|
|
|
35016
35151
|
email: email2,
|
|
35017
35152
|
profile,
|
|
35018
35153
|
readOnly,
|
|
35154
|
+
posture,
|
|
35019
35155
|
attribution,
|
|
35156
|
+
unverifiedStatus,
|
|
35157
|
+
unverifiedStatusLocale,
|
|
35158
|
+
unverifiedStatusName,
|
|
35159
|
+
unverifiedStatusColor,
|
|
35020
35160
|
apiV2: `${url}/wiki/api/v2`,
|
|
35021
35161
|
apiV1: `${url}/wiki/rest/api`,
|
|
35022
35162
|
authHeader,
|
|
35023
35163
|
jsonHeaders,
|
|
35024
35164
|
sealedCloudId,
|
|
35025
|
-
sealedDisplayName
|
|
35165
|
+
sealedDisplayName,
|
|
35166
|
+
// Placeholders — overwritten by validateStartup() once the probe runs.
|
|
35167
|
+
// Until then, treat as read-write to preserve existing behavior.
|
|
35168
|
+
effectivePosture: posture === "read-only" ? "read-only" : "read-write",
|
|
35169
|
+
probedCapability: null,
|
|
35170
|
+
postureSource: "default"
|
|
35026
35171
|
});
|
|
35027
35172
|
return _config;
|
|
35028
35173
|
}
|
|
35174
|
+
async function probeWriteCapability() {
|
|
35175
|
+
const cfg = await getConfig();
|
|
35176
|
+
try {
|
|
35177
|
+
const spaces = await getSpaces(1);
|
|
35178
|
+
if (spaces.length > 0) {
|
|
35179
|
+
const spaceKey = spaces[0].key;
|
|
35180
|
+
const permUrl = new URL(
|
|
35181
|
+
`${cfg.apiV1}/user/current/permission/space/${encodeURIComponent(spaceKey)}`
|
|
35182
|
+
);
|
|
35183
|
+
permUrl.searchParams.set("operationKey", "create");
|
|
35184
|
+
permUrl.searchParams.set("targetType", "page");
|
|
35185
|
+
try {
|
|
35186
|
+
const res = await confluenceRequest(permUrl.toString());
|
|
35187
|
+
const raw = await res.json();
|
|
35188
|
+
if (raw && typeof raw === "object") {
|
|
35189
|
+
const obj = raw;
|
|
35190
|
+
if (obj.havePermission === true) return "write";
|
|
35191
|
+
if (obj.havePermission === false) return "read-only";
|
|
35192
|
+
if (Array.isArray(obj.permissions)) {
|
|
35193
|
+
const perms = obj.permissions;
|
|
35194
|
+
const hasCreate = perms.some((p) => {
|
|
35195
|
+
const op = p.operation;
|
|
35196
|
+
return op?.operation === "create" && op?.targetType === "page";
|
|
35197
|
+
});
|
|
35198
|
+
return hasCreate ? "write" : "read-only";
|
|
35199
|
+
}
|
|
35200
|
+
}
|
|
35201
|
+
} catch (permErr) {
|
|
35202
|
+
if (permErr instanceof ConfluenceAuthError) throw permErr;
|
|
35203
|
+
if (permErr instanceof ConfluenceNotFoundError) {
|
|
35204
|
+
} else if (permErr instanceof ConfluencePermissionError) {
|
|
35205
|
+
return "read-only";
|
|
35206
|
+
}
|
|
35207
|
+
}
|
|
35208
|
+
}
|
|
35209
|
+
} catch (spacesErr) {
|
|
35210
|
+
if (spacesErr instanceof ConfluenceAuthError) throw spacesErr;
|
|
35211
|
+
}
|
|
35212
|
+
try {
|
|
35213
|
+
await confluenceRequest(`${cfg.apiV2}/pages/999999999999`, {
|
|
35214
|
+
method: "PUT",
|
|
35215
|
+
body: JSON.stringify({})
|
|
35216
|
+
});
|
|
35217
|
+
return "write";
|
|
35218
|
+
} catch (err) {
|
|
35219
|
+
if (err instanceof ConfluenceAuthError) throw err;
|
|
35220
|
+
if (err instanceof ConfluencePermissionError) return "read-only";
|
|
35221
|
+
if (err instanceof ConfluenceNotFoundError) return "write";
|
|
35222
|
+
if (err instanceof ConfluenceApiError && err.status >= 400 && err.status < 500) {
|
|
35223
|
+
return "write";
|
|
35224
|
+
}
|
|
35225
|
+
console.error(
|
|
35226
|
+
`epimethian-mcp: warning \u2014 write-capability probe failed with unexpected error: ${err instanceof Error ? err.message : String(err)}`
|
|
35227
|
+
);
|
|
35228
|
+
return "inconclusive";
|
|
35229
|
+
}
|
|
35230
|
+
}
|
|
35029
35231
|
async function validateStartup(config3) {
|
|
35030
35232
|
const { url, email: email2, profile } = config3;
|
|
35031
35233
|
const decoded = Buffer.from(
|
|
@@ -35058,12 +35260,38 @@ Expected user: ${email2}
|
|
|
35058
35260
|
if (profile) {
|
|
35059
35261
|
await verifyOrSealTenant(config3, apiToken);
|
|
35060
35262
|
}
|
|
35263
|
+
const configuredPosture = config3.posture ?? "detect";
|
|
35264
|
+
let probedCapability = null;
|
|
35265
|
+
if (configuredPosture === "detect") {
|
|
35266
|
+
try {
|
|
35267
|
+
probedCapability = await probeWriteCapability();
|
|
35268
|
+
} catch (err) {
|
|
35269
|
+
console.error(
|
|
35270
|
+
`epimethian-mcp: warning \u2014 write-capability probe threw unexpectedly: ${err instanceof Error ? err.message : String(err)}`
|
|
35271
|
+
);
|
|
35272
|
+
probedCapability = "inconclusive";
|
|
35273
|
+
}
|
|
35274
|
+
}
|
|
35275
|
+
const resolved = resolveEffectivePosture(configuredPosture, probedCapability);
|
|
35276
|
+
_config = Object.freeze({
|
|
35277
|
+
..._config,
|
|
35278
|
+
effectivePosture: resolved.effective,
|
|
35279
|
+
probedCapability,
|
|
35280
|
+
postureSource: resolved.source
|
|
35281
|
+
});
|
|
35061
35282
|
const profileLabel = profile ? `profile: ${profile}` : "env-var mode";
|
|
35062
35283
|
const readOnlyLabel = config3.readOnly ? ", READ-ONLY" : "";
|
|
35063
35284
|
const attributionLabel = config3.attribution ? "" : ", NO-ATTRIBUTION";
|
|
35064
35285
|
console.error(
|
|
35065
35286
|
`epimethian-mcp: connected to ${url} as ${email2} (${profileLabel}${readOnlyLabel}${attributionLabel})`
|
|
35066
35287
|
);
|
|
35288
|
+
const modeLabel = resolved.effective === "read-only" ? "read-only" : "read-write";
|
|
35289
|
+
console.error(
|
|
35290
|
+
`[epimethian-mcp] Profile "${profile ?? "env-var"}" \u2014 mode: ${modeLabel} (source: ${resolved.source}).`
|
|
35291
|
+
);
|
|
35292
|
+
if (resolved.warning) {
|
|
35293
|
+
console.error(`[epimethian-mcp] Warning: ${resolved.warning}`);
|
|
35294
|
+
}
|
|
35067
35295
|
}
|
|
35068
35296
|
async function verifyOrSealTenant(config3, apiToken) {
|
|
35069
35297
|
const { url, email: email2, profile, sealedCloudId, sealedDisplayName } = config3;
|
|
@@ -35138,7 +35366,15 @@ async function confluenceRequest(url, options2 = {}) {
|
|
|
35138
35366
|
if (!res.ok) {
|
|
35139
35367
|
const body = await res.text();
|
|
35140
35368
|
console.error(`Confluence API error (${res.status}): ${sanitizeError(body)}`);
|
|
35141
|
-
|
|
35369
|
+
if (res.status === 401) {
|
|
35370
|
+
throw new ConfluenceAuthError(res.status, body);
|
|
35371
|
+
} else if (res.status === 403) {
|
|
35372
|
+
throw new ConfluencePermissionError(res.status, body);
|
|
35373
|
+
} else if (res.status === 404) {
|
|
35374
|
+
throw new ConfluenceNotFoundError(res.status, body);
|
|
35375
|
+
} else {
|
|
35376
|
+
throw new ConfluenceApiError(res.status, body);
|
|
35377
|
+
}
|
|
35142
35378
|
}
|
|
35143
35379
|
return res;
|
|
35144
35380
|
}
|
|
@@ -35206,7 +35442,7 @@ async function getPage(pageId, includeBody) {
|
|
|
35206
35442
|
async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
|
|
35207
35443
|
const cfg = await getConfig();
|
|
35208
35444
|
const pageBody = normalizeBodyForSubmit(body);
|
|
35209
|
-
const epimethianTag = `Epimethian v${"6.
|
|
35445
|
+
const epimethianTag = `Epimethian v${"6.1.0"}`;
|
|
35210
35446
|
const versionMsg = cfg.attribution && clientLabel ? `Created by ${clientLabel} (via ${epimethianTag})` : `Created by ${epimethianTag}`;
|
|
35211
35447
|
const payload = {
|
|
35212
35448
|
title,
|
|
@@ -35222,16 +35458,12 @@ async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
|
|
|
35222
35458
|
const raw = await v2Post("/pages", payload);
|
|
35223
35459
|
const page = PageSchema.parse(raw);
|
|
35224
35460
|
pageCache.set(page.id, page.version?.number ?? 1, pageBody);
|
|
35225
|
-
try {
|
|
35226
|
-
await ensureAttributionLabel(page.id);
|
|
35227
|
-
} catch {
|
|
35228
|
-
}
|
|
35229
35461
|
return page;
|
|
35230
35462
|
}
|
|
35231
35463
|
async function _rawUpdatePage(pageId, opts) {
|
|
35232
35464
|
const cfg = await getConfig();
|
|
35233
35465
|
const newVersion = opts.version + 1;
|
|
35234
|
-
const epimethianTag = `Epimethian v${"6.
|
|
35466
|
+
const epimethianTag = `Epimethian v${"6.1.0"}`;
|
|
35235
35467
|
const effectiveClient = cfg.attribution ? opts.clientLabel : void 0;
|
|
35236
35468
|
let versionMessage;
|
|
35237
35469
|
if (opts.versionMessage && effectiveClient)
|
|
@@ -35276,10 +35508,6 @@ async function _rawUpdatePage(pageId, opts) {
|
|
|
35276
35508
|
if (pageBody !== void 0) {
|
|
35277
35509
|
pageCache.set(pageId, newVersion, pageBody);
|
|
35278
35510
|
}
|
|
35279
|
-
try {
|
|
35280
|
-
await ensureAttributionLabel(page.id);
|
|
35281
|
-
} catch {
|
|
35282
|
-
}
|
|
35283
35511
|
return { page, newVersion };
|
|
35284
35512
|
}
|
|
35285
35513
|
async function deletePage(pageId, expectedVersion) {
|
|
@@ -35448,10 +35676,20 @@ async function uploadAttachment(pageId, fileData, filename, comment2) {
|
|
|
35448
35676
|
return { title: att.title, id: att.id, fileSize: att.extensions?.fileSize };
|
|
35449
35677
|
}
|
|
35450
35678
|
async function ensureAttributionLabel(pageId) {
|
|
35451
|
-
|
|
35452
|
-
|
|
35453
|
-
|
|
35454
|
-
|
|
35679
|
+
try {
|
|
35680
|
+
await addLabels(pageId, [ATTRIBUTION_LABEL]);
|
|
35681
|
+
const labels = await getLabels(pageId);
|
|
35682
|
+
if (labels.some((l) => l.name === LEGACY_ATTRIBUTION_LABEL)) {
|
|
35683
|
+
await removeLabel(pageId, LEGACY_ATTRIBUTION_LABEL);
|
|
35684
|
+
}
|
|
35685
|
+
return {};
|
|
35686
|
+
} catch (err) {
|
|
35687
|
+
if (err instanceof ConfluencePermissionError) {
|
|
35688
|
+
return {
|
|
35689
|
+
warning: `Could not apply 'epimethian-edited' label (permission denied). Provenance label is missing for page ${pageId}.`
|
|
35690
|
+
};
|
|
35691
|
+
}
|
|
35692
|
+
throw err;
|
|
35455
35693
|
}
|
|
35456
35694
|
}
|
|
35457
35695
|
function stripAttributionFooter(body) {
|
|
@@ -35908,7 +36146,7 @@ async function formatPage(page, optionsOrIncludeBody) {
|
|
|
35908
36146
|
}
|
|
35909
36147
|
return lines.join("\n");
|
|
35910
36148
|
}
|
|
35911
|
-
var import_turndown, CLIENT_LABEL_DISALLOWED_RE, _clientLabel, _config, ProfileNotConfiguredError, PageSchema, PagesResultSchema, SpaceSchema, SpacesResultSchema, AttachmentSchema, AttachmentsResultSchema, LabelSchema, LabelsResultSchema, ContentStateSchema, CommentSchema, CommentsResultSchema, UploadResultSchema, VersionMetadataSchema, VersionsResultSchema, V1PageVersionSchema, ConfluenceApiError, ConfluenceConflictError, UserSchema, UserSearchResultSchema, ATTRIBUTION_LABEL, LEGACY_ATTRIBUTION_LABEL, DANGEROUS_TAG_RE, HTML_TAG_RE, HTML_ENTITY_RE, SAFE_MACRO_PARAMS;
|
|
36149
|
+
var import_turndown, CLIENT_LABEL_DISALLOWED_RE, _clientLabel, _config, ProfileNotConfiguredError, PageSchema, PagesResultSchema, SpaceSchema, SpacesResultSchema, AttachmentSchema, AttachmentsResultSchema, LabelSchema, LabelsResultSchema, ContentStateSchema, CommentSchema, CommentsResultSchema, UploadResultSchema, VersionMetadataSchema, VersionsResultSchema, V1PageVersionSchema, ConfluenceApiError, ConfluenceAuthError, ConfluencePermissionError, ConfluenceNotFoundError, ConfluenceConflictError, UserSchema, UserSearchResultSchema, ATTRIBUTION_LABEL, LEGACY_ATTRIBUTION_LABEL, DANGEROUS_TAG_RE, HTML_TAG_RE, HTML_ENTITY_RE, SAFE_MACRO_PARAMS;
|
|
35912
36150
|
var init_confluence_client = __esm({
|
|
35913
36151
|
"src/server/confluence-client.ts"() {
|
|
35914
36152
|
"use strict";
|
|
@@ -35920,6 +36158,7 @@ var init_confluence_client = __esm({
|
|
|
35920
36158
|
init_escape();
|
|
35921
36159
|
init_untrusted_fence();
|
|
35922
36160
|
init_page_cache();
|
|
36161
|
+
init_config();
|
|
35923
36162
|
CLIENT_LABEL_DISALLOWED_RE = /[^A-Za-z0-9 _./()\-]/g;
|
|
35924
36163
|
_config = null;
|
|
35925
36164
|
ProfileNotConfiguredError = class extends Error {
|
|
@@ -36039,6 +36278,12 @@ var init_confluence_client = __esm({
|
|
|
36039
36278
|
this.status = status;
|
|
36040
36279
|
}
|
|
36041
36280
|
};
|
|
36281
|
+
ConfluenceAuthError = class extends ConfluenceApiError {
|
|
36282
|
+
};
|
|
36283
|
+
ConfluencePermissionError = class extends ConfluenceApiError {
|
|
36284
|
+
};
|
|
36285
|
+
ConfluenceNotFoundError = class extends ConfluenceApiError {
|
|
36286
|
+
};
|
|
36042
36287
|
ConfluenceConflictError = class extends Error {
|
|
36043
36288
|
constructor(pageId) {
|
|
36044
36289
|
super(
|
|
@@ -46240,7 +46485,7 @@ function extractRawAcBlocks(md) {
|
|
|
46240
46485
|
function escapeRegex2(s) {
|
|
46241
46486
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46242
46487
|
}
|
|
46243
|
-
function extractConfluenceSchemeLinks(md) {
|
|
46488
|
+
function extractConfluenceSchemeLinks(md, confluenceBaseUrl) {
|
|
46244
46489
|
const links = /* @__PURE__ */ new Map();
|
|
46245
46490
|
let idx = 0;
|
|
46246
46491
|
const processed = md.replace(
|
|
@@ -46248,7 +46493,18 @@ function extractConfluenceSchemeLinks(md) {
|
|
|
46248
46493
|
(_match, text2, href) => {
|
|
46249
46494
|
const rest = href.slice("confluence://".length);
|
|
46250
46495
|
const slashIdx = rest.indexOf("/");
|
|
46251
|
-
if (slashIdx === -1)
|
|
46496
|
+
if (slashIdx === -1) {
|
|
46497
|
+
if (!/^\d+$/.test(rest)) return _match;
|
|
46498
|
+
if (!confluenceBaseUrl) {
|
|
46499
|
+
throw new ConverterError(
|
|
46500
|
+
`confluence://${rest} cannot be rewritten: no Confluence base URL is configured. Either configure one (the harness normally injects this), or use the confluence://SPACE_KEY/PAGE_TITLE form instead.`,
|
|
46501
|
+
"CONFLUENCE_LINK_NO_BASE_URL"
|
|
46502
|
+
);
|
|
46503
|
+
}
|
|
46504
|
+
const base = confluenceBaseUrl.replace(/\/+$/, "");
|
|
46505
|
+
const absoluteUrl = `${base}/wiki/pages/viewpage.action?pageId=${rest}`;
|
|
46506
|
+
return `[${text2}](${absoluteUrl})`;
|
|
46507
|
+
}
|
|
46252
46508
|
const spaceKey = rest.slice(0, slashIdx);
|
|
46253
46509
|
let pageTitle;
|
|
46254
46510
|
try {
|
|
@@ -46386,7 +46642,7 @@ function markdownToStorage(md, opts) {
|
|
|
46386
46642
|
mdi
|
|
46387
46643
|
);
|
|
46388
46644
|
const { processed: mdWithoutAcBlocks, blocks: acBlocks } = extractRawAcBlocks(mdWithoutColumns);
|
|
46389
|
-
const { processed: mdWithoutConfluenceLinks, links: confluenceLinks } = extractConfluenceSchemeLinks(mdWithoutAcBlocks);
|
|
46645
|
+
const { processed: mdWithoutConfluenceLinks, links: confluenceLinks } = extractConfluenceSchemeLinks(mdWithoutAcBlocks, opts?.confluenceBaseUrl);
|
|
46390
46646
|
let html;
|
|
46391
46647
|
try {
|
|
46392
46648
|
html = mdi.render(mdWithoutConfluenceLinks);
|
|
@@ -47364,6 +47620,62 @@ var init_safe_write = __esm({
|
|
|
47364
47620
|
}
|
|
47365
47621
|
});
|
|
47366
47622
|
|
|
47623
|
+
// src/server/check-permissions.ts
|
|
47624
|
+
var check_permissions_exports = {};
|
|
47625
|
+
__export(check_permissions_exports, {
|
|
47626
|
+
buildCheckPermissionsPayload: () => buildCheckPermissionsPayload
|
|
47627
|
+
});
|
|
47628
|
+
function buildCheckPermissionsPayload(config3) {
|
|
47629
|
+
const effective = config3.effectivePosture ?? "read-write";
|
|
47630
|
+
const configured = config3.posture ?? config3.effectivePosture ?? "read-write";
|
|
47631
|
+
const source = config3.postureSource ?? "default";
|
|
47632
|
+
let writePages;
|
|
47633
|
+
if (config3.probedCapability === "write") {
|
|
47634
|
+
writePages = true;
|
|
47635
|
+
} else if (config3.probedCapability === "read-only") {
|
|
47636
|
+
writePages = false;
|
|
47637
|
+
} else {
|
|
47638
|
+
writePages = "unknown";
|
|
47639
|
+
}
|
|
47640
|
+
const notes = [];
|
|
47641
|
+
if (effective === "read-only" && writePages === true) {
|
|
47642
|
+
notes.push(
|
|
47643
|
+
"This profile is pinned to read-only mode by user configuration. The underlying token has write access, but write tools are not exposed to the agent."
|
|
47644
|
+
);
|
|
47645
|
+
} else if (effective === "read-only" && writePages === false) {
|
|
47646
|
+
notes.push("Both the profile and the token are read-only. Write tools are not exposed.");
|
|
47647
|
+
} else if (effective === "read-write" && writePages === false) {
|
|
47648
|
+
notes.push(
|
|
47649
|
+
"WARNING: this profile is configured read-write but the token does not appear to have write access. Writes will likely fail."
|
|
47650
|
+
);
|
|
47651
|
+
}
|
|
47652
|
+
return {
|
|
47653
|
+
profile: config3.profile,
|
|
47654
|
+
user: { email: config3.email },
|
|
47655
|
+
posture: {
|
|
47656
|
+
effective,
|
|
47657
|
+
configured,
|
|
47658
|
+
source
|
|
47659
|
+
},
|
|
47660
|
+
tokenCapability: {
|
|
47661
|
+
authenticated: true,
|
|
47662
|
+
listSpaces: true,
|
|
47663
|
+
readPages: true,
|
|
47664
|
+
writePages,
|
|
47665
|
+
addLabels: "unknown",
|
|
47666
|
+
setContentState: "unknown",
|
|
47667
|
+
addAttachments: "unknown",
|
|
47668
|
+
addComments: "unknown"
|
|
47669
|
+
},
|
|
47670
|
+
notes
|
|
47671
|
+
};
|
|
47672
|
+
}
|
|
47673
|
+
var init_check_permissions = __esm({
|
|
47674
|
+
"src/server/check-permissions.ts"() {
|
|
47675
|
+
"use strict";
|
|
47676
|
+
}
|
|
47677
|
+
});
|
|
47678
|
+
|
|
47367
47679
|
// src/shared/update-check.ts
|
|
47368
47680
|
function parseSemVer(version2) {
|
|
47369
47681
|
const match2 = version2.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
@@ -47716,23 +48028,39 @@ Profile will be saved without a tenant seal. Cross-tenant verification at startu
|
|
|
47716
48028
|
);
|
|
47717
48029
|
if (profile) {
|
|
47718
48030
|
await addToProfileRegistry(profile);
|
|
47719
|
-
const
|
|
47720
|
-
|
|
47721
|
-
|
|
47722
|
-
|
|
47723
|
-
|
|
47724
|
-
|
|
47725
|
-
|
|
47726
|
-
|
|
47727
|
-
|
|
47728
|
-
|
|
48031
|
+
const rl2 = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
48032
|
+
try {
|
|
48033
|
+
let posture = "read-only";
|
|
48034
|
+
let validInput = false;
|
|
48035
|
+
while (!validInput) {
|
|
48036
|
+
const answer = (await rl2.question(
|
|
48037
|
+
`MCP access mode for this profile:
|
|
48038
|
+
[1] Read-only \u2014 the agent cannot modify Confluence through this profile,
|
|
48039
|
+
regardless of what the API token can do. Recommended for
|
|
48040
|
+
untrusted agents, exploratory work, and defense-in-depth.
|
|
48041
|
+
[2] Read-write \u2014 the agent can create, update, and delete pages.
|
|
48042
|
+
[3] Detect at startup \u2014 infer from the token's actual permissions.
|
|
48043
|
+
Your choice [default: 1]: `
|
|
48044
|
+
)).trim();
|
|
48045
|
+
if (answer === "" || answer === "1") {
|
|
48046
|
+
posture = "read-only";
|
|
48047
|
+
validInput = true;
|
|
48048
|
+
} else if (answer === "2") {
|
|
48049
|
+
posture = "read-write";
|
|
48050
|
+
validInput = true;
|
|
48051
|
+
} else if (answer === "3") {
|
|
48052
|
+
posture = "detect";
|
|
48053
|
+
validInput = true;
|
|
48054
|
+
} else {
|
|
48055
|
+
console.log(`Invalid choice "${answer}". Please enter 1, 2, or 3.`);
|
|
48056
|
+
}
|
|
47729
48057
|
}
|
|
47730
|
-
|
|
47731
|
-
|
|
47732
|
-
await setProfileSettings(profile, { readOnly });
|
|
47733
|
-
const modeLabel = readOnly ? "read-only" : "read-write";
|
|
47734
|
-
console.log(`Credentials saved to OS keychain (profile: ${profile}, ${modeLabel}).
|
|
48058
|
+
await setProfileSettings(profile, { posture });
|
|
48059
|
+
console.log(`Credentials saved to OS keychain (profile: ${profile}, posture: ${posture}).
|
|
47735
48060
|
`);
|
|
48061
|
+
} finally {
|
|
48062
|
+
rl2.close();
|
|
48063
|
+
}
|
|
47736
48064
|
} else {
|
|
47737
48065
|
console.log("Credentials saved to OS keychain.\n");
|
|
47738
48066
|
console.log(
|
|
@@ -48001,6 +48329,41 @@ var init_status = __esm({
|
|
|
48001
48329
|
}
|
|
48002
48330
|
});
|
|
48003
48331
|
|
|
48332
|
+
// src/cli/permissions.ts
|
|
48333
|
+
var permissions_exports = {};
|
|
48334
|
+
__export(permissions_exports, {
|
|
48335
|
+
runPermissions: () => runPermissions
|
|
48336
|
+
});
|
|
48337
|
+
async function runPermissions(profileArg) {
|
|
48338
|
+
const profile = profileArg || process.env.CONFLUENCE_PROFILE;
|
|
48339
|
+
if (!profile) {
|
|
48340
|
+
console.error(
|
|
48341
|
+
"Usage: epimethian-mcp permissions <profile>\nOr set CONFLUENCE_PROFILE in the environment."
|
|
48342
|
+
);
|
|
48343
|
+
process.exit(1);
|
|
48344
|
+
}
|
|
48345
|
+
if (!PROFILE_NAME_RE.test(profile)) {
|
|
48346
|
+
console.error(
|
|
48347
|
+
`Invalid profile name: "${profile}". Use lowercase alphanumeric and hyphens only (1-63 chars).`
|
|
48348
|
+
);
|
|
48349
|
+
process.exit(1);
|
|
48350
|
+
}
|
|
48351
|
+
process.env.CONFLUENCE_PROFILE = profile;
|
|
48352
|
+
const { getConfig: getConfig2, validateStartup: validateStartup2 } = await Promise.resolve().then(() => (init_confluence_client(), confluence_client_exports));
|
|
48353
|
+
const { buildCheckPermissionsPayload: buildCheckPermissionsPayload2 } = await Promise.resolve().then(() => (init_check_permissions(), check_permissions_exports));
|
|
48354
|
+
const config3 = await getConfig2();
|
|
48355
|
+
await validateStartup2(config3);
|
|
48356
|
+
const resolved = await getConfig2();
|
|
48357
|
+
const payload = buildCheckPermissionsPayload2(resolved);
|
|
48358
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
48359
|
+
}
|
|
48360
|
+
var init_permissions = __esm({
|
|
48361
|
+
"src/cli/permissions.ts"() {
|
|
48362
|
+
"use strict";
|
|
48363
|
+
init_keychain();
|
|
48364
|
+
}
|
|
48365
|
+
});
|
|
48366
|
+
|
|
48004
48367
|
// src/cli/fix-legacy-links.ts
|
|
48005
48368
|
var fix_legacy_links_exports = {};
|
|
48006
48369
|
__export(fix_legacy_links_exports, {
|
|
@@ -48313,7 +48676,7 @@ Informational:
|
|
|
48313
48676
|
var install_agent_default;
|
|
48314
48677
|
var init_install_agent = __esm({
|
|
48315
48678
|
"install-agent.md"() {
|
|
48316
|
-
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\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-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## Available Tools (33)\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| `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';
|
|
48679
|
+
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\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-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## Available Tools (34)\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 |\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';
|
|
48317
48680
|
}
|
|
48318
48681
|
});
|
|
48319
48682
|
|
|
@@ -48338,7 +48701,7 @@ __export(upgrade_exports, {
|
|
|
48338
48701
|
runUpgrade: () => runUpgrade
|
|
48339
48702
|
});
|
|
48340
48703
|
async function runUpgrade() {
|
|
48341
|
-
const currentVersion = "6.
|
|
48704
|
+
const currentVersion = "6.1.0";
|
|
48342
48705
|
console.log(`epimethian-mcp upgrade: current version v${currentVersion}`);
|
|
48343
48706
|
let pending = await getPendingUpdate();
|
|
48344
48707
|
if (!pending) {
|
|
@@ -59151,6 +59514,81 @@ function storageToMarkdown(storage) {
|
|
|
59151
59514
|
|
|
59152
59515
|
// src/server/index.ts
|
|
59153
59516
|
init_mutation_log();
|
|
59517
|
+
|
|
59518
|
+
// src/server/provenance.ts
|
|
59519
|
+
init_confluence_client();
|
|
59520
|
+
var UNVERIFIED_COLOR = "#FFC400";
|
|
59521
|
+
var UNVERIFIED_LABELS = {
|
|
59522
|
+
en: "AI-edited",
|
|
59523
|
+
fr: "Modifi\xE9 par IA",
|
|
59524
|
+
de: "KI-bearbeitet",
|
|
59525
|
+
es: "Editado por IA",
|
|
59526
|
+
pt: "Editado por IA",
|
|
59527
|
+
it: "Modificato da IA",
|
|
59528
|
+
nl: "AI-bewerkt",
|
|
59529
|
+
ja: "AI\u7DE8\u96C6\u6E08\u307F",
|
|
59530
|
+
zh: "AI\u5DF2\u7F16\u8F91",
|
|
59531
|
+
ko: "AI \uD3B8\uC9D1\uB428"
|
|
59532
|
+
};
|
|
59533
|
+
for (const [locale, label] of Object.entries(UNVERIFIED_LABELS)) {
|
|
59534
|
+
const codePoints = [...label].length;
|
|
59535
|
+
if (codePoints > 20) {
|
|
59536
|
+
throw new Error(
|
|
59537
|
+
`UNVERIFIED_LABELS["${locale}"] = "${label}" has ${codePoints} code points, exceeding the 20-code-point Confluence limit.`
|
|
59538
|
+
);
|
|
59539
|
+
}
|
|
59540
|
+
}
|
|
59541
|
+
var KNOWN_LABELS = new Set(Object.values(UNVERIFIED_LABELS));
|
|
59542
|
+
function isKnownUnverifiedLabel(name, customOverride) {
|
|
59543
|
+
if (customOverride !== void 0 && name === customOverride) return true;
|
|
59544
|
+
return KNOWN_LABELS.has(name);
|
|
59545
|
+
}
|
|
59546
|
+
function pickLocale(cfg) {
|
|
59547
|
+
const raw = cfg.unverifiedStatusLocale || process.env.CONFLUENCE_UNVERIFIED_STATUS_LOCALE || Intl.DateTimeFormat().resolvedOptions().locale || "en";
|
|
59548
|
+
return raw.split("-")[0].toLowerCase();
|
|
59549
|
+
}
|
|
59550
|
+
function resolveUnverifiedStatus(cfg) {
|
|
59551
|
+
const color = cfg.unverifiedStatusColor ?? UNVERIFIED_COLOR;
|
|
59552
|
+
if (cfg.unverifiedStatusName) {
|
|
59553
|
+
return { name: cfg.unverifiedStatusName, color };
|
|
59554
|
+
}
|
|
59555
|
+
const locale = pickLocale(cfg);
|
|
59556
|
+
const name = UNVERIFIED_LABELS[locale] ?? UNVERIFIED_LABELS["en"];
|
|
59557
|
+
return { name, color };
|
|
59558
|
+
}
|
|
59559
|
+
async function markPageUnverified(pageId, cfg) {
|
|
59560
|
+
if (cfg.unverifiedStatus === false) {
|
|
59561
|
+
return {};
|
|
59562
|
+
}
|
|
59563
|
+
const target = resolveUnverifiedStatus(cfg);
|
|
59564
|
+
let skipSet = false;
|
|
59565
|
+
try {
|
|
59566
|
+
const current = await getContentState(pageId);
|
|
59567
|
+
if (current != null && current.color === target.color && isKnownUnverifiedLabel(current.name, cfg.unverifiedStatusName)) {
|
|
59568
|
+
return {};
|
|
59569
|
+
}
|
|
59570
|
+
} catch (err) {
|
|
59571
|
+
if (err instanceof ConfluencePermissionError) {
|
|
59572
|
+
}
|
|
59573
|
+
}
|
|
59574
|
+
if (skipSet) return {};
|
|
59575
|
+
try {
|
|
59576
|
+
await setContentState(pageId, target.name, target.color);
|
|
59577
|
+
return {};
|
|
59578
|
+
} catch (err) {
|
|
59579
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59580
|
+
if (err instanceof ConfluencePermissionError) {
|
|
59581
|
+
return {
|
|
59582
|
+
warning: `Could not apply 'AI-edited' status badge (permission denied). Provenance badge is missing for page ${pageId}.`
|
|
59583
|
+
};
|
|
59584
|
+
}
|
|
59585
|
+
return {
|
|
59586
|
+
warning: `Could not apply 'AI-edited' status badge: ${message}. Provenance badge is missing for page ${pageId}.`
|
|
59587
|
+
};
|
|
59588
|
+
}
|
|
59589
|
+
}
|
|
59590
|
+
|
|
59591
|
+
// src/server/index.ts
|
|
59154
59592
|
init_safe_write();
|
|
59155
59593
|
|
|
59156
59594
|
// src/server/source-provenance.ts
|
|
@@ -59416,6 +59854,7 @@ async function assertSpaceAllowed(opts) {
|
|
|
59416
59854
|
}
|
|
59417
59855
|
|
|
59418
59856
|
// src/server/index.ts
|
|
59857
|
+
init_check_permissions();
|
|
59419
59858
|
init_update_check();
|
|
59420
59859
|
function getClientLabel(server) {
|
|
59421
59860
|
const client = server.server.getClientVersion();
|
|
@@ -59470,7 +59909,16 @@ ${markdown}`;
|
|
|
59470
59909
|
|
|
59471
59910
|
${body}`;
|
|
59472
59911
|
}
|
|
59912
|
+
var _sessionIsReadOnly = false;
|
|
59913
|
+
var _readOnlyNoteEmitted = false;
|
|
59473
59914
|
function toolResult(text2) {
|
|
59915
|
+
if (_sessionIsReadOnly && !_readOnlyNoteEmitted) {
|
|
59916
|
+
_readOnlyNoteEmitted = true;
|
|
59917
|
+
const note = "[epimethian-mcp] This profile is read-only; write tools are not exposed.";
|
|
59918
|
+
return { content: [{ type: "text", text: `${note}
|
|
59919
|
+
|
|
59920
|
+
${text2}` }] };
|
|
59921
|
+
}
|
|
59474
59922
|
return { content: [{ type: "text", text: text2 }] };
|
|
59475
59923
|
}
|
|
59476
59924
|
function toolError(err) {
|
|
@@ -59478,6 +59926,42 @@ function toolError(err) {
|
|
|
59478
59926
|
const message = sanitizeError(raw);
|
|
59479
59927
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
59480
59928
|
}
|
|
59929
|
+
function toolErrorWithContext(err, ctx) {
|
|
59930
|
+
if (err instanceof ConfluenceAuthError) {
|
|
59931
|
+
const profileName = ctx.profile ?? "<profile>";
|
|
59932
|
+
return {
|
|
59933
|
+
content: [{
|
|
59934
|
+
type: "text",
|
|
59935
|
+
text: `Error: Your Confluence API token is invalid or expired. Reauthenticate with 'epimethian-mcp login ${profileName}'.`
|
|
59936
|
+
}],
|
|
59937
|
+
isError: true
|
|
59938
|
+
};
|
|
59939
|
+
}
|
|
59940
|
+
if (err instanceof ConfluencePermissionError) {
|
|
59941
|
+
const resourcePart = ctx.resource ? ` on ${ctx.resource}` : "";
|
|
59942
|
+
return {
|
|
59943
|
+
content: [{
|
|
59944
|
+
type: "text",
|
|
59945
|
+
text: `Error: Your token lacks permission for ${ctx.operation}${resourcePart}. The operation was not performed.`
|
|
59946
|
+
}],
|
|
59947
|
+
isError: true
|
|
59948
|
+
};
|
|
59949
|
+
}
|
|
59950
|
+
if (err instanceof ConfluenceNotFoundError) {
|
|
59951
|
+
return {
|
|
59952
|
+
content: [{
|
|
59953
|
+
type: "text",
|
|
59954
|
+
text: `Error: Resource not found. Confluence may return 'not found' when a token lacks permission to see the resource \u2014 verify the token has at least read access.`
|
|
59955
|
+
}],
|
|
59956
|
+
isError: true
|
|
59957
|
+
};
|
|
59958
|
+
}
|
|
59959
|
+
return toolError(err);
|
|
59960
|
+
}
|
|
59961
|
+
function appendWarnings(primary, warnings) {
|
|
59962
|
+
if (warnings.length === 0) return primary;
|
|
59963
|
+
return primary + "\n\n" + warnings.map((w) => `\u26A0 ${w}`).join("\n");
|
|
59964
|
+
}
|
|
59481
59965
|
function tenantEcho(config3) {
|
|
59482
59966
|
const host = new URL(config3.url).hostname;
|
|
59483
59967
|
const mode = config3.profile ? `profile: ${config3.profile}` : "env-var mode";
|
|
@@ -59506,6 +59990,24 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
59506
59990
|
"lookup_user",
|
|
59507
59991
|
"resolve_page_link"
|
|
59508
59992
|
]);
|
|
59993
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
59994
|
+
"create_page",
|
|
59995
|
+
"update_page",
|
|
59996
|
+
"append_to_page",
|
|
59997
|
+
"prepend_to_page",
|
|
59998
|
+
"update_page_section",
|
|
59999
|
+
"delete_page",
|
|
60000
|
+
"add_drawio_diagram",
|
|
60001
|
+
"revert_page",
|
|
60002
|
+
"add_attachment",
|
|
60003
|
+
"add_label",
|
|
60004
|
+
"remove_label",
|
|
60005
|
+
"create_comment",
|
|
60006
|
+
"delete_comment",
|
|
60007
|
+
"resolve_comment",
|
|
60008
|
+
"set_page_status",
|
|
60009
|
+
"remove_page_status"
|
|
60010
|
+
]);
|
|
59509
60011
|
function writeGuard(toolName, config3) {
|
|
59510
60012
|
if (!config3.readOnly) return null;
|
|
59511
60013
|
if (READ_ONLY_TOOLS.has(toolName)) return null;
|
|
@@ -59564,27 +60066,38 @@ function formatComments(footer, inline2, pageId) {
|
|
|
59564
60066
|
}
|
|
59565
60067
|
return lines.join("\n");
|
|
59566
60068
|
}
|
|
59567
|
-
function formatCommentThreads(footer, inline2, pageId) {
|
|
60069
|
+
function formatCommentThreads(footer, inline2, pageId, failedFetches = 0, totalFetches = 0) {
|
|
59568
60070
|
const lines = [`Comments on page ${pageId}:`, ""];
|
|
59569
60071
|
if (footer.length > 0) {
|
|
59570
60072
|
lines.push(`Footer comments (${footer.length}):`);
|
|
59571
|
-
footer.forEach(({ comment: comment2, replies }) => {
|
|
60073
|
+
footer.forEach(({ comment: comment2, replies, error: error2 }) => {
|
|
59572
60074
|
lines.push(formatCommentLine(comment2));
|
|
59573
|
-
|
|
60075
|
+
if (error2) {
|
|
60076
|
+
lines.push(` Error fetching replies: ${error2}`);
|
|
60077
|
+
} else if (replies) {
|
|
60078
|
+
replies.forEach((r) => lines.push(formatCommentLine(r, " ")));
|
|
60079
|
+
}
|
|
59574
60080
|
});
|
|
59575
60081
|
lines.push("");
|
|
59576
60082
|
}
|
|
59577
60083
|
if (inline2.length > 0) {
|
|
59578
60084
|
lines.push(`Inline comments (${inline2.length}):`);
|
|
59579
|
-
inline2.forEach(({ comment: comment2, replies }) => {
|
|
60085
|
+
inline2.forEach(({ comment: comment2, replies, error: error2 }) => {
|
|
59580
60086
|
lines.push(formatCommentLine(comment2));
|
|
59581
|
-
|
|
60087
|
+
if (error2) {
|
|
60088
|
+
lines.push(` Error fetching replies: ${error2}`);
|
|
60089
|
+
} else if (replies) {
|
|
60090
|
+
replies.forEach((r) => lines.push(formatCommentLine(r, " ")));
|
|
60091
|
+
}
|
|
59582
60092
|
});
|
|
59583
60093
|
lines.push("");
|
|
59584
60094
|
}
|
|
59585
60095
|
if (footer.length === 0 && inline2.length === 0) {
|
|
59586
60096
|
lines.push("No comments found.");
|
|
59587
60097
|
}
|
|
60098
|
+
if (failedFetches > 0 && totalFetches > 0) {
|
|
60099
|
+
lines.push(`Note: ${failedFetches} of ${totalFetches} reply fetches failed \u2014 partial results shown.`);
|
|
60100
|
+
}
|
|
59588
60101
|
return lines.join("\n");
|
|
59589
60102
|
}
|
|
59590
60103
|
async function registerTools(server, config3) {
|
|
@@ -59593,11 +60106,30 @@ async function registerTools(server, config3) {
|
|
|
59593
60106
|
const isToolEnabled = resolveToolFilter(settings);
|
|
59594
60107
|
const allowedSpaces = settings?.spaces;
|
|
59595
60108
|
const checkSpaceAllowed = (opts) => assertSpaceAllowed({ spaces: allowedSpaces, ...opts });
|
|
60109
|
+
const effectivePosture = config3.effectivePosture ?? "read-write";
|
|
60110
|
+
const isReadOnly = effectivePosture === "read-only";
|
|
60111
|
+
const profileLabel = config3.profile ? `"${config3.profile}"` : `"env-var"`;
|
|
60112
|
+
const sourceLabel = config3.postureSource ?? "default";
|
|
60113
|
+
if (isReadOnly) {
|
|
60114
|
+
console.error(
|
|
60115
|
+
`[epimethian-mcp] Profile ${profileLabel} \u2014 mode: read-only (${sourceLabel}).
|
|
60116
|
+
Write tools are not exposed. Set posture: "read-write" in the profile to enable writes.`
|
|
60117
|
+
);
|
|
60118
|
+
} else {
|
|
60119
|
+
console.error(
|
|
60120
|
+
`[epimethian-mcp] Profile ${profileLabel} \u2014 mode: read-write (${sourceLabel}).`
|
|
60121
|
+
);
|
|
60122
|
+
}
|
|
60123
|
+
_sessionIsReadOnly = isReadOnly;
|
|
60124
|
+
_readOnlyNoteEmitted = false;
|
|
59596
60125
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
59597
60126
|
server.registerTool = function(name, ...rest) {
|
|
59598
60127
|
if (!isToolEnabled(name)) {
|
|
59599
60128
|
return server;
|
|
59600
60129
|
}
|
|
60130
|
+
if (isReadOnly && WRITE_TOOLS.has(name)) {
|
|
60131
|
+
return server;
|
|
60132
|
+
}
|
|
59601
60133
|
return originalRegisterTool(name, ...rest);
|
|
59602
60134
|
};
|
|
59603
60135
|
const labelNameSchema = external_exports.string().min(1).max(255).regex(/^[a-z0-9][a-z0-9_-]*$/, "Label must be lowercase alphanumeric, hyphens, underscores only");
|
|
@@ -59687,9 +60219,14 @@ async function registerTools(server, config3) {
|
|
|
59687
60219
|
deletedTokens: prepared.deletedTokens,
|
|
59688
60220
|
clientLabel: getClientLabel(server)
|
|
59689
60221
|
});
|
|
59690
|
-
|
|
60222
|
+
const warnings = [];
|
|
60223
|
+
const labelResult = await ensureAttributionLabel(submitted.page.id);
|
|
60224
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
60225
|
+
const badgeResult = await markPageUnverified(submitted.page.id, cfg);
|
|
60226
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
60227
|
+
return toolResult(appendWarnings(await formatPage(submitted.page, false), warnings) + echo);
|
|
59691
60228
|
} catch (err) {
|
|
59692
|
-
return
|
|
60229
|
+
return toolErrorWithContext(err, { operation: "create_page", resource: `space ${space_key}`, profile: config3.profile });
|
|
59693
60230
|
}
|
|
59694
60231
|
}
|
|
59695
60232
|
);
|
|
@@ -59884,17 +60421,22 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
59884
60421
|
source: effectiveSource
|
|
59885
60422
|
});
|
|
59886
60423
|
const isTitleOnly = prepared.finalStorage === void 0;
|
|
60424
|
+
const warnings = [];
|
|
60425
|
+
const labelResult = await ensureAttributionLabel(submitted.page.id);
|
|
60426
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
60427
|
+
const badgeResult = await markPageUnverified(submitted.page.id, cfg);
|
|
60428
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
59887
60429
|
if (isTitleOnly) {
|
|
59888
60430
|
return toolResult(
|
|
59889
|
-
`Updated: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, title only, body unchanged)
|
|
60431
|
+
appendWarnings(`Updated: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, title only, body unchanged)`, warnings) + echo
|
|
59890
60432
|
);
|
|
59891
60433
|
}
|
|
59892
60434
|
const removalNote = submitted.deletedTokens.length > 0 ? `; removed ${submitted.deletedTokens.length} preserved macro${submitted.deletedTokens.length === 1 ? "" : "s"}: ${submitted.deletedTokens.map((t) => t.fingerprint).join(", ")}` : "";
|
|
59893
60435
|
return toolResult(
|
|
59894
|
-
`Updated: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, body: ${submitted.oldLen}\u2192${submitted.newLen} chars${removalNote})
|
|
60436
|
+
appendWarnings(`Updated: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, body: ${submitted.oldLen}\u2192${submitted.newLen} chars${removalNote})`, warnings) + echo
|
|
59895
60437
|
);
|
|
59896
60438
|
} catch (err) {
|
|
59897
|
-
return
|
|
60439
|
+
return toolErrorWithContext(err, { operation: "update_page", resource: `page ${page_id}`, profile: config3.profile });
|
|
59898
60440
|
}
|
|
59899
60441
|
}
|
|
59900
60442
|
);
|
|
@@ -59956,7 +60498,7 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
59956
60498
|
return toolResult(`Deleted page ${page_id}` + echo);
|
|
59957
60499
|
} catch (err) {
|
|
59958
60500
|
logMutation(errorRecord("delete_page", page_id, err));
|
|
59959
|
-
return
|
|
60501
|
+
return toolErrorWithContext(err, { operation: "delete_page", resource: `page ${page_id}`, profile: config3.profile });
|
|
59960
60502
|
}
|
|
59961
60503
|
}
|
|
59962
60504
|
);
|
|
@@ -60022,12 +60564,17 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
60022
60564
|
operation: "update_page_section",
|
|
60023
60565
|
clientLabel: getClientLabel(server)
|
|
60024
60566
|
});
|
|
60567
|
+
const warnings = [];
|
|
60568
|
+
const labelResult = await ensureAttributionLabel(submitted.page.id);
|
|
60569
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
60570
|
+
const badgeResult = await markPageUnverified(submitted.page.id, cfg);
|
|
60571
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
60025
60572
|
const removalNote = submitted.deletedTokens.length > 0 ? `; removed ${submitted.deletedTokens.length} preserved macro${submitted.deletedTokens.length === 1 ? "" : "s"}: ${submitted.deletedTokens.map((t) => t.fingerprint).join(", ")}` : "";
|
|
60026
60573
|
return toolResult(
|
|
60027
|
-
`Updated section "${section}" in: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}${removalNote})
|
|
60574
|
+
appendWarnings(`Updated section "${section}" in: ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}${removalNote})`, warnings) + echo
|
|
60028
60575
|
);
|
|
60029
60576
|
} catch (err) {
|
|
60030
|
-
return
|
|
60577
|
+
return toolErrorWithContext(err, { operation: "update_page_section", resource: `page ${page_id}`, profile: config3.profile });
|
|
60031
60578
|
}
|
|
60032
60579
|
}
|
|
60033
60580
|
);
|
|
@@ -60064,9 +60611,14 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
60064
60611
|
"prepend",
|
|
60065
60612
|
{ separator, versionMessage: version_message ?? "Prepend content", allowRawHtml: allow_raw_html, confluenceBaseUrl: confluence_base_url ?? cfg.url }
|
|
60066
60613
|
);
|
|
60067
|
-
|
|
60614
|
+
const warnings = [];
|
|
60615
|
+
const labelResult = await ensureAttributionLabel(page.id);
|
|
60616
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
60617
|
+
const badgeResult = await markPageUnverified(page.id, cfg);
|
|
60618
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
60619
|
+
return toolResult(appendWarnings(`Prepended to: ${page.title} (ID: ${page.id}, version: ${newVersion}, body: ${oldLen}\u2192${newLen} chars)`, warnings) + echo);
|
|
60068
60620
|
} catch (err) {
|
|
60069
|
-
return
|
|
60621
|
+
return toolErrorWithContext(err, { operation: "prepend_to_page", resource: `page ${page_id}`, profile: config3.profile });
|
|
60070
60622
|
}
|
|
60071
60623
|
}
|
|
60072
60624
|
);
|
|
@@ -60103,9 +60655,14 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
60103
60655
|
"append",
|
|
60104
60656
|
{ separator, versionMessage: version_message ?? "Append content", allowRawHtml: allow_raw_html, confluenceBaseUrl: confluence_base_url ?? cfg.url }
|
|
60105
60657
|
);
|
|
60106
|
-
|
|
60658
|
+
const warnings = [];
|
|
60659
|
+
const labelResult = await ensureAttributionLabel(page.id);
|
|
60660
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
60661
|
+
const badgeResult = await markPageUnverified(page.id, cfg);
|
|
60662
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
60663
|
+
return toolResult(appendWarnings(`Appended to: ${page.title} (ID: ${page.id}, version: ${newVersion}, body: ${oldLen}\u2192${newLen} chars)`, warnings) + echo);
|
|
60107
60664
|
} catch (err) {
|
|
60108
|
-
return
|
|
60665
|
+
return toolErrorWithContext(err, { operation: "append_to_page", resource: `page ${page_id}`, profile: config3.profile });
|
|
60109
60666
|
}
|
|
60110
60667
|
}
|
|
60111
60668
|
);
|
|
@@ -60228,6 +60785,23 @@ ${truncated}${truncationNote(origLen)}`
|
|
|
60228
60785
|
}
|
|
60229
60786
|
}
|
|
60230
60787
|
);
|
|
60788
|
+
server.registerTool(
|
|
60789
|
+
"check_permissions",
|
|
60790
|
+
{
|
|
60791
|
+
description: "Report the current profile's MCP access mode and the token's capabilities. Always available in every posture.",
|
|
60792
|
+
inputSchema: {},
|
|
60793
|
+
annotations: { readOnlyHint: true }
|
|
60794
|
+
},
|
|
60795
|
+
async () => {
|
|
60796
|
+
try {
|
|
60797
|
+
const cfg = await getConfig();
|
|
60798
|
+
const payload = buildCheckPermissionsPayload(cfg);
|
|
60799
|
+
return toolResult(JSON.stringify(payload, null, 2));
|
|
60800
|
+
} catch (err) {
|
|
60801
|
+
return toolError(err);
|
|
60802
|
+
}
|
|
60803
|
+
}
|
|
60804
|
+
);
|
|
60231
60805
|
server.registerTool(
|
|
60232
60806
|
"get_page_by_title",
|
|
60233
60807
|
{
|
|
@@ -60363,7 +60937,7 @@ ${truncated}`);
|
|
|
60363
60937
|
`Attached: ${att.title} (ID: ${att.id}, size: ${att.fileSize ?? "unknown"} bytes) to page ${page_id}` + echo
|
|
60364
60938
|
);
|
|
60365
60939
|
} catch (err) {
|
|
60366
|
-
return
|
|
60940
|
+
return toolErrorWithContext(err, { operation: "add_attachment", resource: `page ${page_id}`, profile: config3.profile });
|
|
60367
60941
|
}
|
|
60368
60942
|
}
|
|
60369
60943
|
);
|
|
@@ -60447,11 +61021,16 @@ ${macro}` : macro;
|
|
|
60447
61021
|
clientLabel: getClientLabel(server),
|
|
60448
61022
|
operation: "add_drawio_diagram"
|
|
60449
61023
|
});
|
|
61024
|
+
const warnings = [];
|
|
61025
|
+
const labelResult = await ensureAttributionLabel(submitted.page.id);
|
|
61026
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
61027
|
+
const badgeResult = await markPageUnverified(submitted.page.id, config3);
|
|
61028
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
60450
61029
|
return toolResult(
|
|
60451
|
-
`Diagram "${filename}" added to page ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion})
|
|
61030
|
+
appendWarnings(`Diagram "${filename}" added to page ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion})`, warnings) + echo
|
|
60452
61031
|
);
|
|
60453
61032
|
} catch (err) {
|
|
60454
|
-
return
|
|
61033
|
+
return toolErrorWithContext(err, { operation: "add_drawio_diagram", resource: `page ${page_id}`, profile: config3.profile });
|
|
60455
61034
|
}
|
|
60456
61035
|
}
|
|
60457
61036
|
);
|
|
@@ -60532,7 +61111,7 @@ ${lines}`);
|
|
|
60532
61111
|
await addLabels(page_id, labels);
|
|
60533
61112
|
return toolResult(`Added ${labels.length} label(s) to page ${page_id}: ${labels.join(", ")}` + echo);
|
|
60534
61113
|
} catch (err) {
|
|
60535
|
-
return
|
|
61114
|
+
return toolErrorWithContext(err, { operation: "add_label", resource: `page ${page_id}`, profile: config3.profile });
|
|
60536
61115
|
}
|
|
60537
61116
|
}
|
|
60538
61117
|
);
|
|
@@ -60557,7 +61136,7 @@ ${lines}`);
|
|
|
60557
61136
|
await removeLabel(page_id, label);
|
|
60558
61137
|
return toolResult(`Removed label "${label}" from page ${page_id}` + echo);
|
|
60559
61138
|
} catch (err) {
|
|
60560
|
-
return
|
|
61139
|
+
return toolErrorWithContext(err, { operation: "remove_label", resource: `page ${page_id}`, profile: config3.profile });
|
|
60561
61140
|
}
|
|
60562
61141
|
}
|
|
60563
61142
|
);
|
|
@@ -60630,7 +61209,7 @@ Color: ${state.color}` + echo
|
|
|
60630
61209
|
await setContentState(page_id, name, color);
|
|
60631
61210
|
return toolResult(`Set status on page ${page_id}: "${name}" (${color})` + echo);
|
|
60632
61211
|
} catch (err) {
|
|
60633
|
-
return
|
|
61212
|
+
return toolErrorWithContext(err, { operation: "set_page_status", resource: `page ${page_id}`, profile: config3.profile });
|
|
60634
61213
|
}
|
|
60635
61214
|
}
|
|
60636
61215
|
);
|
|
@@ -60656,7 +61235,7 @@ Color: ${state.color}` + echo
|
|
|
60656
61235
|
await removeContentState(page_id);
|
|
60657
61236
|
return toolResult(`Removed status from page ${page_id}` + echo);
|
|
60658
61237
|
} catch (err) {
|
|
60659
|
-
return
|
|
61238
|
+
return toolErrorWithContext(err, { operation: "remove_page_status", resource: `page ${page_id}`, profile: config3.profile });
|
|
60660
61239
|
}
|
|
60661
61240
|
}
|
|
60662
61241
|
);
|
|
@@ -60681,17 +61260,34 @@ Color: ${state.color}` + echo
|
|
|
60681
61260
|
type !== "footer" ? getInlineComments(page_id, resolution_status) : Promise.resolve([])
|
|
60682
61261
|
]);
|
|
60683
61262
|
if (include_replies) {
|
|
60684
|
-
const
|
|
60685
|
-
|
|
61263
|
+
const footerRepliesResults = await Promise.allSettled(
|
|
61264
|
+
footerComments.map((c) => getCommentReplies(c.id, "footer"))
|
|
61265
|
+
);
|
|
61266
|
+
const inlineRepliesResults = await Promise.allSettled(
|
|
61267
|
+
inlineComments.map((c) => getCommentReplies(c.id, "inline"))
|
|
61268
|
+
);
|
|
61269
|
+
const fr = footerComments.map((c, i) => {
|
|
61270
|
+
const result = footerRepliesResults[i];
|
|
61271
|
+
return {
|
|
60686
61272
|
comment: c,
|
|
60687
|
-
replies:
|
|
60688
|
-
}
|
|
60689
|
-
|
|
61273
|
+
...result.status === "fulfilled" ? { replies: result.value } : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) }
|
|
61274
|
+
};
|
|
61275
|
+
});
|
|
61276
|
+
const ir = inlineComments.map((c, i) => {
|
|
61277
|
+
const result = inlineRepliesResults[i];
|
|
61278
|
+
return {
|
|
60690
61279
|
comment: c,
|
|
60691
|
-
replies:
|
|
60692
|
-
}
|
|
60693
|
-
|
|
60694
|
-
|
|
61280
|
+
...result.status === "fulfilled" ? { replies: result.value } : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) }
|
|
61281
|
+
};
|
|
61282
|
+
});
|
|
61283
|
+
const totalFetches = footerRepliesResults.length + inlineRepliesResults.length;
|
|
61284
|
+
const failedFetches = [
|
|
61285
|
+
...footerRepliesResults,
|
|
61286
|
+
...inlineRepliesResults
|
|
61287
|
+
].filter((r) => r.status === "rejected").length;
|
|
61288
|
+
return toolResult(
|
|
61289
|
+
formatCommentThreads(fr, ir, page_id, failedFetches, totalFetches)
|
|
61290
|
+
);
|
|
60695
61291
|
}
|
|
60696
61292
|
return toolResult(formatComments(footerComments, inlineComments, page_id));
|
|
60697
61293
|
} catch (err) {
|
|
@@ -60745,7 +61341,7 @@ Color: ${state.color}` + echo
|
|
|
60745
61341
|
`Created ${type} comment ${comment2.id} on page ${page_id}` + echo
|
|
60746
61342
|
);
|
|
60747
61343
|
} catch (err) {
|
|
60748
|
-
return
|
|
61344
|
+
return toolErrorWithContext(err, { operation: "create_comment", resource: `page ${page_id}`, profile: config3.profile });
|
|
60749
61345
|
}
|
|
60750
61346
|
}
|
|
60751
61347
|
);
|
|
@@ -60774,7 +61370,7 @@ Color: ${state.color}` + echo
|
|
|
60774
61370
|
`Comment ${comment_id} ${state} (version: ${comment2.version?.number ?? "??"})` + echo
|
|
60775
61371
|
);
|
|
60776
61372
|
} catch (err) {
|
|
60777
|
-
return
|
|
61373
|
+
return toolErrorWithContext(err, { operation: "resolve_comment", resource: `comment ${comment_id}`, profile: config3.profile });
|
|
60778
61374
|
}
|
|
60779
61375
|
}
|
|
60780
61376
|
);
|
|
@@ -60804,7 +61400,7 @@ Color: ${state.color}` + echo
|
|
|
60804
61400
|
}
|
|
60805
61401
|
return toolResult(`Deleted ${type} comment ${comment_id}` + echo);
|
|
60806
61402
|
} catch (err) {
|
|
60807
|
-
return
|
|
61403
|
+
return toolErrorWithContext(err, { operation: "delete_comment", resource: `comment ${comment_id}`, profile: config3.profile });
|
|
60808
61404
|
}
|
|
60809
61405
|
}
|
|
60810
61406
|
);
|
|
@@ -61087,11 +61683,19 @@ ${sectionFenced}`
|
|
|
61087
61683
|
// E2: thread validated source for the mutation log.
|
|
61088
61684
|
source: effectiveSource
|
|
61089
61685
|
});
|
|
61686
|
+
const warnings = [];
|
|
61687
|
+
const labelResult = await ensureAttributionLabel(submitted.page.id);
|
|
61688
|
+
if (labelResult.warning) warnings.push(labelResult.warning);
|
|
61689
|
+
const badgeResult = await markPageUnverified(submitted.page.id, config3);
|
|
61690
|
+
if (badgeResult.warning) warnings.push(badgeResult.warning);
|
|
61090
61691
|
return toolResult(
|
|
61091
|
-
|
|
61692
|
+
appendWarnings(
|
|
61693
|
+
`Reverted: ${submitted.page.title} (ID: ${submitted.page.id}, v${target_version}\u2192v${submitted.newVersion}, body: ${submitted.oldLen}\u2192${submitted.newLen} chars)`,
|
|
61694
|
+
warnings
|
|
61695
|
+
) + echo
|
|
61092
61696
|
);
|
|
61093
61697
|
} catch (err) {
|
|
61094
|
-
return
|
|
61698
|
+
return toolErrorWithContext(err, { operation: "revert_page", resource: `page ${page_id}`, profile: config3.profile });
|
|
61095
61699
|
}
|
|
61096
61700
|
}
|
|
61097
61701
|
);
|
|
@@ -61136,7 +61740,7 @@ ${lines.join("\n")}${echo2}`
|
|
|
61136
61740
|
"resolve_page_link",
|
|
61137
61741
|
{
|
|
61138
61742
|
description: withUntrustedNote(
|
|
61139
|
-
"Resolve a Confluence page to its stable content ID and URL given a page title and space key. Returns { contentId, url, spaceKey, title } for the matched page.
|
|
61743
|
+
"Resolve a Confluence page to its stable content ID and URL given a page title and space key. Returns { contentId, url, spaceKey, title } for the matched page. When authoring pages, use the returned values to construct a confluence:// markdown link in either form: `[text](confluence://SPACE_KEY/PAGE_TITLE)` (preferred \u2014 produces an <ac:link> reference that follows the page across renames) or `[text](confluence://CONTENT_ID)` (produces a plain anchor to the page's stable URL). Policy: if multiple pages share the same title in the space the first match is returned with a notice; use the exact page URL to disambiguate if needed."
|
|
61140
61744
|
),
|
|
61141
61745
|
inputSchema: {
|
|
61142
61746
|
title: external_exports.string().min(1).describe("Exact page title to look up."),
|
|
@@ -61180,7 +61784,7 @@ ${titleFenced}${echo2}`
|
|
|
61180
61784
|
inputSchema: {}
|
|
61181
61785
|
},
|
|
61182
61786
|
async () => {
|
|
61183
|
-
let text2 = `epimethian-mcp v${"6.
|
|
61787
|
+
let text2 = `epimethian-mcp v${"6.1.0"}`;
|
|
61184
61788
|
try {
|
|
61185
61789
|
const pending = await getPendingUpdate();
|
|
61186
61790
|
if (pending) {
|
|
@@ -61211,7 +61815,7 @@ ${label} update available: v${pending.current} \u2192 v${pending.latest}. Run \`
|
|
|
61211
61815
|
const pending = await getPendingUpdate();
|
|
61212
61816
|
if (!pending) {
|
|
61213
61817
|
return toolResult(
|
|
61214
|
-
`epimethian-mcp v${"6.
|
|
61818
|
+
`epimethian-mcp v${"6.1.0"} is already up to date.`
|
|
61215
61819
|
);
|
|
61216
61820
|
}
|
|
61217
61821
|
const output = await performUpgrade(pending.latest);
|
|
@@ -61233,7 +61837,7 @@ async function startRecoveryServer(profile) {
|
|
|
61233
61837
|
const server = new McpServer(
|
|
61234
61838
|
{
|
|
61235
61839
|
name: `confluence-${profile}-setup-needed`,
|
|
61236
|
-
version: "6.
|
|
61840
|
+
version: "6.1.0"
|
|
61237
61841
|
},
|
|
61238
61842
|
{
|
|
61239
61843
|
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.`
|
|
@@ -61284,21 +61888,21 @@ async function main() {
|
|
|
61284
61888
|
const serverName = config3.profile ? `confluence-${config3.profile}` : "confluence";
|
|
61285
61889
|
const server = new McpServer({
|
|
61286
61890
|
name: serverName,
|
|
61287
|
-
version: "6.
|
|
61891
|
+
version: "6.1.0"
|
|
61288
61892
|
});
|
|
61289
61893
|
await registerTools(server, config3);
|
|
61290
61894
|
const transport = new StdioServerTransport();
|
|
61291
61895
|
await server.connect(transport);
|
|
61292
61896
|
try {
|
|
61293
61897
|
const pending = await getPendingUpdate();
|
|
61294
|
-
if (pending && pending.current === "6.
|
|
61898
|
+
if (pending && pending.current === "6.1.0") {
|
|
61295
61899
|
console.error(
|
|
61296
61900
|
`epimethian-mcp: update available: v${pending.current} \u2192 v${pending.latest} (${pending.type}). Run \`epimethian-mcp upgrade\` to install.`
|
|
61297
61901
|
);
|
|
61298
61902
|
}
|
|
61299
61903
|
} catch {
|
|
61300
61904
|
}
|
|
61301
|
-
checkForUpdates("6.
|
|
61905
|
+
checkForUpdates("6.1.0").catch(() => {
|
|
61302
61906
|
});
|
|
61303
61907
|
}
|
|
61304
61908
|
|
|
@@ -61316,6 +61920,10 @@ async function run() {
|
|
|
61316
61920
|
} else if (command === "status") {
|
|
61317
61921
|
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
61318
61922
|
await runStatus2();
|
|
61923
|
+
} else if (command === "permissions") {
|
|
61924
|
+
const profile = process.argv[3];
|
|
61925
|
+
const { runPermissions: runPermissions2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
|
|
61926
|
+
await runPermissions2(profile);
|
|
61319
61927
|
} else if (command === "fix-legacy-links") {
|
|
61320
61928
|
const { runFixLegacyLinks: runFixLegacyLinks2 } = await Promise.resolve().then(() => (init_fix_legacy_links(), fix_legacy_links_exports));
|
|
61321
61929
|
await runFixLegacyLinks2(process.argv.slice(3));
|