@codazen/harmonica-mcp 4.0.2 → 4.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 +63 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -26329,6 +26329,15 @@ var normalizeSystemId = (raw) => {
26329
26329
  if (!id) return raw;
26330
26330
  return { ...b, systemId: id };
26331
26331
  };
26332
+ var normalizeSystemIdAndName = (raw) => {
26333
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26334
+ const b = raw;
26335
+ const result = { ...b };
26336
+ const id = b["systemId"] || b["projectId"];
26337
+ if (id) result["systemId"] = id;
26338
+ if ((b["systemName"] == null || b["systemName"] === "") && b["projectName"]) result["systemName"] = b["projectName"];
26339
+ return result;
26340
+ };
26332
26341
  var normalizeSystemIdAndTitle = (raw) => {
26333
26342
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26334
26343
  const b = raw;
@@ -26344,15 +26353,17 @@ var BeatApiSchema = external_exports.preprocess(
26344
26353
  if (typeof raw !== "object" || raw === null) return raw;
26345
26354
  const b = raw;
26346
26355
  const id = b["systemId"] || b["projectId"];
26347
- if (!id) return raw;
26348
- return { ...b, systemId: id };
26356
+ const result = { ...b };
26357
+ if (id) result["systemId"] = id;
26358
+ if ((b["systemName"] == null || b["systemName"] === "") && b["projectName"]) result["systemName"] = b["projectName"];
26359
+ return result;
26349
26360
  },
26350
26361
  external_exports.object({
26351
26362
  beatId: external_exports.string(),
26352
26363
  systemId: external_exports.string(),
26353
- /** Source project's title; attached by the flat /api/beats endpoint for
26354
- * multi-project (teamspace/org) responses. */
26355
- projectName: external_exports.string().nullish(),
26364
+ /** Source system's title; attached by the flat /api/beats endpoint for
26365
+ * multi-system (teamspace/org) responses. */
26366
+ systemName: external_exports.string().nullish(),
26356
26367
  title: external_exports.string(),
26357
26368
  version: external_exports.number().nullish(),
26358
26369
  description: external_exports.string().nullish(),
@@ -26612,6 +26623,7 @@ var CHECK_TARGET_TYPES = [
26612
26623
  "revision",
26613
26624
  "beat_version",
26614
26625
  "project",
26626
+ "system",
26615
26627
  "drop",
26616
26628
  "organization"
26617
26629
  ];
@@ -26818,13 +26830,13 @@ var NextActionSchema = external_exports.object({
26818
26830
  var NextActionsResponseSchema = external_exports.object({
26819
26831
  actions: external_exports.array(NextActionSchema)
26820
26832
  }).passthrough();
26821
- var SearchResultSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26833
+ var SearchResultSchema = external_exports.preprocess(normalizeSystemIdAndName, external_exports.object({
26822
26834
  entityId: external_exports.string(),
26823
26835
  entityType: external_exports.enum(["beat", "note", "revision", "beatVersion", "drop", "project", "system"]),
26824
26836
  score: external_exports.number(),
26825
26837
  snippet: external_exports.string(),
26826
26838
  systemId: external_exports.string().optional(),
26827
- projectName: external_exports.string().optional(),
26839
+ systemName: external_exports.string().optional(),
26828
26840
  teamspaceId: external_exports.string().optional()
26829
26841
  }).passthrough());
26830
26842
  var SearchScopeSchema = external_exports.object({
@@ -27816,7 +27828,7 @@ function registerNoteTools(server, ctx, client) {
27816
27828
  const noteId = await client.getNextNoteId(projectCode, systemId);
27817
27829
  note = await client.createNote({
27818
27830
  noteId,
27819
- projectId: systemId,
27831
+ systemId,
27820
27832
  beatId,
27821
27833
  revisionId,
27822
27834
  beatVersionId,
@@ -28157,9 +28169,9 @@ ${trimmed}` : `# ${title}`;
28157
28169
  }
28158
28170
 
28159
28171
  // ../../libs/harmonica-services/src/mcp/tools/notebook-tools.ts
28160
- var SCOPE_TYPE_VALUES = ["org", "teamspace", "project", "account"];
28172
+ var SCOPE_TYPE_VALUES = ["org", "teamspace", "system", "account", "project"];
28161
28173
  var scopeRefSchema = external_exports.object({
28162
- scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, project, or account"),
28174
+ scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
28163
28175
  scopeId: external_exports.string().min(1).describe("ID of the scope entity")
28164
28176
  });
28165
28177
  function formatNotebook(nb) {
@@ -28384,7 +28396,7 @@ ${formatTree(tree.root)}` }] };
28384
28396
  if (!scopeProjectId) {
28385
28397
  try {
28386
28398
  const bindings = await client.getNotebookScopeBindings(notebookId);
28387
- scopeProjectId = bindings?.find((b) => b.scopeType === "project")?.scopeId;
28399
+ scopeProjectId = bindings?.find((b) => b.scopeType === "system" || b.scopeType === "project")?.scopeId;
28388
28400
  } catch {
28389
28401
  }
28390
28402
  }
@@ -28550,7 +28562,7 @@ ${formatNotebook(updated)}` }] };
28550
28562
  );
28551
28563
  server.tool(
28552
28564
  "set_notebook_scopes",
28553
- "Replace all scope bindings for a Notebook. Pass an empty array to remove all bindings. Bindable scope types: org, teamspace, project. (projectContainer is reserved.)",
28565
+ "Replace all scope bindings for a Notebook. Pass an empty array to remove all bindings. Bindable scope types: org, teamspace, system. (systemContainer is reserved.)",
28554
28566
  {
28555
28567
  notebookId: external_exports.string().describe("The Notebook ID"),
28556
28568
  scopes: external_exports.array(scopeRefSchema).max(45).describe("New complete set of scope bindings (max 45)")
@@ -28915,7 +28927,7 @@ Systems (${createdProjects.length}):`);
28915
28927
  const n = await client.createNote({
28916
28928
  noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
28917
28929
  // overridden server-side
28918
- projectId: project.systemId,
28930
+ systemId: project.systemId,
28919
28931
  noteType: note.noteType,
28920
28932
  content: note.content,
28921
28933
  rationale: note.rationale,
@@ -29575,6 +29587,7 @@ function registerPulseReportTools(server, _ctx, client) {
29575
29587
  }
29576
29588
 
29577
29589
  // ../../libs/harmonica-services/src/delivery-policy.decide.ts
29590
+ var DELIVERY_STEPS = ["openPullRequest", "requestAgentReview", "watchPullRequest"];
29578
29591
  function decideDeliveryStep(resolution, step) {
29579
29592
  if (!resolution.resolved) return "ask";
29580
29593
  const value = resolution.policy[step];
@@ -29582,6 +29595,19 @@ function decideDeliveryStep(resolution, step) {
29582
29595
  if (value === false) return "prohibited";
29583
29596
  return "ask";
29584
29597
  }
29598
+ function mergeAgentDeliveryPolicy(current, patch) {
29599
+ const merged = { ...current };
29600
+ for (const step of DELIVERY_STEPS) {
29601
+ if (!(step in patch)) continue;
29602
+ const value = patch[step];
29603
+ if (value === null) {
29604
+ delete merged[step];
29605
+ } else if (value === true || value === false) {
29606
+ merged[step] = value;
29607
+ }
29608
+ }
29609
+ return merged;
29610
+ }
29585
29611
 
29586
29612
  // ../../libs/harmonica-services/src/mcp/tools/revision-lifecycle-tools.ts
29587
29613
  async function checkDeliveryStep(client, revisionId, step) {
@@ -29958,9 +29984,10 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29958
29984
  revisionId: external_exports.string().describe("The Revision ID whose PR to review (e.g., rev-abc123)"),
29959
29985
  autoMerge: external_exports.boolean().optional().describe("Merge automatically once converged. Default false \u2192 stop at ready-for-review."),
29960
29986
  maxIterations: external_exports.number().int().min(1).max(3).optional().describe("Review rounds (default and hard cap: 3)."),
29961
- confirm: external_exports.boolean().optional().describe("Set true to start the review. Omitted/false performs a dry-run \u2014 no session, scan, or task is created.")
29987
+ confirm: external_exports.boolean().optional().describe("Set true to start the review. Omitted/false performs a dry-run \u2014 no session, scan, or task is created."),
29988
+ requestingSessionId: external_exports.string().min(1).optional().describe("Your current Session ID (sess-<uuid>). Pass it so the review Session nests under yours and reports its outcome back to you when it finishes \u2014 otherwise you have no way to learn how the review turned out.")
29962
29989
  },
29963
- async ({ revisionId, autoMerge, maxIterations, confirm }) => {
29990
+ async ({ revisionId, autoMerge, maxIterations, confirm, requestingSessionId }) => {
29964
29991
  try {
29965
29992
  const revision = await client.getRevision(revisionId);
29966
29993
  if (!revision) {
@@ -29971,7 +29998,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29971
29998
  if ("refusal" in latitude) {
29972
29999
  return { content: [{ type: "text", text: latitude.refusal }], isError: true };
29973
30000
  }
29974
- const result = await client.requestCodeReview({ revisionId, autoMerge, maxIterations, confirm });
30001
+ const result = await client.requestCodeReview({ revisionId, autoMerge, maxIterations, confirm, requestingSessionId });
29975
30002
  if (!result.success) {
29976
30003
  return { content: [{ type: "text", text: `Failed to request code review (${result.error.code}): ${result.error.message}` }], isError: true };
29977
30004
  }
@@ -30685,7 +30712,7 @@ function formatImportSummary(result, targetTeamspaceId) {
30685
30712
  lines.push("", `Errors (${result.errors.length}):`);
30686
30713
  result.errors.forEach((e) => lines.push(` - ${e}`));
30687
30714
  }
30688
- const hasTeamspaceData = result.projectTeamspaceId !== void 0 || (result.counts.drops ?? 0) + (result.counts.deliverables ?? 0) > 0;
30715
+ const hasTeamspaceData = result.systemTeamspaceId !== void 0 || (result.counts.drops ?? 0) + (result.counts.deliverables ?? 0) > 0;
30689
30716
  if (!targetTeamspaceId && hasTeamspaceData) {
30690
30717
  lines.push(
30691
30718
  "",
@@ -30733,13 +30760,17 @@ function assertSnapshotShape(val, sourceHint) {
30733
30760
  }
30734
30761
 
30735
30762
  // ../../libs/harmonica-services/src/mcp/tools/subscription-tools.ts
30736
- var ENTITY_TYPES = ["project", "beat", "revision"];
30763
+ var ENTITY_TYPES = ["system", "beat", "revision"];
30764
+ var entityTypeField = external_exports.preprocess(
30765
+ (v) => v === "project" ? "system" : v,
30766
+ external_exports.enum(ENTITY_TYPES)
30767
+ );
30737
30768
  function registerSubscriptionTools(server, ctx, client) {
30738
30769
  server.tool(
30739
30770
  "subscribe_to_entity",
30740
- "Follow a Project, Beat, or Revision to receive notifications when it changes. You are auto-subscribed to things you own \u2014 use this to follow additional entities.",
30771
+ "Follow a System, Beat, or Revision to receive notifications when it changes. You are auto-subscribed to things you own \u2014 use this to follow additional entities.",
30741
30772
  {
30742
- entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
30773
+ entityType: entityTypeField.describe("Type of entity to follow"),
30743
30774
  entityId: external_exports.string().describe("The entity ID (system ID, beat ID, or revision ID)"),
30744
30775
  systemId: external_exports.string().describe("The system ID (for access control)")
30745
30776
  },
@@ -30761,9 +30792,9 @@ function registerSubscriptionTools(server, ctx, client) {
30761
30792
  );
30762
30793
  server.tool(
30763
30794
  "unsubscribe_from_entity",
30764
- "Stop following a Project, Beat, or Revision. Use durable=true to prevent auto-resubscribe if you are later re-assigned.",
30795
+ "Stop following a System, Beat, or Revision. Use durable=true to prevent auto-resubscribe if you are later re-assigned.",
30765
30796
  {
30766
- entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to unfollow"),
30797
+ entityType: entityTypeField.describe("Type of entity to unfollow"),
30767
30798
  entityId: external_exports.string().describe("The entity ID"),
30768
30799
  durable: external_exports.boolean().optional().default(false).describe("If true, prevents auto-resubscribe on reassignment")
30769
30800
  },
@@ -30959,8 +30990,14 @@ function registerSystemTools(server, ctx, client) {
30959
30990
  if (Object.keys(nonEmpty).length === 0) {
30960
30991
  return { content: [{ type: "text", text: "No updates provided." }], isError: true };
30961
30992
  }
30962
- await assertSystemInOrg(client, systemId, ctx.orgId);
30993
+ const currentSystem = await fetchSystemInOrg(client, systemId, ctx.orgId);
30963
30994
  if (nonEmpty["accountId"] === "") delete nonEmpty["accountId"];
30995
+ if (nonEmpty["agentDeliveryPolicy"] && typeof nonEmpty["agentDeliveryPolicy"] === "object") {
30996
+ nonEmpty["agentDeliveryPolicy"] = mergeAgentDeliveryPolicy(
30997
+ currentSystem.agentDeliveryPolicy,
30998
+ nonEmpty["agentDeliveryPolicy"]
30999
+ );
31000
+ }
30964
31001
  if (typeof updates.teamspaceId === "string") {
30965
31002
  const teamspace = await client.getTeamspace(updates.teamspaceId);
30966
31003
  if (!teamspace) {
@@ -33160,9 +33197,8 @@ function createHttpClient(config2) {
33160
33197
  return result?.note ?? result;
33161
33198
  },
33162
33199
  createNote: async (input) => {
33163
- const inputRec = input;
33164
- const effectiveSystemId = inputRec.systemId ?? input.projectId;
33165
- if (!effectiveSystemId) throw new Error("createNote: systemId (or legacy projectId) is required");
33200
+ const effectiveSystemId = input.systemId;
33201
+ if (!effectiveSystemId) throw new Error("createNote: systemId is required");
33166
33202
  const result = await request("POST", `/api/systems/${encodeURIComponent(effectiveSystemId)}/notes`, input);
33167
33203
  return result?.note ?? result;
33168
33204
  },
@@ -34994,7 +35030,7 @@ function loadConfig() {
34994
35030
  };
34995
35031
  }
34996
35032
  async function main() {
34997
- console.error(`[harmonica-mcp] v${"4.0.2"} starting\u2026`);
35033
+ console.error(`[harmonica-mcp] v${"4.2.0"} starting\u2026`);
34998
35034
  const config2 = loadConfig();
34999
35035
  const client = createHttpClient({
35000
35036
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "4.0.2",
3
+ "version": "4.2.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {