@codazen/harmonica-mcp 5.1.0 → 5.2.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/index.js +94 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22480,20 +22480,30 @@ var POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
22480
22480
|
async function pollForCheck(client, taskId) {
|
|
22481
22481
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
22482
22482
|
let consecutiveErrors = 0;
|
|
22483
|
+
let consecutiveMissing = 0;
|
|
22483
22484
|
while (Date.now() < deadline) {
|
|
22484
22485
|
const [task, err] = await client.getTask(taskId).then(
|
|
22485
22486
|
(t) => [t, null],
|
|
22486
22487
|
(e) => [null, e]
|
|
22487
22488
|
);
|
|
22488
22489
|
if (err) {
|
|
22490
|
+
consecutiveMissing = 0;
|
|
22489
22491
|
consecutiveErrors++;
|
|
22490
22492
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22491
22493
|
throw new Error(`getTask failed ${consecutiveErrors} consecutive times: ${err instanceof Error ? err.message : String(err)}`);
|
|
22492
22494
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22493
22495
|
continue;
|
|
22494
22496
|
}
|
|
22497
|
+
if (!task) {
|
|
22498
|
+
consecutiveErrors = 0;
|
|
22499
|
+
consecutiveMissing++;
|
|
22500
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22501
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
22502
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22503
|
+
continue;
|
|
22504
|
+
}
|
|
22495
22505
|
consecutiveErrors = 0;
|
|
22496
|
-
|
|
22506
|
+
consecutiveMissing = 0;
|
|
22497
22507
|
if (task.status === "completed") {
|
|
22498
22508
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
22499
22509
|
return task.result;
|
|
@@ -22801,6 +22811,18 @@ ${revision.description}`);
|
|
|
22801
22811
|
}
|
|
22802
22812
|
return sections.join("\n");
|
|
22803
22813
|
}
|
|
22814
|
+
function formatRevisionPreview(revisionId, preview) {
|
|
22815
|
+
if (!preview) return `No preview recorded for revision: ${revisionId}`;
|
|
22816
|
+
const freshness = preview.isCurrent ? "Current \u2014 built from the head commit." : `Stale \u2014 built from ${preview.commitSha?.slice(0, 7) ?? "an unknown commit"}, which is no longer the head. Still opens.`;
|
|
22817
|
+
return [
|
|
22818
|
+
`# Preview: ${revisionId}`,
|
|
22819
|
+
`**URL:** ${preview.url}`,
|
|
22820
|
+
`**Built from:** ${preview.commitSha}`,
|
|
22821
|
+
`**Recorded:** ${preview.recordedAt}`,
|
|
22822
|
+
`
|
|
22823
|
+
${freshness}`
|
|
22824
|
+
].join("\n");
|
|
22825
|
+
}
|
|
22804
22826
|
|
|
22805
22827
|
// ../../libs/harmonica-services/src/mcp/formatters/note-formatter.ts
|
|
22806
22828
|
var TYPE_LABELS = {
|
|
@@ -23309,6 +23331,26 @@ ${formatDeliveryPolicy(
|
|
|
23309
23331
|
}
|
|
23310
23332
|
}
|
|
23311
23333
|
);
|
|
23334
|
+
server.tool(
|
|
23335
|
+
"get_revision_preview",
|
|
23336
|
+
"Get the preview link recorded for a Revision \u2014 the running environment its own pipeline published, and whether that build is still the pull request's head. A Revision with no recorded preview is reported plainly, not as an error: one whose change artifact is a document never gets one. A stale link still opens; it was built before the latest commit.",
|
|
23337
|
+
{
|
|
23338
|
+
revisionId: external_exports.string().describe("The revision ID (e.g., rev-abc123)")
|
|
23339
|
+
},
|
|
23340
|
+
async ({ revisionId }) => {
|
|
23341
|
+
try {
|
|
23342
|
+
const revision = await client.getRevision(revisionId);
|
|
23343
|
+
if (!revision) {
|
|
23344
|
+
return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
|
|
23345
|
+
}
|
|
23346
|
+
const preview = await client.resolveRevisionPreview(revisionId);
|
|
23347
|
+
return { content: [{ type: "text", text: formatRevisionPreview(revisionId, preview) }] };
|
|
23348
|
+
} catch (err) {
|
|
23349
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23350
|
+
return { content: [{ type: "text", text: `Failed to get revision preview: ${message}` }], isError: true };
|
|
23351
|
+
}
|
|
23352
|
+
}
|
|
23353
|
+
);
|
|
23312
23354
|
}
|
|
23313
23355
|
async function logBeatActivity(client, params) {
|
|
23314
23356
|
try {
|
|
@@ -24227,12 +24269,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS2 = 3;
|
|
|
24227
24269
|
async function pollForCheck2(client, taskId) {
|
|
24228
24270
|
const deadline = Date.now() + POLL_TIMEOUT_MS2;
|
|
24229
24271
|
let consecutiveErrors = 0;
|
|
24272
|
+
let consecutiveMissing = 0;
|
|
24230
24273
|
while (Date.now() < deadline) {
|
|
24231
24274
|
const [task, err] = await client.getTask(taskId).then(
|
|
24232
24275
|
(t) => [t, null],
|
|
24233
24276
|
(e) => [null, e]
|
|
24234
24277
|
);
|
|
24235
24278
|
if (err) {
|
|
24279
|
+
consecutiveMissing = 0;
|
|
24236
24280
|
consecutiveErrors++;
|
|
24237
24281
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS2) {
|
|
24238
24282
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24240,8 +24284,16 @@ async function pollForCheck2(client, taskId) {
|
|
|
24240
24284
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24241
24285
|
continue;
|
|
24242
24286
|
}
|
|
24287
|
+
if (!task) {
|
|
24288
|
+
consecutiveErrors = 0;
|
|
24289
|
+
consecutiveMissing++;
|
|
24290
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS2)
|
|
24291
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
24292
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24293
|
+
continue;
|
|
24294
|
+
}
|
|
24243
24295
|
consecutiveErrors = 0;
|
|
24244
|
-
|
|
24296
|
+
consecutiveMissing = 0;
|
|
24245
24297
|
if (task.status === "completed") {
|
|
24246
24298
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
24247
24299
|
return task.result;
|
|
@@ -25344,6 +25396,14 @@ var RevisionScmSchema = external_exports.object({
|
|
|
25344
25396
|
state: external_exports.enum(["draft", "open", "merged", "closed"]),
|
|
25345
25397
|
mergeSha: external_exports.string().optional()
|
|
25346
25398
|
});
|
|
25399
|
+
var RevisionPreviewSchema = external_exports.object({
|
|
25400
|
+
url: external_exports.string(),
|
|
25401
|
+
commitSha: external_exports.string(),
|
|
25402
|
+
recordedAt: external_exports.string()
|
|
25403
|
+
});
|
|
25404
|
+
var RevisionPreviewApiSchema = RevisionPreviewSchema.extend({
|
|
25405
|
+
isCurrent: external_exports.boolean()
|
|
25406
|
+
});
|
|
25347
25407
|
var RevisionApiSchema = external_exports.object({
|
|
25348
25408
|
revisionId: external_exports.string(),
|
|
25349
25409
|
beatId: external_exports.string(),
|
|
@@ -28362,7 +28422,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28362
28422
|
);
|
|
28363
28423
|
server.tool(
|
|
28364
28424
|
"update_notebook_collection",
|
|
28365
|
-
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type,
|
|
28425
|
+
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type, system scope, limit, order, significance tier) or its heading, without removing and re-adding it. Only the fields you pass change; the rest keep their current values. Identify the section by its elementId (from get_notebook_tree) and the page it sits on.",
|
|
28366
28426
|
{
|
|
28367
28427
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28368
28428
|
elementId: external_exports.string().describe("The collection section's elementId (from get_notebook_tree)"),
|
|
@@ -28503,9 +28563,9 @@ ${scopeText}` }] };
|
|
|
28503
28563
|
);
|
|
28504
28564
|
server.tool(
|
|
28505
28565
|
"list_notebooks",
|
|
28506
|
-
"List Notebooks reachable from a scope (org, teamspace, or
|
|
28566
|
+
"List Notebooks reachable from a scope (org, teamspace, system, or account). Returns all notebooks bound to that scope.",
|
|
28507
28567
|
{
|
|
28508
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, or
|
|
28568
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28509
28569
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity"),
|
|
28510
28570
|
includeArchived: external_exports.boolean().optional().describe("Include archived Notebooks in the results. Default: false (archived excluded).")
|
|
28511
28571
|
},
|
|
@@ -28566,10 +28626,10 @@ ${formatNotebook(updated)}` }] };
|
|
|
28566
28626
|
);
|
|
28567
28627
|
server.tool(
|
|
28568
28628
|
"add_notebook_shortcut",
|
|
28569
|
-
"Add a curated, read-only shortcut from a scope (org, teamspace,
|
|
28629
|
+
"Add a curated, read-only shortcut from a scope (org, teamspace, system, or account) to a Notebook owned elsewhere in the SAME organization. Unlike set_notebook_scopes, a shortcut grants no rename/archive/delete rights over the Notebook in the consuming scope \u2014 it is a link, not ownership.",
|
|
28570
28630
|
{
|
|
28571
28631
|
notebookId: external_exports.string().describe("The Notebook ID to shortcut"),
|
|
28572
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28632
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28573
28633
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity"),
|
|
28574
28634
|
createdBy: external_exports.string().optional().describe("Actor ID \u2014 defaults to the session user")
|
|
28575
28635
|
},
|
|
@@ -28598,7 +28658,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28598
28658
|
"Remove a Notebook shortcut from a scope. Only removes the shortcut reference \u2014 never affects the source Notebook or its real scope bindings.",
|
|
28599
28659
|
{
|
|
28600
28660
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28601
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28661
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28602
28662
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity")
|
|
28603
28663
|
},
|
|
28604
28664
|
async ({ notebookId, scopeType, scopeId }) => {
|
|
@@ -28621,7 +28681,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28621
28681
|
"list_notebook_shortcuts",
|
|
28622
28682
|
"List a scope\u2019s curated Notebook shortcuts \u2014 Notebooks owned elsewhere in the org that this scope has chosen to surface, hydrated with each source Notebook\u2019s live title/status.",
|
|
28623
28683
|
{
|
|
28624
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace,
|
|
28684
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28625
28685
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity")
|
|
28626
28686
|
},
|
|
28627
28687
|
async ({ scopeType, scopeId }) => {
|
|
@@ -28976,12 +29036,14 @@ var ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
28976
29036
|
async function pollForRootCodaCheck(client, taskId) {
|
|
28977
29037
|
const deadline = Date.now() + ROOT_CODA_POLL_TIMEOUT_MS;
|
|
28978
29038
|
let consecutiveErrors = 0;
|
|
29039
|
+
let consecutiveMissing = 0;
|
|
28979
29040
|
while (Date.now() < deadline) {
|
|
28980
29041
|
const [task, err] = await client.getTask(taskId).then(
|
|
28981
29042
|
(t) => [t, null],
|
|
28982
29043
|
(e) => [null, e]
|
|
28983
29044
|
);
|
|
28984
29045
|
if (err) {
|
|
29046
|
+
consecutiveMissing = 0;
|
|
28985
29047
|
consecutiveErrors++;
|
|
28986
29048
|
if (consecutiveErrors >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS) {
|
|
28987
29049
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -28989,8 +29051,16 @@ async function pollForRootCodaCheck(client, taskId) {
|
|
|
28989
29051
|
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28990
29052
|
continue;
|
|
28991
29053
|
}
|
|
29054
|
+
if (!task) {
|
|
29055
|
+
consecutiveErrors = 0;
|
|
29056
|
+
consecutiveMissing++;
|
|
29057
|
+
if (consecutiveMissing >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS)
|
|
29058
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29059
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
29060
|
+
continue;
|
|
29061
|
+
}
|
|
28992
29062
|
consecutiveErrors = 0;
|
|
28993
|
-
|
|
29063
|
+
consecutiveMissing = 0;
|
|
28994
29064
|
if (task.status === "completed") {
|
|
28995
29065
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
28996
29066
|
const result = task.result;
|
|
@@ -29333,12 +29403,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS4 = 3;
|
|
|
29333
29403
|
async function pollForCheck4(client, taskId) {
|
|
29334
29404
|
const deadline = Date.now() + POLL_TIMEOUT_MS4;
|
|
29335
29405
|
let consecutiveErrors = 0;
|
|
29406
|
+
let consecutiveMissing = 0;
|
|
29336
29407
|
while (Date.now() < deadline) {
|
|
29337
29408
|
const [task, err] = await client.getTask(taskId).then(
|
|
29338
29409
|
(t) => [t, null],
|
|
29339
29410
|
(e) => [null, e]
|
|
29340
29411
|
);
|
|
29341
29412
|
if (err) {
|
|
29413
|
+
consecutiveMissing = 0;
|
|
29342
29414
|
consecutiveErrors++;
|
|
29343
29415
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS4) {
|
|
29344
29416
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -29346,8 +29418,16 @@ async function pollForCheck4(client, taskId) {
|
|
|
29346
29418
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29347
29419
|
continue;
|
|
29348
29420
|
}
|
|
29421
|
+
if (!task) {
|
|
29422
|
+
consecutiveErrors = 0;
|
|
29423
|
+
consecutiveMissing++;
|
|
29424
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS4)
|
|
29425
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29426
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29427
|
+
continue;
|
|
29428
|
+
}
|
|
29349
29429
|
consecutiveErrors = 0;
|
|
29350
|
-
|
|
29430
|
+
consecutiveMissing = 0;
|
|
29351
29431
|
if (task.status === "completed") {
|
|
29352
29432
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
29353
29433
|
return task.result;
|
|
@@ -33664,6 +33744,8 @@ function createHttpClient(config2) {
|
|
|
33664
33744
|
return { revision: result.revision, warnings: result.warnings ?? [] };
|
|
33665
33745
|
},
|
|
33666
33746
|
getRevision: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}`),
|
|
33747
|
+
// `request` maps 404 to undefined, which is what absence must be here.
|
|
33748
|
+
resolveRevisionPreview: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}/preview`),
|
|
33667
33749
|
listConceptRevisions: async (systemId, options) => {
|
|
33668
33750
|
const qs = options?.includeArchived ? "?includeArchived=true" : "";
|
|
33669
33751
|
const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/revisions/pending${qs}`);
|
|
@@ -35471,7 +35553,7 @@ function loadConfig() {
|
|
|
35471
35553
|
};
|
|
35472
35554
|
}
|
|
35473
35555
|
async function main() {
|
|
35474
|
-
console.error(`[harmonica-mcp] v${"5.
|
|
35556
|
+
console.error(`[harmonica-mcp] v${"5.2.0"} starting\u2026`);
|
|
35475
35557
|
const config2 = loadConfig();
|
|
35476
35558
|
const client = createHttpClient({
|
|
35477
35559
|
apiBaseUrl: config2.apiBaseUrl,
|