@codazen/harmonica-mcp 1.1.0 → 2.0.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 +94 -138
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22147,22 +22147,22 @@ function registerBarTools(server, _ctx, client) {
22147
22147
  // ../../libs/harmonica-services/src/mcp/tools/baseline-tools.ts
22148
22148
  function registerBaselineTools(server, ctx, client) {
22149
22149
  server.tool(
22150
- "baseline_project",
22151
- "Create Rev 1 baseline revisions for all eligible Beats in a project, transition them to live, and run build_quality checks. Returns a taskId to poll for progress. Skips Beats that already have revisions or are archived/deprecated.",
22150
+ "baseline_system",
22151
+ "Create Rev 1 baseline revisions for all eligible Beats in a system, transition them to live, and run build_quality checks. Returns a taskId to poll for progress. Skips Beats that already have revisions or are archived/deprecated.",
22152
22152
  {
22153
- projectId: external_exports.string().describe("The project ID to baseline"),
22153
+ systemId: external_exports.string().describe("The system ID to baseline"),
22154
22154
  beatIds: external_exports.array(external_exports.string()).optional().describe("Optional: only baseline these specific beat IDs"),
22155
22155
  runChecks: external_exports.boolean().optional().default(true).describe("Whether to run build_quality checks (default: true)"),
22156
22156
  localPath: external_exports.string().optional().describe("Absolute local filesystem path to the repo \u2014 skips git clone and drives revision state from code analysis (requires HARMONICA_ALLOW_LOCAL_REPO=true)")
22157
22157
  },
22158
- async ({ projectId, beatIds, runChecks, localPath }) => {
22158
+ async ({ systemId, beatIds, runChecks, localPath }) => {
22159
22159
  try {
22160
- await assertProjectInOrg(client, projectId, ctx.orgId);
22161
- const task = await client.baselineProject(projectId, { beatIds, runChecks, localPath });
22160
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22161
+ const task = await client.baselineProject(systemId, { beatIds, runChecks, localPath });
22162
22162
  return {
22163
22163
  content: [{
22164
22164
  type: "text",
22165
- text: `Project baseline started. Job ID: ${task.taskId}
22165
+ text: `System baseline started. Job ID: ${task.taskId}
22166
22166
 
22167
22167
  Poll progress with: get_job_status({ jobId: "${task.taskId}" })`
22168
22168
  }]
@@ -24964,14 +24964,14 @@ ${rows.join("\n\n")}`;
24964
24964
  }
24965
24965
  function registerEmbeddingTools(server, ctx, client) {
24966
24966
  server.tool(
24967
- "embed_project_entities",
24968
- "Batch-embed all Notes and Beats in a project. Generates vector embeddings for similarity search. Run this to populate or refresh embeddings.",
24967
+ "embed_system_entities",
24968
+ "Batch-embed all Notes and Beats in a system. Generates vector embeddings for similarity search. Run this to populate or refresh embeddings.",
24969
24969
  {
24970
- projectId: external_exports.string().describe("The project ID to embed entities for")
24970
+ systemId: external_exports.string().describe("The system ID to embed entities for")
24971
24971
  },
24972
- async ({ projectId }) => {
24973
- await assertProjectInOrg(client, projectId, ctx.orgId);
24974
- const result = await client.embedProjectEntities(projectId);
24972
+ async ({ systemId }) => {
24973
+ await assertProjectInOrg(client, systemId, ctx.orgId);
24974
+ const result = await client.embedProjectEntities(systemId);
24975
24975
  const text = `## Embedding Results
24976
24976
 
24977
24977
  - Embedded: ${result.embedded}
@@ -28593,31 +28593,31 @@ function registerOrganizationTools(server, ctx, client) {
28593
28593
  }
28594
28594
  );
28595
28595
  server.tool(
28596
- "transfer_project",
28597
- "Transfer a project from its current organization to a different one. The project must belong to the configured organization.",
28596
+ "transfer_system",
28597
+ "Transfer a system from its current organization to a different one. The system must belong to the configured organization.",
28598
28598
  {
28599
- projectId: external_exports.string().describe("The project ID to transfer"),
28599
+ systemId: external_exports.string().describe("The system ID to transfer"),
28600
28600
  targetOrgId: external_exports.string().describe("The target organization ID")
28601
28601
  },
28602
- async ({ projectId, targetOrgId }) => {
28603
- const project = await client.getSystem(projectId);
28604
- if (!project) {
28605
- return { content: [{ type: "text", text: `Project not found: ${projectId}` }], isError: true };
28602
+ async ({ systemId, targetOrgId }) => {
28603
+ const system = await client.getSystem(systemId);
28604
+ if (!system) {
28605
+ return { content: [{ type: "text", text: `System not found: ${systemId}` }], isError: true };
28606
28606
  }
28607
- if (project.orgId !== ctx.orgId) {
28608
- return { content: [{ type: "text", text: `Project "${projectId}" does not belong to the configured organization.` }], isError: true };
28607
+ if (system.orgId !== ctx.orgId) {
28608
+ return { content: [{ type: "text", text: `System "${systemId}" does not belong to the configured organization.` }], isError: true };
28609
28609
  }
28610
- const result = await client.transferProject(ctx.orgId, projectId, targetOrgId);
28610
+ const result = await client.transferProject(ctx.orgId, systemId, targetOrgId);
28611
28611
  if (result.error) {
28612
28612
  return { content: [{ type: "text", text: result.error }], isError: true };
28613
28613
  }
28614
28614
  const transferred = result.project;
28615
28615
  if (!transferred) {
28616
- return { content: [{ type: "text", text: "Transfer succeeded but project data was not returned." }], isError: true };
28616
+ return { content: [{ type: "text", text: "Transfer succeeded but system data was not returned." }], isError: true };
28617
28617
  }
28618
- const text = `Project transferred.
28618
+ const text = `System transferred.
28619
28619
 
28620
- **ID:** ${transferred.projectId}
28620
+ **System ID:** ${transferred.projectId}
28621
28621
  **Title:** ${transferred.title}
28622
28622
  **New Org:** ${transferred.orgId}`;
28623
28623
  return { content: [{ type: "text", text }] };
@@ -28970,10 +28970,10 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
28970
28970
  // ../../libs/harmonica-services/src/mcp/tools/project-lifecycle-tools.ts
28971
28971
  function registerProjectLifecycleTools(server, ctx, client) {
28972
28972
  server.tool(
28973
- "transition_project_lifecycle",
28974
- "Advance a Project's capability-maturity lifecycle state. Allowed transitions follow the state machine (Concept \u2192 Incubating \u2192 Piloting \u2192 Activated \u2192 Commercializing \u2192 Scaled, plus paused/killed/sunset/archived exits). The activated \u2192 commercializing edge requires a `decisionNoteId` pointing to a project-scoped Decision Note (the governance review). Other gates are advisory.",
28973
+ "transition_system_lifecycle",
28974
+ "Advance a System's capability-maturity lifecycle state. Allowed transitions follow the state machine (Concept \u2192 Incubating \u2192 Piloting \u2192 Activated \u2192 Commercializing \u2192 Scaled, plus paused/killed/sunset/archived exits). The activated \u2192 commercializing edge requires a `decisionNoteId` pointing to a system-scoped Decision Note (the governance review). Other gates are advisory.",
28975
28975
  {
28976
- projectId: external_exports.string().describe("The project ID"),
28976
+ systemId: external_exports.string().describe("The system ID"),
28977
28977
  targetState: external_exports.enum([
28978
28978
  "concept",
28979
28979
  "incubating",
@@ -28989,16 +28989,16 @@ function registerProjectLifecycleTools(server, ctx, client) {
28989
28989
  reason: external_exports.string().optional().describe("Why this transition is being made (recorded in audit metadata)"),
28990
28990
  decisionNoteId: external_exports.string().optional().describe("Note ID of the governance Decision Note. Required for the activated \u2192 commercializing transition; optional otherwise.")
28991
28991
  },
28992
- async ({ projectId, targetState, reason, decisionNoteId }) => {
28992
+ async ({ systemId, targetState, reason, decisionNoteId }) => {
28993
28993
  try {
28994
- const project = await client.getSystem(projectId);
28995
- if (!project) {
28996
- return { content: [{ type: "text", text: `Project not found: "${projectId}"` }], isError: true };
28994
+ const system = await client.getSystem(systemId);
28995
+ if (!system) {
28996
+ return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
28997
28997
  }
28998
- if (project.orgId !== ctx.orgId) {
28999
- return { content: [{ type: "text", text: `Project "${projectId}" is not in this organization` }], isError: true };
28998
+ if (system.orgId !== ctx.orgId) {
28999
+ return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
29000
29000
  }
29001
- const result = await client.transitionProjectLifecycleState(projectId, targetState, {
29001
+ const result = await client.transitionProjectLifecycleState(systemId, targetState, {
29002
29002
  actor: { type: "human", id: ctx.user.userId, name: ctx.user.name },
29003
29003
  reason,
29004
29004
  decisionNoteId
@@ -29011,10 +29011,10 @@ function registerProjectLifecycleTools(server, ctx, client) {
29011
29011
  };
29012
29012
  }
29013
29013
  const lines = [
29014
- `Project lifecycle transitioned successfully.`,
29014
+ `System lifecycle transitioned successfully.`,
29015
29015
  "",
29016
- `**Project:** ${projectId}`,
29017
- `**Title:** ${project.title}`,
29016
+ `**System:** ${systemId}`,
29017
+ `**Title:** ${system.title}`,
29018
29018
  `**Transition:** ${result.previousState} \u2192 ${result.newState}`
29019
29019
  ];
29020
29020
  if (reason) lines.push(`**Reason:** ${reason}`);
@@ -29022,7 +29022,7 @@ function registerProjectLifecycleTools(server, ctx, client) {
29022
29022
  return { content: [{ type: "text", text: lines.join("\n") }] };
29023
29023
  } catch (err) {
29024
29024
  const message = err instanceof Error ? err.message : String(err);
29025
- return { content: [{ type: "text", text: `Failed to transition project lifecycle: ${message}` }], isError: true };
29025
+ return { content: [{ type: "text", text: `Failed to transition system lifecycle: ${message}` }], isError: true };
29026
29026
  }
29027
29027
  }
29028
29028
  );
@@ -29057,30 +29057,22 @@ function registerProjectTools(server, ctx, client) {
29057
29057
  },
29058
29058
  listSystemsHandler
29059
29059
  );
29060
- server.tool(
29061
- "list_projects",
29062
- "[Deprecated \u2014 use list_systems] List all projects in the configured organization",
29063
- {
29064
- teamspaceId: external_exports.string().optional().describe("Filter to projects belonging to a specific teamspace. Empty or whitespace-only treated as no filter.")
29065
- },
29066
- listSystemsHandler
29067
- );
29068
29060
  const getSystemContextSchema = {
29069
- projectId: external_exports.string().describe("The system ID"),
29061
+ systemId: external_exports.string().describe("The system ID"),
29070
29062
  noteLimit: external_exports.number().int().min(1).max(MAX_CONTEXT_NOTE_LIMIT).optional().describe(
29071
29063
  `Max Notes to include, prioritised by note type (default ${DEFAULT_CONTEXT_NOTE_LIMIT}, max ${MAX_CONTEXT_NOTE_LIMIT}). Use list_notes or search for the full set.`
29072
29064
  )
29073
29065
  };
29074
29066
  const getSystemContextHandler = async ({
29075
- projectId,
29067
+ systemId,
29076
29068
  noteLimit
29077
29069
  }) => {
29078
29070
  try {
29079
29071
  const [project, org] = await Promise.all([
29080
- fetchProjectInOrg(client, projectId, ctx.orgId),
29072
+ fetchProjectInOrg(client, systemId, ctx.orgId),
29081
29073
  client.getOrg(ctx.orgId)
29082
29074
  ]);
29083
- const notes = await client.listProjectNotes(projectId, {
29075
+ const notes = await client.listProjectNotes(systemId, {
29084
29076
  limit: noteLimit ?? DEFAULT_CONTEXT_NOTE_LIMIT
29085
29077
  });
29086
29078
  const text = formatProjectContext(project, notes, org?.coda);
@@ -29096,14 +29088,8 @@ function registerProjectTools(server, ctx, client) {
29096
29088
  getSystemContextSchema,
29097
29089
  getSystemContextHandler
29098
29090
  );
29099
- server.tool(
29100
- "get_project_context",
29101
- "[Deprecated \u2014 use get_system_context] Get project metadata, description, and notes in one view",
29102
- { projectId: external_exports.string().describe("The project ID") },
29103
- getSystemContextHandler
29104
- );
29105
29091
  const updateSystemSchema = {
29106
- projectId: external_exports.string().describe("The system ID"),
29092
+ systemId: external_exports.string().describe("The system ID"),
29107
29093
  title: external_exports.string().optional().describe("New system title"),
29108
29094
  description: external_exports.string().optional().describe("New system description"),
29109
29095
  strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
@@ -29114,7 +29100,7 @@ function registerProjectTools(server, ctx, client) {
29114
29100
  repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
29115
29101
  rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
29116
29102
  };
29117
- const updateSystemHandler = async ({ projectId, ...updates }) => {
29103
+ const updateSystemHandler = async ({ systemId, ...updates }) => {
29118
29104
  try {
29119
29105
  const nonEmpty = Object.fromEntries(
29120
29106
  Object.entries(updates).filter(([, v]) => v !== void 0)
@@ -29122,7 +29108,7 @@ function registerProjectTools(server, ctx, client) {
29122
29108
  if (Object.keys(nonEmpty).length === 0) {
29123
29109
  return { content: [{ type: "text", text: "No updates provided." }], isError: true };
29124
29110
  }
29125
- await assertProjectInOrg(client, projectId, ctx.orgId);
29111
+ await assertProjectInOrg(client, systemId, ctx.orgId);
29126
29112
  if (nonEmpty["accountId"] === "") nonEmpty["accountId"] = null;
29127
29113
  if (typeof updates.teamspaceId === "string") {
29128
29114
  const teamspace = await client.getTeamspace(updates.teamspaceId);
@@ -29133,12 +29119,12 @@ function registerProjectTools(server, ctx, client) {
29133
29119
  return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
29134
29120
  }
29135
29121
  }
29136
- const updated = await client.updateSystem(projectId, nonEmpty);
29122
+ const updated = await client.updateSystem(systemId, nonEmpty);
29137
29123
  if (!updated) {
29138
- return { content: [{ type: "text", text: `System not found: "${projectId}"` }], isError: true };
29124
+ return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
29139
29125
  }
29140
29126
  if (PROJECT_EMBEDDING_FIELDS.some((f) => f in nonEmpty)) {
29141
- void client.triggerProjectEmbedding(projectId);
29127
+ void client.triggerProjectEmbedding(systemId);
29142
29128
  }
29143
29129
  const lines = [
29144
29130
  `System updated successfully.`,
@@ -29158,43 +29144,27 @@ function registerProjectTools(server, ctx, client) {
29158
29144
  };
29159
29145
  server.tool("update_system", "Update system settings such as title, description, repository configuration, or the Account that owns it", updateSystemSchema, updateSystemHandler);
29160
29146
  server.tool(
29161
- "update_project",
29162
- "[Deprecated \u2014 use update_system] Update project settings such as title, description, or repository configuration",
29163
- {
29164
- projectId: external_exports.string().describe("The project ID"),
29165
- title: external_exports.string().optional().describe("New project title"),
29166
- description: external_exports.string().optional().describe("New project description"),
29167
- strategy: external_exports.string().optional().describe('Project strategy markdown \u2014 Org Strategy + Project Strategy ("Project Coda")'),
29168
- teamspaceId: external_exports.string().nullable().optional().describe("Teamspace ID to associate this project with; pass null to remove the association"),
29169
- repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
29170
- repoName: external_exports.string().optional().describe("GitHub repository name"),
29171
- repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
29172
- rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
29173
- },
29174
- updateSystemHandler
29175
- );
29176
- server.tool(
29177
- "archive_project",
29178
- "Archive a project, hiding it from the project dropdown and all active project views. Use this when a project is no longer active and should be removed from navigation.",
29179
- { projectId: external_exports.string().describe("The project ID to archive") },
29180
- async ({ projectId }) => {
29181
- const project = await client.getSystem(projectId);
29182
- if (!project) {
29183
- return { content: [{ type: "text", text: `Project not found: "${projectId}"` }], isError: true };
29147
+ "archive_system",
29148
+ "Archive a system, hiding it from the system dropdown and all active system views. Use this when a system is no longer active and should be removed from navigation.",
29149
+ { systemId: external_exports.string().describe("The system ID to archive") },
29150
+ async ({ systemId }) => {
29151
+ const system = await client.getSystem(systemId);
29152
+ if (!system) {
29153
+ return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
29184
29154
  }
29185
- if (project.orgId !== ctx.orgId) {
29186
- return { content: [{ type: "text", text: `Project "${projectId}" is not in this organization` }], isError: true };
29155
+ if (system.orgId !== ctx.orgId) {
29156
+ return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
29187
29157
  }
29188
29158
  try {
29189
- const updated = await client.archiveSystem(projectId);
29159
+ const updated = await client.archiveSystem(systemId);
29190
29160
  if (!updated) {
29191
- return { content: [{ type: "text", text: `Project not found: "${projectId}"` }], isError: true };
29161
+ return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
29192
29162
  }
29193
29163
  return {
29194
29164
  content: [{
29195
29165
  type: "text",
29196
29166
  text: [
29197
- "Project archived successfully.",
29167
+ "System archived successfully.",
29198
29168
  "",
29199
29169
  `**ID:** ${updated.projectId}`,
29200
29170
  `**Title:** ${updated.title}`,
@@ -29204,7 +29174,7 @@ function registerProjectTools(server, ctx, client) {
29204
29174
  };
29205
29175
  } catch (err) {
29206
29176
  const message = err instanceof Error ? err.message : String(err);
29207
- return { content: [{ type: "text", text: `Failed to archive project: ${message}` }], isError: true };
29177
+ return { content: [{ type: "text", text: `Failed to archive system: ${message}` }], isError: true };
29208
29178
  }
29209
29179
  }
29210
29180
  );
@@ -29258,20 +29228,6 @@ function registerProjectTools(server, ctx, client) {
29258
29228
  }
29259
29229
  };
29260
29230
  server.tool("create_system", "Create a new system in the configured organization", createSystemSchema, createSystemHandler);
29261
- server.tool(
29262
- "create_project",
29263
- "[Deprecated \u2014 use create_system] Create a new project in the configured organization",
29264
- {
29265
- title: external_exports.string().describe("Project title"),
29266
- description: external_exports.string().optional().describe("Project description"),
29267
- strategy: external_exports.string().optional().describe('Project strategy markdown \u2014 Org Strategy + Project Strategy ("Project Coda")'),
29268
- teamspaceId: external_exports.string().optional().describe("Teamspace ID to associate this project with"),
29269
- repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
29270
- repoName: external_exports.string().optional().describe("GitHub repository name"),
29271
- repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")')
29272
- },
29273
- createSystemHandler
29274
- );
29275
29231
  }
29276
29232
 
29277
29233
  // ../../libs/harmonica-services/src/mcp/tools/pulse-report-tools.ts
@@ -29818,21 +29774,21 @@ function registerSessionTools(server, ctx, client) {
29818
29774
  }
29819
29775
  );
29820
29776
  server.tool(
29821
- "list_project_sessions",
29822
- 'List agent Sessions in a project, optionally filtered by status or whether terminal sessions are included. Use this to answer "show me sessions in this project" or to find recent runs across all beats.',
29777
+ "list_system_sessions",
29778
+ 'List agent Sessions in a system, optionally filtered by status or whether terminal sessions are included. Use this to answer "show me sessions in this system" or to find recent runs across all beats.',
29823
29779
  {
29824
- projectId: external_exports.string().min(1).describe("The project ID"),
29780
+ systemId: external_exports.string().min(1).describe("The system ID"),
29825
29781
  status: external_exports.enum(SESSION_STATUSES).optional().describe("Filter by status: active, idle, or closed"),
29826
29782
  includeTerminal: external_exports.boolean().optional().describe("Include terminal (closed) sessions. Default false.")
29827
29783
  },
29828
- async ({ projectId, status, includeTerminal }) => {
29829
- const sessions = await client.listProjectSessions(projectId, {
29784
+ async ({ systemId, status, includeTerminal }) => {
29785
+ const sessions = await client.listProjectSessions(systemId, {
29830
29786
  status,
29831
29787
  includeTerminal
29832
29788
  });
29833
29789
  return {
29834
29790
  content: [
29835
- { type: "text", text: formatSessionList(sessions, `project ${projectId}`) }
29791
+ { type: "text", text: formatSessionList(sessions, `system ${systemId}`) }
29836
29792
  ]
29837
29793
  };
29838
29794
  }
@@ -30068,12 +30024,12 @@ var SNAPSHOT_VERSION = 2;
30068
30024
  // ../../libs/harmonica-services/src/mcp/tools/snapshot-tools.ts
30069
30025
  function registerSnapshotTools(server, ctx, client) {
30070
30026
  server.tool(
30071
- "export_project_snapshot",
30072
- "Export a complete project 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.",
30073
- { projectId: external_exports.string().describe("The project ID to export") },
30074
- async ({ projectId }) => {
30075
- await assertProjectInOrg(client, projectId, ctx.orgId);
30076
- const result = await client.exportProjectSnapshot(projectId);
30027
+ "export_system_snapshot",
30028
+ "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.",
30029
+ { systemId: external_exports.string().describe("The system ID to export") },
30030
+ async ({ systemId }) => {
30031
+ await assertProjectInOrg(client, systemId, ctx.orgId);
30032
+ const result = await client.exportProjectSnapshot(systemId);
30077
30033
  if (typeof result === "string") {
30078
30034
  if (!result.startsWith("https://")) {
30079
30035
  throw new Error(`exportProjectSnapshot returned an unexpected string value (expected an https:// presigned URL): ${result.slice(0, 80)}`);
@@ -30082,7 +30038,7 @@ function registerSnapshotTools(server, ctx, client) {
30082
30038
  content: [{
30083
30039
  type: "text",
30084
30040
  text: [
30085
- "Snapshot exported to S3. Pass this URL to import_project_snapshot on the target environment:",
30041
+ "Snapshot exported to S3. Pass this URL to import_system_snapshot on the target environment:",
30086
30042
  "",
30087
30043
  result,
30088
30044
  "",
@@ -30092,10 +30048,10 @@ function registerSnapshotTools(server, ctx, client) {
30092
30048
  };
30093
30049
  }
30094
30050
  const snapshot = normalizeSnapshot(result);
30095
- const filename = `harmonica-snapshot-${projectId}-${Date.now()}.json`;
30051
+ const filename = `harmonica-snapshot-${systemId}-${Date.now()}.json`;
30096
30052
  const filePath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), filename);
30097
30053
  await (0, import_promises.writeFile)(filePath, JSON.stringify(snapshot), "utf-8");
30098
- const title = snapshot.project?.title ?? projectId;
30054
+ const title = snapshot.project?.title ?? systemId;
30099
30055
  const versionNote = snapshot.version !== SNAPSHOT_VERSION ? [`Warning: snapshot version ${snapshot.version} (local expects ${SNAPSHOT_VERSION}) \u2014 some counts may be zero.`, ""] : [];
30100
30056
  const summary = [
30101
30057
  `Exported "${title}" to: ${filePath}`,
@@ -30111,14 +30067,14 @@ function registerSnapshotTools(server, ctx, client) {
30111
30067
  ` Drops: ${snapshot.drops?.length ?? 0}`,
30112
30068
  ` Deliverables: ${snapshot.deliverables?.length ?? 0}`,
30113
30069
  "",
30114
- "Use import_project_snapshot with this file path to import into another environment."
30070
+ "Use import_system_snapshot with this file path to import into another environment."
30115
30071
  ];
30116
30072
  return { content: [{ type: "text", text: summary.join("\n") }] };
30117
30073
  }
30118
30074
  );
30119
30075
  server.tool(
30120
- "import_project_snapshot",
30121
- "Import a project snapshot into the current environment. Accepts an S3 presigned URL (https:// only), a file path, or inline JSON. Blocked in production by default.",
30076
+ "import_system_snapshot",
30077
+ "Import a system snapshot into the current environment. Accepts an S3 presigned URL (https:// only), a file path, or inline JSON. Blocked in production by default.",
30122
30078
  {
30123
30079
  snapshot: external_exports.string().describe("S3 presigned URL (https://...), file path, or inline JSON string"),
30124
30080
  targetTeamspaceId: external_exports.string().min(1).optional().describe(
@@ -30127,12 +30083,12 @@ function registerSnapshotTools(server, ctx, client) {
30127
30083
  },
30128
30084
  async ({ snapshot: snapshotInput, targetTeamspaceId }) => {
30129
30085
  const isProduction = process.env.NODE_ENV === "production";
30130
- const importAllowed = process.env.ALLOW_PROJECT_IMPORT === "true";
30086
+ const importAllowed = process.env.ALLOW_SYSTEM_IMPORT === "true";
30131
30087
  if (isProduction && !importAllowed) {
30132
30088
  return {
30133
30089
  content: [{
30134
30090
  type: "text",
30135
- text: "Import is disabled in production. Set ALLOW_PROJECT_IMPORT=true to override."
30091
+ text: "Import is disabled in production. Set ALLOW_SYSTEM_IMPORT=true to override."
30136
30092
  }],
30137
30093
  isError: true
30138
30094
  };
@@ -30203,7 +30159,7 @@ async function resolveSnapshotInput(input) {
30203
30159
  }
30204
30160
  function formatImportSummary(result, targetTeamspaceId) {
30205
30161
  const lines = [
30206
- `Imported project: ${result.projectId}`,
30162
+ `Imported system: ${result.projectId}`,
30207
30163
  ` Beats: ${result.counts.beats}`,
30208
30164
  ` Proposals: ${result.counts.proposals}`,
30209
30165
  ` Revisions: ${result.counts.revisions}`,
@@ -30547,19 +30503,19 @@ function registerValueVelocityTools(server, ctx, client) {
30547
30503
  }
30548
30504
  );
30549
30505
  server.tool(
30550
- "list_project_beat_versions_ranked",
30551
- "Rank all Beat Versions in a project by Value Velocity score. Only Beat Versions with confirmed inputs appear. Results are sorted descending by chosen score. Use this to prioritize work or surface the highest-impact items first.",
30506
+ "list_system_beat_versions_ranked",
30507
+ "Rank all Beat Versions in a system by Value Velocity score. Only Beat Versions with confirmed inputs appear. Results are sorted descending by chosen score. Use this to prioritize work or surface the highest-impact items first.",
30552
30508
  {
30553
- projectId: external_exports.string().describe("The project ID"),
30509
+ systemId: external_exports.string().describe("The system ID"),
30554
30510
  lens: lensSchema.describe("Scoring lens: velocity | roi | valuePrimary (default: velocity)"),
30555
30511
  withLeverage: external_exports.boolean().optional().describe("Include cascade leverage scores (v2). Default: false.")
30556
30512
  },
30557
- async ({ projectId, lens, withLeverage }) => {
30513
+ async ({ systemId, lens, withLeverage }) => {
30558
30514
  try {
30559
- await assertProjectInOrg(client, projectId, ctx.orgId);
30515
+ await assertProjectInOrg(client, systemId, ctx.orgId);
30560
30516
  const [rankedResult, staleWResult] = await Promise.allSettled([
30561
- withLeverage ? client.rankProjectBeatVersionsWithLeverage(projectId, lens) : client.rankProjectBeatVersions(projectId, lens),
30562
- client.getStaleWBeatIds(projectId)
30517
+ withLeverage ? client.rankProjectBeatVersionsWithLeverage(systemId, lens) : client.rankProjectBeatVersions(systemId, lens),
30518
+ client.getStaleWBeatIds(systemId)
30563
30519
  ]);
30564
30520
  if (rankedResult.status === "rejected") {
30565
30521
  const msg = rankedResult.reason instanceof Error ? rankedResult.reason.message : String(rankedResult.reason);
@@ -30571,7 +30527,7 @@ function registerValueVelocityTools(server, ctx, client) {
30571
30527
  return { content: [{ type: "text", text: "No Beat Versions with confirmed Value Velocity inputs found." }] };
30572
30528
  }
30573
30529
  const lines = [
30574
- `Ranked Beat Versions for project ${projectId} (lens: ${lens}):`,
30530
+ `Ranked Beat Versions for system ${systemId} (lens: ${lens}):`,
30575
30531
  "",
30576
30532
  withLeverage ? "| Rank | Beat Version | Title | Chosen Score | Leverage Score |" : "| Rank | Beat Version | Title | Chosen Score |",
30577
30533
  withLeverage ? "|------|-------------|-------|-------------|----------------|" : "|------|-------------|-------|-------------|"
@@ -31371,7 +31327,7 @@ var TOOL_PROFILES = Object.freeze({
31371
31327
  // Copilot's orchestrator degrades with large tool sets, so each module is trimmed
31372
31328
  // to its query tools; create_note is the single write action.
31373
31329
  "copilot": Object.freeze([
31374
- allow(registerProjectTools, ["list_systems", "get_system_context", "list_projects", "get_project_context"]),
31330
+ allow(registerProjectTools, ["list_systems", "get_system_context"]),
31375
31331
  allow(registerBeatTools, ["list_beats", "get_beat", "list_revisions", "get_revision"]),
31376
31332
  allow(registerBeatVersionTools, ["list_beat_versions", "get_beat_version"]),
31377
31333
  allow(registerNoteTools, ["list_notes", "get_note", "create_note", "list_documents"]),
@@ -33729,7 +33685,7 @@ function loadConfig() {
33729
33685
  };
33730
33686
  }
33731
33687
  async function main() {
33732
- console.error(`[harmonica-mcp] v${"1.1.0"} starting\u2026`);
33688
+ console.error(`[harmonica-mcp] v${"2.0.0"} starting\u2026`);
33733
33689
  const config2 = loadConfig();
33734
33690
  const client = createHttpClient({
33735
33691
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {