@codazen/harmonica-mcp 4.1.0 → 4.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.
Files changed (2) hide show
  1. package/dist/index.js +36 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -26623,6 +26623,7 @@ var CHECK_TARGET_TYPES = [
26623
26623
  "revision",
26624
26624
  "beat_version",
26625
26625
  "project",
26626
+ "system",
26626
26627
  "drop",
26627
26628
  "organization"
26628
26629
  ];
@@ -26937,6 +26938,8 @@ var StartSessionRequestBodySchema = external_exports.object({
26937
26938
  systemId: external_exports.string().min(1),
26938
26939
  message: external_exports.string().min(1),
26939
26940
  revisionId: external_exports.string().min(1).optional(),
26941
+ /** Caller's own Session id — nests the new Session under it (B-231). */
26942
+ parentSessionId: external_exports.string().min(1).optional(),
26940
26943
  enableWebSearch: external_exports.boolean().optional()
26941
26944
  });
26942
26945
  var StartSessionResponseSchema = external_exports.object({
@@ -28168,9 +28171,9 @@ ${trimmed}` : `# ${title}`;
28168
28171
  }
28169
28172
 
28170
28173
  // ../../libs/harmonica-services/src/mcp/tools/notebook-tools.ts
28171
- var SCOPE_TYPE_VALUES = ["org", "teamspace", "project", "account"];
28174
+ var SCOPE_TYPE_VALUES = ["org", "teamspace", "system", "account", "project"];
28172
28175
  var scopeRefSchema = external_exports.object({
28173
- scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, project, or account"),
28176
+ scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
28174
28177
  scopeId: external_exports.string().min(1).describe("ID of the scope entity")
28175
28178
  });
28176
28179
  function formatNotebook(nb) {
@@ -28395,7 +28398,7 @@ ${formatTree(tree.root)}` }] };
28395
28398
  if (!scopeProjectId) {
28396
28399
  try {
28397
28400
  const bindings = await client.getNotebookScopeBindings(notebookId);
28398
- scopeProjectId = bindings?.find((b) => b.scopeType === "project")?.scopeId;
28401
+ scopeProjectId = bindings?.find((b) => b.scopeType === "system" || b.scopeType === "project")?.scopeId;
28399
28402
  } catch {
28400
28403
  }
28401
28404
  }
@@ -28561,7 +28564,7 @@ ${formatNotebook(updated)}` }] };
28561
28564
  );
28562
28565
  server.tool(
28563
28566
  "set_notebook_scopes",
28564
- "Replace all scope bindings for a Notebook. Pass an empty array to remove all bindings. Bindable scope types: org, teamspace, project. (projectContainer is reserved.)",
28567
+ "Replace all scope bindings for a Notebook. Pass an empty array to remove all bindings. Bindable scope types: org, teamspace, system. (systemContainer is reserved.)",
28565
28568
  {
28566
28569
  notebookId: external_exports.string().describe("The Notebook ID"),
28567
28570
  scopes: external_exports.array(scopeRefSchema).max(45).describe("New complete set of scope bindings (max 45)")
@@ -29983,9 +29986,10 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29983
29986
  revisionId: external_exports.string().describe("The Revision ID whose PR to review (e.g., rev-abc123)"),
29984
29987
  autoMerge: external_exports.boolean().optional().describe("Merge automatically once converged. Default false \u2192 stop at ready-for-review."),
29985
29988
  maxIterations: external_exports.number().int().min(1).max(3).optional().describe("Review rounds (default and hard cap: 3)."),
29986
- 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.")
29989
+ 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."),
29990
+ 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.")
29987
29991
  },
29988
- async ({ revisionId, autoMerge, maxIterations, confirm }) => {
29992
+ async ({ revisionId, autoMerge, maxIterations, confirm, requestingSessionId }) => {
29989
29993
  try {
29990
29994
  const revision = await client.getRevision(revisionId);
29991
29995
  if (!revision) {
@@ -29996,7 +30000,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29996
30000
  if ("refusal" in latitude) {
29997
30001
  return { content: [{ type: "text", text: latitude.refusal }], isError: true };
29998
30002
  }
29999
- const result = await client.requestCodeReview({ revisionId, autoMerge, maxIterations, confirm });
30003
+ const result = await client.requestCodeReview({ revisionId, autoMerge, maxIterations, confirm, requestingSessionId });
30000
30004
  if (!result.success) {
30001
30005
  return { content: [{ type: "text", text: `Failed to request code review (${result.error.code}): ${result.error.message}` }], isError: true };
30002
30006
  }
@@ -30758,13 +30762,17 @@ function assertSnapshotShape(val, sourceHint) {
30758
30762
  }
30759
30763
 
30760
30764
  // ../../libs/harmonica-services/src/mcp/tools/subscription-tools.ts
30761
- var ENTITY_TYPES = ["project", "beat", "revision"];
30765
+ var ENTITY_TYPES = ["system", "beat", "revision"];
30766
+ var entityTypeField = external_exports.preprocess(
30767
+ (v) => v === "project" ? "system" : v,
30768
+ external_exports.enum(ENTITY_TYPES)
30769
+ );
30762
30770
  function registerSubscriptionTools(server, ctx, client) {
30763
30771
  server.tool(
30764
30772
  "subscribe_to_entity",
30765
- "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.",
30773
+ "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.",
30766
30774
  {
30767
- entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
30775
+ entityType: entityTypeField.describe("Type of entity to follow"),
30768
30776
  entityId: external_exports.string().describe("The entity ID (system ID, beat ID, or revision ID)"),
30769
30777
  systemId: external_exports.string().describe("The system ID (for access control)")
30770
30778
  },
@@ -30786,9 +30794,9 @@ function registerSubscriptionTools(server, ctx, client) {
30786
30794
  );
30787
30795
  server.tool(
30788
30796
  "unsubscribe_from_entity",
30789
- "Stop following a Project, Beat, or Revision. Use durable=true to prevent auto-resubscribe if you are later re-assigned.",
30797
+ "Stop following a System, Beat, or Revision. Use durable=true to prevent auto-resubscribe if you are later re-assigned.",
30790
30798
  {
30791
- entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to unfollow"),
30799
+ entityType: entityTypeField.describe("Type of entity to unfollow"),
30792
30800
  entityId: external_exports.string().describe("The entity ID"),
30793
30801
  durable: external_exports.boolean().optional().default(false).describe("If true, prevents auto-resubscribe on reassignment")
30794
30802
  },
@@ -32310,16 +32318,20 @@ ${JSON.stringify(task.result, null, 2)}
32310
32318
  {
32311
32319
  systemId: external_exports.string().min(1).describe("The system ID \u2014 the trusted scope the session is rooted at"),
32312
32320
  message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
32313
- revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions.")
32321
+ revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions."),
32322
+ parentSessionId: external_exports.string().min(1).optional().describe("Your current Session ID (sess-<uuid>). Pass it when you are kicking off this session as part of your own work \u2014 the new session then nests under yours in the session tree and can report back to you. Omit for a standalone session.")
32314
32323
  },
32315
- async ({ systemId, message, revisionId }) => {
32324
+ async ({ systemId, message, revisionId, parentSessionId }) => {
32316
32325
  try {
32317
32326
  await assertSystemInOrg(client, systemId, ctx.orgId);
32318
32327
  const task = await client.startSession(
32319
32328
  systemId,
32320
32329
  message,
32321
32330
  { name: ctx.user.name, email: ctx.user.email },
32322
- revisionId?.trim() ? { revisionId: revisionId.trim() } : void 0
32331
+ revisionId?.trim() || parentSessionId?.trim() ? {
32332
+ ...revisionId?.trim() && { revisionId: revisionId.trim() },
32333
+ ...parentSessionId?.trim() && { parentSessionId: parentSessionId.trim() }
32334
+ } : void 0
32323
32335
  );
32324
32336
  return {
32325
32337
  content: [{
@@ -33911,10 +33923,17 @@ function createHttpClient(config2) {
33911
33923
  // Agent sessions
33912
33924
  startSession: async (systemId, message, triggeredBy, options) => {
33913
33925
  const trimmedRevisionId = options?.revisionId?.trim();
33926
+ const trimmedParentSessionId = options?.parentSessionId?.trim();
33914
33927
  const result = await request(
33915
33928
  "POST",
33916
33929
  `/api/sessions`,
33917
- { systemId, message, ...triggeredBy && { triggeredBy }, ...trimmedRevisionId && { revisionId: trimmedRevisionId } }
33930
+ {
33931
+ systemId,
33932
+ message,
33933
+ ...triggeredBy && { triggeredBy },
33934
+ ...trimmedRevisionId && { revisionId: trimmedRevisionId },
33935
+ ...trimmedParentSessionId && { parentSessionId: trimmedParentSessionId }
33936
+ }
33918
33937
  );
33919
33938
  if (!result?.taskId) throw new Error("Start session enqueue failed: no taskId returned");
33920
33939
  return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", systemId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -35024,7 +35043,7 @@ function loadConfig() {
35024
35043
  };
35025
35044
  }
35026
35045
  async function main() {
35027
- console.error(`[harmonica-mcp] v${"4.1.0"} starting\u2026`);
35046
+ console.error(`[harmonica-mcp] v${"4.3.0"} starting\u2026`);
35028
35047
  const config2 = loadConfig();
35029
35048
  const client = createHttpClient({
35030
35049
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {