@codazen/harmonica-mcp 3.0.1 → 3.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.
Files changed (2) hide show
  1. package/dist/index.js +231 -13
  2. 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
@@ -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
- return { content: [{ type: "text", text: formatRevisionDetail(revision) }] };
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 };
@@ -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
- projectId: external_exports.string().min(1),
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()
@@ -26862,7 +26885,7 @@ var EligibleBeatVersionForDropSchema = external_exports.object({
26862
26885
  createdAt: external_exports.string(),
26863
26886
  updatedAt: external_exports.string(),
26864
26887
  humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
26865
- currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string(), state: external_exports.string() }).optional()
26888
+ currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string().optional(), state: external_exports.string() }).optional()
26866
26889
  }).passthrough();
26867
26890
  var ScoredDropCandidateSchema = external_exports.object({
26868
26891
  bv: EligibleBeatVersionForDropSchema,
@@ -26879,6 +26902,34 @@ var ScoredDropCandidateSchema = external_exports.object({
26879
26902
  atRisk: external_exports.boolean(),
26880
26903
  requiredSlope: external_exports.number().nullable().optional()
26881
26904
  }).passthrough();
26905
+ var DUTY_ALTITUDES = ["portfolio", "system", "account"];
26906
+ var DUTY_HOLDER_TYPES = ["human", "agent"];
26907
+ var DUTY_MODES = ["accountable", "responsible"];
26908
+ var DutyAltitudeApiSchema = external_exports.enum(DUTY_ALTITUDES);
26909
+ var DutyHolderTypeApiSchema = external_exports.enum(DUTY_HOLDER_TYPES);
26910
+ var DutyModeApiSchema = external_exports.enum(DUTY_MODES);
26911
+ var DerivedStaffingStateApiSchema = external_exports.enum(["agent-held", "supported", "observed"]);
26912
+ var DutyAssignmentApiSchema = external_exports.object({
26913
+ dutyAssignmentId: external_exports.string(),
26914
+ accountId: external_exports.string(),
26915
+ orgId: external_exports.string(),
26916
+ role: external_exports.string(),
26917
+ altitude: DutyAltitudeApiSchema,
26918
+ scopeId: external_exports.string(),
26919
+ holderType: DutyHolderTypeApiSchema,
26920
+ holderId: external_exports.string(),
26921
+ supportingAssistantId: external_exports.string().optional(),
26922
+ mode: DutyModeApiSchema,
26923
+ parentDutyAssignmentId: external_exports.string().optional(),
26924
+ effectiveFrom: external_exports.string(),
26925
+ effectiveTo: external_exports.string().optional(),
26926
+ createdBy: external_exports.string().optional(),
26927
+ version: external_exports.number().optional(),
26928
+ createdAt: external_exports.string(),
26929
+ updatedAt: external_exports.string()
26930
+ }).passthrough();
26931
+ var DutyAssignmentsResponseSchema = external_exports.object({ dutyAssignments: external_exports.array(DutyAssignmentApiSchema).optional() }).passthrough();
26932
+ var DutyAssignmentResponseSchema = external_exports.object({ dutyAssignment: DutyAssignmentApiSchema }).passthrough();
26882
26933
  var DropProposalApiSchema = external_exports.object({
26883
26934
  accountId: external_exports.string(),
26884
26935
  revisionIds: external_exports.array(external_exports.string()),
@@ -28459,6 +28510,59 @@ Notes filed: ${createdNotes.length}`);
28459
28510
  }
28460
28511
 
28461
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
+ }
28462
28566
  function registerOrganizationTools(server, ctx, client) {
28463
28567
  server.tool(
28464
28568
  "get_organization",
@@ -28588,6 +28692,67 @@ function registerOrganizationTools(server, ctx, client) {
28588
28692
  return { content: [{ type: "text", text }] };
28589
28693
  }
28590
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
+ );
28591
28756
  }
28592
28757
 
28593
28758
  // ../../libs/harmonica-services/src/mcp/tools/plan-quality-tools.ts
@@ -30213,6 +30378,13 @@ function registerProjectLifecycleTools(server, ctx, client) {
30213
30378
 
30214
30379
  // ../../libs/harmonica-services/src/mcp/tools/system-tools.ts
30215
30380
  var import_crypto5 = require("crypto");
30381
+
30382
+ // ../../libs/harmonica-services/src/utils/normalize-create-account-id.ts
30383
+ function normalizeCreateAccountId(accountId) {
30384
+ return accountId?.trim() || void 0;
30385
+ }
30386
+
30387
+ // ../../libs/harmonica-services/src/mcp/tools/system-tools.ts
30216
30388
  function accountLine(request, resolved) {
30217
30389
  const requested = request["accountId"];
30218
30390
  if (requested === null || requested === "") return "**Account:** (unlinked)";
@@ -30281,7 +30453,12 @@ function registerProjectTools(server, ctx, client) {
30281
30453
  repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
30282
30454
  repoName: external_exports.string().optional().describe("GitHub repository name"),
30283
30455
  repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
30284
- 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.')
30456
+ 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.'),
30457
+ agentDeliveryPolicy: external_exports.object({
30458
+ openPullRequest: external_exports.boolean().nullable().optional().describe("Open a pull request when the work is ready."),
30459
+ requestAgentReview: external_exports.boolean().nullable().optional().describe("Request the agent code review (the agent-review label)."),
30460
+ watchPullRequest: external_exports.boolean().nullable().optional().describe("Watch the pull request and drive it to green.")
30461
+ }).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.")
30285
30462
  };
30286
30463
  const updateSystemHandler = async ({ systemId, ...updates }) => {
30287
30464
  try {
@@ -30366,11 +30543,12 @@ function registerProjectTools(server, ctx, client) {
30366
30543
  description: external_exports.string().optional().describe("System description"),
30367
30544
  strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
30368
30545
  teamspaceId: external_exports.string().optional().describe("Teamspace ID to associate this system with"),
30546
+ accountId: external_exports.string().optional().describe("Account that owns this System \u2014 the client or internal org unit. Set it at creation so the System appears in the Account's Systems list; omit only for a System that belongs to no Account."),
30369
30547
  repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
30370
30548
  repoName: external_exports.string().optional().describe("GitHub repository name"),
30371
30549
  repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")')
30372
30550
  };
30373
- const createSystemHandler = async ({ title, description, strategy, teamspaceId, repoOwner, repoName, repoDefaultBranch }) => {
30551
+ const createSystemHandler = async ({ title, description, strategy, teamspaceId, accountId, repoOwner, repoName, repoDefaultBranch }) => {
30374
30552
  if (teamspaceId) {
30375
30553
  const teamspace = await client.getTeamspace(teamspaceId);
30376
30554
  if (!teamspace) {
@@ -30380,6 +30558,7 @@ function registerProjectTools(server, ctx, client) {
30380
30558
  return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
30381
30559
  }
30382
30560
  }
30561
+ const account = normalizeCreateAccountId(accountId);
30383
30562
  try {
30384
30563
  const project = await client.createSystem({
30385
30564
  projectId: (0, import_crypto5.randomUUID)(),
@@ -30390,6 +30569,7 @@ function registerProjectTools(server, ctx, client) {
30390
30569
  strategy,
30391
30570
  status: "active",
30392
30571
  teamspaceId,
30572
+ ...account !== void 0 && { accountId: account },
30393
30573
  repoOwner,
30394
30574
  repoName,
30395
30575
  repoDefaultBranch
@@ -30400,6 +30580,7 @@ function registerProjectTools(server, ctx, client) {
30400
30580
  `**ID:** ${project.projectId}`,
30401
30581
  `**Title:** ${project.title}`,
30402
30582
  `**Status:** ${project.status}`,
30583
+ project.accountId ? `**Account:** ${project.accountId}` : "",
30403
30584
  project.description ? `**Description:** ${project.description}` : "",
30404
30585
  project.repoOwner ? `**Repo:** ${project.repoOwner}/${project.repoName}` : "",
30405
30586
  project.repoDefaultBranch ? `**Default Branch:** ${project.repoDefaultBranch}` : ""
@@ -30410,7 +30591,7 @@ function registerProjectTools(server, ctx, client) {
30410
30591
  return { content: [{ type: "text", text: `Failed to create system: ${message}` }], isError: true };
30411
30592
  }
30412
30593
  };
30413
- server.tool("create_system", "Create a new system in the configured organization", createSystemSchema, createSystemHandler);
30594
+ server.tool("create_system", "Create a new system in the configured organization. Pass accountId whenever the system belongs to a client \u2014 a system created without it is not listed under any Account.", createSystemSchema, createSystemHandler);
30414
30595
  }
30415
30596
 
30416
30597
  // ../../libs/harmonica-services/src/mcp/tools/teamspace-tools.ts
@@ -31828,7 +32009,7 @@ function createHttpClient(config2) {
31828
32009
  const result = await request(
31829
32010
  "POST",
31830
32011
  `/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-project`,
31831
- { projectId, targetOrgId }
32012
+ { systemId: projectId, targetOrgId }
31832
32013
  );
31833
32014
  const project = result?.project ? {
31834
32015
  ...result.project,
@@ -31863,7 +32044,10 @@ function createHttpClient(config2) {
31863
32044
  teamspaceId: input.teamspaceId,
31864
32045
  name: input.title,
31865
32046
  description: input.description,
31866
- strategy: input.strategy
32047
+ strategy: input.strategy,
32048
+ // Without this the owning Account is dropped on the wire and the System
32049
+ // is created unlinked — invisible in the Account's Systems list.
32050
+ ...input.accountId !== void 0 && { accountId: input.accountId }
31867
32051
  });
31868
32052
  return { ...raw, projectId: raw?.id ?? raw?.projectId, title: raw?.name ?? raw?.title, orgId: raw?.organizationId ?? raw?.orgId };
31869
32053
  },
@@ -31898,7 +32082,10 @@ function createHttpClient(config2) {
31898
32082
  teamspaceId: input.teamspaceId,
31899
32083
  name: input.title,
31900
32084
  description: input.description,
31901
- strategy: input.strategy
32085
+ strategy: input.strategy,
32086
+ // Without this the owning Account is dropped on the wire and the System
32087
+ // is created unlinked — invisible in the Account's Systems list.
32088
+ ...input.accountId !== void 0 && { accountId: input.accountId }
31902
32089
  });
31903
32090
  return { ...raw, orgId: raw?.organizationId ?? raw?.orgId };
31904
32091
  },
@@ -32930,6 +33117,15 @@ function createHttpClient(config2) {
32930
33117
  if (!result?.taskId) throw new Error("Draft coda enqueue failed: no taskId returned");
32931
33118
  return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
32932
33119
  },
33120
+ composeRootCoda: async (orgId) => {
33121
+ const result = await request(
33122
+ "POST",
33123
+ `/api/organizations/${encodeURIComponent(orgId)}/compose-root-coda`,
33124
+ {}
33125
+ );
33126
+ if (!result?.taskId) throw new Error("Compose root Coda enqueue failed: no taskId returned");
33127
+ return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", projectId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33128
+ },
32933
33129
  validateAssumptions: async (projectId, noteIds) => {
32934
33130
  const result = await request(
32935
33131
  "POST",
@@ -32997,7 +33193,17 @@ function createHttpClient(config2) {
32997
33193
  return download;
32998
33194
  },
32999
33195
  importSystemSnapshot: (snapshot, options) => request("POST", "/api/systems/import", { ...snapshot, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
33000
- importSystemSnapshotFromUrl: (url2, options) => request("POST", "/api/systems/import", { snapshotUrl: url2, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
33196
+ importSystemSnapshotFromUrl: async (url2, options) => {
33197
+ const enqueued = await request(
33198
+ "POST",
33199
+ "/api/systems/import",
33200
+ { snapshotUrl: url2, importOptions: options }
33201
+ );
33202
+ if (!enqueued?.taskId) {
33203
+ throw new Error("importSystemSnapshotFromUrl: API did not return a taskId");
33204
+ }
33205
+ return pollTaskResult(enqueued.taskId);
33206
+ },
33001
33207
  // Embedding similarity
33002
33208
  embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
33003
33209
  findSimilarNotes: async (noteId, projectId, options) => {
@@ -33393,6 +33599,18 @@ function createHttpClient(config2) {
33393
33599
  // Systems owned directly by an Account (B-278 v17). A 404 here means either
33394
33600
  // no such Account or the feature flag is off; both read as "nothing to show",
33395
33601
  // which is why request() returning undefined maps to an empty list.
33602
+ // Delegates to the endpoint that calls the same service the direct adapter
33603
+ // uses, so neither adapter carries its own copy of the fail-closed rule.
33604
+ // request() maps a 404 to undefined; that is itself unresolvable, so it
33605
+ // becomes an explicit unresolved result rather than a thrown error.
33606
+ resolveDeliveryPolicy: async (revisionId) => {
33607
+ const res = await request(
33608
+ "GET",
33609
+ `/api/revisions/${encodeURIComponent(revisionId)}/delivery-policy`
33610
+ );
33611
+ if (!res) return { resolved: false, reason: "revision_not_found" };
33612
+ return res;
33613
+ },
33396
33614
  listAccountSystems: async (accountId) => {
33397
33615
  const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/systems`);
33398
33616
  return res?.systems ?? [];
@@ -33828,7 +34046,7 @@ function loadConfig() {
33828
34046
  };
33829
34047
  }
33830
34048
  async function main() {
33831
- console.error(`[harmonica-mcp] v${"3.0.1"} starting\u2026`);
34049
+ console.error(`[harmonica-mcp] v${"3.2.0"} starting\u2026`);
33832
34050
  const config2 = loadConfig();
33833
34051
  const client = createHttpClient({
33834
34052
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {