@codazen/harmonica-mcp 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +89 -358
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21587,6 +21587,18 @@ async function fetchProjectInOrg(client, projectId, orgId) {
21587
21587
  }
21588
21588
  return project;
21589
21589
  }
21590
+ async function fetchSystemInOrg(client, systemId, orgId) {
21591
+ const system = await client.getSystem(systemId);
21592
+ if (!system) {
21593
+ throw new Error(`System not found: "${systemId}"`);
21594
+ }
21595
+ if (system.orgId !== orgId) {
21596
+ throw new Error(
21597
+ `System "${systemId}" does not belong to the configured organization. Access denied.`
21598
+ );
21599
+ }
21600
+ return system;
21601
+ }
21590
21602
  async function fetchBeatInOrg(client, beatId, orgId) {
21591
21603
  const beat = await client.getBeat(beatId);
21592
21604
  if (!beat) {
@@ -21968,7 +21980,7 @@ function registerActivityTools(server, ctx, client) {
21968
21980
  },
21969
21981
  async ({ projectId, limit, importance }) => {
21970
21982
  await assertProjectInOrg(client, projectId, ctx.orgId);
21971
- const { entries, hasMore } = await client.listProjectActivities(projectId, {
21983
+ const { entries, hasMore } = await client.listSystemActivities(projectId, {
21972
21984
  limit: limit ?? 30,
21973
21985
  minImportance: importance
21974
21986
  });
@@ -22689,7 +22701,7 @@ function formatDeliveryPolicy(policy, unresolvedReason) {
22689
22701
  }
22690
22702
  function formatProjectContext(project, notes, orgCoda) {
22691
22703
  const sections = [];
22692
- sections.push(`# Project: ${project.title}`);
22704
+ sections.push(`# System: ${project.title}`);
22693
22705
  sections.push(`**ID:** ${project.projectId}`);
22694
22706
  sections.push(`**Status:** ${project.status.replace("_", " ")}`);
22695
22707
  if (project.repoOwner && project.repoName) {
@@ -23275,7 +23287,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23275
23287
  async ({ projectId, beatId, status, includeArchived }) => {
23276
23288
  try {
23277
23289
  await assertProjectInOrg(client, projectId, ctx.orgId);
23278
- const revisions = beatId ? await client.listBeatRevisions(projectId, beatId, { status, includeArchived }) : await client.listProjectRevisions(projectId, { status, includeArchived });
23290
+ const revisions = beatId ? await client.listBeatRevisions(projectId, beatId, { status, includeArchived }) : await client.listSystemRevisions(projectId, { status, includeArchived });
23279
23291
  return { content: [{ type: "text", text: formatRevisionSummaryTable(revisions) }] };
23280
23292
  } catch (err) {
23281
23293
  const message = err instanceof Error ? err.message : String(err);
@@ -23936,7 +23948,7 @@ function registerCheckTools(server, ctx, client) {
23936
23948
  },
23937
23949
  async ({ projectId }) => {
23938
23950
  try {
23939
- const checks = await client.listLatestProjectChecks(projectId);
23951
+ const checks = await client.listLatestSystemChecks(projectId);
23940
23952
  if (checks.length === 0) {
23941
23953
  return { content: [{ type: "text", text: `No checks found for project ${projectId}.` }] };
23942
23954
  }
@@ -24316,18 +24328,6 @@ ${g.acceptanceCriteria}` : null,
24316
24328
  };
24317
24329
  }
24318
24330
  );
24319
- server.tool(
24320
- "list_deliverable_group_deliverables",
24321
- 'List all Deliverables assigned to a DeliverableGroup. Use to answer "what deliverables are in this phase/milestone?" or "what has the team committed to in this engagement group?"',
24322
- {
24323
- deliverableGroupId: external_exports.string().describe("The DeliverableGroup ID")
24324
- },
24325
- async ({ deliverableGroupId }) => {
24326
- const items = await client.listDeliverableGroupDeliverables(deliverableGroupId);
24327
- const text = items.length === 0 ? "No deliverables in this group." : items.map((d) => `[${d.deliverableId}] teamspace: ${d.teamspaceId}`).join("\n");
24328
- return { content: [{ type: "text", text }] };
24329
- }
24330
- );
24331
24331
  server.tool(
24332
24332
  "list_deliverable_group_drops",
24333
24333
  'List all Drops linked to a DeliverableGroup. Use to answer "what releases are part of this phase?" or "which software drops are tied to this engagement milestone?"',
@@ -24385,108 +24385,6 @@ ${g.acceptanceCriteria}` : null,
24385
24385
  );
24386
24386
  }
24387
24387
 
24388
- // ../../libs/harmonica-services/src/mcp/tools/deliverable-tools.ts
24389
- var import_node_crypto = require("node:crypto");
24390
- function registerDeliverableTools(server, ctx, client) {
24391
- server.tool(
24392
- "list_deliverables",
24393
- "List all Deliverables (tangible client-facing artifacts or milestones owed by a date \u2014 e.g. board deck, roadmap doc, audit report) for a Teamspace.",
24394
- {
24395
- teamspaceId: external_exports.string().describe("The teamspace ID")
24396
- },
24397
- async ({ teamspaceId }) => {
24398
- const deliverables = await client.listTeamspaceDeliverables(teamspaceId);
24399
- const text = deliverables.length === 0 ? "No deliverables found." : deliverables.map(
24400
- (d) => `[${d.deliverableId}] ${d.title} \u2014 ${d.status}${d.dueDate ? ` (due ${d.dueDate})` : ""}${d.deliverableGroupId ? ` (group: ${d.deliverableGroupId})` : ""}`
24401
- ).join("\n");
24402
- return { content: [{ type: "text", text }] };
24403
- }
24404
- );
24405
- server.tool(
24406
- "get_deliverable",
24407
- "Get a Deliverable (tangible client-facing artifact or milestone) by ID.",
24408
- {
24409
- deliverableId: external_exports.string().describe("The deliverable ID")
24410
- },
24411
- async ({ deliverableId }) => {
24412
- const d = await client.getDeliverable(deliverableId);
24413
- if (!d) return { content: [{ type: "text", text: `Deliverable not found: ${deliverableId}` }] };
24414
- const text = [
24415
- `ID: ${d.deliverableId}`,
24416
- `Title: ${d.title}`,
24417
- `Teamspace: ${d.teamspaceId}`,
24418
- `Status: ${d.status}`,
24419
- d.description ? `Description: ${d.description}` : null,
24420
- d.dueDate ? `Due: ${d.dueDate}` : null,
24421
- d.deliverableGroupId ? `Group: ${d.deliverableGroupId}` : null,
24422
- `Version: ${d.version}`,
24423
- `Created: ${d.createdAt}`
24424
- ].filter(Boolean).join("\n");
24425
- return { content: [{ type: "text", text }] };
24426
- }
24427
- );
24428
- server.tool(
24429
- "create_deliverable",
24430
- 'Create a Deliverable in a Teamspace. A Deliverable is a tangible commitment the agency owes the client by a date \u2014 board presentation, roadmap document, audit report, design mockups, training session, etc. Use this when the user describes something we will hand over. Do NOT use this for software capabilities the product provides to its end users \u2014 those are Beats (use create_beat). Do NOT use this for context, assumptions, constraints, or decisions \u2014 those are Notes (use create_note). Recognition cue: phrases like "we owe them\u2026", "due by\u2026", "the deck", "the report" \u2192 Deliverable. "Users will be able to\u2026", "the screen that\u2026" \u2192 Beat.',
24431
- {
24432
- teamspaceId: external_exports.string().describe("The teamspace ID"),
24433
- title: external_exports.string().min(1).max(300).describe("Deliverable title"),
24434
- description: external_exports.string().max(2e3).optional().describe("Optional description"),
24435
- dueDate: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("Due date (YYYY-MM-DD)"),
24436
- status: external_exports.enum(["draft", "active", "delivered", "cancelled"]).optional().describe("Initial status (default: draft)"),
24437
- deliverableGroupId: external_exports.string().optional().describe("Assign to a DeliverableGroup by ID at creation time")
24438
- },
24439
- async ({ teamspaceId, title, description, dueDate, status, deliverableGroupId }) => {
24440
- const deliverableId = `dlv-${(0, import_node_crypto.randomUUID)()}`;
24441
- const d = await client.createDeliverable({
24442
- deliverableId,
24443
- teamspaceId,
24444
- orgId: ctx.orgId,
24445
- title,
24446
- description,
24447
- dueDate,
24448
- status,
24449
- createdBy: ctx.user.userId,
24450
- ...deliverableGroupId !== void 0 && { deliverableGroupId }
24451
- });
24452
- return {
24453
- content: [{
24454
- type: "text",
24455
- text: `Created deliverable: [${d.deliverableId}] ${d.title} \u2014 ${d.status}`
24456
- }]
24457
- };
24458
- }
24459
- );
24460
- server.tool(
24461
- "update_deliverable",
24462
- "Update an existing Deliverable. Transition status as the artifact progresses: draft \u2192 active \u2192 delivered (or cancelled).",
24463
- {
24464
- deliverableId: external_exports.string().describe("The deliverable ID"),
24465
- title: external_exports.string().min(1).max(300).optional().describe("New title"),
24466
- description: external_exports.string().max(2e3).optional().describe("New description"),
24467
- dueDate: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional().describe("New due date (YYYY-MM-DD), or null to clear"),
24468
- status: external_exports.enum(["draft", "active", "delivered", "cancelled"]).optional().describe("New status"),
24469
- deliverableGroupId: external_exports.string().nullable().optional().describe("Assign to a DeliverableGroup by ID, or null to ungroup")
24470
- },
24471
- async ({ deliverableId, title, description, dueDate, status, deliverableGroupId }) => {
24472
- const updates = {};
24473
- if (title !== void 0) updates["title"] = title;
24474
- if (description !== void 0) updates["description"] = description;
24475
- if (dueDate !== void 0) updates["dueDate"] = dueDate;
24476
- if (status !== void 0) updates["status"] = status;
24477
- if (deliverableGroupId !== void 0) updates["deliverableGroupId"] = deliverableGroupId;
24478
- const d = await client.updateDeliverable(deliverableId, updates);
24479
- if (!d) return { content: [{ type: "text", text: `Deliverable not found: ${deliverableId}` }] };
24480
- return {
24481
- content: [{
24482
- type: "text",
24483
- text: `Updated deliverable: [${d.deliverableId}] ${d.title} \u2014 ${d.status}`
24484
- }]
24485
- };
24486
- }
24487
- );
24488
- }
24489
-
24490
24388
  // ../../libs/harmonica-services/src/mcp/tools/downbeat-harmony-report-tools.ts
24491
24389
  function formatQualifyingBeat(b) {
24492
24390
  return `- \`${b.beatId}\` ${b.title}: ${b.signals.join(", ")}`;
@@ -28513,7 +28411,7 @@ ${lines.join("\n")}` }] };
28513
28411
  }
28514
28412
 
28515
28413
  // ../../libs/harmonica-services/src/mcp/tools/onboarding-tools.ts
28516
- var import_node_crypto2 = require("node:crypto");
28414
+ var import_node_crypto = require("node:crypto");
28517
28415
  var OnboardingNoteInputSchema = external_exports.object({
28518
28416
  noteType: external_exports.enum(["context", "assumption", "constraint", "guidance", "decision"]),
28519
28417
  content: external_exports.string().min(1),
@@ -28525,11 +28423,6 @@ var OnboardingProjectInputSchema = external_exports.object({
28525
28423
  description: external_exports.string().max(2e3).optional(),
28526
28424
  notes: external_exports.array(OnboardingNoteInputSchema).optional()
28527
28425
  });
28528
- var OnboardingDeliverableInputSchema = external_exports.object({
28529
- title: external_exports.string().min(1).max(300),
28530
- description: external_exports.string().max(2e3).optional(),
28531
- dueDate: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
28532
- });
28533
28426
  function registerOnboardingTools(server, ctx, client) {
28534
28427
  server.tool(
28535
28428
  "initiate_teamspace_onboarding",
@@ -28627,7 +28520,7 @@ function registerOnboardingTools(server, ctx, client) {
28627
28520
  server.tool(
28628
28521
  "create_teamspace_onboarding_batch",
28629
28522
  [
28630
- "Create a Teamspace, Projects, Notes, and Deliverables",
28523
+ "Create a Teamspace, Projects, and Notes",
28631
28524
  "from a PM-approved onboarding payload in a single batch operation.",
28632
28525
  "Call this after the PM approves the pre-flight review card from initiate_teamspace_onboarding.",
28633
28526
  "Returns a creation receipt with IDs and counts for all created entities.",
@@ -28636,20 +28529,18 @@ function registerOnboardingTools(server, ctx, client) {
28636
28529
  {
28637
28530
  teamspaceName: external_exports.string().min(1).max(200).describe("Teamspace display name"),
28638
28531
  projects: external_exports.array(OnboardingProjectInputSchema).min(1).describe("Phase 1 projects to create, each linked to the Teamspace"),
28639
- teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/project level"),
28640
- deliverables: external_exports.array(OnboardingDeliverableInputSchema).describe("Deliverables detected from commitment language in documents")
28532
+ teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/project level")
28641
28533
  },
28642
28534
  async ({
28643
28535
  teamspaceName,
28644
28536
  projects,
28645
- teamspaceNotes,
28646
- deliverables
28537
+ teamspaceNotes
28647
28538
  }) => {
28648
28539
  const lines = [];
28649
28540
  let teamspace;
28650
28541
  try {
28651
28542
  teamspace = await client.createTeamspace({
28652
- teamspaceId: (0, import_node_crypto2.randomUUID)(),
28543
+ teamspaceId: (0, import_node_crypto.randomUUID)(),
28653
28544
  slug: teamspaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
28654
28545
  orgId: ctx.orgId,
28655
28546
  name: teamspaceName,
@@ -28666,8 +28557,8 @@ Error: ${msg}`);
28666
28557
  const createdProjects = [];
28667
28558
  for (const p of projects) {
28668
28559
  try {
28669
- const project = await client.createProject({
28670
- projectId: (0, import_node_crypto2.randomUUID)(),
28560
+ const project = await client.createSystem({
28561
+ projectId: (0, import_node_crypto.randomUUID)(),
28671
28562
  orgId: ctx.orgId,
28672
28563
  teamspaceId: teamspace.teamspaceId,
28673
28564
  ownerUserId: ctx.user?.userId ?? "system",
@@ -28711,7 +28602,7 @@ Projects (${createdProjects.length}):`);
28711
28602
  for (const note of projects[i]?.notes ?? []) {
28712
28603
  try {
28713
28604
  const n = await client.createNote({
28714
- noteId: `N-TEMP-${(0, import_node_crypto2.randomUUID)()}`,
28605
+ noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
28715
28606
  // overridden server-side
28716
28607
  projectId: project.projectId,
28717
28608
  noteType: note.noteType,
@@ -28728,24 +28619,6 @@ Projects (${createdProjects.length}):`);
28728
28619
  }
28729
28620
  lines.push(`
28730
28621
  Notes filed: ${createdNotes.length}`);
28731
- const createdDeliverables = [];
28732
- for (const d of deliverables) {
28733
- try {
28734
- const del = await client.createDeliverable({
28735
- deliverableId: (0, import_node_crypto2.randomUUID)(),
28736
- // overridden server-side
28737
- teamspaceId: teamspace.teamspaceId,
28738
- orgId: ctx.orgId,
28739
- title: d.title,
28740
- description: d.description,
28741
- dueDate: d.dueDate,
28742
- createdBy: ctx.user?.userId ?? "mcp"
28743
- });
28744
- createdDeliverables.push(del.deliverableId);
28745
- } catch {
28746
- }
28747
- }
28748
- lines.push(`Deliverables: ${createdDeliverables.length}`);
28749
28622
  lines.unshift(`\u2705 Onboarding batch created successfully.`);
28750
28623
  return { content: [{ type: "text", text: lines.join("\n") }] };
28751
28624
  }
@@ -30051,7 +29924,7 @@ function registerSessionTools(server, ctx, client) {
30051
29924
  includeTerminal: external_exports.boolean().optional().describe("Include terminal (closed) sessions. Default false.")
30052
29925
  },
30053
29926
  async ({ systemId, status, includeTerminal }) => {
30054
- const sessions = await client.listProjectSessions(systemId, {
29927
+ const sessions = await client.listSystemSessions(systemId, {
30055
29928
  status,
30056
29929
  includeTerminal
30057
29930
  });
@@ -30718,7 +30591,7 @@ function registerProjectTools(server, ctx, client) {
30718
30591
  fetchProjectInOrg(client, systemId, ctx.orgId),
30719
30592
  client.getOrg(ctx.orgId)
30720
30593
  ]);
30721
- const notes = await client.listProjectNotes(systemId, {
30594
+ const notes = await client.listSystemNotes(systemId, {
30722
30595
  limit: noteLimit ?? DEFAULT_CONTEXT_NOTE_LIMIT
30723
30596
  });
30724
30597
  const text = formatProjectContext(project, notes, org?.coda);
@@ -30886,7 +30759,7 @@ function registerProjectTools(server, ctx, client) {
30886
30759
  }
30887
30760
 
30888
30761
  // ../../libs/harmonica-services/src/mcp/tools/teamspace-tools.ts
30889
- var import_node_crypto3 = require("node:crypto");
30762
+ var import_node_crypto2 = require("node:crypto");
30890
30763
  var EnsembleMemberSchema = external_exports.discriminatedUnion("type", [
30891
30764
  external_exports.object({ type: external_exports.literal("human"), email: external_exports.string().email().max(254), name: external_exports.string().min(1).max(200) }),
30892
30765
  external_exports.object({ type: external_exports.literal("agent"), agentId: external_exports.string().min(1).max(100), name: external_exports.string().min(1).max(200) })
@@ -30925,7 +30798,7 @@ function registerTeamspaceTools(server, ctx, client) {
30925
30798
  if (!ts || ts.orgId !== ctx.orgId) {
30926
30799
  return { content: [{ type: "text", text: `Teamspace not found: ${teamspaceId}` }], isError: true };
30927
30800
  }
30928
- const allProjects = await client.listOrgProjects(ctx.orgId);
30801
+ const allProjects = await client.listOrgSystems(ctx.orgId);
30929
30802
  const tsProjects = allProjects.filter((p) => p.teamspaceId === teamspaceId);
30930
30803
  const lines = [
30931
30804
  `ID: ${ts.teamspaceId}`,
@@ -30962,7 +30835,7 @@ function registerTeamspaceTools(server, ctx, client) {
30962
30835
  name: external_exports.string().min(1).max(200).describe("Teamspace name")
30963
30836
  },
30964
30837
  async ({ name }) => {
30965
- const teamspaceId = `ts-${(0, import_node_crypto3.randomUUID)()}`;
30838
+ const teamspaceId = `ts-${(0, import_node_crypto2.randomUUID)()}`;
30966
30839
  const slug = generateTeamspaceSlug(name);
30967
30840
  const ts = await client.createTeamspace({
30968
30841
  teamspaceId,
@@ -31100,7 +30973,7 @@ function registerValueVelocityTools(server, ctx, client) {
31100
30973
  try {
31101
30974
  await assertProjectInOrg(client, systemId, ctx.orgId);
31102
30975
  const [rankedResult, staleWResult] = await Promise.allSettled([
31103
- withLeverage ? client.rankProjectBeatVersionsWithLeverage(systemId, lens) : client.rankProjectBeatVersions(systemId, lens),
30976
+ withLeverage ? client.rankSystemBeatVersionsWithLeverage(systemId, lens) : client.rankSystemBeatVersions(systemId, lens),
31104
30977
  client.getStaleWBeatIds(systemId)
31105
30978
  ]);
31106
30979
  if (rankedResult.status === "rejected") {
@@ -31594,7 +31467,7 @@ ${JSON.stringify(task.result, null, 2)}
31594
31467
  },
31595
31468
  async ({ projectId, status, limit }) => {
31596
31469
  await assertProjectInOrg(client, projectId, ctx.orgId);
31597
- const tasks = await client.listProjectTasks(projectId, {
31470
+ const tasks = await client.listSystemTasks(projectId, {
31598
31471
  status,
31599
31472
  limit
31600
31473
  });
@@ -31652,7 +31525,7 @@ ${JSON.stringify(task.result, null, 2)}
31652
31525
  },
31653
31526
  async ({ projectId, status, limit }) => {
31654
31527
  await assertProjectInOrg(client, projectId, ctx.orgId);
31655
- const tasks = await client.listProjectTasks(projectId, {
31528
+ const tasks = await client.listSystemTasks(projectId, {
31656
31529
  status,
31657
31530
  limit
31658
31531
  });
@@ -31954,7 +31827,6 @@ var TOOL_PROFILES = Object.freeze({
31954
31827
  allow(registerBeatVersionTools, ["list_beat_versions", "get_beat_version"]),
31955
31828
  allow(registerNoteTools, ["list_notes", "get_note", "create_note", "list_documents"]),
31956
31829
  allow(registerDropTools, ["list_drops", "get_drop"]),
31957
- allow(registerDeliverableTools, ["list_deliverables", "get_deliverable"]),
31958
31830
  allow(registerEmbeddingTools, ["search", "find_similar_notes"])
31959
31831
  ])
31960
31832
  });
@@ -32007,7 +31879,6 @@ function registerAllTools(server, ctx, client, profile) {
32007
31879
  registerBaselineTools(server, ctx, client);
32008
31880
  registerTeamspaceTools(server, ctx, client);
32009
31881
  registerDeliverableGroupTools(server, ctx, client);
32010
- registerDeliverableTools(server, ctx, client);
32011
31882
  registerDropTools(server, ctx, client);
32012
31883
  registerSessionTools(server, ctx, client);
32013
31884
  registerOnboardingTools(server, ctx, client);
@@ -32026,12 +31897,12 @@ function registerAllTools(server, ctx, client, profile) {
32026
31897
  // ../../libs/harmonica-services/src/mcp/resources/beat-resources.ts
32027
31898
  function registerBeatResources(server, ctx, client) {
32028
31899
  server.resource(
32029
- "project-beats",
32030
- new ResourceTemplate("harmonica://project/{projectId}/beats", { list: void 0 }),
32031
- async (uri, { projectId }) => {
32032
- const pid = String(projectId);
32033
- await assertProjectInOrg(client, pid, ctx.orgId);
32034
- const beats = await client.listSystemBeats(pid);
31900
+ "system-beats",
31901
+ new ResourceTemplate("harmonica://system/{systemId}/beats", { list: void 0 }),
31902
+ async (uri, { systemId }) => {
31903
+ const sid = String(systemId);
31904
+ await fetchSystemInOrg(client, sid, ctx.orgId);
31905
+ const beats = await client.listSystemBeats(sid);
32035
31906
  const text = formatBeatSummaryTable(beats);
32036
31907
  return { contents: [{ uri: uri.href, text }] };
32037
31908
  }
@@ -32066,14 +31937,14 @@ function registerGuidelinesResources(server, client) {
32066
31937
  }
32067
31938
 
32068
31939
  // ../../libs/harmonica-services/src/mcp/resources/system-resources.ts
32069
- function registerProjectResources(server, ctx, client) {
31940
+ function registerSystemResources(server, ctx, client) {
32070
31941
  server.resource(
32071
- "project-context",
32072
- new ResourceTemplate("harmonica://project/{projectId}/context", { list: void 0 }),
32073
- async (uri, { projectId }) => {
32074
- const pid = String(projectId);
32075
- const project = await fetchProjectInOrg(client, pid, ctx.orgId);
32076
- const notes = await client.listSystemNotes(pid, { limit: DEFAULT_CONTEXT_NOTE_LIMIT });
31942
+ "system-context",
31943
+ new ResourceTemplate("harmonica://system/{systemId}/context", { list: void 0 }),
31944
+ async (uri, { systemId }) => {
31945
+ const sid = String(systemId);
31946
+ const project = await fetchSystemInOrg(client, sid, ctx.orgId);
31947
+ const notes = await client.listSystemNotes(sid, { limit: DEFAULT_CONTEXT_NOTE_LIMIT });
32077
31948
  const text = formatProjectContext(project, notes);
32078
31949
  return { contents: [{ uri: uri.href, text }] };
32079
31950
  }
@@ -32082,7 +31953,7 @@ function registerProjectResources(server, ctx, client) {
32082
31953
 
32083
31954
  // ../../libs/harmonica-services/src/mcp/resources/index.ts
32084
31955
  function registerAllResources(server, ctx, client) {
32085
- registerProjectResources(server, ctx, client);
31956
+ registerSystemResources(server, ctx, client);
32086
31957
  registerBeatResources(server, ctx, client);
32087
31958
  registerGuidelinesResources(server, client);
32088
31959
  }
@@ -32353,46 +32224,11 @@ function createHttpClient(config2) {
32353
32224
  if (!raw) return void 0;
32354
32225
  return { ...raw, projectId: raw.id ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
32355
32226
  },
32356
- // @deprecated aliases — remove in cleanup increment (BV2)
32357
- listOrgProjects: async (orgId) => {
32358
- const raw = await request("GET", `/api/organizations/${encodeURIComponent(orgId)}/projects/summaries`);
32359
- return (raw ?? []).map((p) => ({ ...p, projectId: p.id, title: p.name, orgId: p.organizationId }));
32360
- },
32361
- getProject: async (projectId) => {
32362
- const raw = await request("GET", `/api/systems/${encodeURIComponent(projectId)}`);
32363
- if (!raw) return void 0;
32364
- return {
32365
- ...raw,
32366
- projectId: raw.id ?? raw.projectId,
32367
- title: raw.name ?? raw.title,
32368
- orgId: raw.organizationId ?? raw.orgId
32369
- };
32370
- },
32371
- createProject: async (input) => {
32372
- const raw = await request("POST", "/api/systems", {
32373
- organizationId: input.orgId,
32374
- teamspaceId: input.teamspaceId,
32375
- name: input.title,
32376
- description: input.description,
32377
- strategy: input.strategy,
32378
- // Without this the owning Account is dropped on the wire and the System
32379
- // is created unlinked — invisible in the Account's Systems list.
32380
- ...input.accountId !== void 0 && { accountId: input.accountId }
32381
- });
32382
- return { ...raw, orgId: raw?.organizationId ?? raw?.orgId };
32383
- },
32384
- updateProject: (projectId, updates) => request("PATCH", `/api/systems/${encodeURIComponent(projectId)}`, updates),
32385
- archiveProject: (projectId) => request("PATCH", `/api/systems/${encodeURIComponent(projectId)}/archive`),
32386
32227
  getSystemFact: async (projectId) => {
32387
32228
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/facts`);
32388
32229
  return result?.data;
32389
32230
  },
32390
- getProjectFact: async (projectId) => {
32391
- const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/facts`);
32392
- return result?.data;
32393
- },
32394
32231
  saveSystemFact: (input) => request("PUT", `/api/systems/${encodeURIComponent(String(input.projectId))}/facts`, input),
32395
- saveProjectFact: (input) => request("PUT", `/api/systems/${encodeURIComponent(String(input.projectId))}/facts`, input),
32396
32232
  transitionSystemLifecycleState: async (projectId, targetState, options) => {
32397
32233
  return request("POST", `/api/systems/${encodeURIComponent(projectId)}/lifecycle/transitions`, {
32398
32234
  targetState,
@@ -32400,13 +32236,6 @@ function createHttpClient(config2) {
32400
32236
  decisionNoteId: options.decisionNoteId
32401
32237
  });
32402
32238
  },
32403
- transitionProjectLifecycleState: async (projectId, targetState, options) => {
32404
- return request("POST", `/api/systems/${encodeURIComponent(projectId)}/lifecycle/transitions`, {
32405
- targetState,
32406
- reason: options.reason,
32407
- decisionNoteId: options.decisionNoteId
32408
- });
32409
- },
32410
32239
  // Beats
32411
32240
  getNextBeatId: async (projectId) => {
32412
32241
  const result = await request("POST", `/api/systems/${encodeURIComponent(projectId)}/beats/next-id`);
@@ -32584,22 +32413,6 @@ function createHttpClient(config2) {
32584
32413
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes${qs}`);
32585
32414
  return result?.notes ?? [];
32586
32415
  },
32587
- listProjectNotes: async (projectId, filters) => {
32588
- const params = new URLSearchParams();
32589
- if (filters?.noteType) {
32590
- const types = Array.isArray(filters.noteType) ? filters.noteType : [filters.noteType];
32591
- types.forEach((t) => params.append("type", t));
32592
- }
32593
- if (filters?.status) params.set("status", filters.status);
32594
- if (filters?.revisionId) params.set("revisionId", filters.revisionId);
32595
- if (filters?.beatVersionId) params.set("beatVersionId", filters.beatVersionId);
32596
- if (filters?.excludeChildren) params.set("excludeChildren", "true");
32597
- if (filters?.significance) params.set("significance", filters.significance);
32598
- if (filters?.limit !== void 0) params.set("limit", String(filters.limit));
32599
- const qs = params.toString() ? `?${params.toString()}` : "";
32600
- const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes${qs}`);
32601
- return result?.notes ?? [];
32602
- },
32603
32416
  listAllSystemNotes: async (projectId, filters, limit, cursor) => {
32604
32417
  const params = new URLSearchParams();
32605
32418
  if (filters?.noteType) {
@@ -32618,24 +32431,6 @@ function createHttpClient(config2) {
32618
32431
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes/all${qs}`);
32619
32432
  return { notes: result?.notes ?? [], cursor: result?.cursor, hasMore: result?.hasMore ?? false };
32620
32433
  },
32621
- listAllProjectNotes: async (projectId, filters, limit, cursor) => {
32622
- const params = new URLSearchParams();
32623
- if (filters?.noteType) {
32624
- const types = Array.isArray(filters.noteType) ? filters.noteType : [filters.noteType];
32625
- types.forEach((t) => params.append("type", t));
32626
- }
32627
- if (filters?.excludeNoteType) params.set("excludeType", filters.excludeNoteType);
32628
- if (filters?.status) params.set("status", filters.status);
32629
- if (filters?.revisionId) params.set("revisionId", filters.revisionId);
32630
- if (filters?.beatVersionId) params.set("beatVersionId", filters.beatVersionId);
32631
- if (filters?.excludeChildren) params.set("excludeChildren", "true");
32632
- if (filters?.significance) params.set("significance", filters.significance);
32633
- if (limit !== void 0) params.set("limit", String(limit));
32634
- if (cursor !== void 0) params.set("cursor", cursor);
32635
- const qs = params.toString() ? `?${params.toString()}` : "";
32636
- const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes/all${qs}`);
32637
- return { notes: result?.notes ?? [], cursor: result?.cursor, hasMore: result?.hasMore ?? false };
32638
- },
32639
32434
  listBeatNotes: async (beatId, filters) => {
32640
32435
  const beat = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
32641
32436
  if (!beat) return [];
@@ -32728,7 +32523,6 @@ function createHttpClient(config2) {
32728
32523
  },
32729
32524
  // Gaps are deprecated — replaced by assumption Notes. Return empty results.
32730
32525
  getSystemGaps: async () => [],
32731
- getProjectGaps: async () => [],
32732
32526
  updateInformationGap: async () => false,
32733
32527
  // Revisions
32734
32528
  createRevision: async (projectId, beatId, input) => {
@@ -32753,14 +32547,6 @@ function createHttpClient(config2) {
32753
32547
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/revisions${qs}`);
32754
32548
  return result ?? [];
32755
32549
  },
32756
- listProjectRevisions: async (projectId, options) => {
32757
- const params = new URLSearchParams();
32758
- if (options?.status) params.set("status", options.status);
32759
- if (options?.includeArchived) params.set("includeArchived", "true");
32760
- const qs = params.toString() ? `?${params.toString()}` : "";
32761
- const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/revisions${qs}`);
32762
- return result ?? [];
32763
- },
32764
32550
  listBeatRevisions: async (projectId, beatId, options) => {
32765
32551
  const params = new URLSearchParams();
32766
32552
  params.set("beatId", beatId);
@@ -32990,14 +32776,6 @@ function createHttpClient(config2) {
32990
32776
  const result = await request("GET", `/api/sessions?${params.toString()}`);
32991
32777
  return result ?? [];
32992
32778
  },
32993
- listProjectSessions: async (projectId, options) => {
32994
- const params = new URLSearchParams();
32995
- params.set("projectId", projectId);
32996
- if (options?.status) params.set("status", options.status);
32997
- if (options?.includeTerminal) params.set("includeTerminal", "true");
32998
- const result = await request("GET", `/api/sessions?${params.toString()}`);
32999
- return result ?? [];
33000
- },
33001
32779
  listBeatSessions: async (beatId, options) => {
33002
32780
  const params = new URLSearchParams();
33003
32781
  params.set("beatId", beatId);
@@ -33185,14 +32963,6 @@ function createHttpClient(config2) {
33185
32963
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/tasks${qs}`);
33186
32964
  return result?.tasks ?? [];
33187
32965
  },
33188
- listProjectTasks: async (projectId, options) => {
33189
- const params = new URLSearchParams();
33190
- if (options?.status) params.set("status", options.status);
33191
- if (options?.limit) params.set("limit", String(options.limit));
33192
- const qs = params.toString() ? `?${params.toString()}` : "";
33193
- const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/tasks${qs}`);
33194
- return result?.tasks ?? [];
33195
- },
33196
32966
  listTaskEvents: async (taskId, options) => {
33197
32967
  const params = new URLSearchParams();
33198
32968
  if (options?.afterSeq !== void 0) params.set("after", String(options.afterSeq));
@@ -33225,15 +32995,6 @@ function createHttpClient(config2) {
33225
32995
  const result = await request("GET", path);
33226
32996
  return { entries: result.activities ?? [], hasMore: false };
33227
32997
  },
33228
- listProjectActivities: async (projectId, options) => {
33229
- const params = new URLSearchParams();
33230
- if (options?.limit) params.set("limit", String(options.limit));
33231
- if (options?.minImportance) params.set("importance", options.minImportance);
33232
- const qs = params.toString();
33233
- const path = `/api/systems/${encodeURIComponent(projectId)}/activities${qs ? `?${qs}` : ""}`;
33234
- const result = await request("GET", path);
33235
- return { entries: result.activities ?? [], hasMore: false };
33236
- },
33237
32998
  createActivity: async (input) => {
33238
32999
  const result = await request("POST", `/api/systems/${encodeURIComponent(input.projectId)}/activities`, {
33239
33000
  action: input.action,
@@ -33308,20 +33069,6 @@ function createHttpClient(config2) {
33308
33069
  );
33309
33070
  return result.ranked ?? [];
33310
33071
  },
33311
- rankProjectBeatVersions: async (projectId, lens) => {
33312
- const result = await request(
33313
- "GET",
33314
- `/api/systems/${encodeURIComponent(projectId)}/beat-versions/ranked?lens=${encodeURIComponent(lens)}`
33315
- );
33316
- return result.ranked ?? [];
33317
- },
33318
- rankProjectBeatVersionsWithLeverage: async (projectId, lens) => {
33319
- const result = await request(
33320
- "GET",
33321
- `/api/systems/${encodeURIComponent(projectId)}/beat-versions/ranked?lens=${encodeURIComponent(lens)}&withLeverage=true`
33322
- );
33323
- return result.ranked ?? [];
33324
- },
33325
33072
  // All four use requestOrThrow, not request. These endpoints answer 404 for a
33326
33073
  // missing edge/Beat with a real domain message, and `request()` turns a 404
33327
33074
  // into `undefined` — so the old `result.edge` access threw
@@ -33620,18 +33367,6 @@ function createHttpClient(config2) {
33620
33367
  );
33621
33368
  return { checks: res?.checks ?? [], nextCursor: res?.nextCursor };
33622
33369
  },
33623
- listProjectChecks: async (projectId, checkType, options) => {
33624
- const params = new URLSearchParams();
33625
- if (checkType) params.set("type", checkType);
33626
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
33627
- if (options?.cursor) params.set("cursor", options.cursor);
33628
- const qs = params.toString() ? `?${params.toString()}` : "";
33629
- const res = await request(
33630
- "GET",
33631
- `/api/systems/${encodeURIComponent(projectId)}/checks${qs}`
33632
- );
33633
- return { checks: res?.checks ?? [], nextCursor: res?.nextCursor };
33634
- },
33635
33370
  listLatestSystemChecks: async (projectId) => {
33636
33371
  const res = await request(
33637
33372
  "GET",
@@ -33639,13 +33374,6 @@ function createHttpClient(config2) {
33639
33374
  );
33640
33375
  return res?.checks ?? [];
33641
33376
  },
33642
- listLatestProjectChecks: async (projectId) => {
33643
- const res = await request(
33644
- "GET",
33645
- `/api/checks/latest?projectId=${encodeURIComponent(projectId)}`
33646
- );
33647
- return res?.checks ?? [];
33648
- },
33649
33377
  listBeatChecks: async (beatId, projectId, checkType) => {
33650
33378
  const qs = checkType ? `?type=${encodeURIComponent(checkType)}` : "";
33651
33379
  const res = await request(
@@ -33705,20 +33433,6 @@ function createHttpClient(config2) {
33705
33433
  updateTeamspace: async (teamspaceId, updates) => {
33706
33434
  return request("PATCH", `/api/teamspaces/${encodeURIComponent(teamspaceId)}`, updates);
33707
33435
  },
33708
- // Deliverables
33709
- listTeamspaceDeliverables: async (teamspaceId) => {
33710
- const res = await request("GET", `/api/teamspaces/${encodeURIComponent(teamspaceId)}/deliverables`);
33711
- return res?.deliverables ?? [];
33712
- },
33713
- getDeliverable: async (deliverableId) => {
33714
- return request("GET", `/api/deliverables/${encodeURIComponent(deliverableId)}`);
33715
- },
33716
- createDeliverable: async (input) => {
33717
- return request("POST", `/api/teamspaces/${encodeURIComponent(input.teamspaceId)}/deliverables`, input);
33718
- },
33719
- updateDeliverable: async (deliverableId, updates) => {
33720
- return request("PATCH", `/api/deliverables/${encodeURIComponent(deliverableId)}`, updates);
33721
- },
33722
33436
  // DeliverableGroups
33723
33437
  listTeamspaceDeliverableGroups: async (teamspaceId) => {
33724
33438
  const res = await request("GET", `/api/teamspaces/${encodeURIComponent(teamspaceId)}/deliverable-groups`);
@@ -33730,11 +33444,6 @@ function createHttpClient(config2) {
33730
33444
  if (res === void 0) throw new Error(`Project ${projectId} not found`);
33731
33445
  return res.deliverableGroups;
33732
33446
  },
33733
- listProjectDeliverableGroups: async (projectId) => {
33734
- const res = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/deliverable-groups`);
33735
- if (res === void 0) throw new Error(`Project ${projectId} not found`);
33736
- return res.deliverableGroups;
33737
- },
33738
33447
  listMovementDeliverableGroups: async (movementId) => {
33739
33448
  const res = await request("GET", `/api/movements/${encodeURIComponent(movementId)}/deliverable-groups`);
33740
33449
  if (res === void 0) throw new Error(`Movement ${movementId} not found`);
@@ -33771,10 +33480,6 @@ function createHttpClient(config2) {
33771
33480
  ...opts ?? {}
33772
33481
  });
33773
33482
  },
33774
- listDeliverableGroupDeliverables: async (deliverableGroupId) => {
33775
- const res = await request("GET", `/api/deliverable-groups/${encodeURIComponent(deliverableGroupId)}/deliverables`);
33776
- return res?.deliverables ?? [];
33777
- },
33778
33483
  listDeliverableGroupDrops: async (deliverableGroupId) => {
33779
33484
  const res = await request("GET", `/api/deliverable-groups/${encodeURIComponent(deliverableGroupId)}/drops`);
33780
33485
  return res?.drops ?? [];
@@ -34354,28 +34059,54 @@ function createHttpClient(config2) {
34354
34059
  );
34355
34060
  return result?.usedBy;
34356
34061
  },
34357
- // Feature flags (B-135 v9/v10) — not available in HTTP transport; the published
34358
- // npm package has no DynamoDB credentials.
34359
- listFlags: async () => {
34360
- throw new Error("Feature flag queries are not available in HTTP transport mode");
34062
+ // Feature flags (B-135 v11) — routed through /api/mcp/feature-flags/* so the
34063
+ // published npm package (no DynamoDB credentials) can manage flags via HTTP.
34064
+ listFlags: async (options) => {
34065
+ const params = new URLSearchParams();
34066
+ if (options?.includeArchived) params.set("includeArchived", "true");
34067
+ if (options?.search) params.set("search", options.search);
34068
+ if (options?.cursor) params.set("cursor", options.cursor);
34069
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
34070
+ const qs = params.toString();
34071
+ return requestOrThrow(
34072
+ "GET",
34073
+ `/api/mcp/feature-flags${qs ? `?${qs}` : ""}`
34074
+ );
34361
34075
  },
34362
- getFlag: async () => {
34363
- throw new Error("Feature flag queries are not available in HTTP transport mode");
34076
+ getFlag: async (name, options) => {
34077
+ const qs = options?.includeHistory ? "?includeHistory=true" : "";
34078
+ return request("GET", `/api/mcp/feature-flags/${encodeURIComponent(name)}${qs}`);
34364
34079
  },
34365
- checkFlag: async () => {
34366
- throw new Error("Feature flag queries are not available in HTTP transport mode");
34080
+ checkFlag: async (name, stage) => {
34081
+ const flag = await request(
34082
+ "GET",
34083
+ `/api/mcp/feature-flags/${encodeURIComponent(name)}`
34084
+ );
34085
+ return flag ? flag.enabledIn.includes(stage) : false;
34367
34086
  },
34368
- createFlag: async () => {
34369
- throw new Error("Feature flag writes are not available in HTTP transport mode");
34087
+ createFlag: async (name, enabledIn, options) => {
34088
+ return requestOrThrow("POST", "/api/mcp/feature-flags", {
34089
+ name,
34090
+ enabledIn,
34091
+ description: options?.description,
34092
+ note: options?.note
34093
+ });
34370
34094
  },
34371
- setFlag: async () => {
34372
- throw new Error("Feature flag writes are not available in HTTP transport mode");
34095
+ setFlag: async (name, enabledIn, options) => {
34096
+ return requestOrThrow("PUT", `/api/mcp/feature-flags/${encodeURIComponent(name)}`, {
34097
+ enabledIn,
34098
+ note: options?.note
34099
+ });
34373
34100
  },
34374
- archiveFlag: async () => {
34375
- throw new Error("Feature flag writes are not available in HTTP transport mode");
34101
+ archiveFlag: async (name, options) => {
34102
+ return requestOrThrow("POST", `/api/mcp/feature-flags/${encodeURIComponent(name)}/archive`, {
34103
+ note: options?.note
34104
+ });
34376
34105
  },
34377
- unarchiveFlag: async () => {
34378
- throw new Error("Feature flag writes are not available in HTTP transport mode");
34106
+ unarchiveFlag: async (name, options) => {
34107
+ return requestOrThrow("POST", `/api/mcp/feature-flags/${encodeURIComponent(name)}/unarchive`, {
34108
+ note: options?.note
34109
+ });
34379
34110
  }
34380
34111
  };
34381
34112
  return client;
@@ -34426,7 +34157,7 @@ function loadConfig() {
34426
34157
  };
34427
34158
  }
34428
34159
  async function main() {
34429
- console.error(`[harmonica-mcp] v${"3.5.0"} starting\u2026`);
34160
+ console.error(`[harmonica-mcp] v${"3.6.0"} starting\u2026`);
34430
34161
  const config2 = loadConfig();
34431
34162
  const client = createHttpClient({
34432
34163
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {