@codazen/harmonica-mcp 4.3.1 → 4.4.1

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 +667 -646
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21514,44 +21514,22 @@ if (typeof global.crypto === "undefined") {
21514
21514
  var outgoingEnded = Symbol("outgoingEnded");
21515
21515
 
21516
21516
  // ../../libs/harmonica-services/src/mcp/auth.ts
21517
- async function resolveOrgSlug(client, slug) {
21518
- const orgs = await client.listAllOrgs();
21519
- const match = orgs.find((o) => o.slug === slug);
21520
- if (!match) {
21521
- throw new Error(
21522
- `Organization with slug "${slug}" not found. Check HARMONICA_ORG_SLUG in your .mcp.json env config.`
21523
- );
21524
- }
21525
- return match.orgId;
21526
- }
21527
- async function validateAuth(client, orgIdentifier, isOrgSlug, userId) {
21528
- const orgId = isOrgSlug ? await resolveOrgSlug(client, orgIdentifier) : orgIdentifier;
21529
- const user = await client.getUser(userId);
21517
+ async function validateAuth(client) {
21518
+ const user = await client.getCurrentUser();
21530
21519
  if (!user) {
21531
21520
  throw new Error(
21532
- `User not found: "${userId}". HARMONICA_USER_ID should be your email address (e.g. you@company.com).`
21521
+ `Could not resolve an identity for this credential. Check that HARMONICA_API_KEY is a valid Harmonica API key and that the user it authenticates as exists (HARMONICA_USER_ID is your email address).`
21533
21522
  );
21534
21523
  }
21535
- const membership = await client.getMembership(userId, orgId);
21536
- if (!membership) {
21537
- throw new Error(
21538
- `User "${userId}" is not a member of org "${orgId}". Log into Harmonica first to create your membership, or check HARMONICA_ORG_ID / HARMONICA_ORG_SLUG in your .mcp.json.`
21539
- );
21540
- }
21541
- return { user, orgId, membership };
21524
+ return { user };
21542
21525
  }
21543
21526
 
21544
- // ../../libs/harmonica-services/src/mcp/org-guard.ts
21545
- async function assertSystemInOrg(client, systemId, orgId) {
21527
+ // ../../libs/harmonica-services/src/mcp/entity-guard.ts
21528
+ async function assertSystemExists(client, systemId) {
21546
21529
  const system = await client.getSystem(systemId);
21547
21530
  if (!system) {
21548
21531
  throw new Error(`System not found: "${systemId}"`);
21549
21532
  }
21550
- if (system.orgId !== orgId) {
21551
- throw new Error(
21552
- `System "${systemId}" does not belong to the configured organization. Access denied.`
21553
- );
21554
- }
21555
21533
  }
21556
21534
  async function assertRevisionInSystem(client, revisionId, systemId) {
21557
21535
  const revision = await client.getRevision(revisionId);
@@ -21564,96 +21542,60 @@ async function assertRevisionInSystem(client, revisionId, systemId) {
21564
21542
  );
21565
21543
  }
21566
21544
  }
21567
- async function assertBeatInOrg(client, beatId, orgId) {
21545
+ async function assertBeatExists(client, beatId) {
21568
21546
  const beat = await client.getBeat(beatId);
21569
21547
  if (!beat) {
21570
21548
  throw new Error(`Beat not found: "${beatId}"`);
21571
21549
  }
21572
- if (beat.orgId && beat.orgId !== orgId) {
21573
- throw new Error(
21574
- `Beat "${beatId}" does not belong to the configured organization. Access denied.`
21575
- );
21576
- }
21577
21550
  }
21578
- async function fetchSystemInOrg(client, systemId, orgId) {
21551
+ async function fetchSystem(client, systemId) {
21579
21552
  const system = await client.getSystem(systemId);
21580
21553
  if (!system) {
21581
21554
  throw new Error(`System not found: "${systemId}"`);
21582
21555
  }
21583
- if (system.orgId !== orgId) {
21584
- throw new Error(
21585
- `System "${systemId}" does not belong to the configured organization. Access denied.`
21586
- );
21587
- }
21588
21556
  return system;
21589
21557
  }
21590
- async function fetchBeatInOrg(client, beatId, orgId) {
21558
+ async function fetchBeat(client, beatId) {
21591
21559
  const beat = await client.getBeat(beatId);
21592
21560
  if (!beat) {
21593
21561
  throw new Error(`Beat not found: "${beatId}"`);
21594
21562
  }
21595
- if (beat.orgId && beat.orgId !== orgId) {
21596
- throw new Error(
21597
- `Beat "${beatId}" does not belong to the configured organization. Access denied.`
21598
- );
21599
- }
21600
21563
  return beat;
21601
21564
  }
21602
- async function assertTeamspaceInOrg(client, teamspaceId, orgId) {
21565
+ async function assertTeamspaceExists(client, teamspaceId) {
21566
+ await fetchTeamspace(client, teamspaceId);
21567
+ }
21568
+ async function fetchTeamspace(client, teamspaceId) {
21603
21569
  const teamspace = await client.getTeamspace(teamspaceId);
21604
21570
  if (!teamspace) {
21605
21571
  throw new Error(`Teamspace not found: "${teamspaceId}"`);
21606
21572
  }
21607
- if (teamspace.orgId !== orgId) {
21608
- throw new Error(
21609
- `Teamspace "${teamspaceId}" does not belong to the configured organization. Access denied.`
21610
- );
21611
- }
21573
+ return teamspace;
21612
21574
  }
21613
- async function assertAccountInOrg(client, accountId, orgId) {
21575
+ async function assertAccountExists(client, accountId) {
21614
21576
  const account = await client.getAccount(accountId);
21615
21577
  if (!account) {
21616
21578
  throw new Error(`Account not found: "${accountId}"`);
21617
21579
  }
21618
- if (account.orgId !== orgId) {
21619
- throw new Error(
21620
- `Account "${accountId}" does not belong to the configured organization. Access denied.`
21621
- );
21622
- }
21623
21580
  }
21624
- async function assertMovementInOrg(client, movementId, orgId) {
21581
+ async function assertMovementExists(client, movementId) {
21625
21582
  const movement = await client.getMovement(movementId);
21626
21583
  if (!movement) {
21627
21584
  throw new Error(`Movement not found: "${movementId}"`);
21628
21585
  }
21629
- if (movement.orgId !== orgId) {
21630
- throw new Error(
21631
- `Movement "${movementId}" does not belong to the configured organization. Access denied.`
21632
- );
21633
- }
21634
21586
  }
21635
- async function assertDeliverableGroupInOrg(client, deliverableGroupId, orgId) {
21587
+ async function fetchDeliverableGroup(client, deliverableGroupId) {
21636
21588
  const group = await client.getDeliverableGroup(deliverableGroupId);
21637
21589
  if (!group) {
21638
21590
  throw new Error(`DeliverableGroup not found: "${deliverableGroupId}"`);
21639
21591
  }
21640
- if (group.orgId !== orgId) {
21641
- throw new Error(
21642
- `DeliverableGroup "${deliverableGroupId}" does not belong to the configured organization. Access denied.`
21643
- );
21644
- }
21645
21592
  return group;
21646
21593
  }
21647
- async function fetchNotebookInOrg(client, notebookId, orgId) {
21594
+ async function fetchNotebook(client, notebookId) {
21648
21595
  const notebook = await client.getNotebook(notebookId);
21649
21596
  if (!notebook) {
21650
21597
  throw new Error(`Notebook not found: "${notebookId}"`);
21651
21598
  }
21652
- if (notebook.orgId !== orgId) {
21653
- throw new Error(
21654
- `Notebook "${notebookId}" does not belong to the configured organization. Access denied.`
21655
- );
21656
- }
21657
21599
  return notebook;
21658
21600
  }
21659
21601
 
@@ -21678,9 +21620,6 @@ function registerAccountTools(server, ctx, client) {
21678
21620
  orgId: external_exports.string().describe("The organization ID")
21679
21621
  },
21680
21622
  async ({ orgId }) => {
21681
- if (orgId !== ctx.orgId) {
21682
- return { content: [{ type: "text", text: `Org not found: ${orgId}` }] };
21683
- }
21684
21623
  const accounts = await client.listOrgAccounts(orgId);
21685
21624
  const text = accounts.length === 0 ? "No accounts found." : accounts.map(
21686
21625
  (a) => `[${a.accountId}] ${a.accountCode ?? "\u2014"} ${a.name} (${a.accountType ?? "client"}, ${a.status})`
@@ -21696,7 +21635,7 @@ function registerAccountTools(server, ctx, client) {
21696
21635
  },
21697
21636
  async ({ accountId }) => {
21698
21637
  const a = await client.getAccount(accountId);
21699
- if (!a || a.orgId !== ctx.orgId) {
21638
+ if (!a) {
21700
21639
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21701
21640
  }
21702
21641
  const lines = [
@@ -21717,8 +21656,9 @@ function registerAccountTools(server, ctx, client) {
21717
21656
  );
21718
21657
  server.tool(
21719
21658
  "create_account",
21720
- `Create an Account in the configured Organization. An Account is an org unit that owns Movements \u2014 either a paying client OR the agency itself (accountType). It is NOT a Beat. Pass parentAccountId to nest it under another Account, which may itself be a sub-account \u2014 up to ${MAX_ACCOUNT_DEPTH} levels deep, e.g. Meta \u2192 Reality Labs \u2192 Lifecycle CRM, where the middle level is organisational grouping rather than a separately billed entity. Recognition cues: "new client account", "add a paying client", "create account for Acme Corp", "add a department under this client" \u2192 create_account.`,
21659
+ `Create an Account in an Organization. An Account is an org unit that owns Movements \u2014 either a paying client OR the agency itself (accountType). It is NOT a Beat. Pass parentAccountId to nest it under another Account, which may itself be a sub-account \u2014 up to ${MAX_ACCOUNT_DEPTH} levels deep, e.g. Meta \u2192 Reality Labs \u2192 Lifecycle CRM, where the middle level is organisational grouping rather than a separately billed entity. Recognition cues: "new client account", "add a paying client", "create account for Acme Corp", "add a department under this client" \u2192 create_account.`,
21721
21660
  {
21661
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
21722
21662
  name: external_exports.string().min(1).max(300).describe('Account name (e.g. "Acme Corporation")'),
21723
21663
  accountType: external_exports.enum(["internal", "client"]).describe(
21724
21664
  "'client' for a paying client, 'internal' for one of the agency's own org units. An org may have several internal Accounts, and an internal Account may be a sub-account of another internal Account \u2014 but never of a client Account."
@@ -21731,10 +21671,10 @@ function registerAccountTools(server, ctx, client) {
21731
21671
  `Nests this Account under the given parent, which may itself be a sub-account. Rejected if the result would exceed ${MAX_ACCOUNT_DEPTH} levels.`
21732
21672
  )
21733
21673
  },
21734
- async ({ name, accountType, description, accountCode, parentAccountId }) => {
21674
+ async ({ orgId, name, accountType, description, accountCode, parentAccountId }) => {
21735
21675
  try {
21736
21676
  const a = await client.createAccount({
21737
- orgId: ctx.orgId,
21677
+ orgId,
21738
21678
  name,
21739
21679
  accountType,
21740
21680
  ...description !== void 0 && { description },
@@ -21773,7 +21713,7 @@ function registerAccountTools(server, ctx, client) {
21773
21713
  },
21774
21714
  async ({ accountId, name, description, accountType, parentAccountId, cadenceConfig }) => {
21775
21715
  const existing = await client.getAccount(accountId);
21776
- if (!existing || existing.orgId !== ctx.orgId) {
21716
+ if (!existing) {
21777
21717
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21778
21718
  }
21779
21719
  try {
@@ -21809,7 +21749,7 @@ function registerAccountTools(server, ctx, client) {
21809
21749
  },
21810
21750
  async ({ accountId }) => {
21811
21751
  const existing = await client.getAccount(accountId);
21812
- if (!existing || existing.orgId !== ctx.orgId) {
21752
+ if (!existing) {
21813
21753
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21814
21754
  }
21815
21755
  const children = await client.listChildAccounts(accountId);
@@ -21832,7 +21772,7 @@ function registerAccountTools(server, ctx, client) {
21832
21772
  // published thin client, so gating them costs the tool bundle nothing.
21833
21773
  async ({ accountId }) => {
21834
21774
  const existing = await client.getAccount(accountId);
21835
- if (!existing || existing.orgId !== ctx.orgId) {
21775
+ if (!existing) {
21836
21776
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21837
21777
  }
21838
21778
  const systems = await client.listAccountSystems(accountId);
@@ -21850,7 +21790,7 @@ function registerAccountTools(server, ctx, client) {
21850
21790
  // no isEnabled() call here to keep @cdz/feature-flags out of the tool bundle.
21851
21791
  async ({ accountId }) => {
21852
21792
  const existing = await client.getAccount(accountId);
21853
- if (!existing || existing.orgId !== ctx.orgId) {
21793
+ if (!existing) {
21854
21794
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21855
21795
  }
21856
21796
  const documents = await client.listAccountDocuments(accountId);
@@ -21866,7 +21806,7 @@ function registerAccountTools(server, ctx, client) {
21866
21806
  },
21867
21807
  async ({ accountId }) => {
21868
21808
  const existing = await client.getAccount(accountId);
21869
- if (!existing || existing.orgId !== ctx.orgId) {
21809
+ if (!existing) {
21870
21810
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21871
21811
  }
21872
21812
  const movements = await client.listAccountMovements(accountId);
@@ -21930,7 +21870,7 @@ function registerAccountTools(server, ctx, client) {
21930
21870
  },
21931
21871
  async ({ accountId, status }) => {
21932
21872
  const existing = await client.getAccount(accountId);
21933
- if (!existing || existing.orgId !== ctx.orgId) {
21873
+ if (!existing) {
21934
21874
  return { content: [{ type: "text", text: `Account not found: ${accountId}` }] };
21935
21875
  }
21936
21876
  const a = await client.setAccountStatus(accountId, status);
@@ -21967,7 +21907,7 @@ function registerActivityTools(server, ctx, client) {
21967
21907
  importance: external_exports.enum(IMPORTANCE_VALUES).optional().describe("Filter by minimum importance level")
21968
21908
  },
21969
21909
  async ({ systemId, limit, importance }) => {
21970
- await assertSystemInOrg(client, systemId, ctx.orgId);
21910
+ await assertSystemExists(client, systemId);
21971
21911
  const { entries, hasMore } = await client.listSystemActivities(systemId, {
21972
21912
  limit: limit ?? 30,
21973
21913
  minImportance: importance
@@ -21999,7 +21939,7 @@ function registerAnalysisTools(server, ctx, client) {
21999
21939
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum cluster size. Default 3")
22000
21940
  },
22001
21941
  async ({ systemId, entityType, threshold, minClusterSize }) => {
22002
- await assertSystemInOrg(client, systemId, ctx.orgId);
21942
+ await assertSystemExists(client, systemId);
22003
21943
  const result = await client.clusterEntities(systemId, entityType, { threshold, minClusterSize });
22004
21944
  if (result.clusters.length === 0) {
22005
21945
  return {
@@ -22031,7 +21971,7 @@ Analyzed: ${result.totalAnalyzed} | Clusters: ${result.clusters.length} | Noise:
22031
21971
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum cluster size. Default 2")
22032
21972
  },
22033
21973
  async ({ systemId, analysisType, entityType, threshold, minClusterSize }) => {
22034
- await assertSystemInOrg(client, systemId, ctx.orgId);
21974
+ await assertSystemExists(client, systemId);
22035
21975
  const result = await client.runClusteredAnalysis(systemId, analysisType, {
22036
21976
  entityType,
22037
21977
  threshold,
@@ -22066,7 +22006,7 @@ function registerAuditIntegrityTools(server, ctx, client) {
22066
22006
  },
22067
22007
  async ({ systemId, limit }) => {
22068
22008
  try {
22069
- await assertSystemInOrg(client, systemId, ctx.orgId);
22009
+ await assertSystemExists(client, systemId);
22070
22010
  const report = await client.verifyAuditIntegrity(systemId, { limit });
22071
22011
  const lines = [
22072
22012
  `## Audit Integrity Report`,
@@ -22195,7 +22135,7 @@ function registerBaselineTools(server, ctx, client) {
22195
22135
  },
22196
22136
  async ({ systemId, beatIds, runChecks, localPath }) => {
22197
22137
  try {
22198
- await assertSystemInOrg(client, systemId, ctx.orgId);
22138
+ await assertSystemExists(client, systemId);
22199
22139
  const task = await client.baselineSystem(systemId, { beatIds, runChecks, localPath });
22200
22140
  return {
22201
22141
  content: [{
@@ -22225,7 +22165,7 @@ function registerBeatComposerTools(server, ctx, client) {
22225
22165
  },
22226
22166
  async ({ survivingBeatId, mergedFromBeatIds, rationale }) => {
22227
22167
  try {
22228
- await assertBeatInOrg(client, survivingBeatId, ctx.orgId);
22168
+ await assertBeatExists(client, survivingBeatId);
22229
22169
  await client.recordMergeEvent(
22230
22170
  survivingBeatId,
22231
22171
  mergedFromBeatIds,
@@ -22253,7 +22193,7 @@ Rationale: ${rationale}`;
22253
22193
  },
22254
22194
  async ({ originalBeatId, splitIntoBeatIds, rationale }) => {
22255
22195
  try {
22256
- await assertBeatInOrg(client, originalBeatId, ctx.orgId);
22196
+ await assertBeatExists(client, originalBeatId);
22257
22197
  await client.recordSplitEvent(
22258
22198
  originalBeatId,
22259
22199
  splitIntoBeatIds,
@@ -22284,7 +22224,7 @@ Rationale: ${rationale}`;
22284
22224
  },
22285
22225
  async ({ systemId, items, reason }) => {
22286
22226
  try {
22287
- await assertSystemInOrg(client, systemId, ctx.orgId);
22227
+ await assertSystemExists(client, systemId);
22288
22228
  const result = await client.bulkReassignRevisions(items, reason, ctx.user.userId);
22289
22229
  const lines = [
22290
22230
  `# Bulk reassignment complete: ${result.reassigned.length} succeeded, ${result.failed.length} failed`,
@@ -22323,7 +22263,7 @@ Rationale: ${rationale}`;
22323
22263
  },
22324
22264
  async ({ systemId, revisionId, toBeatId, reason }) => {
22325
22265
  try {
22326
- await assertSystemInOrg(client, systemId, ctx.orgId);
22266
+ await assertSystemExists(client, systemId);
22327
22267
  const result = await client.reassignRevision(revisionId, toBeatId, reason, ctx.user.userId);
22328
22268
  const text = [
22329
22269
  `Revision reassigned successfully.`,
@@ -22361,7 +22301,7 @@ function registerBeatConvergenceTools(server, ctx, client) {
22361
22301
  "Read-only convergence-vs-churn report for a Beat \u2014 is it converging on its Coda or churning? Returns net Coda coverage, gross delivered, churn ratio, diminishing-delta trend, confirmed-\u0394f coverage, and an overall signal, computed from the Beat\u2019s confirmed Value Velocity inputs. No writes.",
22362
22302
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
22363
22303
  async ({ beatId }) => {
22364
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
22304
+ const beat = await fetchBeat(client, beatId);
22365
22305
  const report = await client.getBeatConvergence(beat.systemId, beatId);
22366
22306
  if (!report) {
22367
22307
  return { content: [{ type: "text", text: `No convergence data for beat "${beatId}".` }], isError: true };
@@ -22389,7 +22329,7 @@ function registerBeatLifecycleTools(server, ctx, client) {
22389
22329
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
22390
22330
  }
22391
22331
  const currentStatus = beat.beatStatus ?? "open";
22392
- await assertBeatInOrg(client, beatId, ctx.orgId);
22332
+ await assertBeatExists(client, beatId);
22393
22333
  const result = await client.updateBeatStatus(beatId, targetStatus, {
22394
22334
  actor: { type: "human", id: ctx.user.userId, name: ctx.user.userId },
22395
22335
  reason
@@ -22437,7 +22377,7 @@ function registerBeatPlanningTools(server, ctx, client) {
22437
22377
  if (!revision) {
22438
22378
  return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
22439
22379
  }
22440
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
22380
+ await assertBeatExists(client, revision.beatId);
22441
22381
  const messages = [
22442
22382
  ...conversationHistory ?? [],
22443
22383
  { role: "user", content: message }
@@ -22498,7 +22438,7 @@ async function pollForCheck(client, taskId) {
22498
22438
  consecutiveErrors = 0;
22499
22439
  if (!task) throw new Error(`Task ${taskId} not found`);
22500
22440
  if (task.status === "completed") {
22501
- if (!task.result) throw new Error(`Task ${taskId} completed with no result`);
22441
+ if (!task.result) throw new Error(`async:completed:no-result Task ${taskId} succeeded on the async path but the scorecard is not yet readable on the blocking path \u2014 use list_checks or get_job_status to retrieve it.`);
22502
22442
  return task.result;
22503
22443
  }
22504
22444
  if (task.status === "failed") throw new Error(`Quality check failed: ${task.error ?? "unknown error"}`);
@@ -22530,7 +22470,7 @@ function registerBeatQualityTools(server, ctx, client) {
22530
22470
  };
22531
22471
  const handler = async ({ beatId, systemId, wait }) => {
22532
22472
  try {
22533
- await assertBeatInOrg(client, beatId, ctx.orgId);
22473
+ await assertBeatExists(client, beatId);
22534
22474
  const { taskId } = await client.checkBeatQuality(systemId, beatId);
22535
22475
  if (!wait) {
22536
22476
  return {
@@ -22580,7 +22520,7 @@ function registerBeatReframeTools(server, ctx, client) {
22580
22520
  },
22581
22521
  async ({ beatId, systemId }) => {
22582
22522
  try {
22583
- await assertBeatInOrg(client, beatId, ctx.orgId);
22523
+ await assertBeatExists(client, beatId);
22584
22524
  const result = await client.reframeBeat(systemId, beatId);
22585
22525
  const text = formatReframeResult(beatId, result);
22586
22526
  return { content: [{ type: "text", text }] };
@@ -22673,7 +22613,6 @@ var DEFAULT_CONTEXT_NOTE_LIMIT = 200;
22673
22613
  var MAX_CONTEXT_NOTE_LIMIT = 1e3;
22674
22614
  var NOTE_TYPE_LABELS = {
22675
22615
  context: "Context",
22676
- assumption: "Assumptions",
22677
22616
  constraint: "Constraints",
22678
22617
  guidance: "Guidance",
22679
22618
  decision: "Decisions",
@@ -22808,7 +22747,6 @@ ${revision.description}`);
22808
22747
  // ../../libs/harmonica-services/src/mcp/formatters/note-formatter.ts
22809
22748
  var TYPE_LABELS = {
22810
22749
  context: "Context",
22811
- assumption: "Assumption",
22812
22750
  constraint: "Constraint",
22813
22751
  guidance: "Guidance",
22814
22752
  decision: "Decision",
@@ -23094,7 +23032,7 @@ function registerBeatTools(server, ctx, client) {
23094
23032
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived and deprecated beats (excluded by default)")
23095
23033
  },
23096
23034
  async ({ systemId, offset = 0, limit = 100, includeArchived }) => {
23097
- await assertSystemInOrg(client, systemId, ctx.orgId);
23035
+ await assertSystemExists(client, systemId);
23098
23036
  let beats = await client.listSystemBeats(systemId);
23099
23037
  if (!includeArchived) {
23100
23038
  beats = beats.filter((b) => b.beatStatus !== "archived");
@@ -23116,7 +23054,7 @@ function registerBeatTools(server, ctx, client) {
23116
23054
  "Get full beat detail including description",
23117
23055
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
23118
23056
  async ({ beatId }) => {
23119
- await assertBeatInOrg(client, beatId, ctx.orgId);
23057
+ await assertBeatExists(client, beatId);
23120
23058
  const beat = await client.getBeat(beatId);
23121
23059
  if (!beat) {
23122
23060
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
@@ -23133,7 +23071,7 @@ function registerBeatTools(server, ctx, client) {
23133
23071
  systemId: external_exports.string().optional().describe("System ID (auto-resolved from beat if omitted)")
23134
23072
  },
23135
23073
  async ({ beatId, systemId }) => {
23136
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
23074
+ const beat = await fetchBeat(client, beatId);
23137
23075
  const resolvedProjectId = systemId ?? beat.systemId;
23138
23076
  const timeline = await client.getUnifiedBeatTimeline(resolvedProjectId, beatId);
23139
23077
  if (timeline.length === 0) {
@@ -23160,12 +23098,12 @@ function registerBeatTools(server, ctx, client) {
23160
23098
  },
23161
23099
  async ({ systemId, title, description, tags, priority, estimatedEffort, beatId, humanAssignee, agentAssignee, branch }) => {
23162
23100
  try {
23163
- await fetchSystemInOrg(client, systemId, ctx.orgId);
23101
+ const system = await fetchSystem(client, systemId);
23164
23102
  const resolvedBeatId = beatId ?? await client.getNextBeatId(systemId);
23165
23103
  const beat = await client.createBeat({
23166
23104
  beatId: resolvedBeatId,
23167
23105
  systemId,
23168
- orgId: ctx.orgId,
23106
+ orgId: system.orgId,
23169
23107
  title,
23170
23108
  description,
23171
23109
  tags,
@@ -23234,7 +23172,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23234
23172
  async ({ beatId, changeSummary, ...changes }) => {
23235
23173
  try {
23236
23174
  console.error("[MCP:UpdateBeat] beatId=%s actor=%s", beatId, ctx.user.userId);
23237
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
23175
+ const beat = await fetchBeat(client, beatId);
23238
23176
  const updates = {};
23239
23177
  for (const [k, v] of Object.entries(changes)) {
23240
23178
  if (v !== void 0) updates[k] = v;
@@ -23277,7 +23215,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23277
23215
  },
23278
23216
  async ({ systemId, beatId, status, includeArchived }) => {
23279
23217
  try {
23280
- await assertSystemInOrg(client, systemId, ctx.orgId);
23218
+ await assertSystemExists(client, systemId);
23281
23219
  const revisions = beatId ? await client.listBeatRevisions(systemId, beatId, { status, includeArchived }) : await client.listSystemRevisions(systemId, { status, includeArchived });
23282
23220
  return { content: [{ type: "text", text: formatRevisionSummaryTable(revisions) }] };
23283
23221
  } catch (err) {
@@ -23298,7 +23236,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23298
23236
  if (!revision) {
23299
23237
  return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
23300
23238
  }
23301
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
23239
+ await assertBeatExists(client, revision.beatId);
23302
23240
  const resolution = await client.resolveDeliveryPolicy(revisionId);
23303
23241
  const policyBlock = `
23304
23242
 
@@ -23383,6 +23321,11 @@ var PlanVersionsIdempotencyError = class extends Error {
23383
23321
  this.existingBeatVersionIds = existingBeatVersionIds;
23384
23322
  }
23385
23323
  };
23324
+ function isRenderableGateFailure(value) {
23325
+ if (typeof value !== "object" || value === null) return false;
23326
+ const f = value;
23327
+ return typeof f.reason === "string" && typeof f.message === "string";
23328
+ }
23386
23329
 
23387
23330
  // ../../libs/harmonica-services/src/mcp/tools/beat-version-tools.ts
23388
23331
  function registerBeatVersionTools(server, ctx, client) {
@@ -23466,7 +23409,7 @@ function registerBeatVersionTools(server, ctx, client) {
23466
23409
  if (!beat) {
23467
23410
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
23468
23411
  }
23469
- await assertBeatInOrg(client, beatId, ctx.orgId);
23412
+ await assertBeatExists(client, beatId);
23470
23413
  const beatVersion = await client.createBeatVersion(beat.systemId, beatId, {
23471
23414
  title,
23472
23415
  description,
@@ -23505,6 +23448,22 @@ function registerBeatVersionTools(server, ctx, client) {
23505
23448
  ];
23506
23449
  return { content: [{ type: "text", text: lines.join("\n") }] };
23507
23450
  } catch (err) {
23451
+ if (err instanceof BeatQualityGateError && isRenderableGateFailure(err.failure)) {
23452
+ const f = err.failure;
23453
+ const isStale = f.reason === "stale_check_hash" || f.reason === "stale_check_timestamp";
23454
+ const lines = [
23455
+ `**Gate:** coda_quality`,
23456
+ `**Status:** ${isStale ? "stale" : "failed"} (${f.reason})`,
23457
+ `**Detail:** ${f.message}`
23458
+ ];
23459
+ if (f.currentScore !== void 0) lines.push(`**Current:** ${f.currentScore}`);
23460
+ if (f.requiredScore !== void 0) lines.push(`**Required:** ${f.requiredScore}`);
23461
+ if (f.remedy) lines.push(`**Action:** ${f.remedy}`);
23462
+ return {
23463
+ content: [{ type: "text", text: lines.join("\n") }],
23464
+ isError: true
23465
+ };
23466
+ }
23508
23467
  const message = err instanceof Error ? err.message : String(err);
23509
23468
  return { content: [{ type: "text", text: `Failed to create Beat Version: ${message}` }], isError: true };
23510
23469
  }
@@ -23531,7 +23490,7 @@ function registerBeatVersionTools(server, ctx, client) {
23531
23490
  if (!bv) {
23532
23491
  return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
23533
23492
  }
23534
- await assertBeatInOrg(client, bv.beatId, ctx.orgId);
23493
+ await assertBeatExists(client, bv.beatId);
23535
23494
  const updates = {};
23536
23495
  for (const [k, v] of Object.entries(changes)) {
23537
23496
  if (v !== void 0) updates[k] = v;
@@ -23572,7 +23531,7 @@ function registerBeatVersionTools(server, ctx, client) {
23572
23531
  },
23573
23532
  async ({ beatId, force }) => {
23574
23533
  try {
23575
- await assertBeatInOrg(client, beatId, ctx.orgId);
23534
+ await assertBeatExists(client, beatId);
23576
23535
  const actorId = ctx.user.email ?? ctx.user.userId;
23577
23536
  const beatVersions = await client.planBeatVersions(beatId, actorId, { force });
23578
23537
  const lines = [
@@ -23635,7 +23594,7 @@ function registerBeatVersionTools(server, ctx, client) {
23635
23594
  if (bv.status === targetState) {
23636
23595
  return { content: [{ type: "text", text: `Beat Version is already at ${targetState}.` }], isError: true };
23637
23596
  }
23638
- await assertBeatInOrg(client, bv.beatId, ctx.orgId);
23597
+ await assertBeatExists(client, bv.beatId);
23639
23598
  const result = await client.transitionBeatVersionStatus(beatVersionId, targetState, {
23640
23599
  actor: { type: "human", id: ctx.user.userId, name: ctx.user.name, email: ctx.user.email },
23641
23600
  trigger: "human_action",
@@ -23677,7 +23636,7 @@ function registerBeatVersionTools(server, ctx, client) {
23677
23636
  if (!bv) {
23678
23637
  return { content: [{ type: "text", text: `Beat Version not found: ${beatVersionId}` }], isError: true };
23679
23638
  }
23680
- await assertBeatInOrg(client, bv.beatId, ctx.orgId);
23639
+ await assertBeatExists(client, bv.beatId);
23681
23640
  if (!confirm) {
23682
23641
  const revisions = bv.computedStatus ? `${bv.computedStatus.totalRevisions} child Revision(s)` : "child Revisions (count unknown until execution)";
23683
23642
  const dropNote = bv.targetDropId ? ` Beat Version is assigned to Drop ${bv.targetDropId} \u2014 Drop membership is preserved.` : "";
@@ -23963,7 +23922,7 @@ function registerCheckTools(server, ctx, client) {
23963
23922
  },
23964
23923
  async ({ checkType, targetId, systemId, branch, localPath }) => {
23965
23924
  try {
23966
- await assertSystemInOrg(client, systemId, ctx.orgId);
23925
+ await assertSystemExists(client, systemId);
23967
23926
  const targetError = checkType === "build_quality" ? buildQualityTargetError(targetId) : checkType === "beat_version_quality" || checkType === "plan_quality" ? beatVersionQualityTargetError(targetId) : null;
23968
23927
  if (targetError) {
23969
23928
  return { content: [{ type: "text", text: targetError }], isError: true };
@@ -24061,7 +24020,7 @@ function registerClaimTools(server, ctx, client) {
24061
24020
  if (!revision) {
24062
24021
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
24063
24022
  }
24064
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
24023
+ await assertBeatExists(client, revision.beatId);
24065
24024
  const result = await client.claimRevision(revision.systemId, revisionId);
24066
24025
  if (!result.success) {
24067
24026
  return { content: [{ type: "text", text: `Failed to claim revision (${result.error.code}): ${result.error.message}` }], isError: true };
@@ -24095,7 +24054,7 @@ function registerClaimTools(server, ctx, client) {
24095
24054
  if (!revision) {
24096
24055
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
24097
24056
  }
24098
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
24057
+ await assertBeatExists(client, revision.beatId);
24099
24058
  const result = await client.releaseRevisionClaim(revision.systemId, revisionId, reason);
24100
24059
  if (!result.success) {
24101
24060
  return { content: [{ type: "text", text: `Failed to release claim (${result.error.code}): ${result.error.message}` }], isError: true };
@@ -24141,7 +24100,7 @@ function registerDeliverableGroupTools(server, ctx, client) {
24141
24100
  movementId: external_exports.string().describe("The Movement ID (e.g., mv-abc123)")
24142
24101
  },
24143
24102
  async ({ movementId }) => {
24144
- await assertMovementInOrg(client, movementId, ctx.orgId);
24103
+ await assertMovementExists(client, movementId);
24145
24104
  const groups = await client.listMovementDeliverableGroups(movementId);
24146
24105
  const text = groups.length === 0 ? "No deliverable groups found for this Movement." : groups.map(
24147
24106
  (g) => `[${g.deliverableGroupId}] ${g.groupCode} \u2014 ${g.title} (${g.status})`
@@ -24162,11 +24121,6 @@ function registerDeliverableGroupTools(server, ctx, client) {
24162
24121
  content: [{ type: "text", text: `DeliverableGroup not found: ${deliverableGroupId}` }]
24163
24122
  };
24164
24123
  }
24165
- if (g.orgId !== ctx.orgId) {
24166
- throw new Error(
24167
- `DeliverableGroup "${deliverableGroupId}" does not belong to the configured organization. Access denied.`
24168
- );
24169
- }
24170
24124
  const lines = [
24171
24125
  `ID: ${g.deliverableGroupId}`,
24172
24126
  `Code: ${g.groupCode}`,
@@ -24201,16 +24155,16 @@ ${g.acceptanceCriteria}` : null,
24201
24155
  parentGroupId: external_exports.string().optional().describe("Optional parent Track ID for nested grouping (Phase 0 scaffolding)")
24202
24156
  };
24203
24157
  const createTrackHandler = async ({ teamspaceId, title, description, deliverables, acceptanceCriteria, vuRate, systemId, movementId, parentGroupId }) => {
24204
- await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
24158
+ const teamspace = await fetchTeamspace(client, teamspaceId);
24205
24159
  if (movementId !== void 0) {
24206
- await assertMovementInOrg(client, movementId, ctx.orgId);
24160
+ await assertMovementExists(client, movementId);
24207
24161
  }
24208
24162
  if (parentGroupId !== void 0) {
24209
- await assertDeliverableGroupInOrg(client, parentGroupId, ctx.orgId);
24163
+ await fetchDeliverableGroup(client, parentGroupId);
24210
24164
  }
24211
24165
  const g = await client.createDeliverableGroup({
24212
24166
  teamspaceId,
24213
- orgId: ctx.orgId,
24167
+ orgId: teamspace.orgId,
24214
24168
  title,
24215
24169
  ...description !== void 0 && { description },
24216
24170
  ...deliverables !== void 0 && { deliverables },
@@ -24340,12 +24294,12 @@ ${g.acceptanceCriteria}` : null,
24340
24294
  dropId: external_exports.string().describe("The Drop ID to link to the group")
24341
24295
  },
24342
24296
  async ({ deliverableGroupId, dropId }) => {
24343
- await assertDeliverableGroupInOrg(client, deliverableGroupId, ctx.orgId);
24297
+ await fetchDeliverableGroup(client, deliverableGroupId);
24344
24298
  const drop = await client.getDrop(dropId);
24345
24299
  if (!drop) {
24346
24300
  return { content: [{ type: "text", text: `Drop not found: ${dropId}` }] };
24347
24301
  }
24348
- await assertAccountInOrg(client, drop.accountId, ctx.orgId);
24302
+ await assertAccountExists(client, drop.accountId);
24349
24303
  const ok = await client.setDropDeliverableGroup(deliverableGroupId, dropId);
24350
24304
  if (!ok) {
24351
24305
  return { content: [{ type: "text", text: `Drop not found: ${dropId}` }] };
@@ -24362,7 +24316,7 @@ ${g.acceptanceCriteria}` : null,
24362
24316
  deliverableGroupId: external_exports.string().describe("The DeliverableGroup ID")
24363
24317
  },
24364
24318
  async ({ deliverableGroupId }) => {
24365
- await assertDeliverableGroupInOrg(client, deliverableGroupId, ctx.orgId);
24319
+ await fetchDeliverableGroup(client, deliverableGroupId);
24366
24320
  const workItems = await client.listTrackWorkItems(deliverableGroupId);
24367
24321
  const bvIds = [...new Set(
24368
24322
  workItems.map((wi) => wi.beatVersionId).filter((id) => id !== void 0)
@@ -24438,7 +24392,7 @@ function registerDownbeatTools(server, ctx, client) {
24438
24392
  "Set a Downbeat on a Beat \u2014 a non-destructive recalibration anchor that freezes the current convergence reading and Coda content-hash. Future progress is measured from here without erasing prior increments. Returns the created anchor. A Beat with zero increments can still be anchored.",
24439
24393
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24440
24394
  async ({ beatId }) => {
24441
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24395
+ const beat = await fetchBeat(client, beatId);
24442
24396
  const downbeat = await client.setDownbeat(beat.systemId, beatId);
24443
24397
  if (!downbeat) {
24444
24398
  return { content: [{ type: "text", text: `Could not set a Downbeat for beat "${beatId}".` }], isError: true };
@@ -24451,7 +24405,7 @@ function registerDownbeatTools(server, ctx, client) {
24451
24405
  "Read-only: the Beat Versions delivered since the latest Downbeat (the non-destructive default view window). When no Downbeat has been set, returns every increment.",
24452
24406
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24453
24407
  async ({ beatId }) => {
24454
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24408
+ const beat = await fetchBeat(client, beatId);
24455
24409
  const result = await client.listIncrementsSinceDownbeat(beat.systemId, beatId);
24456
24410
  if (!result) {
24457
24411
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
@@ -24464,7 +24418,7 @@ function registerDownbeatTools(server, ctx, client) {
24464
24418
  "Read-only: whether the Beat\u2019s Coda has changed since the latest Downbeat was set (the goal moved). Reports hasDownbeat=false when the Beat has never been recalibrated.",
24465
24419
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24466
24420
  async ({ beatId }) => {
24467
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24421
+ const beat = await fetchBeat(client, beatId);
24468
24422
  const report = await client.getCodaDrift(beat.systemId, beatId);
24469
24423
  if (!report) {
24470
24424
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
@@ -24477,7 +24431,7 @@ function registerDownbeatTools(server, ctx, client) {
24477
24431
  "Read-only: whether sibling Beats created since the latest Downbeat leave this Beat coherent with what got built around it \u2014 a drift signal edited-goal content-hash comparison (get_coda_drift) cannot catch, since this Beat's own text may be unchanged. Reports hasDownbeat=false when the Beat has never been recalibrated.",
24478
24432
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24479
24433
  async ({ beatId }) => {
24480
- const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24434
+ const beat = await fetchBeat(client, beatId);
24481
24435
  const report = await client.getRelationalDrift(beat.systemId, beatId);
24482
24436
  if (!report) {
24483
24437
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
@@ -24552,7 +24506,7 @@ function registerDropQualityTools(server, ctx, client) {
24552
24506
  },
24553
24507
  async ({ dropId, systemId, wait }) => {
24554
24508
  try {
24555
- await assertSystemInOrg(client, systemId, ctx.orgId);
24509
+ await assertSystemExists(client, systemId);
24556
24510
  const drop = await client.getDrop(dropId);
24557
24511
  if (!drop) {
24558
24512
  return { content: [{ type: "text", text: `Drop "${dropId}" not found.` }], isError: true };
@@ -24946,7 +24900,7 @@ function registerEmbeddingTools(server, ctx, client) {
24946
24900
  systemId: external_exports.string().describe("The system ID to embed entities for")
24947
24901
  },
24948
24902
  async ({ systemId }) => {
24949
- await assertSystemInOrg(client, systemId, ctx.orgId);
24903
+ await assertSystemExists(client, systemId);
24950
24904
  const result = await client.embedSystemEntities(systemId);
24951
24905
  const text = `## Embedding Results
24952
24906
 
@@ -24958,33 +24912,33 @@ function registerEmbeddingTools(server, ctx, client) {
24958
24912
  );
24959
24913
  server.tool(
24960
24914
  "search",
24961
- "Free-text semantic search across project, teamspace, or org scope. Provide exactly one of systemId / teamspaceId / orgLevel. Teamspace and org searches fan out across child projects and merge by score. Cross-project results carry the source project name.",
24915
+ "Free-text semantic search across system, teamspace, or org scope. Provide exactly one of systemId / teamspaceId / orgId. Teamspace and org searches fan out across child projects and merge by score. Cross-project results carry the source project name.",
24962
24916
  {
24963
24917
  systemId: external_exports.string().optional().describe("Search within a single project"),
24964
24918
  teamspaceId: external_exports.string().optional().describe("Search across all projects in a teamspace"),
24965
- orgLevel: external_exports.boolean().optional().describe("Search across every project in the configured org"),
24919
+ orgId: external_exports.string().optional().describe("Search across every system in this organization \u2014 see list_organizations"),
24966
24920
  query: external_exports.string().describe("Natural language search query"),
24967
24921
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10"),
24968
24922
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated entities in results (excluded by default)"),
24969
24923
  entityType: external_exports.enum(["note", "beat", "revision"]).optional().describe("Restrict results to a specific entity type"),
24970
- noteType: external_exports.enum(["context", "assumption", "constraint", "guidance", "decision", "document"]).optional().describe("Restrict results to a specific note type (only applies when entityType is note or unset). Requires embeddings generated after this filter was introduced."),
24924
+ noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("Restrict results to a specific note type (only applies when entityType is note or unset). Requires embeddings generated after this filter was introduced."),
24971
24925
  noteStatus: external_exports.enum(["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"]).optional().describe("Restrict note results to a specific status (e.g. unvalidated). DynamoDB post-filter \u2014 works without re-embedding."),
24972
24926
  beatId: external_exports.string().optional().describe("Restrict note results to those belonging to a specific beat (DynamoDB post-filter, no re-embedding needed)")
24973
24927
  },
24974
- async ({ systemId, teamspaceId, orgLevel, query, limit, includeArchived, entityType, noteType, noteStatus, beatId }) => {
24975
- const provided = [systemId, teamspaceId, orgLevel].filter(Boolean).length;
24928
+ async ({ systemId, teamspaceId, orgId, query, limit, includeArchived, entityType, noteType, noteStatus, beatId }) => {
24929
+ const provided = [systemId, teamspaceId, orgId].filter(Boolean).length;
24976
24930
  if (provided !== 1) {
24977
- throw new Error("Specify exactly one of systemId, teamspaceId, or orgLevel.");
24931
+ throw new Error("Specify exactly one of systemId, teamspaceId, or orgId.");
24978
24932
  }
24979
24933
  let scope;
24980
24934
  if (systemId) {
24981
- await assertSystemInOrg(client, systemId, ctx.orgId);
24935
+ await assertSystemExists(client, systemId);
24982
24936
  scope = { systemId };
24983
24937
  } else if (teamspaceId) {
24984
- await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
24938
+ await assertTeamspaceExists(client, teamspaceId);
24985
24939
  scope = { teamspaceId };
24986
24940
  } else {
24987
- scope = { orgId: ctx.orgId };
24941
+ scope = { orgId };
24988
24942
  }
24989
24943
  const results = await client.searchByText(scope, query, { limit, includeArchived, entityType, noteType, noteStatus, beatId });
24990
24944
  const text = formatSimilarityResults(results, query, "Search Results");
@@ -25004,7 +24958,7 @@ function registerEmbeddingTools(server, ctx, client) {
25004
24958
  beatId: external_exports.string().optional().describe("Restrict results to notes belonging to a specific beat (DynamoDB post-filter)")
25005
24959
  },
25006
24960
  async ({ noteId, systemId, threshold, limit, includeArchived = false, noteStatus, beatId }) => {
25007
- await assertSystemInOrg(client, systemId, ctx.orgId);
24961
+ await assertSystemExists(client, systemId);
25008
24962
  const ids = Array.isArray(noteId) ? noteId : [noteId];
25009
24963
  if (ids.length === 1) {
25010
24964
  const results = await client.findSimilarNotes(ids[0], systemId, { threshold, limit, includeArchived, noteStatus, beatId });
@@ -25029,7 +24983,7 @@ function registerEmbeddingTools(server, ctx, client) {
25029
24983
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated beats in results (excluded by default)")
25030
24984
  },
25031
24985
  async ({ beatId, systemId, threshold, limit, includeArchived = false }) => {
25032
- await assertBeatInOrg(client, beatId, ctx.orgId);
24986
+ await assertBeatExists(client, beatId);
25033
24987
  const results = await client.findSimilarBeats(beatId, systemId, { threshold, limit, includeArchived });
25034
24988
  const text = formatSimilarityResults(results, beatId, "Similar Beats");
25035
24989
  return { content: [{ type: "text", text }] };
@@ -25045,7 +24999,7 @@ function registerEmbeddingTools(server, ctx, client) {
25045
24999
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10")
25046
25000
  },
25047
25001
  async ({ noteId, systemId, threshold, limit }) => {
25048
- await assertSystemInOrg(client, systemId, ctx.orgId);
25002
+ await assertSystemExists(client, systemId);
25049
25003
  const results = await client.findRelatedBeats(noteId, systemId, { threshold, limit });
25050
25004
  const text = formatSimilarityResults(results, noteId, "Related Beats");
25051
25005
  return { content: [{ type: "text", text }] };
@@ -25061,7 +25015,7 @@ function registerEmbeddingTools(server, ctx, client) {
25061
25015
  includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false (excludes them). Set true for thematic grouping pass.")
25062
25016
  },
25063
25017
  async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25064
- await assertSystemInOrg(client, systemId, ctx.orgId);
25018
+ await assertSystemExists(client, systemId);
25065
25019
  const result = await client.clusterNotes(systemId, { threshold, minClusterSize, excludeAlreadyLinked: !includeLinked });
25066
25020
  if (result.clusters.length === 0) {
25067
25021
  return { content: [{ type: "text", text: `No clusters found among ${result.totalAnalyzed} analyzed notes. Try lowering the threshold.` }] };
@@ -25092,7 +25046,7 @@ ${members}`;
25092
25046
  includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false.")
25093
25047
  },
25094
25048
  async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25095
- await assertSystemInOrg(client, systemId, ctx.orgId);
25049
+ await assertSystemExists(client, systemId);
25096
25050
  const result = await client.consolidateNotes(systemId, { threshold, minClusterSize, includeLinked });
25097
25051
  if (result.proposals.length === 0) {
25098
25052
  return { content: [{ type: "text", text: `No consolidation candidates found among ${result.totalAnalyzed} analyzed notes.` }] };
@@ -25333,7 +25287,7 @@ function registerLayerTools(server, ctx, client) {
25333
25287
  },
25334
25288
  async ({ systemId }) => {
25335
25289
  try {
25336
- await assertSystemInOrg(client, systemId, ctx.orgId);
25290
+ await assertSystemExists(client, systemId);
25337
25291
  } catch (err) {
25338
25292
  const message = err instanceof Error ? err.message : String(err);
25339
25293
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25358,7 +25312,7 @@ function registerLayerTools(server, ctx, client) {
25358
25312
  },
25359
25313
  async ({ systemId, layerId }) => {
25360
25314
  try {
25361
- await assertSystemInOrg(client, systemId, ctx.orgId);
25315
+ await assertSystemExists(client, systemId);
25362
25316
  } catch (err) {
25363
25317
  const message = err instanceof Error ? err.message : String(err);
25364
25318
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25393,7 +25347,7 @@ function registerLayerTools(server, ctx, client) {
25393
25347
  },
25394
25348
  async ({ systemId, layerKey, name, description, parentLayerId, repoPatterns }) => {
25395
25349
  try {
25396
- await assertSystemInOrg(client, systemId, ctx.orgId);
25350
+ await assertSystemExists(client, systemId);
25397
25351
  } catch (err) {
25398
25352
  const message = err instanceof Error ? err.message : String(err);
25399
25353
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25432,7 +25386,7 @@ ${formatLayerDetail(layer)}` }] };
25432
25386
  },
25433
25387
  async ({ systemId, layerId, name, description, repoPatterns }) => {
25434
25388
  try {
25435
- await assertSystemInOrg(client, systemId, ctx.orgId);
25389
+ await assertSystemExists(client, systemId);
25436
25390
  } catch (err) {
25437
25391
  const message = err instanceof Error ? err.message : String(err);
25438
25392
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25476,7 +25430,7 @@ ${formatLayerDetail(layer)}` }] };
25476
25430
  },
25477
25431
  async ({ systemId, layerId, status, reason }) => {
25478
25432
  try {
25479
- await assertSystemInOrg(client, systemId, ctx.orgId);
25433
+ await assertSystemExists(client, systemId);
25480
25434
  } catch (err) {
25481
25435
  const message = err instanceof Error ? err.message : String(err);
25482
25436
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25508,14 +25462,17 @@ ${formatLayerDetail(layer)}` }] };
25508
25462
  }
25509
25463
 
25510
25464
  // ../../libs/harmonica-services/src/mcp/tools/membership-tools.ts
25465
+ var orgIdArg = external_exports.string().describe("The organization ID \u2014 see list_organizations");
25511
25466
  function registerMembershipTools(server, ctx, client) {
25512
25467
  server.tool(
25513
25468
  "list_org_members",
25514
- "List all members of the configured organization",
25515
- {},
25516
- async () => {
25469
+ "List all members of an organization",
25470
+ {
25471
+ orgId: orgIdArg
25472
+ },
25473
+ async ({ orgId }) => {
25517
25474
  try {
25518
- const members = await client.listOrgMembers(ctx.orgId);
25475
+ const members = await client.listOrgMembers(orgId);
25519
25476
  if (members.length === 0) {
25520
25477
  return { content: [{ type: "text", text: "No members found." }] };
25521
25478
  }
@@ -25533,14 +25490,15 @@ function registerMembershipTools(server, ctx, client) {
25533
25490
  );
25534
25491
  server.tool(
25535
25492
  "add_member",
25536
- "Grant a user access to the configured organization. Their name and avatar are filled in when they first sign in.",
25493
+ "Grant a user access to an organization. Their name and avatar are filled in when they first sign in.",
25537
25494
  {
25495
+ orgId: orgIdArg,
25538
25496
  email: external_exports.string().email().describe("Email address (used as the user ID)"),
25539
25497
  role: external_exports.enum(["member", "admin", "client"]).optional().describe('Organization role (defaults to "member")')
25540
25498
  },
25541
- async ({ email: email2, role }) => {
25499
+ async ({ orgId, email: email2, role }) => {
25542
25500
  try {
25543
- const member = await client.addOrgMember(ctx.orgId, { email: email2, role });
25501
+ const member = await client.addOrgMember(orgId, { email: email2, role });
25544
25502
  const lines = [
25545
25503
  "Member added successfully.",
25546
25504
  "",
@@ -25559,14 +25517,15 @@ function registerMembershipTools(server, ctx, client) {
25559
25517
  );
25560
25518
  server.tool(
25561
25519
  "update_member_role",
25562
- "Change a member's role within the configured organization.",
25520
+ "Change a member's role within an organization.",
25563
25521
  {
25522
+ orgId: orgIdArg,
25564
25523
  userId: external_exports.string().describe("User ID (email address) of the member to update"),
25565
25524
  role: external_exports.enum(["member", "admin", "client"]).describe("New organization role")
25566
25525
  },
25567
- async ({ userId, role }) => {
25526
+ async ({ orgId, userId, role }) => {
25568
25527
  try {
25569
- const member = await client.updateOrgMember(ctx.orgId, userId, { role });
25528
+ const member = await client.updateOrgMember(orgId, userId, { role });
25570
25529
  if (!member) {
25571
25530
  return { content: [{ type: "text", text: `Member not found: "${userId}"` }], isError: true };
25572
25531
  }
@@ -25589,13 +25548,14 @@ function registerMembershipTools(server, ctx, client) {
25589
25548
  );
25590
25549
  server.tool(
25591
25550
  "remove_member",
25592
- "Remove a user from the configured organization.",
25551
+ "Remove a user from an organization.",
25593
25552
  {
25553
+ orgId: orgIdArg,
25594
25554
  userId: external_exports.string().describe("User ID (email address) of the member to remove")
25595
25555
  },
25596
- async ({ userId }) => {
25556
+ async ({ orgId, userId }) => {
25597
25557
  try {
25598
- await client.removeOrgMember(ctx.orgId, userId);
25558
+ await client.removeOrgMember(orgId, userId);
25599
25559
  return { content: [{ type: "text", text: `Member "${userId}" has been removed from the organization.` }] };
25600
25560
  } catch (err) {
25601
25561
  const message = err instanceof Error ? err.message : String(err);
@@ -25814,7 +25774,7 @@ function registerNextActionsTools(server, ctx, client) {
25814
25774
  },
25815
25775
  async ({ systemId, limit }) => {
25816
25776
  try {
25817
- await assertSystemInOrg(client, systemId, ctx.orgId);
25777
+ await assertSystemExists(client, systemId);
25818
25778
  const actions = await client.getNextActions(systemId, { limit });
25819
25779
  return { content: [{ type: "text", text: formatNextActions(actions) }] };
25820
25780
  } catch (err) {
@@ -25831,7 +25791,7 @@ function registerNextActionsTools(server, ctx, client) {
25831
25791
  },
25832
25792
  async ({ systemId }) => {
25833
25793
  try {
25834
- await assertSystemInOrg(client, systemId, ctx.orgId);
25794
+ await assertSystemExists(client, systemId);
25835
25795
  const actions = await client.listTriageActions(systemId);
25836
25796
  return { content: [{ type: "text", text: formatTriageActions(actions) }] };
25837
25797
  } catch (err) {
@@ -25849,7 +25809,7 @@ function registerNextActionsTools(server, ctx, client) {
25849
25809
  },
25850
25810
  async ({ systemId, actionKey }) => {
25851
25811
  try {
25852
- await assertSystemInOrg(client, systemId, ctx.orgId);
25812
+ await assertSystemExists(client, systemId);
25853
25813
  await client.dismissTriageAction(systemId, actionKey, ctx.user?.userId ?? "mcp");
25854
25814
  return { content: [{ type: "text", text: `Action ${actionKey} dismissed.` }] };
25855
25815
  } catch (err) {
@@ -26302,14 +26262,10 @@ var BeatVersionStatusSchema = external_exports.preprocess(
26302
26262
  (value) => typeof value === "string" && ACTIVE_BEAT_VERSION_STATUSES.has(value) ? "active" : "archived",
26303
26263
  external_exports.enum(BINARY_ENTITY_STATUS_VALUES)
26304
26264
  );
26305
- var NoteTypeSchema = external_exports.enum([
26306
- "context",
26307
- "assumption",
26308
- "constraint",
26309
- "guidance",
26310
- "decision",
26311
- "document"
26312
- ]);
26265
+ var NoteTypeSchema = external_exports.preprocess(
26266
+ (v) => v === "assumption" ? "context" : v,
26267
+ external_exports.enum(["context", "constraint", "guidance", "decision", "document"])
26268
+ );
26313
26269
  var NoteStatusSchema = external_exports.enum([
26314
26270
  "active",
26315
26271
  "unvalidated",
@@ -26322,62 +26278,25 @@ var NoteStatusSchema = external_exports.enum([
26322
26278
  ]);
26323
26279
  var SystemStatusSchema = external_exports.enum(["active", "archived", "draft"]);
26324
26280
  var ChangeImportanceSchema = external_exports.enum(["minor", "moderate", "major"]);
26325
- var normalizeSystemId = (raw) => {
26326
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26327
- const b = raw;
26328
- const id = b["systemId"] || b["projectId"];
26329
- if (!id) return raw;
26330
- return { ...b, systemId: id };
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
- };
26341
- var normalizeSystemIdAndTitle = (raw) => {
26342
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26343
- const b = raw;
26344
- const id = b["systemId"] || b["projectId"];
26345
- const title = b["systemTitle"] || b["projectTitle"];
26346
- const result = { ...b };
26347
- if (id) result["systemId"] = id;
26348
- if (title) result["systemTitle"] = title;
26349
- return result;
26350
- };
26351
- var BeatApiSchema = external_exports.preprocess(
26352
- (raw) => {
26353
- if (typeof raw !== "object" || raw === null) return raw;
26354
- const b = raw;
26355
- const id = b["systemId"] || b["projectId"];
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;
26360
- },
26361
- external_exports.object({
26362
- beatId: external_exports.string(),
26363
- systemId: external_exports.string(),
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(),
26367
- title: external_exports.string(),
26368
- version: external_exports.number().nullish(),
26369
- description: external_exports.string().nullish(),
26370
- beatStatus: BeatStatusSchema.nullish(),
26371
- tags: external_exports.array(external_exports.string()).nullish(),
26372
- priority: external_exports.number().nullish(),
26373
- estimatedEffort: external_exports.string().nullish(),
26374
- humanAssignee: HumanAssigneeSchema.nullish(),
26375
- agentAssignee: AgentAssigneeSchema.nullish(),
26376
- revisionCount: external_exports.number().nullish(),
26377
- createdAt: external_exports.string(),
26378
- updatedAt: external_exports.string()
26379
- }).passthrough()
26380
- );
26281
+ var BeatApiSchema = external_exports.object({
26282
+ beatId: external_exports.string(),
26283
+ systemId: external_exports.string(),
26284
+ /** Source system's title; attached by the flat /api/beats endpoint for
26285
+ * multi-system (teamspace/org) responses. */
26286
+ systemName: external_exports.string().nullish(),
26287
+ title: external_exports.string(),
26288
+ version: external_exports.number().nullish(),
26289
+ description: external_exports.string().nullish(),
26290
+ beatStatus: BeatStatusSchema.nullish(),
26291
+ tags: external_exports.array(external_exports.string()).nullish(),
26292
+ priority: external_exports.number().nullish(),
26293
+ estimatedEffort: external_exports.string().nullish(),
26294
+ humanAssignee: HumanAssigneeSchema.nullish(),
26295
+ agentAssignee: AgentAssigneeSchema.nullish(),
26296
+ revisionCount: external_exports.number().nullish(),
26297
+ createdAt: external_exports.string(),
26298
+ updatedAt: external_exports.string()
26299
+ }).passthrough();
26381
26300
  var RevisionScmSchema = external_exports.object({
26382
26301
  provider: external_exports.literal("github"),
26383
26302
  number: external_exports.string(),
@@ -26386,33 +26305,30 @@ var RevisionScmSchema = external_exports.object({
26386
26305
  state: external_exports.enum(["draft", "open", "merged", "closed"]),
26387
26306
  mergeSha: external_exports.string().optional()
26388
26307
  });
26389
- var RevisionApiSchema = external_exports.preprocess(
26390
- normalizeSystemId,
26391
- external_exports.object({
26392
- revisionId: external_exports.string(),
26393
- beatId: external_exports.string(),
26394
- systemId: external_exports.string(),
26395
- status: RevisionStatusSchema,
26396
- title: external_exports.string().default(""),
26397
- versionNumber: external_exports.number(),
26398
- changeSummary: external_exports.string().nullish(),
26399
- changeImportance: ChangeImportanceSchema.nullish(),
26400
- primaryUser: external_exports.string(),
26401
- description: external_exports.string().nullish(),
26402
- tags: external_exports.array(external_exports.string()).nullish(),
26403
- priority: external_exports.number().nullish(),
26404
- estimatedEffort: external_exports.string().nullish(),
26405
- humanAssignee: HumanAssigneeSchema.nullish(),
26406
- agentAssignee: AgentAssigneeSchema.nullish(),
26407
- scm: RevisionScmSchema.optional(),
26408
- // The Layer this Revision was created under (immutable). Absent on Revisions
26409
- // predating Layers — never backfilled — so consumers group those as "Unassigned".
26410
- layerId: external_exports.string().nullish(),
26411
- createdAt: external_exports.string(),
26412
- updatedAt: external_exports.string()
26413
- }).passthrough()
26414
- );
26415
- var NoteApiSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26308
+ var RevisionApiSchema = external_exports.object({
26309
+ revisionId: external_exports.string(),
26310
+ beatId: external_exports.string(),
26311
+ systemId: external_exports.string(),
26312
+ status: RevisionStatusSchema,
26313
+ title: external_exports.string().default(""),
26314
+ versionNumber: external_exports.number(),
26315
+ changeSummary: external_exports.string().nullish(),
26316
+ changeImportance: ChangeImportanceSchema.nullish(),
26317
+ primaryUser: external_exports.string(),
26318
+ description: external_exports.string().nullish(),
26319
+ tags: external_exports.array(external_exports.string()).nullish(),
26320
+ priority: external_exports.number().nullish(),
26321
+ estimatedEffort: external_exports.string().nullish(),
26322
+ humanAssignee: HumanAssigneeSchema.nullish(),
26323
+ agentAssignee: AgentAssigneeSchema.nullish(),
26324
+ scm: RevisionScmSchema.optional(),
26325
+ // The Layer this Revision was created under (immutable). Absent on Revisions
26326
+ // predating Layers — never backfilled — so consumers group those as "Unassigned".
26327
+ layerId: external_exports.string().nullish(),
26328
+ createdAt: external_exports.string(),
26329
+ updatedAt: external_exports.string()
26330
+ }).passthrough();
26331
+ var NoteApiSchema = external_exports.object({
26416
26332
  noteId: external_exports.string(),
26417
26333
  noteType: NoteTypeSchema,
26418
26334
  status: NoteStatusSchema,
@@ -26428,21 +26344,18 @@ var NoteApiSchema = external_exports.preprocess(normalizeSystemId, external_expo
26428
26344
  agentAssignee: AgentAssigneeSchema.nullish(),
26429
26345
  createdAt: external_exports.string(),
26430
26346
  updatedAt: external_exports.string()
26431
- }).passthrough());
26432
- var SystemApiSchema = external_exports.preprocess(
26433
- normalizeSystemId,
26434
- external_exports.object({
26435
- systemId: external_exports.string(),
26436
- title: external_exports.string(),
26437
- status: SystemStatusSchema,
26438
- description: external_exports.string().optional(),
26439
- strategy: external_exports.string().optional(),
26440
- orgId: external_exports.string().optional(),
26441
- ownerUserId: external_exports.string().optional(),
26442
- createdAt: external_exports.string(),
26443
- updatedAt: external_exports.string()
26444
- }).passthrough()
26445
- );
26347
+ }).passthrough();
26348
+ var SystemApiSchema = external_exports.object({
26349
+ systemId: external_exports.string(),
26350
+ title: external_exports.string(),
26351
+ status: SystemStatusSchema,
26352
+ description: external_exports.string().optional(),
26353
+ strategy: external_exports.string().optional(),
26354
+ orgId: external_exports.string().optional(),
26355
+ ownerUserId: external_exports.string().optional(),
26356
+ createdAt: external_exports.string(),
26357
+ updatedAt: external_exports.string()
26358
+ }).passthrough();
26446
26359
  var BeatsResponseSchema = external_exports.object({
26447
26360
  beats: external_exports.array(BeatApiSchema),
26448
26361
  total: external_exports.number().optional(),
@@ -26628,43 +26541,37 @@ var CHECK_TARGET_TYPES = [
26628
26541
  "organization"
26629
26542
  ];
26630
26543
  var CheckTargetTypeSchema = external_exports.enum(CHECK_TARGET_TYPES);
26631
- var CheckApiSchema = external_exports.preprocess(
26632
- normalizeSystemId,
26633
- external_exports.object({
26634
- checkId: external_exports.string(),
26635
- checkType: CheckTypeSchema,
26636
- targetType: CheckTargetTypeSchema,
26637
- targetId: external_exports.string(),
26638
- systemId: external_exports.string(),
26639
- overallScore: external_exports.number(),
26640
- dimensions: external_exports.array(CheckDimensionScoreSchema),
26641
- summary: external_exports.string(),
26642
- topSuggestion: external_exports.string().optional(),
26643
- gaps: external_exports.array(CheckGapSchema).optional(),
26644
- taskId: external_exports.string().optional(),
26645
- triggeredBy: external_exports.string().optional(),
26646
- createdAt: external_exports.string(),
26647
- updatedAt: external_exports.string()
26648
- }).passthrough()
26649
- );
26650
- var SystemCheckApiSchema = external_exports.preprocess(
26651
- normalizeSystemId,
26652
- external_exports.object({
26653
- checkId: external_exports.string(),
26654
- checkType: CheckTypeSchema,
26655
- targetType: CheckTargetTypeSchema,
26656
- targetId: external_exports.string(),
26657
- systemId: external_exports.string(),
26658
- overallScore: external_exports.number(),
26659
- summary: external_exports.string(),
26660
- triggeredBy: external_exports.string().optional(),
26661
- createdAt: external_exports.string(),
26662
- updatedAt: external_exports.string(),
26663
- /** Populated only when the API was called with ?includeDimensions=true.
26664
- * Read-time enrichment from the underlying Check entity. */
26665
- dimensions: external_exports.array(CheckDimensionScoreSchema).optional()
26666
- }).passthrough()
26667
- );
26544
+ var CheckApiSchema = external_exports.object({
26545
+ checkId: external_exports.string(),
26546
+ checkType: CheckTypeSchema,
26547
+ targetType: CheckTargetTypeSchema,
26548
+ targetId: external_exports.string(),
26549
+ systemId: external_exports.string(),
26550
+ overallScore: external_exports.number(),
26551
+ dimensions: external_exports.array(CheckDimensionScoreSchema),
26552
+ summary: external_exports.string(),
26553
+ topSuggestion: external_exports.string().optional(),
26554
+ gaps: external_exports.array(CheckGapSchema).optional(),
26555
+ taskId: external_exports.string().optional(),
26556
+ triggeredBy: external_exports.string().optional(),
26557
+ createdAt: external_exports.string(),
26558
+ updatedAt: external_exports.string()
26559
+ }).passthrough();
26560
+ var SystemCheckApiSchema = external_exports.object({
26561
+ checkId: external_exports.string(),
26562
+ checkType: CheckTypeSchema,
26563
+ targetType: CheckTargetTypeSchema,
26564
+ targetId: external_exports.string(),
26565
+ systemId: external_exports.string(),
26566
+ overallScore: external_exports.number(),
26567
+ summary: external_exports.string(),
26568
+ triggeredBy: external_exports.string().optional(),
26569
+ createdAt: external_exports.string(),
26570
+ updatedAt: external_exports.string(),
26571
+ /** Populated only when the API was called with ?includeDimensions=true.
26572
+ * Read-time enrichment from the underlying Check entity. */
26573
+ dimensions: external_exports.array(CheckDimensionScoreSchema).optional()
26574
+ }).passthrough();
26668
26575
  var CheckResponseSchema = external_exports.object({ check: CheckApiSchema }).passthrough();
26669
26576
  var ChecksResponseSchema = external_exports.object({ checks: external_exports.array(SystemCheckApiSchema), nextCursor: external_exports.string().optional() }).passthrough();
26670
26577
  var TaskStatusSchema2 = external_exports.enum(["pending", "running", "completed", "failed"]);
@@ -26709,30 +26616,27 @@ var OrgMemberSchema = external_exports.object({
26709
26616
  var OrganizationsResponseSchema = external_exports.array(OrganizationSummarySchema);
26710
26617
  var OrgMembersResponseSchema = external_exports.array(OrgMemberSchema);
26711
26618
  var WrappedOrgMembersResponseSchema = external_exports.object({ members: OrgMembersResponseSchema });
26712
- var ActivitySchema = external_exports.preprocess(
26713
- normalizeSystemId,
26714
- external_exports.object({
26715
- activityId: external_exports.string(),
26716
- systemId: external_exports.string(),
26717
- action: external_exports.string(),
26718
- importance: external_exports.enum(["minor", "moderate", "major"]),
26719
- reason: external_exports.string(),
26720
- // actorType is typed as string (not a strict enum) because the API accepts values
26721
- // beyond 'user'/'system' (e.g. 'human', 'agent') depending on the call site.
26722
- actorType: external_exports.string().optional(),
26723
- actorId: external_exports.string().optional(),
26724
- affectedBeatIds: external_exports.array(external_exports.string()).optional(),
26725
- affectedBeatTitles: external_exports.array(external_exports.string()).optional(),
26726
- metadata: external_exports.record(external_exports.unknown()).optional(),
26727
- createdAt: external_exports.string()
26728
- }).passthrough()
26729
- );
26619
+ var ActivitySchema = external_exports.object({
26620
+ activityId: external_exports.string(),
26621
+ systemId: external_exports.string(),
26622
+ action: external_exports.string(),
26623
+ importance: external_exports.enum(["minor", "moderate", "major"]),
26624
+ reason: external_exports.string(),
26625
+ // actorType is typed as string (not a strict enum) because the API accepts values
26626
+ // beyond 'user'/'system' (e.g. 'human', 'agent') depending on the call site.
26627
+ actorType: external_exports.string().optional(),
26628
+ actorId: external_exports.string().optional(),
26629
+ affectedBeatIds: external_exports.array(external_exports.string()).optional(),
26630
+ affectedBeatTitles: external_exports.array(external_exports.string()).optional(),
26631
+ metadata: external_exports.record(external_exports.unknown()).optional(),
26632
+ createdAt: external_exports.string()
26633
+ }).passthrough();
26730
26634
  var ActivitiesResponseSchema = external_exports.object({
26731
26635
  activities: external_exports.array(ActivitySchema),
26732
26636
  hasMore: external_exports.boolean(),
26733
26637
  nextCursor: external_exports.string().optional()
26734
26638
  }).passthrough();
26735
- var NotificationSchema2 = external_exports.preprocess(normalizeSystemId, external_exports.object({
26639
+ var NotificationSchema2 = external_exports.object({
26736
26640
  notificationId: external_exports.string(),
26737
26641
  action: external_exports.string(),
26738
26642
  title: external_exports.string(),
@@ -26747,7 +26651,7 @@ var NotificationSchema2 = external_exports.preprocess(normalizeSystemId, externa
26747
26651
  actorType: external_exports.enum(["user", "system"]),
26748
26652
  actorId: external_exports.string().optional(),
26749
26653
  actorName: external_exports.string().optional()
26750
- }).passthrough());
26654
+ }).passthrough();
26751
26655
  var NotificationsResponseSchema = external_exports.object({
26752
26656
  notifications: external_exports.array(NotificationSchema2),
26753
26657
  hasMore: external_exports.boolean().optional(),
@@ -26791,29 +26695,26 @@ var InboxItemsResponseSchema = external_exports.object({
26791
26695
  hasMore: external_exports.boolean(),
26792
26696
  nextCursor: external_exports.string().optional()
26793
26697
  }).passthrough();
26794
- var TriageActionSchema = external_exports.preprocess(
26795
- normalizeSystemId,
26796
- external_exports.object({
26797
- actionKey: external_exports.string(),
26798
- systemId: external_exports.string(),
26799
- entityId: external_exports.string().optional(),
26800
- entityType: external_exports.enum(["beat", "revision", "note"]).optional(),
26801
- beatId: external_exports.string().optional(),
26802
- category: external_exports.string(),
26803
- priority: external_exports.string(),
26804
- beatStatus: external_exports.string().optional(),
26805
- title: external_exports.string(),
26806
- description: external_exports.string(),
26807
- status: external_exports.enum(["open", "acted", "dismissed", "resolved"]),
26808
- decision: external_exports.enum(["auto-execute", "escalate", "skip"]).optional(),
26809
- taskId: external_exports.string().optional(),
26810
- taskStatus: external_exports.string().optional(),
26811
- firstSeenAt: external_exports.string(),
26812
- lastSeenAt: external_exports.string(),
26813
- dismissedBy: external_exports.string().optional(),
26814
- dismissedAt: external_exports.string().optional()
26815
- }).passthrough()
26816
- );
26698
+ var TriageActionSchema = external_exports.object({
26699
+ actionKey: external_exports.string(),
26700
+ systemId: external_exports.string(),
26701
+ entityId: external_exports.string().optional(),
26702
+ entityType: external_exports.enum(["beat", "revision", "note"]).optional(),
26703
+ beatId: external_exports.string().optional(),
26704
+ category: external_exports.string(),
26705
+ priority: external_exports.string(),
26706
+ beatStatus: external_exports.string().optional(),
26707
+ title: external_exports.string(),
26708
+ description: external_exports.string(),
26709
+ status: external_exports.enum(["open", "acted", "dismissed", "resolved"]),
26710
+ decision: external_exports.enum(["auto-execute", "escalate", "skip"]).optional(),
26711
+ taskId: external_exports.string().optional(),
26712
+ taskStatus: external_exports.string().optional(),
26713
+ firstSeenAt: external_exports.string(),
26714
+ lastSeenAt: external_exports.string(),
26715
+ dismissedBy: external_exports.string().optional(),
26716
+ dismissedAt: external_exports.string().optional()
26717
+ }).passthrough();
26817
26718
  var TriageActionsResponseSchema = external_exports.object({
26818
26719
  actions: external_exports.array(TriageActionSchema)
26819
26720
  }).passthrough();
@@ -26830,7 +26731,7 @@ var NextActionSchema = external_exports.object({
26830
26731
  var NextActionsResponseSchema = external_exports.object({
26831
26732
  actions: external_exports.array(NextActionSchema)
26832
26733
  }).passthrough();
26833
- var SearchResultSchema = external_exports.preprocess(normalizeSystemIdAndName, external_exports.object({
26734
+ var SearchResultSchema = external_exports.object({
26834
26735
  entityId: external_exports.string(),
26835
26736
  entityType: external_exports.enum(["beat", "note", "revision", "beatVersion", "drop", "project", "system"]),
26836
26737
  score: external_exports.number(),
@@ -26838,7 +26739,7 @@ var SearchResultSchema = external_exports.preprocess(normalizeSystemIdAndName, e
26838
26739
  systemId: external_exports.string().optional(),
26839
26740
  systemName: external_exports.string().optional(),
26840
26741
  teamspaceId: external_exports.string().optional()
26841
- }).passthrough());
26742
+ }).passthrough();
26842
26743
  var SearchScopeSchema = external_exports.object({
26843
26744
  kind: external_exports.enum(["project", "teamspace", "org"]),
26844
26745
  id: external_exports.string()
@@ -26882,7 +26783,7 @@ var LandscapeResponseSchema = external_exports.object({
26882
26783
  (raw) => {
26883
26784
  if (typeof raw !== "object" || raw === null) return raw;
26884
26785
  const o = raw;
26885
- const id = o["systemId"] ?? o["projectId"];
26786
+ const id = o["systemId"];
26886
26787
  if (!id) return raw;
26887
26788
  return { ...o, systemId: id };
26888
26789
  },
@@ -26900,14 +26801,16 @@ var ClusterDescriptionSchema = external_exports.object({
26900
26801
  title: external_exports.string(),
26901
26802
  description: external_exports.string()
26902
26803
  }).passthrough();
26903
- var BeatVersionItemSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26804
+ var BeatVersionItemSchema = external_exports.object({
26904
26805
  beatVersionId: external_exports.string(),
26905
26806
  beatId: external_exports.string(),
26906
26807
  systemId: external_exports.string().optional(),
26907
26808
  status: external_exports.string()
26908
- }).passthrough());
26809
+ }).passthrough();
26909
26810
  var BeatVersionsResponseSchema = external_exports.object({
26910
- beatVersions: external_exports.array(BeatVersionItemSchema)
26811
+ beatVersions: external_exports.array(BeatVersionItemSchema),
26812
+ cursor: external_exports.string().optional(),
26813
+ hasMore: external_exports.boolean().optional()
26911
26814
  }).passthrough();
26912
26815
  var ConversationSummaryBaseSchema = external_exports.object({
26913
26816
  conversationId: external_exports.string(),
@@ -26924,7 +26827,7 @@ var ConversationSummaryBaseSchema = external_exports.object({
26924
26827
  createdAt: external_exports.string(),
26925
26828
  updatedAt: external_exports.string()
26926
26829
  });
26927
- var ConversationSummarySchema = external_exports.preprocess(normalizeSystemId, ConversationSummaryBaseSchema.passthrough());
26830
+ var ConversationSummarySchema = ConversationSummaryBaseSchema.passthrough();
26928
26831
  var StoredMessageSchema = external_exports.object({
26929
26832
  messageId: external_exports.string(),
26930
26833
  conversationId: external_exports.string(),
@@ -27169,30 +27072,21 @@ var AccountSystemApiSchema = external_exports.object({
27169
27072
  var AccountSystemsResponseSchema = external_exports.object({ systems: external_exports.array(AccountSystemApiSchema) }).passthrough();
27170
27073
  var LAYER_STATUSES = ["active", "archived"];
27171
27074
  var LayerStatusApiSchema = external_exports.enum(LAYER_STATUSES);
27172
- var LayerApiSchema = external_exports.preprocess(
27173
- (raw) => {
27174
- if (typeof raw !== "object" || raw === null) return raw;
27175
- const l = raw;
27176
- const id = l["systemId"] || l["projectId"];
27177
- if (!id) return raw;
27178
- return { ...l, systemId: id };
27179
- },
27180
- external_exports.object({
27181
- layerId: external_exports.string(),
27182
- systemId: external_exports.string(),
27183
- layerKey: external_exports.string(),
27184
- name: external_exports.string(),
27185
- description: external_exports.string().nullish(),
27186
- parentLayerId: external_exports.string().nullish(),
27187
- repoPatterns: external_exports.array(external_exports.string()).nullish(),
27188
- status: LayerStatusApiSchema,
27189
- archivedAt: external_exports.string().nullish(),
27190
- createdBy: external_exports.string().nullish(),
27191
- version: external_exports.number().nullish(),
27192
- createdAt: external_exports.string(),
27193
- updatedAt: external_exports.string()
27194
- }).passthrough()
27195
- );
27075
+ var LayerApiSchema = external_exports.object({
27076
+ layerId: external_exports.string(),
27077
+ systemId: external_exports.string(),
27078
+ layerKey: external_exports.string(),
27079
+ name: external_exports.string(),
27080
+ description: external_exports.string().nullish(),
27081
+ parentLayerId: external_exports.string().nullish(),
27082
+ repoPatterns: external_exports.array(external_exports.string()).nullish(),
27083
+ status: LayerStatusApiSchema,
27084
+ archivedAt: external_exports.string().nullish(),
27085
+ createdBy: external_exports.string().nullish(),
27086
+ version: external_exports.number().nullish(),
27087
+ createdAt: external_exports.string(),
27088
+ updatedAt: external_exports.string()
27089
+ }).passthrough();
27196
27090
  var LayersResponseSchema = external_exports.object({ layers: external_exports.array(LayerApiSchema) }).passthrough();
27197
27091
  var LAYER_RUBRIC_SCOPES_LIST = ["account", "teamspace", "movement"];
27198
27092
  var LayerRubricScopeApiSchema = external_exports.enum(LAYER_RUBRIC_SCOPES_LIST);
@@ -27231,7 +27125,7 @@ var DELIVERABLE_GROUP_STATUSES = [
27231
27125
  "cancelled"
27232
27126
  ];
27233
27127
  var DeliverableGroupStatusSchema = external_exports.enum(DELIVERABLE_GROUP_STATUSES);
27234
- var DeliverableGroupApiSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
27128
+ var DeliverableGroupApiSchema = external_exports.object({
27235
27129
  deliverableGroupId: external_exports.string(),
27236
27130
  groupCode: external_exports.string(),
27237
27131
  teamspaceId: external_exports.string(),
@@ -27252,7 +27146,7 @@ var DeliverableGroupApiSchema = external_exports.preprocess(normalizeSystemId, e
27252
27146
  version: external_exports.number().nullish(),
27253
27147
  createdAt: external_exports.string(),
27254
27148
  updatedAt: external_exports.string()
27255
- }).passthrough());
27149
+ }).passthrough();
27256
27150
  var DeliverableGroupsResponseSchema = external_exports.object({
27257
27151
  deliverableGroups: external_exports.array(DeliverableGroupApiSchema)
27258
27152
  }).passthrough();
@@ -27269,33 +27163,27 @@ var MovementDropSummaryApiSchema = external_exports.object({
27269
27163
  var MovementDropsResponseSchema = external_exports.object({
27270
27164
  drops: external_exports.array(MovementDropSummaryApiSchema)
27271
27165
  }).passthrough();
27272
- var WorkPlanBeatVersionApiSchema = external_exports.preprocess(
27273
- normalizeSystemId,
27274
- external_exports.object({
27275
- beatVersionId: external_exports.string(),
27276
- beatId: external_exports.string(),
27277
- systemId: external_exports.string(),
27278
- versionNumber: external_exports.number(),
27279
- title: external_exports.string(),
27280
- status: BeatVersionStatusSchema,
27281
- // Derived Value Units (VU = Δf × W), read-only. 0 when the BV has no confirmed
27282
- // value-velocity inputs. Computed server-side from the parent Beat's weight.
27283
- // `.default(0)` tolerates a rolling deploy where an older API omits the field —
27284
- // validatedApiFetch would otherwise throw and drop the whole hook into error.
27285
- valueUnits: external_exports.number().default(0),
27286
- createdAt: external_exports.string(),
27287
- updatedAt: external_exports.string()
27288
- }).passthrough()
27289
- );
27166
+ var WorkPlanBeatVersionApiSchema = external_exports.object({
27167
+ beatVersionId: external_exports.string(),
27168
+ beatId: external_exports.string(),
27169
+ systemId: external_exports.string(),
27170
+ versionNumber: external_exports.number(),
27171
+ title: external_exports.string(),
27172
+ status: BeatVersionStatusSchema,
27173
+ // Derived Value Units (VU = Δf × W), read-only. 0 when the BV has no confirmed
27174
+ // value-velocity inputs. Computed server-side from the parent Beat's weight.
27175
+ // `.default(0)` tolerates a rolling deploy where an older API omits the field —
27176
+ // validatedApiFetch would otherwise throw and drop the whole hook into error.
27177
+ valueUnits: external_exports.number().default(0),
27178
+ createdAt: external_exports.string(),
27179
+ updatedAt: external_exports.string()
27180
+ }).passthrough();
27290
27181
  var DeliverableGroupBeatVersionsResponseSchema = external_exports.object({
27291
27182
  beatVersions: external_exports.array(WorkPlanBeatVersionApiSchema)
27292
27183
  }).passthrough();
27293
27184
  var RevisionClaimActorSchema = external_exports.union([
27294
27185
  external_exports.object({ kind: external_exports.literal("user"), userId: external_exports.string() }),
27295
- external_exports.preprocess(
27296
- normalizeSystemId,
27297
- external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), systemId: external_exports.string() })
27298
- )
27186
+ external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), systemId: external_exports.string() })
27299
27187
  ]);
27300
27188
  var RevisionClaimSchema = external_exports.object({
27301
27189
  claimId: external_exports.string(),
@@ -27306,39 +27194,33 @@ var RevisionClaimSchema = external_exports.object({
27306
27194
  var RevisionClaimsResponseSchema = external_exports.object({
27307
27195
  claims: external_exports.array(RevisionClaimSchema)
27308
27196
  }).passthrough();
27309
- var ProposedRevisionSchema = external_exports.preprocess(
27310
- normalizeSystemIdAndTitle,
27311
- external_exports.object({
27312
- revisionId: external_exports.string(),
27313
- title: external_exports.string(),
27314
- state: external_exports.string(),
27315
- beatId: external_exports.string(),
27316
- beatTitle: external_exports.string().optional(),
27317
- systemId: external_exports.string(),
27318
- systemTitle: external_exports.string().optional()
27319
- }).passthrough()
27320
- );
27197
+ var ProposedRevisionSchema = external_exports.object({
27198
+ revisionId: external_exports.string(),
27199
+ title: external_exports.string(),
27200
+ state: external_exports.string(),
27201
+ beatId: external_exports.string(),
27202
+ beatTitle: external_exports.string().optional(),
27203
+ systemId: external_exports.string(),
27204
+ systemTitle: external_exports.string().optional()
27205
+ }).passthrough();
27321
27206
  var ReleaseIntervalSchema = external_exports.object({
27322
27207
  averageIntervalDays: external_exports.number(),
27323
27208
  sampleSize: external_exports.number(),
27324
27209
  isFallback: external_exports.boolean()
27325
27210
  }).passthrough();
27326
- var EligibleBeatVersionForDropSchema = external_exports.preprocess(
27327
- normalizeSystemIdAndTitle,
27328
- external_exports.object({
27329
- beatVersionId: external_exports.string(),
27330
- title: external_exports.string(),
27331
- state: external_exports.string(),
27332
- beatId: external_exports.string(),
27333
- beatTitle: external_exports.string().optional(),
27334
- systemId: external_exports.string(),
27335
- systemTitle: external_exports.string().optional(),
27336
- createdAt: external_exports.string(),
27337
- updatedAt: external_exports.string(),
27338
- humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
27339
- currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string().optional(), state: external_exports.string() }).optional()
27340
- }).passthrough()
27341
- );
27211
+ var EligibleBeatVersionForDropSchema = external_exports.object({
27212
+ beatVersionId: external_exports.string(),
27213
+ title: external_exports.string(),
27214
+ state: external_exports.string(),
27215
+ beatId: external_exports.string(),
27216
+ beatTitle: external_exports.string().optional(),
27217
+ systemId: external_exports.string(),
27218
+ systemTitle: external_exports.string().optional(),
27219
+ createdAt: external_exports.string(),
27220
+ updatedAt: external_exports.string(),
27221
+ humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
27222
+ currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string().optional(), state: external_exports.string() }).optional()
27223
+ }).passthrough();
27342
27224
  var ScoredDropCandidateSchema = external_exports.object({
27343
27225
  bv: EligibleBeatVersionForDropSchema,
27344
27226
  // Score fields are nullable — the ranking engine returns null when there is
@@ -27558,7 +27440,7 @@ function sortNotesByCreatedAt(notes, orderBy) {
27558
27440
 
27559
27441
  // ../../libs/harmonica-services/src/mcp/tools/note-tools.ts
27560
27442
  var DISMISSED_NOTE_STATUS = "dismissed";
27561
- var NOTE_TYPE_VALUES = ["context", "assumption", "constraint", "guidance", "decision", "document"];
27443
+ var NOTE_TYPE_VALUES = ["context", "constraint", "guidance", "decision", "document"];
27562
27444
  var NOTE_STATUS_VALUES = ["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"];
27563
27445
  var DECISION_SIGNIFICANCE_VALUES2 = ["strategic", "structural", "implementation"];
27564
27446
  async function deliveryPolicyBlock(client, note) {
@@ -27582,7 +27464,7 @@ function registerNoteTools(server, ctx, client) {
27582
27464
  {
27583
27465
  systemId: external_exports.string().describe("The system ID"),
27584
27466
  beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all system notes)"),
27585
- noteType: external_exports.union([external_exports.enum(NOTE_TYPE_VALUES), external_exports.array(external_exports.enum(NOTE_TYPE_VALUES)).min(1)]).optional().describe('Filter by note type \u2014 single value or array for multi-select (e.g. ["constraint", "assumption"])'),
27467
+ noteType: external_exports.union([external_exports.enum(NOTE_TYPE_VALUES), external_exports.array(external_exports.enum(NOTE_TYPE_VALUES)).min(1)]).optional().describe('Filter by note type \u2014 single value or array for multi-select (e.g. ["constraint", "guidance"])'),
27586
27468
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
27587
27469
  revisionId: external_exports.string().optional().describe("Filter to notes scoped to a specific revision"),
27588
27470
  beatVersionId: external_exports.string().optional().describe("Filter to notes scoped to a specific Beat Version (planning increment)"),
@@ -27605,7 +27487,7 @@ function registerNoteTools(server, ctx, client) {
27605
27487
  return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
27606
27488
  }
27607
27489
  try {
27608
- await assertSystemInOrg(client, systemId, ctx.orgId);
27490
+ await assertSystemExists(client, systemId);
27609
27491
  } catch (err) {
27610
27492
  const message = err instanceof Error ? err.message : String(err);
27611
27493
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -27688,13 +27570,14 @@ function registerNoteTools(server, ctx, client) {
27688
27570
  "list_org_notes",
27689
27571
  "List Notes at the organization level (cross-cutting constraints, org-wide decisions, and context that applies to all systems). Filterable by type and status. Use offset/limit to paginate.",
27690
27572
  {
27573
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
27691
27574
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Filter by note type"),
27692
27575
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
27693
27576
  offset: external_exports.coerce.number().int().min(0).default(0).describe("Number of notes to skip (for pagination)"),
27694
27577
  limit: external_exports.coerce.number().int().min(1).max(500).default(100).describe("Max notes to return (default 100, max 500)")
27695
27578
  },
27696
- async ({ noteType, status, offset = 0, limit = 100 }) => {
27697
- const notes = await client.listOrgNotes(ctx.orgId, {
27579
+ async ({ orgId, noteType, status, offset = 0, limit = 100 }) => {
27580
+ const notes = await client.listOrgNotes(orgId, {
27698
27581
  noteType,
27699
27582
  status
27700
27583
  });
@@ -27725,9 +27608,6 @@ function registerNoteTools(server, ctx, client) {
27725
27608
  if (!movement) {
27726
27609
  return { content: [{ type: "text", text: `Movement not found: "${movementId}"` }], isError: true };
27727
27610
  }
27728
- if (movement.orgId !== ctx.orgId) {
27729
- return { content: [{ type: "text", text: `Movement "${movementId}" is not in the configured organization.` }], isError: true };
27730
- }
27731
27611
  const notes = await client.listMovementNotes(movementId, {
27732
27612
  noteType,
27733
27613
  status
@@ -27751,13 +27631,13 @@ function registerNoteTools(server, ctx, client) {
27751
27631
  systemId: external_exports.string().describe("The system ID")
27752
27632
  },
27753
27633
  async ({ systemId }) => {
27754
- await assertSystemInOrg(client, systemId, ctx.orgId);
27755
- const DOCUMENT_FETCH_LIMIT = 5e3;
27756
- const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
27634
+ await assertSystemExists(client, systemId);
27635
+ const { notes: docs, hasMore } = await client.listAllSystemNotes(systemId, { noteType: "document" });
27757
27636
  const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
27758
- const truncationNotice = truncated && visibleDocs.length > 0 ? "\n\n> \u26A0\uFE0F This system has more than 5000 documents. Some documents may not be shown.\n" : "";
27759
- const text = formatDocumentList(visibleDocs) + truncationNotice;
27760
- return { content: [{ type: "text", text }] };
27637
+ if (hasMore) {
27638
+ return { content: [{ type: "text", text: `Unexpected: listAllSystemNotes returned hasMore=true on the no-limit path for system ${systemId} \u2014 the document list may be incomplete. This is a server-side regression.` }], isError: true };
27639
+ }
27640
+ return { content: [{ type: "text", text: formatDocumentList(visibleDocs) }] };
27761
27641
  }
27762
27642
  );
27763
27643
  server.tool(
@@ -27777,16 +27657,13 @@ function registerNoteTools(server, ctx, client) {
27777
27657
  }
27778
27658
  try {
27779
27659
  if (orgId) {
27780
- if (orgId !== ctx.orgId) {
27781
- return { content: [{ type: "text", text: "Access denied: orgId does not match the configured organization." }], isError: true };
27782
- }
27783
27660
  const note2 = await client.getOrgNote(orgId, noteId);
27784
27661
  if (!note2 || note2.organizationId !== orgId) {
27785
27662
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
27786
27663
  }
27787
27664
  return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
27788
27665
  }
27789
- await assertSystemInOrg(client, systemId, ctx.orgId);
27666
+ await assertSystemExists(client, systemId);
27790
27667
  const note = await client.getNote(noteId);
27791
27668
  if (!note) {
27792
27669
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27803,6 +27680,7 @@ function registerNoteTools(server, ctx, client) {
27803
27680
  "create_note",
27804
27681
  "Create a new Note. Provide exactly one of systemId, teamspaceId, movementId, or none (org-level): systemId scopes the note to a system (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child systems), movementId scopes it to a Movement (commercial SOW container \u2014 engagement-level knowledge), and omitting all creates an org-level note that applies across all systems.",
27805
27682
  {
27683
+ orgId: external_exports.string().optional().describe("The organization ID \u2014 required for an org-level Note (when systemId, teamspaceId and movementId are all omitted). See list_organizations."),
27806
27684
  systemId: external_exports.string().optional().describe("The system ID (omit for teamspace-, movement-, or org-level note)"),
27807
27685
  teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for system-, movement-, or org-level note)"),
27808
27686
  movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for system-, teamspace-, or org-level note)"),
@@ -27812,20 +27690,18 @@ function registerNoteTools(server, ctx, client) {
27812
27690
  revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires systemId)"),
27813
27691
  beatVersionId: external_exports.string().optional().describe("Beat Version ID if this note governs a specific planning increment (requires beatId)"),
27814
27692
  rationale: external_exports.string().optional().describe("Why this note exists"),
27815
- confidence: external_exports.coerce.number().min(0).max(1).optional().describe("Confidence level for assumptions (0-1)"),
27816
27693
  affectsBeats: external_exports.array(external_exports.string()).optional().describe("Beat IDs this note impacts"),
27817
27694
  dependsOnNotes: external_exports.array(external_exports.string()).optional().describe("Note IDs this note depends on (child \u2192 parent links for consolidation)"),
27818
27695
  significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Defaults to implementation when unset. Only meaningful on noteType=decision.")
27819
27696
  },
27820
- async ({ systemId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, confidence, affectsBeats, dependsOnNotes, significance }) => {
27697
+ async ({ orgId, systemId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, affectsBeats, dependsOnNotes, significance }) => {
27821
27698
  try {
27822
27699
  if ([systemId, teamspaceId, movementId].filter(Boolean).length > 1) {
27823
27700
  return { content: [{ type: "text", text: "Specify at most one of systemId, teamspaceId, or movementId." }], isError: true };
27824
27701
  }
27825
- const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
27826
27702
  let note;
27827
27703
  if (systemId) {
27828
- await assertSystemInOrg(client, systemId, ctx.orgId);
27704
+ await assertSystemExists(client, systemId);
27829
27705
  const projectCode = systemId.substring(0, 4).toUpperCase();
27830
27706
  const noteId = await client.getNextNoteId(projectCode, systemId);
27831
27707
  note = await client.createNote({
@@ -27841,7 +27717,6 @@ function registerNoteTools(server, ctx, client) {
27841
27717
  aiGenerated: false,
27842
27718
  affectsBeats,
27843
27719
  dependsOnNotes,
27844
- assumptionMeta,
27845
27720
  ...noteType === "decision" && significance !== void 0 && { significance }
27846
27721
  });
27847
27722
  } else if (teamspaceId) {
@@ -27849,8 +27724,11 @@ function registerNoteTools(server, ctx, client) {
27849
27724
  if (!teamspace) {
27850
27725
  return { content: [{ type: "text", text: `Teamspace not found: "${teamspaceId}"` }], isError: true };
27851
27726
  }
27852
- if (teamspace.orgId !== ctx.orgId) {
27853
- return { content: [{ type: "text", text: `Teamspace "${teamspaceId}" is not in the configured organization.` }], isError: true };
27727
+ if (orgId && teamspace.orgId !== orgId) {
27728
+ return {
27729
+ content: [{ type: "text", text: "Teamspace belongs to a different organization" }],
27730
+ isError: true
27731
+ };
27854
27732
  }
27855
27733
  note = await client.createTeamspaceNote(teamspaceId, teamspace.slug, {
27856
27734
  noteType,
@@ -27858,7 +27736,6 @@ function registerNoteTools(server, ctx, client) {
27858
27736
  rationale,
27859
27737
  createdBy: ctx.user.userId,
27860
27738
  aiGenerated: false,
27861
- assumptionMeta,
27862
27739
  ...noteType === "decision" && significance !== void 0 && { significance }
27863
27740
  });
27864
27741
  } else if (movementId) {
@@ -27866,26 +27743,24 @@ function registerNoteTools(server, ctx, client) {
27866
27743
  if (!movement) {
27867
27744
  return { content: [{ type: "text", text: `Movement not found: "${movementId}"` }], isError: true };
27868
27745
  }
27869
- if (movement.orgId !== ctx.orgId) {
27870
- return { content: [{ type: "text", text: `Movement "${movementId}" is not in the configured organization.` }], isError: true };
27871
- }
27872
27746
  note = await client.createMovementNote(movementId, movement.movementCode, {
27873
27747
  noteType,
27874
27748
  content,
27875
27749
  rationale,
27876
27750
  createdBy: ctx.user.userId,
27877
27751
  aiGenerated: false,
27878
- assumptionMeta,
27879
27752
  ...noteType === "decision" && significance !== void 0 && { significance }
27880
27753
  });
27881
27754
  } else {
27882
- note = await client.createOrgNote(ctx.orgId, {
27755
+ if (!orgId) {
27756
+ return { content: [{ type: "text", text: "Provide orgId for an org-level Note, or scope the Note with systemId, teamspaceId, or movementId." }], isError: true };
27757
+ }
27758
+ note = await client.createOrgNote(orgId, {
27883
27759
  noteType,
27884
27760
  content,
27885
27761
  rationale,
27886
27762
  createdBy: ctx.user.userId,
27887
- aiGenerated: false,
27888
- assumptionMeta
27763
+ aiGenerated: false
27889
27764
  });
27890
27765
  }
27891
27766
  const text = formatNoteDetail(note);
@@ -27905,7 +27780,7 @@ ${text}` }] };
27905
27780
  systemId: external_exports.string().optional().describe("The system ID (for access control). Provide systemId or orgId, not both."),
27906
27781
  orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
27907
27782
  noteId: external_exports.string().describe("The note ID to update"),
27908
- noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. assumption \u2192 guidance)"),
27783
+ noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. context \u2192 guidance)"),
27909
27784
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status"),
27910
27785
  content: external_exports.string().optional().describe("Updated content"),
27911
27786
  rationale: external_exports.string().optional().describe("Why this note exists or why this decision was made"),
@@ -27926,9 +27801,6 @@ ${text}` }] };
27926
27801
  return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
27927
27802
  }
27928
27803
  if (orgId) {
27929
- if (orgId !== ctx.orgId) {
27930
- return { content: [{ type: "text", text: "Access denied: orgId does not match the configured organization." }], isError: true };
27931
- }
27932
27804
  if (beatId !== void 0) {
27933
27805
  return { content: [{ type: "text", text: "beatId is not supported for org-level notes." }], isError: true };
27934
27806
  }
@@ -27953,7 +27825,7 @@ ${text}` }] };
27953
27825
 
27954
27826
  ${formatNoteDetail(updated2)}` }] };
27955
27827
  }
27956
- await assertSystemInOrg(client, systemId, ctx.orgId);
27828
+ await assertSystemExists(client, systemId);
27957
27829
  const updated = await client.updateNote(noteId, {
27958
27830
  ...noteType !== void 0 && { noteType },
27959
27831
  status,
@@ -27995,7 +27867,7 @@ ${text}` }] };
27995
27867
  },
27996
27868
  async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
27997
27869
  try {
27998
- await assertSystemInOrg(client, systemId, ctx.orgId);
27870
+ await assertSystemExists(client, systemId);
27999
27871
  const result = await client.bulkUpdateNotes(noteIds, {
28000
27872
  ...noteType !== void 0 && { noteType },
28001
27873
  status,
@@ -28034,7 +27906,7 @@ ${text}` }] };
28034
27906
  },
28035
27907
  async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
28036
27908
  try {
28037
- await assertSystemInOrg(client, systemId, ctx.orgId);
27909
+ await assertSystemExists(client, systemId);
28038
27910
  const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
28039
27911
  if (!updated) {
28040
27912
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -28056,7 +27928,7 @@ ${text}` }] };
28056
27928
  },
28057
27929
  async ({ systemId, noteId, reason }) => {
28058
27930
  try {
28059
- await assertSystemInOrg(client, systemId, ctx.orgId);
27931
+ await assertSystemExists(client, systemId);
28060
27932
  const note = await client.getNote(noteId);
28061
27933
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
28062
27934
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -28083,7 +27955,7 @@ ${text}` }] };
28083
27955
  },
28084
27956
  async ({ systemId, noteId, reason }) => {
28085
27957
  try {
28086
- await assertSystemInOrg(client, systemId, ctx.orgId);
27958
+ await assertSystemExists(client, systemId);
28087
27959
  const note = await client.getNote(noteId);
28088
27960
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
28089
27961
  return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
@@ -28106,29 +27978,6 @@ ${text}` }] };
28106
27978
  }
28107
27979
  }
28108
27980
  );
28109
- server.tool(
28110
- "list_key_assumptions",
28111
- "Get key assumptions sorted by criticality (highest risk first)",
28112
- {
28113
- systemId: external_exports.string().describe("The system ID")
28114
- },
28115
- async ({ systemId }) => {
28116
- await assertSystemInOrg(client, systemId, ctx.orgId);
28117
- const assumptions = await client.getKeyAssumptions(systemId);
28118
- if (assumptions.length === 0) {
28119
- return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
28120
- }
28121
- const lines = assumptions.map((a, i) => {
28122
- const conf = a.assumptionMeta?.confidence?.toFixed(2) ?? "?";
28123
- const crit = a.criticality?.toFixed(2) ?? "?";
28124
- return `${i + 1}. [confidence: ${conf}, criticality: ${crit}] ${a.content}`;
28125
- });
28126
- const text = `**${assumptions.length} Key Assumptions** (sorted by criticality)
28127
-
28128
- ${lines.join("\n")}`;
28129
- return { content: [{ type: "text", text }] };
28130
- }
28131
- );
28132
27981
  }
28133
27982
 
28134
27983
  // ../../libs/harmonica-services/src/mcp/tools/notebook-tools.ts
@@ -28193,26 +28042,27 @@ function formatTree(node, depth = 0) {
28193
28042
  function registerNotebookTools(server, ctx, client) {
28194
28043
  server.tool(
28195
28044
  "create_notebook",
28196
- "Create a new Notebook in the configured organization. Optionally bind it to one or more scopes (org, teamspace, or system) at creation.",
28045
+ "Create a new Notebook in an organization. Optionally bind it to one or more scopes (org, teamspace, or system) at creation.",
28197
28046
  {
28047
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
28198
28048
  title: external_exports.string().min(1).max(300).describe("Notebook title (1\u2013300 characters)"),
28199
28049
  description: external_exports.string().max(2e4).optional().describe("Optional description (max 20 000 characters)"),
28200
28050
  scopes: external_exports.array(scopeRefSchema).optional().describe("Optional initial scope bindings"),
28201
28051
  createdBy: external_exports.string().optional().describe("Actor ID \u2014 defaults to the session user")
28202
28052
  },
28203
- async ({ title, description, scopes, createdBy }) => {
28053
+ async ({ orgId, title, description, scopes, createdBy }) => {
28204
28054
  const actorId = createdBy ?? ctx.user.userId ?? "agent";
28205
28055
  try {
28206
28056
  const notebook = await client.createNotebook({
28207
28057
  notebookId: `nb-${(0, import_crypto4.randomUUID)()}`,
28208
- orgId: ctx.orgId,
28058
+ orgId,
28209
28059
  title,
28210
28060
  description,
28211
28061
  createdBy: actorId,
28212
28062
  scopes
28213
28063
  });
28214
28064
  if (!notebook) {
28215
- return { content: [{ type: "text", text: `Organization not found: "${ctx.orgId}"` }], isError: true };
28065
+ return { content: [{ type: "text", text: `Organization not found: "${orgId}"` }], isError: true };
28216
28066
  }
28217
28067
  return { content: [{ type: "text", text: `Notebook created.
28218
28068
 
@@ -28232,7 +28082,7 @@ ${formatNotebook(notebook)}` }] };
28232
28082
  async ({ notebookId }) => {
28233
28083
  let notebook;
28234
28084
  try {
28235
- notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28085
+ notebook = await fetchNotebook(client, notebookId);
28236
28086
  } catch (err) {
28237
28087
  const message = err instanceof Error ? err.message : String(err);
28238
28088
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28256,7 +28106,7 @@ ${formatNotebook(notebook)}` }] };
28256
28106
  },
28257
28107
  async ({ notebookId }) => {
28258
28108
  try {
28259
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28109
+ await fetchNotebook(client, notebookId);
28260
28110
  } catch (err) {
28261
28111
  const message = err instanceof Error ? err.message : String(err);
28262
28112
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28291,7 +28141,7 @@ ${formatTree(tree.root)}` }] };
28291
28141
  async ({ notebookId, title, content, parentNoteId, style }) => {
28292
28142
  let notebook;
28293
28143
  try {
28294
- notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28144
+ notebook = await fetchNotebook(client, notebookId);
28295
28145
  } catch (err) {
28296
28146
  const message = err instanceof Error ? err.message : String(err);
28297
28147
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28328,7 +28178,7 @@ ${formatTree(tree.root)}` }] };
28328
28178
  const pageContent = buildPageNoteContent(title, content);
28329
28179
  let note;
28330
28180
  try {
28331
- note = await client.createOrgNote(ctx.orgId, {
28181
+ note = await client.createOrgNote(notebook.orgId, {
28332
28182
  noteType: "context",
28333
28183
  content: pageContent,
28334
28184
  createdBy: actorId,
@@ -28347,7 +28197,7 @@ ${formatTree(tree.root)}` }] };
28347
28197
  variant: style ?? "card",
28348
28198
  position,
28349
28199
  depth: placement.childDepth,
28350
- orgId: ctx.orgId,
28200
+ orgId: notebook.orgId,
28351
28201
  writeUsedByEdge: true
28352
28202
  });
28353
28203
  if (!addResult.success) {
@@ -28384,7 +28234,7 @@ ${formatTree(tree.root)}` }] };
28384
28234
  async ({ notebookId, heading, parentNoteId, noteType, systemId, limit, order, significance }) => {
28385
28235
  let notebook;
28386
28236
  try {
28387
- notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28237
+ notebook = await fetchNotebook(client, notebookId);
28388
28238
  } catch (err) {
28389
28239
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
28390
28240
  }
@@ -28442,7 +28292,7 @@ ${formatTree(tree.root)}` }] };
28442
28292
  heading,
28443
28293
  position,
28444
28294
  depth: placement.childDepth,
28445
- orgId: ctx.orgId
28295
+ orgId: notebook.orgId
28446
28296
  });
28447
28297
  if (!result.success) {
28448
28298
  return { content: [{ type: "text", text: `Failed to add collection: ${result.error?.message ?? "unknown error"}` }], isError: true };
@@ -28472,7 +28322,7 @@ ${formatTree(tree.root)}` }] };
28472
28322
  async ({ notebookId, elementId, parentNoteId, heading, noteType, systemId, limit, order, significance }) => {
28473
28323
  let notebook;
28474
28324
  try {
28475
- notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28325
+ notebook = await fetchNotebook(client, notebookId);
28476
28326
  } catch (err) {
28477
28327
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
28478
28328
  }
@@ -28543,7 +28393,7 @@ ${formatTree(tree.root)}` }] };
28543
28393
  },
28544
28394
  async ({ notebookId, title, description }) => {
28545
28395
  try {
28546
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28396
+ await fetchNotebook(client, notebookId);
28547
28397
  } catch (err) {
28548
28398
  const message = err instanceof Error ? err.message : String(err);
28549
28399
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28571,7 +28421,7 @@ ${formatNotebook(updated)}` }] };
28571
28421
  },
28572
28422
  async ({ notebookId, scopes }) => {
28573
28423
  try {
28574
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28424
+ await fetchNotebook(client, notebookId);
28575
28425
  } catch (err) {
28576
28426
  const message = err instanceof Error ? err.message : String(err);
28577
28427
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28628,7 +28478,7 @@ ${lines.join("\n")}` }] };
28628
28478
  },
28629
28479
  async ({ notebookId, status, reason }) => {
28630
28480
  try {
28631
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28481
+ await fetchNotebook(client, notebookId);
28632
28482
  } catch (err) {
28633
28483
  const message = err instanceof Error ? err.message : String(err);
28634
28484
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28665,7 +28515,7 @@ ${formatNotebook(updated)}` }] };
28665
28515
  },
28666
28516
  async ({ notebookId, scopeType, scopeId, createdBy }) => {
28667
28517
  try {
28668
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28518
+ await fetchNotebook(client, notebookId);
28669
28519
  } catch (err) {
28670
28520
  const message = err instanceof Error ? err.message : String(err);
28671
28521
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28693,7 +28543,7 @@ ${formatNotebook(updated)}` }] };
28693
28543
  },
28694
28544
  async ({ notebookId, scopeType, scopeId }) => {
28695
28545
  try {
28696
- await fetchNotebookInOrg(client, notebookId, ctx.orgId);
28546
+ await fetchNotebook(client, notebookId);
28697
28547
  } catch (err) {
28698
28548
  const message = err instanceof Error ? err.message : String(err);
28699
28549
  return { content: [{ type: "text", text: message }], isError: true };
@@ -28738,7 +28588,7 @@ ${lines.join("\n")}` }] };
28738
28588
  // ../../libs/harmonica-services/src/mcp/tools/onboarding-tools.ts
28739
28589
  var import_node_crypto = require("node:crypto");
28740
28590
  var OnboardingNoteInputSchema = external_exports.object({
28741
- noteType: external_exports.enum(["context", "assumption", "constraint", "guidance", "decision"]),
28591
+ noteType: external_exports.enum(["context", "constraint", "guidance", "decision"]),
28742
28592
  content: external_exports.string().min(1),
28743
28593
  rationale: external_exports.string().optional(),
28744
28594
  confidence: external_exports.number().min(0).max(1).optional()
@@ -28760,12 +28610,13 @@ function registerOnboardingTools(server, ctx, client) {
28760
28610
  "Returns the full analysis and gap classification for pre-flight review \u2014 nothing is persisted yet."
28761
28611
  ].join(" "),
28762
28612
  {
28613
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
28763
28614
  clientName: external_exports.string().min(1).max(200).describe("The client company name \u2014 used to check for duplicate Teamspaces"),
28764
28615
  pmContext: external_exports.string().min(1).describe('What the PM said to initiate the session (e.g. "I just had an intro call with...")'),
28765
28616
  documentContent: external_exports.string().min(1).describe("Full text content of all attached documents, concatenated")
28766
28617
  },
28767
- async ({ clientName, pmContext, documentContent }) => {
28768
- const result = await client.initiateOnboarding(ctx.orgId, { clientName, pmContext, documentContent });
28618
+ async ({ orgId, clientName, pmContext, documentContent }) => {
28619
+ const result = await client.initiateOnboarding(orgId, { clientName, pmContext, documentContent });
28769
28620
  if (result.isDuplicate) {
28770
28621
  return {
28771
28622
  content: [{
@@ -28816,7 +28667,7 @@ function registerOnboardingTools(server, ctx, client) {
28816
28667
  analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
28817
28668
  },
28818
28669
  async ({ systemId, systemTitle, systemDescription, analysisJson }) => {
28819
- await assertSystemInOrg(client, systemId, ctx.orgId);
28670
+ await assertSystemExists(client, systemId);
28820
28671
  const result = await client.draftOnboardingBeats(systemId, { systemTitle, systemDescription, analysisJson });
28821
28672
  if (!result.suggestions || result.suggestions.length === 0) {
28822
28673
  return {
@@ -28842,6 +28693,10 @@ function registerOnboardingTools(server, ctx, client) {
28842
28693
  return { content: [{ type: "text", text: lines.join("\n") }] };
28843
28694
  }
28844
28695
  );
28696
+ const OnboardingTrackInputSchema = external_exports.object({
28697
+ title: external_exports.string().min(1).max(300),
28698
+ description: external_exports.string().max(2e3).optional()
28699
+ });
28845
28700
  server.tool(
28846
28701
  "create_teamspace_onboarding_batch",
28847
28702
  [
@@ -28852,22 +28707,40 @@ function registerOnboardingTools(server, ctx, client) {
28852
28707
  "If any step fails, the receipt reports what succeeded and where creation stopped."
28853
28708
  ].join(" "),
28854
28709
  {
28710
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
28855
28711
  teamspaceName: external_exports.string().min(1).max(200).describe("Teamspace display name"),
28856
- projects: external_exports.array(OnboardingSystemInputSchema).min(1).describe("Phase 1 systems to create, each linked to the Teamspace"),
28857
- teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/system level")
28712
+ systems: external_exports.array(OnboardingSystemInputSchema).min(1).describe("Phase 1 systems to create, each linked to the Teamspace"),
28713
+ teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/system level"),
28714
+ account: external_exports.object({ name: external_exports.string().min(1).max(300), description: external_exports.string().max(2e3).optional() }).optional().describe("New client Account to create for this engagement. Omit to skip commercial scaffolding."),
28715
+ movement: external_exports.object({ title: external_exports.string().min(1).max(300), description: external_exports.string().max(2e3).optional() }).optional().describe("SOW/engagement Movement to create under the Account. Only used when account is provided."),
28716
+ tracks: external_exports.array(OnboardingTrackInputSchema).max(10).optional().describe("Tracks (phases/workstreams) to create under the Movement, capped at 10. Defaults to one Track named after the first System when omitted or empty."),
28717
+ drop: external_exports.object({ name: external_exports.string().min(1).max(200), description: external_exports.string().max(2e3).optional() }).optional().describe('Optional initial Drop (e.g. "MVP") to create under the Account. Only created when explicitly provided.')
28858
28718
  },
28859
28719
  async ({
28720
+ orgId,
28860
28721
  teamspaceName,
28861
- projects,
28862
- teamspaceNotes
28722
+ systems,
28723
+ teamspaceNotes,
28724
+ account,
28725
+ movement,
28726
+ tracks,
28727
+ drop
28863
28728
  }) => {
28729
+ if ((movement || tracks && tracks.length > 0 || drop) && !account) {
28730
+ return {
28731
+ content: [{
28732
+ type: "text",
28733
+ text: "\u26A0\uFE0F movement, tracks, and drop are only created when account is provided. Add an account or omit the commercial fields."
28734
+ }]
28735
+ };
28736
+ }
28864
28737
  const lines = [];
28865
28738
  let teamspace;
28866
28739
  try {
28867
28740
  teamspace = await client.createTeamspace({
28868
28741
  teamspaceId: (0, import_node_crypto.randomUUID)(),
28869
28742
  slug: teamspaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
28870
- orgId: ctx.orgId,
28743
+ orgId,
28871
28744
  name: teamspaceName,
28872
28745
  createdBy: ctx.user?.userId
28873
28746
  });
@@ -28879,31 +28752,31 @@ Teamspace: [${teamspace.teamspaceId}] ${teamspace.name}`);
28879
28752
  Error: ${msg}`);
28880
28753
  return { content: [{ type: "text", text: lines.join("\n") }] };
28881
28754
  }
28882
- const createdProjects = [];
28883
- for (const p of projects) {
28755
+ const createdSystems = [];
28756
+ for (const p of systems) {
28884
28757
  try {
28885
28758
  const project = await client.createSystem({
28886
28759
  systemId: (0, import_node_crypto.randomUUID)(),
28887
- orgId: ctx.orgId,
28760
+ orgId,
28888
28761
  teamspaceId: teamspace.teamspaceId,
28889
28762
  ownerUserId: ctx.user?.userId ?? "system",
28890
28763
  title: p.title,
28891
28764
  description: p.description
28892
28765
  });
28893
- createdProjects.push({ systemId: project.systemId, title: project.title });
28766
+ createdSystems.push({ systemId: project.systemId, title: project.title });
28894
28767
  } catch (err) {
28895
28768
  const msg = err instanceof Error ? err.message : String(err);
28896
28769
  lines.push(`
28897
- Systems created: ${createdProjects.length}`);
28770
+ Systems created: ${createdSystems.length}`);
28898
28771
  lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: system "${p.title}"
28899
28772
  Error: ${msg}`);
28900
28773
  return { content: [{ type: "text", text: lines.join("\n") }] };
28901
28774
  }
28902
28775
  }
28903
- if (createdProjects.length > 0) {
28776
+ if (createdSystems.length > 0) {
28904
28777
  lines.push(`
28905
- Systems (${createdProjects.length}):`);
28906
- for (const p of createdProjects) lines.push(` [${p.systemId}] ${p.title}`);
28778
+ Systems (${createdSystems.length}):`);
28779
+ for (const p of createdSystems) lines.push(` [${p.systemId}] ${p.title}`);
28907
28780
  }
28908
28781
  const createdNotes = [];
28909
28782
  const teamspaceSlug = teamspace.slug ?? teamspaceName.toLowerCase().replace(/\s+/g, "-");
@@ -28922,9 +28795,9 @@ Systems (${createdProjects.length}):`);
28922
28795
  } catch {
28923
28796
  }
28924
28797
  }
28925
- for (let i = 0; i < createdProjects.length; i++) {
28926
- const project = createdProjects[i];
28927
- for (const note of projects[i]?.notes ?? []) {
28798
+ for (let i = 0; i < createdSystems.length; i++) {
28799
+ const project = createdSystems[i];
28800
+ for (const note of systems[i]?.notes ?? []) {
28928
28801
  try {
28929
28802
  const n = await client.createNote({
28930
28803
  noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
@@ -28944,6 +28817,93 @@ Systems (${createdProjects.length}):`);
28944
28817
  }
28945
28818
  lines.push(`
28946
28819
  Notes filed: ${createdNotes.length}`);
28820
+ if (account) {
28821
+ let createdAccountId;
28822
+ try {
28823
+ const createdAccount = await client.createAccount({
28824
+ orgId,
28825
+ name: account.name,
28826
+ description: account.description,
28827
+ accountType: "client",
28828
+ createdBy: ctx.user?.userId
28829
+ });
28830
+ createdAccountId = createdAccount.accountId;
28831
+ lines.push(`
28832
+ Account: [${createdAccount.accountId}] ${createdAccount.name}`);
28833
+ } catch (err) {
28834
+ const msg = err instanceof Error ? err.message : String(err);
28835
+ lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: account
28836
+ Error: ${msg}`);
28837
+ return { content: [{ type: "text", text: lines.join("\n") }] };
28838
+ }
28839
+ if (movement && createdAccountId) {
28840
+ let createdMovementId;
28841
+ try {
28842
+ const createdMovement = await client.createMovement({
28843
+ teamspaceId: teamspace.teamspaceId,
28844
+ title: movement.title,
28845
+ description: movement.description,
28846
+ accountId: createdAccountId,
28847
+ createdBy: ctx.user?.userId
28848
+ });
28849
+ createdMovementId = createdMovement.movementId;
28850
+ lines.push(`
28851
+ Movement: [${createdMovement.movementId}] ${createdMovement.title}`);
28852
+ } catch (err) {
28853
+ const msg = err instanceof Error ? err.message : String(err);
28854
+ lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: movement
28855
+ Error: ${msg}`);
28856
+ return { content: [{ type: "text", text: lines.join("\n") }] };
28857
+ }
28858
+ const MAX_TRACKS = 10;
28859
+ const trackInputs = (tracks ?? []).slice(0, MAX_TRACKS);
28860
+ const fallbackTitle = createdSystems[0]?.title ?? "Phase 1";
28861
+ const tracksToCreate = trackInputs.length > 0 ? trackInputs : [{ title: fallbackTitle }];
28862
+ const createdTracks = [];
28863
+ for (const trackInput of tracksToCreate) {
28864
+ try {
28865
+ const track = await client.createDeliverableGroup({
28866
+ orgId,
28867
+ teamspaceId: teamspace.teamspaceId,
28868
+ movementId: createdMovementId,
28869
+ title: trackInput.title,
28870
+ description: trackInput.description,
28871
+ vuRate: 0,
28872
+ createdBy: ctx.user?.userId
28873
+ });
28874
+ createdTracks.push({ id: track.deliverableGroupId, title: track.title });
28875
+ } catch (err) {
28876
+ const msg = err instanceof Error ? err.message : String(err);
28877
+ lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: track:${trackInput.title}
28878
+ Error: ${msg}`);
28879
+ return { content: [{ type: "text", text: lines.join("\n") }] };
28880
+ }
28881
+ }
28882
+ if (createdTracks.length > 0) {
28883
+ lines.push(`
28884
+ Tracks (${createdTracks.length}):`);
28885
+ for (const t of createdTracks) lines.push(` [${t.id}] ${t.title}`);
28886
+ }
28887
+ }
28888
+ if (drop && createdAccountId) {
28889
+ try {
28890
+ const createdDrop = await client.createDrop({
28891
+ accountId: createdAccountId,
28892
+ teamspaceId: teamspace.teamspaceId,
28893
+ name: drop.name,
28894
+ description: drop.description,
28895
+ createdBy: ctx.user?.userId
28896
+ });
28897
+ lines.push(`
28898
+ Drop: [${createdDrop.dropId}] ${createdDrop.name}`);
28899
+ } catch (err) {
28900
+ const msg = err instanceof Error ? err.message : String(err);
28901
+ lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: drop
28902
+ Error: ${msg}`);
28903
+ return { content: [{ type: "text", text: lines.join("\n") }] };
28904
+ }
28905
+ }
28906
+ }
28947
28907
  lines.unshift(`\u2705 Onboarding batch created successfully.`);
28948
28908
  return { content: [{ type: "text", text: lines.join("\n") }] };
28949
28909
  }
@@ -29114,10 +29074,7 @@ function registerOrganizationTools(server, ctx, client) {
29114
29074
  if (!system) {
29115
29075
  return { content: [{ type: "text", text: `System not found: ${systemId}` }], isError: true };
29116
29076
  }
29117
- if (system.orgId !== ctx.orgId) {
29118
- return { content: [{ type: "text", text: `System "${systemId}" does not belong to the configured organization.` }], isError: true };
29119
- }
29120
- const result = await client.transferSystem(ctx.orgId, systemId, targetOrgId);
29077
+ const result = await client.transferSystem(system.orgId, systemId, targetOrgId);
29121
29078
  if (result.error) {
29122
29079
  return { content: [{ type: "text", text: result.error }], isError: true };
29123
29080
  }
@@ -29224,7 +29181,7 @@ async function pollForCheck3(client, taskId) {
29224
29181
  }
29225
29182
  consecutiveErrors = 0;
29226
29183
  if (task.status === "completed") {
29227
- if (!task.result) throw new Error(`Task ${taskId} completed with no result`);
29184
+ if (!task.result) throw new Error(`async:completed:no-result Task ${taskId} succeeded on the async path but the scorecard is not yet readable on the blocking path \u2014 use list_checks or get_job_status to retrieve it.`);
29228
29185
  return task.result;
29229
29186
  }
29230
29187
  if (task.status === "failed") throw new Error(`Beat version quality check failed: ${task.error ?? "unknown error"}`);
@@ -29268,7 +29225,7 @@ function registerPlanQualityTools(server, ctx, client) {
29268
29225
  isError: true
29269
29226
  };
29270
29227
  }
29271
- await assertSystemInOrg(client, systemId, ctx.orgId);
29228
+ await assertSystemExists(client, systemId);
29272
29229
  const opts = branch || localPath ? { branch, localPath } : void 0;
29273
29230
  const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
29274
29231
  if (!wait) {
@@ -29415,7 +29372,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29415
29372
  },
29416
29373
  async ({ systemId, wait }) => {
29417
29374
  try {
29418
- await assertSystemInOrg(client, systemId, ctx.orgId);
29375
+ await assertSystemExists(client, systemId);
29419
29376
  const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
29420
29377
  if (!wait) {
29421
29378
  return {
@@ -29452,7 +29409,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29452
29409
  const archivedBeatIds = [];
29453
29410
  let totalNotesReassigned = 0;
29454
29411
  try {
29455
- await assertSystemInOrg(client, systemId, ctx.orgId);
29412
+ await assertSystemExists(client, systemId);
29456
29413
  const uniqueBeatIds = [...new Set(beatIds)];
29457
29414
  const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
29458
29415
  if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
@@ -29656,7 +29613,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29656
29613
  },
29657
29614
  async ({ beatId, title, description, changeSummary, beatVersionId, baseRevisionId, tags, priority, estimatedEffort, humanAssignee, agentAssignee }) => {
29658
29615
  try {
29659
- await assertBeatInOrg(client, beatId, ctx.orgId);
29616
+ await assertBeatExists(client, beatId);
29660
29617
  const beat = await client.getBeat(beatId);
29661
29618
  if (!beat) {
29662
29619
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
@@ -29711,8 +29668,8 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29711
29668
  }
29712
29669
  if (parentBv && !parentBv.targetDropId) {
29713
29670
  try {
29714
- const project = await client.getSystem(parentBv.systemId);
29715
- const accountId = project?.accountId;
29671
+ const system = await client.getSystem(parentBv.systemId);
29672
+ const accountId = system?.accountId;
29716
29673
  if (accountId) {
29717
29674
  const draftDrops = await client.listAccountDrops(accountId, { state: "draft" });
29718
29675
  lines.push("");
@@ -29735,6 +29692,21 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29735
29692
  }
29736
29693
  return { content: [{ type: "text", text: lines.join("\n") }] };
29737
29694
  } catch (err) {
29695
+ if (err instanceof Error && err.name === "BeatVersionPlanQualityError" && "failure" in err && isRenderableGateFailure(err.failure)) {
29696
+ const f = err.failure;
29697
+ const isStale = f.reason === "stale_check_hash" || f.reason === "stale_check_timestamp";
29698
+ const lines = [
29699
+ `**Gate:** beat_version_quality`,
29700
+ `**Status:** ${isStale ? "stale" : "failed"} (${f.reason})`,
29701
+ `**Detail:** ${f.message}`
29702
+ ];
29703
+ if (f.entityId) lines.push(`**Beat Version:** ${f.entityId}`);
29704
+ if (f.remedy) lines.push(`**Action:** ${f.remedy}`);
29705
+ return {
29706
+ content: [{ type: "text", text: lines.join("\n") }],
29707
+ isError: true
29708
+ };
29709
+ }
29738
29710
  const message = err instanceof Error ? err.message : String(err);
29739
29711
  return { content: [{ type: "text", text: `Failed to create revision: ${message}` }], isError: true };
29740
29712
  }
@@ -29761,7 +29733,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29761
29733
  if (!revision) {
29762
29734
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
29763
29735
  }
29764
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
29736
+ await assertBeatExists(client, revision.beatId);
29765
29737
  if (typeof beatVersionId === "string") {
29766
29738
  const bv = await client.getBeatVersion(beatVersionId);
29767
29739
  if (!bv) {
@@ -29830,7 +29802,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29830
29802
  if (revision.status === targetState) {
29831
29803
  return { content: [{ type: "text", text: `Revision is already at ${targetState}.` }], isError: true };
29832
29804
  }
29833
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
29805
+ await assertBeatExists(client, revision.beatId);
29834
29806
  const transitionOptions = {
29835
29807
  actor: { type: "human", id: ctx.user.userId, name: ctx.user.name, email: ctx.user.email },
29836
29808
  trigger: "human_action",
@@ -29880,7 +29852,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29880
29852
  if (!revision) {
29881
29853
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
29882
29854
  }
29883
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
29855
+ await assertBeatExists(client, revision.beatId);
29884
29856
  if (!revision.scm && (!url || !branch)) {
29885
29857
  return {
29886
29858
  content: [{ type: "text", text: "This Revision has no existing SCM record. Provide url and branch to retro-attach a PR from scratch." }],
@@ -29928,7 +29900,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29928
29900
  if (!revision) {
29929
29901
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
29930
29902
  }
29931
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
29903
+ await assertBeatExists(client, revision.beatId);
29932
29904
  const latitude = await checkDeliveryStep(client, revisionId, "openPullRequest");
29933
29905
  if ("refusal" in latitude) {
29934
29906
  return { content: [{ type: "text", text: latitude.refusal }], isError: true };
@@ -29944,13 +29916,13 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29944
29916
  if (revision.scm) {
29945
29917
  lines2.push("", `\u26A0 This revision already has a linked PR (#${revision.scm.number} \u2014 ${revision.scm.url}). create_pr_for_revision would be rejected.`);
29946
29918
  } else {
29947
- const project = await client.getSystem(revision.systemId);
29948
- if (!project?.repoOwner || !project?.repoName || !project?.repoDefaultBranch) {
29949
- lines2.push("", "\u26A0 The project has no repo configured (repoOwner/repoName/repoDefaultBranch). create_pr_for_revision would be rejected.");
29919
+ const system = await client.getSystem(revision.systemId);
29920
+ if (!system?.repoOwner || !system?.repoName || !system?.repoDefaultBranch) {
29921
+ lines2.push("", "\u26A0 The system has no repo configured (repoOwner/repoName/repoDefaultBranch). create_pr_for_revision would be rejected.");
29950
29922
  } else {
29951
29923
  lines2.push(
29952
29924
  "",
29953
- `Would open a draft PR against \`${project.repoOwner}/${project.repoName}:${project.repoDefaultBranch}\` on a new \`agent/${shortId}-\u2026\` branch, with body starting \`Harmonica: ${revisionId}\`.`
29925
+ `Would open a draft PR against \`${system.repoOwner}/${system.repoName}:${system.repoDefaultBranch}\` on a new \`agent/${shortId}-\u2026\` branch, with body starting \`Harmonica: ${revisionId}\`.`
29954
29926
  );
29955
29927
  }
29956
29928
  }
@@ -29995,7 +29967,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29995
29967
  if (!revision) {
29996
29968
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
29997
29969
  }
29998
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
29970
+ await assertBeatExists(client, revision.beatId);
29999
29971
  const latitude = await checkDeliveryStep(client, revisionId, "requestAgentReview");
30000
29972
  if ("refusal" in latitude) {
30001
29973
  return { content: [{ type: "text", text: latitude.refusal }], isError: true };
@@ -30025,7 +29997,7 @@ ${latitude.notice}` }] };
30025
29997
  if (!revision) {
30026
29998
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
30027
29999
  }
30028
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
30000
+ await assertBeatExists(client, revision.beatId);
30029
30001
  const result = await client.getPrDiff(revisionId);
30030
30002
  if (!result.success) {
30031
30003
  return { content: [{ type: "text", text: `Failed to get diff (${result.error.code}): ${result.error.message}` }], isError: true };
@@ -30059,7 +30031,7 @@ Line-level comments are optional \u2014 include them to anchor feedback to speci
30059
30031
  if (!revision) {
30060
30032
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
30061
30033
  }
30062
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
30034
+ await assertBeatExists(client, revision.beatId);
30063
30035
  const result = await client.postPrReview(revisionId, { prNumber: 0, event, body, comments });
30064
30036
  if (!result.success) {
30065
30037
  return { content: [{ type: "text", text: `Failed to post review (${result.error.code}): ${result.error.message}` }], isError: true };
@@ -30095,7 +30067,7 @@ Error codes: PR_NOT_OPEN (draft or already merged/closed), MERGE_FAILED (GitHub
30095
30067
  if (!revision) {
30096
30068
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
30097
30069
  }
30098
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
30070
+ await assertBeatExists(client, revision.beatId);
30099
30071
  const options = mergeMethod !== void 0 || commitTitle !== void 0 ? { mergeMethod, commitTitle } : void 0;
30100
30072
  const result = await client.mergePr(revisionId, options);
30101
30073
  if (!result.success) {
@@ -30129,7 +30101,7 @@ Error code: PR_NOT_DRAFT (the PR is already ready for review or was never a draf
30129
30101
  if (!revision) {
30130
30102
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
30131
30103
  }
30132
- await assertBeatInOrg(client, revision.beatId, ctx.orgId);
30104
+ await assertBeatExists(client, revision.beatId);
30133
30105
  const result = await client.markPrReadyForReview(revisionId);
30134
30106
  if (!result.success) {
30135
30107
  return { content: [{ type: "text", text: `Failed to mark PR ready (${result.error.code}): ${result.error.message}` }], isError: true };
@@ -30220,7 +30192,7 @@ function registerRevisionQualityTools(server, ctx, client) {
30220
30192
  isError: true
30221
30193
  };
30222
30194
  }
30223
- await assertSystemInOrg(client, systemId, ctx.orgId);
30195
+ await assertSystemExists(client, systemId);
30224
30196
  await assertRevisionInSystem(client, revisionId, systemId);
30225
30197
  const task = await client.runCheck(systemId, "revision_quality", revisionId);
30226
30198
  if (!wait) {
@@ -30526,7 +30498,7 @@ function registerSnapshotTools(server, ctx, client) {
30526
30498
  "Export a complete system snapshot for environment sync. On a remote server (production/staging) returns an S3 presigned URL; on a local server writes to a temp file and returns the path.",
30527
30499
  { systemId: external_exports.string().describe("The system ID to export") },
30528
30500
  async ({ systemId }) => {
30529
- await assertSystemInOrg(client, systemId, ctx.orgId);
30501
+ await assertSystemExists(client, systemId);
30530
30502
  const result = await client.exportSystemSnapshot(systemId);
30531
30503
  if (typeof result === "string") {
30532
30504
  if (!result.startsWith("https://")) {
@@ -30586,8 +30558,8 @@ function registerSnapshotTools(server, ctx, client) {
30586
30558
  targetTeamspaceId: external_exports.string().min(1).optional().describe(
30587
30559
  "Remap all teamspace references (Drops, Deliverables, Project record) to this teamspace ID in the target environment. Required when the source and target environments use different teamspace UUIDs. When omitted and the snapshot contains Drops or Deliverables, a warning is included in the import summary."
30588
30560
  ),
30589
- targetOrgId: external_exports.string().min(1).optional().describe(
30590
- "Import all records under this org ID instead of the caller's org. Required when syncing from production to a local environment where the org UUID differs. When omitted, the caller's org ID is used."
30561
+ targetOrgId: external_exports.string().min(1).describe(
30562
+ "Import all records under this org ID. Named explicitly rather than defaulted: a snapshot carries the org UUID of wherever it was taken, which differs between environments, and there is no session org to fall back to. See list_organizations."
30591
30563
  )
30592
30564
  },
30593
30565
  async ({ snapshot: snapshotInput, targetTeamspaceId, targetOrgId }) => {
@@ -30603,7 +30575,7 @@ function registerSnapshotTools(server, ctx, client) {
30603
30575
  };
30604
30576
  }
30605
30577
  const trimmed = snapshotInput.trim();
30606
- const importOptions = { targetOrgId: targetOrgId ?? ctx.orgId, targetUserId: ctx.user.userId, targetTeamspaceId };
30578
+ const importOptions = { targetOrgId, targetUserId: ctx.user.userId, targetTeamspaceId };
30607
30579
  if (trimmed.startsWith("https://")) {
30608
30580
  if (isPrivateHost(trimmed)) {
30609
30581
  throw new Error("Snapshot URL must point to a public host \u2014 private, link-local, and loopback addresses are not permitted.");
@@ -30778,7 +30750,7 @@ function registerSubscriptionTools(server, ctx, client) {
30778
30750
  },
30779
30751
  async ({ entityType, entityId, systemId }) => {
30780
30752
  try {
30781
- await assertSystemInOrg(client, systemId, ctx.orgId);
30753
+ await assertSystemExists(client, systemId);
30782
30754
  await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
30783
30755
  return {
30784
30756
  content: [{
@@ -30869,9 +30841,6 @@ function registerSystemLifecycleTools(server, ctx, client) {
30869
30841
  if (!system) {
30870
30842
  return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
30871
30843
  }
30872
- if (system.orgId !== ctx.orgId) {
30873
- return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
30874
- }
30875
30844
  const result = await client.transitionSystemLifecycleState(systemId, targetState, {
30876
30845
  actor: { type: "human", id: ctx.user.userId, name: ctx.user.name },
30877
30846
  reason,
@@ -30916,9 +30885,9 @@ function accountLine(_request, resolved) {
30916
30885
  }
30917
30886
  var PROJECT_EMBEDDING_FIELDS = ["title", "description", "strategy"];
30918
30887
  function registerSystemTools(server, ctx, client) {
30919
- const listSystemsHandler = async ({ teamspaceId }) => {
30888
+ const listSystemsHandler = async ({ orgId, teamspaceId }) => {
30920
30889
  try {
30921
- const projects = await client.listOrgSystems(ctx.orgId);
30890
+ const projects = await client.listOrgSystems(orgId);
30922
30891
  const trimmed = teamspaceId?.trim();
30923
30892
  const filtered = trimmed ? projects.filter((p) => p.teamspaceId === trimmed) : projects;
30924
30893
  const text = formatSystemSummaryTable(filtered);
@@ -30930,8 +30899,9 @@ function registerSystemTools(server, ctx, client) {
30930
30899
  };
30931
30900
  server.tool(
30932
30901
  "list_systems",
30933
- "List all systems in the configured organization",
30902
+ "List all systems in an organization",
30934
30903
  {
30904
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
30935
30905
  teamspaceId: external_exports.string().optional().describe("Filter to systems belonging to a specific teamspace. Empty or whitespace-only treated as no filter.")
30936
30906
  },
30937
30907
  listSystemsHandler
@@ -30947,10 +30917,10 @@ function registerSystemTools(server, ctx, client) {
30947
30917
  noteLimit
30948
30918
  }) => {
30949
30919
  try {
30950
- const [project, org] = await Promise.all([
30951
- fetchSystemInOrg(client, systemId, ctx.orgId),
30952
- client.getOrg(ctx.orgId)
30920
+ const [project] = await Promise.all([
30921
+ fetchSystem(client, systemId)
30953
30922
  ]);
30923
+ const org = await client.getOrg(project.orgId);
30954
30924
  const notes = await client.listSystemNotes(systemId, {
30955
30925
  limit: noteLimit ?? DEFAULT_CONTEXT_NOTE_LIMIT
30956
30926
  });
@@ -30992,7 +30962,7 @@ function registerSystemTools(server, ctx, client) {
30992
30962
  if (Object.keys(nonEmpty).length === 0) {
30993
30963
  return { content: [{ type: "text", text: "No updates provided." }], isError: true };
30994
30964
  }
30995
- const currentSystem = await fetchSystemInOrg(client, systemId, ctx.orgId);
30965
+ const currentSystem = await fetchSystem(client, systemId);
30996
30966
  if (nonEmpty["accountId"] === "") delete nonEmpty["accountId"];
30997
30967
  if (nonEmpty["agentDeliveryPolicy"] && typeof nonEmpty["agentDeliveryPolicy"] === "object") {
30998
30968
  nonEmpty["agentDeliveryPolicy"] = mergeAgentDeliveryPolicy(
@@ -31005,8 +30975,11 @@ function registerSystemTools(server, ctx, client) {
31005
30975
  if (!teamspace) {
31006
30976
  return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
31007
30977
  }
31008
- if (teamspace.orgId !== ctx.orgId) {
31009
- return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
30978
+ if (teamspace.orgId !== currentSystem.orgId) {
30979
+ return {
30980
+ content: [{ type: "text", text: "Teamspace belongs to a different organization than the System" }],
30981
+ isError: true
30982
+ };
31010
30983
  }
31011
30984
  }
31012
30985
  const updated = await client.updateSystem(systemId, nonEmpty);
@@ -31044,9 +31017,6 @@ function registerSystemTools(server, ctx, client) {
31044
31017
  if (!system) {
31045
31018
  return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
31046
31019
  }
31047
- if (system.orgId !== ctx.orgId) {
31048
- return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
31049
- }
31050
31020
  try {
31051
31021
  const updated = await client.archiveSystem(systemId);
31052
31022
  if (!updated) {
@@ -31071,6 +31041,7 @@ function registerSystemTools(server, ctx, client) {
31071
31041
  }
31072
31042
  );
31073
31043
  const createSystemSchema = {
31044
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
31074
31045
  title: external_exports.string().describe("System title"),
31075
31046
  description: external_exports.string().optional().describe("System description"),
31076
31047
  strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
@@ -31080,21 +31051,24 @@ function registerSystemTools(server, ctx, client) {
31080
31051
  repoName: external_exports.string().optional().describe("GitHub repository name"),
31081
31052
  repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")')
31082
31053
  };
31083
- const createSystemHandler = async ({ title, description, strategy, teamspaceId, accountId, repoOwner, repoName, repoDefaultBranch }) => {
31054
+ const createSystemHandler = async ({ orgId, title, description, strategy, teamspaceId, accountId, repoOwner, repoName, repoDefaultBranch }) => {
31084
31055
  if (teamspaceId) {
31085
31056
  const teamspace = await client.getTeamspace(teamspaceId);
31086
31057
  if (!teamspace) {
31087
31058
  return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
31088
31059
  }
31089
- if (teamspace.orgId !== ctx.orgId) {
31090
- return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
31060
+ if (teamspace.orgId !== orgId) {
31061
+ return {
31062
+ content: [{ type: "text", text: "Teamspace belongs to a different organization" }],
31063
+ isError: true
31064
+ };
31091
31065
  }
31092
31066
  }
31093
31067
  const account = normalizeCreateAccountId(accountId);
31094
31068
  try {
31095
31069
  const project = await client.createSystem({
31096
31070
  systemId: (0, import_crypto5.randomUUID)(),
31097
- orgId: ctx.orgId,
31071
+ orgId,
31098
31072
  ownerUserId: ctx.user.userId,
31099
31073
  title,
31100
31074
  description,
@@ -31123,7 +31097,7 @@ function registerSystemTools(server, ctx, client) {
31123
31097
  return { content: [{ type: "text", text: `Failed to create system: ${message}` }], isError: true };
31124
31098
  }
31125
31099
  };
31126
- server.tool("create_system", "Create a new system in the configured organization. accountId is required \u2014 it is the Account that owns the System, and the only parent access follows.", createSystemSchema, createSystemHandler);
31100
+ server.tool("create_system", "Create a new system in an organization. accountId is required \u2014 it is the Account that owns the System, and the only parent access follows.", createSystemSchema, createSystemHandler);
31127
31101
  }
31128
31102
 
31129
31103
  // ../../libs/harmonica-services/src/mcp/tools/teamspace-tools.ts
@@ -31144,10 +31118,12 @@ function formatEnsembleMember(member) {
31144
31118
  function registerTeamspaceTools(server, ctx, client) {
31145
31119
  server.tool(
31146
31120
  "list_teamspaces",
31147
- "List all Teamspaces in the current organization.",
31148
- {},
31149
- async () => {
31150
- const teamspaces = await client.listOrgTeamspaces(ctx.orgId);
31121
+ "List all Teamspaces in an organization.",
31122
+ {
31123
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations")
31124
+ },
31125
+ async ({ orgId }) => {
31126
+ const teamspaces = await client.listOrgTeamspaces(orgId);
31151
31127
  const text = teamspaces.length === 0 ? "No teamspaces found." : teamspaces.map(
31152
31128
  (ts) => `[${ts.teamspaceId}] ${ts.name} \u2014 ${ts.status}`
31153
31129
  ).join("\n");
@@ -31158,15 +31134,16 @@ function registerTeamspaceTools(server, ctx, client) {
31158
31134
  "get_teamspace",
31159
31135
  "Get a Teamspace by ID.",
31160
31136
  {
31161
- teamspaceId: external_exports.string().describe("The teamspace ID")
31137
+ teamspaceId: external_exports.string().describe("The teamspace ID"),
31138
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations")
31162
31139
  },
31163
- async ({ teamspaceId }) => {
31140
+ async ({ teamspaceId, orgId }) => {
31164
31141
  try {
31165
31142
  const ts = await client.getTeamspace(teamspaceId);
31166
- if (!ts || ts.orgId !== ctx.orgId) {
31143
+ if (!ts) {
31167
31144
  return { content: [{ type: "text", text: `Teamspace not found: ${teamspaceId}` }], isError: true };
31168
31145
  }
31169
- const allProjects = await client.listOrgSystems(ctx.orgId);
31146
+ const allProjects = await client.listOrgSystems(orgId);
31170
31147
  const tsProjects = allProjects.filter((p) => p.teamspaceId === teamspaceId);
31171
31148
  const lines = [
31172
31149
  `ID: ${ts.teamspaceId}`,
@@ -31198,16 +31175,17 @@ function registerTeamspaceTools(server, ctx, client) {
31198
31175
  );
31199
31176
  server.tool(
31200
31177
  "create_teamspace",
31201
- "Create a new Teamspace in the current organization.",
31178
+ "Create a new Teamspace in an organization.",
31202
31179
  {
31180
+ orgId: external_exports.string().describe("The organization ID \u2014 see list_organizations"),
31203
31181
  name: external_exports.string().min(1).max(200).describe("Teamspace name")
31204
31182
  },
31205
- async ({ name }) => {
31183
+ async ({ orgId, name }) => {
31206
31184
  const teamspaceId = `ts-${(0, import_node_crypto2.randomUUID)()}`;
31207
31185
  const slug = generateTeamspaceSlug(name);
31208
31186
  const ts = await client.createTeamspace({
31209
31187
  teamspaceId,
31210
- orgId: ctx.orgId,
31188
+ orgId,
31211
31189
  name,
31212
31190
  slug,
31213
31191
  createdBy: ctx.user.userId
@@ -31274,7 +31252,7 @@ function registerValueVelocityTools(server, ctx, client) {
31274
31252
  if (!bv) {
31275
31253
  return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
31276
31254
  }
31277
- await assertBeatInOrg(client, bv.beatId, ctx.orgId);
31255
+ await assertBeatExists(client, bv.beatId);
31278
31256
  const actorId = ctx.user.email ?? ctx.user.userId;
31279
31257
  const updated = await client.confirmValueVelocityInputs(beatVersionId, {
31280
31258
  nextDeltaV,
@@ -31339,7 +31317,7 @@ function registerValueVelocityTools(server, ctx, client) {
31339
31317
  },
31340
31318
  async ({ systemId, lens, withLeverage }) => {
31341
31319
  try {
31342
- await assertSystemInOrg(client, systemId, ctx.orgId);
31320
+ await assertSystemExists(client, systemId);
31343
31321
  const [rankedResult, staleWResult] = await Promise.allSettled([
31344
31322
  withLeverage ? client.rankSystemBeatVersionsWithLeverage(systemId, lens) : client.rankSystemBeatVersions(systemId, lens),
31345
31323
  client.getStaleWBeatIds(systemId)
@@ -31381,7 +31359,7 @@ function registerValueVelocityTools(server, ctx, client) {
31381
31359
  },
31382
31360
  async ({ beatId }) => {
31383
31361
  try {
31384
- await assertBeatInOrg(client, beatId, ctx.orgId);
31362
+ await assertBeatExists(client, beatId);
31385
31363
  const actorId = resolveActorId(ctx);
31386
31364
  if (!actorId) {
31387
31365
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31417,7 +31395,7 @@ function registerValueVelocityTools(server, ctx, client) {
31417
31395
  },
31418
31396
  async ({ beatId, w }) => {
31419
31397
  try {
31420
- await assertBeatInOrg(client, beatId, ctx.orgId);
31398
+ await assertBeatExists(client, beatId);
31421
31399
  const actorId = resolveActorId(ctx);
31422
31400
  if (!actorId) {
31423
31401
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31446,12 +31424,12 @@ function registerValueVelocityTools(server, ctx, client) {
31446
31424
  },
31447
31425
  async ({ systemId, beatId }) => {
31448
31426
  try {
31449
- await assertSystemInOrg(client, systemId, ctx.orgId);
31427
+ await assertSystemExists(client, systemId);
31450
31428
  const actorId = resolveActorId(ctx);
31451
31429
  if (!actorId) {
31452
31430
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
31453
31431
  }
31454
- if (beatId !== void 0) await assertBeatInOrg(client, beatId, ctx.orgId);
31432
+ if (beatId !== void 0) await assertBeatExists(client, beatId);
31455
31433
  const proposed = await client.proposeEnablementEdges(systemId, actorId, beatId);
31456
31434
  if (proposed.length === 0) {
31457
31435
  return { content: [{ type: "text", text: "No enablement edges proposed (fewer than 2 Beats or no relationships found)." }] };
@@ -31482,8 +31460,8 @@ function registerValueVelocityTools(server, ctx, client) {
31482
31460
  },
31483
31461
  async ({ beatId, targetBeatId }) => {
31484
31462
  try {
31485
- await assertBeatInOrg(client, beatId, ctx.orgId);
31486
- await assertBeatInOrg(client, targetBeatId, ctx.orgId);
31463
+ await assertBeatExists(client, beatId);
31464
+ await assertBeatExists(client, targetBeatId);
31487
31465
  const actorId = resolveActorId(ctx);
31488
31466
  if (!actorId) {
31489
31467
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31516,8 +31494,8 @@ function registerValueVelocityTools(server, ctx, client) {
31516
31494
  },
31517
31495
  async ({ beatId, targetBeatId, rationale }) => {
31518
31496
  try {
31519
- await assertBeatInOrg(client, beatId, ctx.orgId);
31520
- await assertBeatInOrg(client, targetBeatId, ctx.orgId);
31497
+ await assertBeatExists(client, beatId);
31498
+ await assertBeatExists(client, targetBeatId);
31521
31499
  const actorId = resolveActorId(ctx);
31522
31500
  if (!actorId) {
31523
31501
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31553,8 +31531,8 @@ function registerValueVelocityTools(server, ctx, client) {
31553
31531
  },
31554
31532
  async ({ beatId, targetBeatId }) => {
31555
31533
  try {
31556
- await assertBeatInOrg(client, beatId, ctx.orgId);
31557
- await assertBeatInOrg(client, targetBeatId, ctx.orgId);
31534
+ await assertBeatExists(client, beatId);
31535
+ await assertBeatExists(client, targetBeatId);
31558
31536
  const actorId = resolveActorId(ctx);
31559
31537
  if (!actorId) {
31560
31538
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -32172,7 +32150,7 @@ ${JSON.stringify(task.result, null, 2)}
32172
32150
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
32173
32151
  },
32174
32152
  async ({ systemId, status, limit }) => {
32175
- await assertSystemInOrg(client, systemId, ctx.orgId);
32153
+ await assertSystemExists(client, systemId);
32176
32154
  const tasks = await client.listSystemTasks(systemId, {
32177
32155
  status,
32178
32156
  limit
@@ -32230,7 +32208,7 @@ ${JSON.stringify(task.result, null, 2)}
32230
32208
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
32231
32209
  },
32232
32210
  async ({ systemId, status, limit }) => {
32233
- await assertSystemInOrg(client, systemId, ctx.orgId);
32211
+ await assertSystemExists(client, systemId);
32234
32212
  const tasks = await client.listSystemTasks(systemId, {
32235
32213
  status,
32236
32214
  limit
@@ -32254,7 +32232,7 @@ ${JSON.stringify(task.result, null, 2)}
32254
32232
  },
32255
32233
  async ({ systemId, beatId }) => {
32256
32234
  try {
32257
- await assertSystemInOrg(client, systemId, ctx.orgId);
32235
+ await assertSystemExists(client, systemId);
32258
32236
  const task = await client.draftCoda(systemId, beatId);
32259
32237
  return {
32260
32238
  content: [{
@@ -32287,7 +32265,7 @@ ${JSON.stringify(task.result, null, 2)}
32287
32265
  },
32288
32266
  async ({ systemId, noteIds }) => {
32289
32267
  try {
32290
- await assertSystemInOrg(client, systemId, ctx.orgId);
32268
+ await assertSystemExists(client, systemId);
32291
32269
  const task = await client.validateAssumptions(systemId, noteIds);
32292
32270
  return {
32293
32271
  content: [{
@@ -32323,7 +32301,7 @@ ${JSON.stringify(task.result, null, 2)}
32323
32301
  },
32324
32302
  async ({ systemId, message, revisionId, parentSessionId }) => {
32325
32303
  try {
32326
- await assertSystemInOrg(client, systemId, ctx.orgId);
32304
+ await assertSystemExists(client, systemId);
32327
32305
  const task = await client.startSession(
32328
32306
  systemId,
32329
32307
  message,
@@ -32363,7 +32341,7 @@ ${JSON.stringify(task.result, null, 2)}
32363
32341
  },
32364
32342
  async ({ systemId, revisionIds }) => {
32365
32343
  try {
32366
- await assertSystemInOrg(client, systemId, ctx.orgId);
32344
+ await assertSystemExists(client, systemId);
32367
32345
  const task = await client.planRevisionBatch(systemId, revisionIds);
32368
32346
  return {
32369
32347
  content: [{
@@ -32395,7 +32373,7 @@ ${JSON.stringify(task.result, null, 2)}
32395
32373
  },
32396
32374
  async ({ systemId }) => {
32397
32375
  try {
32398
- await assertSystemInOrg(client, systemId, ctx.orgId);
32376
+ await assertSystemExists(client, systemId);
32399
32377
  const task = await client.runTriage(systemId);
32400
32378
  return {
32401
32379
  content: [{
@@ -32440,8 +32418,30 @@ ${JSON.stringify(task.result, null, 2)}
32440
32418
  encoding: external_exports.enum(["base64"]).optional().describe('Set to "base64" when submitting binary files (DOCX, XLSX, PPTX, PDF). Omit for plain text.')
32441
32419
  },
32442
32420
  async ({ systemId, content, filename, encoding }) => {
32421
+ const DOCUMENT_MAX_BYTES = 10 * 1024 * 1024;
32422
+ const contentBytes = encoding === "base64" ? Buffer.byteLength(content, "base64") : Buffer.byteLength(content, "utf8");
32423
+ if (contentBytes > DOCUMENT_MAX_BYTES) {
32424
+ const actualMb = (contentBytes / (1024 * 1024)).toFixed(2);
32425
+ return {
32426
+ content: [{
32427
+ type: "text",
32428
+ text: [
32429
+ `**Field:** content`,
32430
+ `**Limit:** 10MB`,
32431
+ `**Actual:** ${actualMb}MB`,
32432
+ `**Action:** Split the document into smaller chunks (each under 10MB) and submit them separately.`
32433
+ ].join("\n")
32434
+ }],
32435
+ isError: true
32436
+ };
32437
+ }
32438
+ try {
32439
+ await assertSystemExists(client, systemId);
32440
+ } catch (err) {
32441
+ const message = err instanceof Error ? err.message : String(err);
32442
+ return { content: [{ type: "text", text: `Failed to analyze document: ${message}` }], isError: true };
32443
+ }
32443
32444
  try {
32444
- await assertSystemInOrg(client, systemId, ctx.orgId);
32445
32445
  const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
32446
32446
  return {
32447
32447
  content: [{
@@ -32460,6 +32460,24 @@ ${JSON.stringify(task.result, null, 2)}
32460
32460
  };
32461
32461
  } catch (err) {
32462
32462
  const message = err instanceof Error ? err.message : String(err);
32463
+ const status = err?.status;
32464
+ const lower = message.toLowerCase();
32465
+ const isSizeError = status === 413 || lower.includes("size limit") || lower.includes("too large");
32466
+ if (isSizeError) {
32467
+ return {
32468
+ content: [{
32469
+ type: "text",
32470
+ text: [
32471
+ `**Field:** content`,
32472
+ `**Limit:** 10MB`,
32473
+ `**Actual:** ${(contentBytes / (1024 * 1024)).toFixed(2)}MB`,
32474
+ `**Detail:** ${message}`,
32475
+ `**Action:** Split the document into smaller chunks (each under 10MB) and submit them separately.`
32476
+ ].join("\n")
32477
+ }],
32478
+ isError: true
32479
+ };
32480
+ }
32463
32481
  return { content: [{ type: "text", text: `Failed to analyze document: ${message}` }], isError: true };
32464
32482
  }
32465
32483
  }
@@ -32613,7 +32631,7 @@ function registerBeatResources(server, ctx, client) {
32613
32631
  new ResourceTemplate("harmonica://system/{systemId}/beats", { list: void 0 }),
32614
32632
  async (uri, { systemId }) => {
32615
32633
  const sid = String(systemId);
32616
- await fetchSystemInOrg(client, sid, ctx.orgId);
32634
+ await fetchSystem(client, sid);
32617
32635
  const beats = await client.listSystemBeats(sid);
32618
32636
  const text = formatBeatSummaryTable(beats);
32619
32637
  return { contents: [{ uri: uri.href, text }] };
@@ -32624,7 +32642,7 @@ function registerBeatResources(server, ctx, client) {
32624
32642
  new ResourceTemplate("harmonica://beat/{beatId}", { list: void 0 }),
32625
32643
  async (uri, { beatId }) => {
32626
32644
  const bid = String(beatId);
32627
- await assertBeatInOrg(client, bid, ctx.orgId);
32645
+ await assertBeatExists(client, bid);
32628
32646
  const beat = await client.getBeat(bid);
32629
32647
  if (!beat) {
32630
32648
  return { contents: [{ uri: uri.href, text: `Beat not found: "${bid}"` }] };
@@ -32655,7 +32673,7 @@ function registerSystemResources(server, ctx, client) {
32655
32673
  new ResourceTemplate("harmonica://system/{systemId}/context", { list: void 0 }),
32656
32674
  async (uri, { systemId }) => {
32657
32675
  const sid = String(systemId);
32658
- const project = await fetchSystemInOrg(client, sid, ctx.orgId);
32676
+ const project = await fetchSystem(client, sid);
32659
32677
  const notes = await client.listSystemNotes(sid, { limit: DEFAULT_CONTEXT_NOTE_LIMIT });
32660
32678
  const text = formatSystemContext(project, notes);
32661
32679
  return { contents: [{ uri: uri.href, text }] };
@@ -32843,8 +32861,17 @@ function createHttpClient(config2) {
32843
32861
  if (!raw) return void 0;
32844
32862
  return { ...raw, orgId: raw.id };
32845
32863
  },
32846
- getUser: async (_userId) => {
32847
- return request("GET", "/api/users/me");
32864
+ getCurrentUser: async () => {
32865
+ return request(
32866
+ "GET",
32867
+ "/api/users/me"
32868
+ );
32869
+ },
32870
+ getUser: async (userId) => {
32871
+ return request(
32872
+ "GET",
32873
+ `/api/users/lookup?email=${encodeURIComponent(userId)}`
32874
+ );
32848
32875
  },
32849
32876
  getMembership: async (userId, orgId) => {
32850
32877
  return { userId, orgId, role: "member" };
@@ -32967,9 +32994,8 @@ function createHttpClient(config2) {
32967
32994
  return result?.data;
32968
32995
  },
32969
32996
  saveSystemFact: (input) => {
32970
- const effectiveId = input.systemId ?? input.projectId;
32971
- if (!effectiveId) throw new Error("saveSystemFact: systemId (or legacy projectId) is required");
32972
- return request("PUT", `/api/systems/${encodeURIComponent(String(effectiveId))}/facts`, input);
32997
+ if (!input.systemId) throw new Error("saveSystemFact: systemId is required");
32998
+ return request("PUT", `/api/systems/${encodeURIComponent(String(input.systemId))}/facts`, input);
32973
32999
  },
32974
33000
  transitionSystemLifecycleState: async (systemId, targetState, options) => {
32975
33001
  return request("POST", `/api/systems/${encodeURIComponent(systemId)}/lifecycle/transitions`, {
@@ -32995,7 +33021,7 @@ function createHttpClient(config2) {
32995
33021
  getBeat: async (beatId) => {
32996
33022
  const raw = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
32997
33023
  if (!raw) return void 0;
32998
- return { ...raw, beatId: raw.beatId ?? raw.id, projectId: raw.projectId ?? raw.systemId, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
33024
+ return { ...raw, beatId: raw.beatId ?? raw.id, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
32999
33025
  },
33000
33026
  getBeatConvergence: async (systemId, beatId) => {
33001
33027
  const raw = await request(
@@ -33042,7 +33068,7 @@ function createHttpClient(config2) {
33042
33068
  }
33043
33069
  const beat = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
33044
33070
  if (!beat) return void 0;
33045
- const systemId = beat.systemId ?? beat.projectId;
33071
+ const systemId = beat.systemId;
33046
33072
  if (!systemId) return void 0;
33047
33073
  const result = await request("PUT", `/api/systems/${encodeURIComponent(systemId)}/beats/${encodeURIComponent(beatId)}`, updates);
33048
33074
  const raw = result?.beat ?? result;
@@ -33178,7 +33204,7 @@ function createHttpClient(config2) {
33178
33204
  listBeatNotes: async (beatId, filters) => {
33179
33205
  const beat = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
33180
33206
  if (!beat) return [];
33181
- const systemId = beat.systemId ?? beat.projectId;
33207
+ const systemId = beat.systemId;
33182
33208
  if (!systemId) return [];
33183
33209
  const params = new URLSearchParams();
33184
33210
  if (filters?.noteType) {
@@ -33242,10 +33268,6 @@ function createHttpClient(config2) {
33242
33268
  );
33243
33269
  return result;
33244
33270
  },
33245
- getKeyAssumptions: async (systemId) => {
33246
- const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/notes/key-assumptions`);
33247
- return result?.assumptions ?? [];
33248
- },
33249
33271
  getNextNoteId: async (projectCode) => {
33250
33272
  return `N-${projectCode}-${Date.now()}`;
33251
33273
  },
@@ -34221,8 +34243,7 @@ function createHttpClient(config2) {
34221
34243
  ...parentGroupId !== void 0 && { parentGroupId },
34222
34244
  ...createdBy !== void 0 && { createdBy }
34223
34245
  };
34224
- const inputRec = input;
34225
- const effectiveSystemId = inputRec.systemId ?? input.projectId;
34246
+ const effectiveSystemId = input.systemId;
34226
34247
  const url2 = effectiveSystemId ? `/api/systems/${encodeURIComponent(effectiveSystemId)}/deliverable-groups` : `/api/teamspaces/${encodeURIComponent(input.teamspaceId)}/deliverable-groups`;
34227
34248
  const result = await request("POST", url2, body);
34228
34249
  if (result === void 0) throw new Error(`Resource not found when creating DeliverableGroup`);
@@ -35043,7 +35064,7 @@ function loadConfig() {
35043
35064
  };
35044
35065
  }
35045
35066
  async function main() {
35046
- console.error(`[harmonica-mcp] v${"4.3.1"} starting\u2026`);
35067
+ console.error(`[harmonica-mcp] v${"4.4.1"} starting\u2026`);
35047
35068
  const config2 = loadConfig();
35048
35069
  const client = createHttpClient({
35049
35070
  apiBaseUrl: config2.apiBaseUrl,