@codazen/harmonica-mcp 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +189 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22444,7 +22444,8 @@ var CHECK_MAX_SCORES = {
|
|
|
22444
22444
|
pii_scan: 100,
|
|
22445
22445
|
portfolio_coherence: 5,
|
|
22446
22446
|
revision_quality: 5,
|
|
22447
|
-
drop_quality: 5
|
|
22447
|
+
drop_quality: 5,
|
|
22448
|
+
root_coda_quality: 5
|
|
22448
22449
|
};
|
|
22449
22450
|
|
|
22450
22451
|
// ../../libs/harmonica-services/src/mcp/tools/beat-quality-tools.ts
|
|
@@ -22523,7 +22524,7 @@ Use get_job_status with jobId="${taskId}" to poll for results, or list_checks to
|
|
|
22523
22524
|
};
|
|
22524
22525
|
server.tool(
|
|
22525
22526
|
"check_beat_coda_quality",
|
|
22526
|
-
"Enqueue a Beat definition quality check across 7 dimensions (Distinctive, Harmonious, Substantial, Durable, Clear, Strategically Aligned,
|
|
22527
|
+
"Enqueue a Beat definition quality check across 7 dimensions (Distinctive, Harmonious, Substantial, Durable, Clear, Strategically Aligned, Outcome-Oriented) \u2014 grades the Beat's Coda and directing context, persisted as beat_coda_quality. By default returns a jobId immediately (fire-and-forget) \u2014 use get_job_status or list_checks to retrieve results. Set wait=true to block until the check completes and receive the scorecard inline. WARNING: wait=true holds the MCP session open for 30\u201360s per check \u2014 for bulk operations (more than one Beat), omit wait and poll get_job_status separately to avoid session exhaustion.",
|
|
22527
22528
|
schema,
|
|
22528
22529
|
handler
|
|
22529
22530
|
);
|
|
@@ -22648,6 +22649,17 @@ var NOTE_TYPE_LABELS = {
|
|
|
22648
22649
|
decision: "Decisions",
|
|
22649
22650
|
document: "Documents"
|
|
22650
22651
|
};
|
|
22652
|
+
function formatDeliveryPolicy(policy, unresolvedReason) {
|
|
22653
|
+
const LABELS = {
|
|
22654
|
+
openPullRequest: "Open a pull request",
|
|
22655
|
+
requestAgentReview: "Request the agent code review",
|
|
22656
|
+
watchPullRequest: "Watch the pull request to green"
|
|
22657
|
+
};
|
|
22658
|
+
const verdict = (value) => value === true ? "yes \u2014 proceed without asking" : value === false ? "NO \u2014 prohibited, say so rather than asking" : "not set \u2014 ask first";
|
|
22659
|
+
const lines = Object.keys(LABELS).map((step) => `- ${LABELS[step]}: ${verdict(policy?.[step] ?? void 0)}`);
|
|
22660
|
+
const footer = unresolvedReason ? [`- _Policy could not be resolved (${unresolvedReason}) \u2014 ask first; this is not proof that no policy is set._`] : [];
|
|
22661
|
+
return ["## Agent Delivery Policy", ...lines, ...footer].join("\n");
|
|
22662
|
+
}
|
|
22651
22663
|
function formatProjectContext(project, notes, orgCoda) {
|
|
22652
22664
|
const sections = [];
|
|
22653
22665
|
sections.push(`# Project: ${project.title}`);
|
|
@@ -22657,6 +22669,8 @@ function formatProjectContext(project, notes, orgCoda) {
|
|
|
22657
22669
|
const branch = project.repoDefaultBranch ?? "main";
|
|
22658
22670
|
sections.push(`**Repo:** ${project.repoOwner}/${project.repoName} (branch: ${branch})`);
|
|
22659
22671
|
}
|
|
22672
|
+
sections.push(`
|
|
22673
|
+
${formatDeliveryPolicy(project.agentDeliveryPolicy)}`);
|
|
22660
22674
|
if (orgCoda) {
|
|
22661
22675
|
sections.push(`
|
|
22662
22676
|
## Organization Coda
|
|
@@ -23255,7 +23269,14 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
|
|
|
23255
23269
|
return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
|
|
23256
23270
|
}
|
|
23257
23271
|
await assertBeatInOrg(client, revision.beatId, ctx.orgId);
|
|
23258
|
-
|
|
23272
|
+
const resolution = await client.resolveDeliveryPolicy(revisionId);
|
|
23273
|
+
const policyBlock = `
|
|
23274
|
+
|
|
23275
|
+
${formatDeliveryPolicy(
|
|
23276
|
+
resolution.resolved ? resolution.policy : void 0,
|
|
23277
|
+
resolution.resolved ? void 0 : resolution.reason
|
|
23278
|
+
)}`;
|
|
23279
|
+
return { content: [{ type: "text", text: formatRevisionDetail(revision) + policyBlock }] };
|
|
23259
23280
|
} catch (err) {
|
|
23260
23281
|
const message = err instanceof Error ? err.message : String(err);
|
|
23261
23282
|
return { content: [{ type: "text", text: `Failed to get revision: ${message}` }], isError: true };
|
|
@@ -25836,7 +25857,7 @@ var BEAT_QUALITY_DIMENSIONS = [
|
|
|
25836
25857
|
"durable",
|
|
25837
25858
|
"clear",
|
|
25838
25859
|
"strategicallyAligned",
|
|
25839
|
-
"
|
|
25860
|
+
"outcomeOriented"
|
|
25840
25861
|
];
|
|
25841
25862
|
var BeatDimensionScoreSchema = external_exports.object({
|
|
25842
25863
|
dimension: external_exports.enum(BEAT_QUALITY_DIMENSIONS),
|
|
@@ -26200,6 +26221,7 @@ var ScopeNotebookApiSchema = external_exports.object({
|
|
|
26200
26221
|
var NotebookResponseSchema = external_exports.object({ notebook: NotebookApiSchema }).passthrough();
|
|
26201
26222
|
var NotebookScopesResponseSchema = external_exports.object({ scopes: external_exports.array(NotebookScopeRefSchema) }).passthrough();
|
|
26202
26223
|
var ScopeNotebooksResponseSchema = external_exports.object({ notebooks: external_exports.array(ScopeNotebookApiSchema) }).passthrough();
|
|
26224
|
+
var NotebookSharingResponseSchema = external_exports.object({ guestView: external_exports.boolean() }).passthrough();
|
|
26203
26225
|
var NotebookShortcutApiSchema = external_exports.object({
|
|
26204
26226
|
notebookId: external_exports.string(),
|
|
26205
26227
|
scopeType: NotebookScopeTypeSchema,
|
|
@@ -26325,7 +26347,8 @@ var CHECK_TARGET_TYPES = [
|
|
|
26325
26347
|
"revision",
|
|
26326
26348
|
"beat_version",
|
|
26327
26349
|
"project",
|
|
26328
|
-
"drop"
|
|
26350
|
+
"drop",
|
|
26351
|
+
"organization"
|
|
26329
26352
|
];
|
|
26330
26353
|
var CheckTargetTypeSchema = external_exports.enum(CHECK_TARGET_TYPES);
|
|
26331
26354
|
var CheckApiSchema = external_exports.object({
|
|
@@ -26577,7 +26600,7 @@ var StoredMessageSchema = external_exports.object({
|
|
|
26577
26600
|
var ConversationsResponseSchema = external_exports.array(ConversationSummarySchema);
|
|
26578
26601
|
var MessagesResponseSchema = external_exports.array(StoredMessageSchema);
|
|
26579
26602
|
var StartSessionRequestBodySchema = external_exports.object({
|
|
26580
|
-
|
|
26603
|
+
systemId: external_exports.string().min(1),
|
|
26581
26604
|
message: external_exports.string().min(1),
|
|
26582
26605
|
revisionId: external_exports.string().min(1).optional(),
|
|
26583
26606
|
enableWebSearch: external_exports.boolean().optional()
|
|
@@ -28487,6 +28510,59 @@ Notes filed: ${createdNotes.length}`);
|
|
|
28487
28510
|
}
|
|
28488
28511
|
|
|
28489
28512
|
// ../../libs/harmonica-services/src/mcp/tools/organization-tools.ts
|
|
28513
|
+
var ROOT_CODA_POLL_INTERVAL_MS = 4e3;
|
|
28514
|
+
var ROOT_CODA_POLL_TIMEOUT_MS = 18e4;
|
|
28515
|
+
var ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
28516
|
+
async function pollForRootCodaCheck(client, taskId) {
|
|
28517
|
+
const deadline = Date.now() + ROOT_CODA_POLL_TIMEOUT_MS;
|
|
28518
|
+
let consecutiveErrors = 0;
|
|
28519
|
+
while (Date.now() < deadline) {
|
|
28520
|
+
const [task, err] = await client.getTask(taskId).then(
|
|
28521
|
+
(t) => [t, null],
|
|
28522
|
+
(e) => [null, e]
|
|
28523
|
+
);
|
|
28524
|
+
if (err) {
|
|
28525
|
+
consecutiveErrors++;
|
|
28526
|
+
if (consecutiveErrors >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS) {
|
|
28527
|
+
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
28528
|
+
}
|
|
28529
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28530
|
+
continue;
|
|
28531
|
+
}
|
|
28532
|
+
consecutiveErrors = 0;
|
|
28533
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
28534
|
+
if (task.status === "completed") {
|
|
28535
|
+
if (!task.result) throw new Error(`Task ${taskId} completed with no result`);
|
|
28536
|
+
const result = task.result;
|
|
28537
|
+
if (!Array.isArray(result.dimensions) || typeof result.overallScore !== "number") {
|
|
28538
|
+
throw new Error(`Task ${taskId} did not return a root Coda scorecard \u2014 was it a run_check task?`);
|
|
28539
|
+
}
|
|
28540
|
+
return result;
|
|
28541
|
+
}
|
|
28542
|
+
if (task.status === "failed") throw new Error(`Root Coda quality check failed: ${task.error ?? "unknown error"}`);
|
|
28543
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28544
|
+
}
|
|
28545
|
+
throw new Error(`Root Coda quality check timed out after ${ROOT_CODA_POLL_TIMEOUT_MS / 1e3}s`);
|
|
28546
|
+
}
|
|
28547
|
+
function formatRootCodaReport(check2) {
|
|
28548
|
+
const bar = (score, max) => {
|
|
28549
|
+
const filled = Math.max(0, Math.min(max, Math.round(score)));
|
|
28550
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(max - filled);
|
|
28551
|
+
};
|
|
28552
|
+
const dims = check2.dimensions.map(
|
|
28553
|
+
(d) => `**${d.label}** ${bar(d.score, d.maxScore)} ${d.score}/${d.maxScore}
|
|
28554
|
+
${d.rationale}${d.suggestions?.length ? `
|
|
28555
|
+
\u2022 ${d.suggestions.join("\n\u2022 ")}` : ""}`
|
|
28556
|
+
).join("\n\n");
|
|
28557
|
+
return [
|
|
28558
|
+
`## Root Coda Quality \u2014 ${check2.targetId}`,
|
|
28559
|
+
`**Overall Score:** ${check2.overallScore}/5`,
|
|
28560
|
+
`**Summary:** ${check2.summary}`,
|
|
28561
|
+
check2.topSuggestion ? `**Top Suggestion:** ${check2.topSuggestion}` : "",
|
|
28562
|
+
"",
|
|
28563
|
+
dims
|
|
28564
|
+
].filter(Boolean).join("\n");
|
|
28565
|
+
}
|
|
28490
28566
|
function registerOrganizationTools(server, ctx, client) {
|
|
28491
28567
|
server.tool(
|
|
28492
28568
|
"get_organization",
|
|
@@ -28616,6 +28692,67 @@ function registerOrganizationTools(server, ctx, client) {
|
|
|
28616
28692
|
return { content: [{ type: "text", text }] };
|
|
28617
28693
|
}
|
|
28618
28694
|
);
|
|
28695
|
+
server.tool(
|
|
28696
|
+
"compose_root_coda",
|
|
28697
|
+
"Generate and apply an Organization's root Coda (top-level purpose statement) from its name, industry, description, and existing Coda if any. Drafts via LLM, applies directly to the Organization, then runs a root_coda_quality check \u2014 if the first score is below 4.0, performs exactly one automatic self-revision pass incorporating the feedback before finishing. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
28698
|
+
{
|
|
28699
|
+
orgId: external_exports.string().min(1).describe("The organization ID")
|
|
28700
|
+
},
|
|
28701
|
+
async ({ orgId }) => {
|
|
28702
|
+
try {
|
|
28703
|
+
const task = await client.composeRootCoda(orgId);
|
|
28704
|
+
return {
|
|
28705
|
+
content: [{
|
|
28706
|
+
type: "text",
|
|
28707
|
+
text: [
|
|
28708
|
+
"Root Coda composition queued.",
|
|
28709
|
+
"",
|
|
28710
|
+
`**Organization:** ${orgId}`,
|
|
28711
|
+
`**Task ID:** ${task.taskId}`,
|
|
28712
|
+
`**Status:** ${task.status}`,
|
|
28713
|
+
"",
|
|
28714
|
+
"The agent will gather context, draft a Coda, apply it to the Organization, and grade it \u2014 revising once automatically if the first score is below 4.0.",
|
|
28715
|
+
"Use `get_job_status` with the job ID to check progress."
|
|
28716
|
+
].join("\n")
|
|
28717
|
+
}]
|
|
28718
|
+
};
|
|
28719
|
+
} catch (err) {
|
|
28720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28721
|
+
return { content: [{ type: "text", text: `compose_root_coda failed: ${message}` }], isError: true };
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
);
|
|
28725
|
+
server.tool(
|
|
28726
|
+
"check_root_coda_quality",
|
|
28727
|
+
"Run a six-dimension quality check on an Organization's root Coda (Discriminating, Attributed, Reciprocal, Bounded, Generative, Consequential). By default returns a taskId immediately \u2014 set wait=true to block and receive the full scorecard inline.",
|
|
28728
|
+
{
|
|
28729
|
+
orgId: external_exports.string().min(1).describe("The organization ID"),
|
|
28730
|
+
wait: external_exports.boolean().optional().describe("Block until the check completes and return the scorecard inline. Default: false.")
|
|
28731
|
+
},
|
|
28732
|
+
async ({ orgId, wait }) => {
|
|
28733
|
+
try {
|
|
28734
|
+
const { taskId } = await client.runCheck(orgId, "root_coda_quality", orgId);
|
|
28735
|
+
if (!wait) {
|
|
28736
|
+
return {
|
|
28737
|
+
content: [{
|
|
28738
|
+
type: "text",
|
|
28739
|
+
text: [
|
|
28740
|
+
`Root Coda quality check enqueued for ${orgId}.`,
|
|
28741
|
+
`Task ID: ${taskId}`,
|
|
28742
|
+
"",
|
|
28743
|
+
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with checkType="root_coda_quality" to view results.`
|
|
28744
|
+
].join("\n")
|
|
28745
|
+
}]
|
|
28746
|
+
};
|
|
28747
|
+
}
|
|
28748
|
+
const check2 = await pollForRootCodaCheck(client, taskId);
|
|
28749
|
+
return { content: [{ type: "text", text: formatRootCodaReport(check2) }] };
|
|
28750
|
+
} catch (err) {
|
|
28751
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28752
|
+
return { content: [{ type: "text", text: `Root Coda quality check failed: ${message}` }], isError: true };
|
|
28753
|
+
}
|
|
28754
|
+
}
|
|
28755
|
+
);
|
|
28619
28756
|
}
|
|
28620
28757
|
|
|
28621
28758
|
// ../../libs/harmonica-services/src/mcp/tools/plan-quality-tools.ts
|
|
@@ -28830,7 +28967,7 @@ function formatConsolidationResult(primaryId, primary, archivedBeatIds, notesRea
|
|
|
28830
28967
|
function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
28831
28968
|
server.tool(
|
|
28832
28969
|
"check_portfolio_coherence",
|
|
28833
|
-
"Run a portfolio coherence check on a project. Clusters Beats by semantic similarity to surface fragmentation (merge candidates) and checks what fraction of Beats
|
|
28970
|
+
"Run a portfolio coherence check on a project. Clusters Beats by semantic similarity to surface fragmentation (merge candidates) and checks what fraction of Beats name an outcome their authoriser would recognise. By default returns a taskId immediately \u2014 set wait=true to block and receive the full report inline.",
|
|
28834
28971
|
{
|
|
28835
28972
|
projectId: external_exports.string().min(1).describe("The project ID"),
|
|
28836
28973
|
wait: external_exports.boolean().optional().describe("Block until the check completes and return the report inline. Default: false.")
|
|
@@ -30029,6 +30166,9 @@ async function resolveSnapshotInput(input) {
|
|
|
30029
30166
|
return trimmed;
|
|
30030
30167
|
}
|
|
30031
30168
|
function formatImportSummary(result, targetTeamspaceId) {
|
|
30169
|
+
if (!result.counts || result.counts.beats == null) {
|
|
30170
|
+
throw new Error(`Import of system ${result.projectId} completed but returned no counts \u2014 check worker logs for a TransactionCanceledException`);
|
|
30171
|
+
}
|
|
30032
30172
|
const lines = [
|
|
30033
30173
|
`Imported system: ${result.projectId}`,
|
|
30034
30174
|
` Beats: ${result.counts.beats}`,
|
|
@@ -30316,7 +30456,12 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30316
30456
|
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
30317
30457
|
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
30318
30458
|
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
|
|
30319
|
-
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
|
|
30459
|
+
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.'),
|
|
30460
|
+
agentDeliveryPolicy: external_exports.object({
|
|
30461
|
+
openPullRequest: external_exports.boolean().nullable().optional().describe("Open a pull request when the work is ready."),
|
|
30462
|
+
requestAgentReview: external_exports.boolean().nullable().optional().describe("Request the agent code review (the agent-review label)."),
|
|
30463
|
+
watchPullRequest: external_exports.boolean().nullable().optional().describe("Watch the pull request and drive it to green.")
|
|
30464
|
+
}).optional().describe("Delivery latitude for agents on this System: which pull-request steps they may take without asking. Each field is tri-state \u2014 omit it to leave it alone, true to let agents proceed, false to forbid the step outright, null to clear it back to asking.")
|
|
30320
30465
|
};
|
|
30321
30466
|
const updateSystemHandler = async ({ systemId, ...updates }) => {
|
|
30322
30467
|
try {
|
|
@@ -31867,7 +32012,7 @@ function createHttpClient(config2) {
|
|
|
31867
32012
|
const result = await request(
|
|
31868
32013
|
"POST",
|
|
31869
32014
|
`/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-project`,
|
|
31870
|
-
{ projectId, targetOrgId }
|
|
32015
|
+
{ systemId: projectId, targetOrgId }
|
|
31871
32016
|
);
|
|
31872
32017
|
const project = result?.project ? {
|
|
31873
32018
|
...result.project,
|
|
@@ -32975,6 +33120,15 @@ function createHttpClient(config2) {
|
|
|
32975
33120
|
if (!result?.taskId) throw new Error("Draft coda enqueue failed: no taskId returned");
|
|
32976
33121
|
return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
32977
33122
|
},
|
|
33123
|
+
composeRootCoda: async (orgId) => {
|
|
33124
|
+
const result = await request(
|
|
33125
|
+
"POST",
|
|
33126
|
+
`/api/organizations/${encodeURIComponent(orgId)}/compose-root-coda`,
|
|
33127
|
+
{}
|
|
33128
|
+
);
|
|
33129
|
+
if (!result?.taskId) throw new Error("Compose root Coda enqueue failed: no taskId returned");
|
|
33130
|
+
return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", projectId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33131
|
+
},
|
|
32978
33132
|
validateAssumptions: async (projectId, noteIds) => {
|
|
32979
33133
|
const result = await request(
|
|
32980
33134
|
"POST",
|
|
@@ -33042,7 +33196,19 @@ function createHttpClient(config2) {
|
|
|
33042
33196
|
return download;
|
|
33043
33197
|
},
|
|
33044
33198
|
importSystemSnapshot: (snapshot, options) => request("POST", "/api/systems/import", { ...snapshot, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
|
|
33045
|
-
importSystemSnapshotFromUrl: (url2, options) =>
|
|
33199
|
+
importSystemSnapshotFromUrl: async (url2, options) => {
|
|
33200
|
+
const enqueued = await request(
|
|
33201
|
+
"POST",
|
|
33202
|
+
"/api/systems/import",
|
|
33203
|
+
{ snapshotUrl: url2, importOptions: options }
|
|
33204
|
+
);
|
|
33205
|
+
if (!enqueued?.taskId) {
|
|
33206
|
+
throw new Error("importSystemSnapshotFromUrl: API did not return a taskId");
|
|
33207
|
+
}
|
|
33208
|
+
const importResult = await pollTaskResult(enqueued.taskId);
|
|
33209
|
+
if (importResult == null) throw new Error(`Task ${enqueued.taskId} completed but returned no result \u2014 check worker logs for a TransactionCanceledException`);
|
|
33210
|
+
return importResult;
|
|
33211
|
+
},
|
|
33046
33212
|
// Embedding similarity
|
|
33047
33213
|
embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
|
|
33048
33214
|
findSimilarNotes: async (noteId, projectId, options) => {
|
|
@@ -33438,6 +33604,18 @@ function createHttpClient(config2) {
|
|
|
33438
33604
|
// Systems owned directly by an Account (B-278 v17). A 404 here means either
|
|
33439
33605
|
// no such Account or the feature flag is off; both read as "nothing to show",
|
|
33440
33606
|
// which is why request() returning undefined maps to an empty list.
|
|
33607
|
+
// Delegates to the endpoint that calls the same service the direct adapter
|
|
33608
|
+
// uses, so neither adapter carries its own copy of the fail-closed rule.
|
|
33609
|
+
// request() maps a 404 to undefined; that is itself unresolvable, so it
|
|
33610
|
+
// becomes an explicit unresolved result rather than a thrown error.
|
|
33611
|
+
resolveDeliveryPolicy: async (revisionId) => {
|
|
33612
|
+
const res = await request(
|
|
33613
|
+
"GET",
|
|
33614
|
+
`/api/revisions/${encodeURIComponent(revisionId)}/delivery-policy`
|
|
33615
|
+
);
|
|
33616
|
+
if (!res) return { resolved: false, reason: "revision_not_found" };
|
|
33617
|
+
return res;
|
|
33618
|
+
},
|
|
33441
33619
|
listAccountSystems: async (accountId) => {
|
|
33442
33620
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/systems`);
|
|
33443
33621
|
return res?.systems ?? [];
|
|
@@ -33873,7 +34051,7 @@ function loadConfig() {
|
|
|
33873
34051
|
};
|
|
33874
34052
|
}
|
|
33875
34053
|
async function main() {
|
|
33876
|
-
console.error(`[harmonica-mcp] v${"3.
|
|
34054
|
+
console.error(`[harmonica-mcp] v${"3.3.0"} starting\u2026`);
|
|
33877
34055
|
const config2 = loadConfig();
|
|
33878
34056
|
const client = createHttpClient({
|
|
33879
34057
|
apiBaseUrl: config2.apiBaseUrl,
|