@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.
- package/dist/index.js +94 -138
- 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
|
-
"
|
|
22151
|
-
"Create Rev 1 baseline revisions for all eligible Beats in a
|
|
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
|
-
|
|
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 ({
|
|
22158
|
+
async ({ systemId, beatIds, runChecks, localPath }) => {
|
|
22159
22159
|
try {
|
|
22160
|
-
await assertProjectInOrg(client,
|
|
22161
|
-
const task = await client.baselineProject(
|
|
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: `
|
|
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
|
-
"
|
|
24968
|
-
"Batch-embed all Notes and Beats in a
|
|
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
|
-
|
|
24970
|
+
systemId: external_exports.string().describe("The system ID to embed entities for")
|
|
24971
24971
|
},
|
|
24972
|
-
async ({
|
|
24973
|
-
await assertProjectInOrg(client,
|
|
24974
|
-
const result = await client.embedProjectEntities(
|
|
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
|
-
"
|
|
28597
|
-
"Transfer a
|
|
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
|
-
|
|
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 ({
|
|
28603
|
-
const
|
|
28604
|
-
if (!
|
|
28605
|
-
return { content: [{ type: "text", text: `
|
|
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 (
|
|
28608
|
-
return { content: [{ type: "text", text: `
|
|
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,
|
|
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
|
|
28616
|
+
return { content: [{ type: "text", text: "Transfer succeeded but system data was not returned." }], isError: true };
|
|
28617
28617
|
}
|
|
28618
|
-
const text = `
|
|
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
|
-
"
|
|
28974
|
-
"Advance a
|
|
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
|
-
|
|
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 ({
|
|
28992
|
+
async ({ systemId, targetState, reason, decisionNoteId }) => {
|
|
28993
28993
|
try {
|
|
28994
|
-
const
|
|
28995
|
-
if (!
|
|
28996
|
-
return { content: [{ type: "text", text: `
|
|
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 (
|
|
28999
|
-
return { content: [{ type: "text", text: `
|
|
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(
|
|
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
|
-
`
|
|
29014
|
+
`System lifecycle transitioned successfully.`,
|
|
29015
29015
|
"",
|
|
29016
|
-
`**
|
|
29017
|
-
`**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
|
|
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
|
-
|
|
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
|
-
|
|
29067
|
+
systemId,
|
|
29076
29068
|
noteLimit
|
|
29077
29069
|
}) => {
|
|
29078
29070
|
try {
|
|
29079
29071
|
const [project, org] = await Promise.all([
|
|
29080
|
-
fetchProjectInOrg(client,
|
|
29072
|
+
fetchProjectInOrg(client, systemId, ctx.orgId),
|
|
29081
29073
|
client.getOrg(ctx.orgId)
|
|
29082
29074
|
]);
|
|
29083
|
-
const notes = await client.listProjectNotes(
|
|
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
|
-
|
|
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 ({
|
|
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,
|
|
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(
|
|
29122
|
+
const updated = await client.updateSystem(systemId, nonEmpty);
|
|
29137
29123
|
if (!updated) {
|
|
29138
|
-
return { content: [{ type: "text", text: `System not found: "${
|
|
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(
|
|
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
|
-
"
|
|
29162
|
-
"
|
|
29163
|
-
{
|
|
29164
|
-
|
|
29165
|
-
|
|
29166
|
-
|
|
29167
|
-
|
|
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 (
|
|
29186
|
-
return { content: [{ type: "text", text: `
|
|
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(
|
|
29159
|
+
const updated = await client.archiveSystem(systemId);
|
|
29190
29160
|
if (!updated) {
|
|
29191
|
-
return { content: [{ type: "text", text: `
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
29822
|
-
'List agent Sessions in a
|
|
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
|
-
|
|
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 ({
|
|
29829
|
-
const sessions = await client.listProjectSessions(
|
|
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, `
|
|
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
|
-
"
|
|
30072
|
-
"Export a complete
|
|
30073
|
-
{
|
|
30074
|
-
async ({
|
|
30075
|
-
await assertProjectInOrg(client,
|
|
30076
|
-
const result = await client.exportProjectSnapshot(
|
|
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
|
|
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-${
|
|
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 ??
|
|
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
|
|
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
|
-
"
|
|
30121
|
-
"Import a
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
"
|
|
30551
|
-
"Rank all Beat Versions in a
|
|
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
|
-
|
|
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 ({
|
|
30513
|
+
async ({ systemId, lens, withLeverage }) => {
|
|
30558
30514
|
try {
|
|
30559
|
-
await assertProjectInOrg(client,
|
|
30515
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
30560
30516
|
const [rankedResult, staleWResult] = await Promise.allSettled([
|
|
30561
|
-
withLeverage ? client.rankProjectBeatVersionsWithLeverage(
|
|
30562
|
-
client.getStaleWBeatIds(
|
|
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
|
|
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"
|
|
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${"
|
|
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,
|