@codazen/harmonica-mcp 4.0.1 → 4.1.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 +101 -42
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25533,17 +25533,14 @@ function registerMembershipTools(server, ctx, client) {
|
|
|
25533
25533
|
);
|
|
25534
25534
|
server.tool(
|
|
25535
25535
|
"add_member",
|
|
25536
|
-
"
|
|
25536
|
+
"Grant a user access to the configured organization. Their name and avatar are filled in when they first sign in.",
|
|
25537
25537
|
{
|
|
25538
|
-
name: external_exports.string().describe("Full name of the user"),
|
|
25539
25538
|
email: external_exports.string().email().describe("Email address (used as the user ID)"),
|
|
25540
|
-
title: external_exports.string().optional().describe("Job title"),
|
|
25541
|
-
responsibility: external_exports.string().optional().describe("Primary responsibility or role description"),
|
|
25542
25539
|
role: external_exports.enum(["member", "admin", "client"]).optional().describe('Organization role (defaults to "member")')
|
|
25543
25540
|
},
|
|
25544
|
-
async ({
|
|
25541
|
+
async ({ email: email2, role }) => {
|
|
25545
25542
|
try {
|
|
25546
|
-
const member = await client.addOrgMember(ctx.orgId, {
|
|
25543
|
+
const member = await client.addOrgMember(ctx.orgId, { email: email2, role });
|
|
25547
25544
|
const lines = [
|
|
25548
25545
|
"Member added successfully.",
|
|
25549
25546
|
"",
|
|
@@ -25562,17 +25559,14 @@ function registerMembershipTools(server, ctx, client) {
|
|
|
25562
25559
|
);
|
|
25563
25560
|
server.tool(
|
|
25564
25561
|
"update_member_role",
|
|
25565
|
-
"
|
|
25562
|
+
"Change a member's role within the configured organization.",
|
|
25566
25563
|
{
|
|
25567
25564
|
userId: external_exports.string().describe("User ID (email address) of the member to update"),
|
|
25568
|
-
role: external_exports.enum(["member", "admin", "client"]).
|
|
25569
|
-
name: external_exports.string().optional().describe("Updated display name"),
|
|
25570
|
-
title: external_exports.string().optional().describe("Updated job title"),
|
|
25571
|
-
responsibility: external_exports.string().optional().describe("Updated responsibility description")
|
|
25565
|
+
role: external_exports.enum(["member", "admin", "client"]).describe("New organization role")
|
|
25572
25566
|
},
|
|
25573
|
-
async ({ userId, role
|
|
25567
|
+
async ({ userId, role }) => {
|
|
25574
25568
|
try {
|
|
25575
|
-
const member = await client.updateOrgMember(ctx.orgId, userId, { role
|
|
25569
|
+
const member = await client.updateOrgMember(ctx.orgId, userId, { role });
|
|
25576
25570
|
if (!member) {
|
|
25577
25571
|
return { content: [{ type: "text", text: `Member not found: "${userId}"` }], isError: true };
|
|
25578
25572
|
}
|
|
@@ -26335,6 +26329,15 @@ var normalizeSystemId = (raw) => {
|
|
|
26335
26329
|
if (!id) return raw;
|
|
26336
26330
|
return { ...b, systemId: id };
|
|
26337
26331
|
};
|
|
26332
|
+
var normalizeSystemIdAndName = (raw) => {
|
|
26333
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
|
|
26334
|
+
const b = raw;
|
|
26335
|
+
const result = { ...b };
|
|
26336
|
+
const id = b["systemId"] || b["projectId"];
|
|
26337
|
+
if (id) result["systemId"] = id;
|
|
26338
|
+
if ((b["systemName"] == null || b["systemName"] === "") && b["projectName"]) result["systemName"] = b["projectName"];
|
|
26339
|
+
return result;
|
|
26340
|
+
};
|
|
26338
26341
|
var normalizeSystemIdAndTitle = (raw) => {
|
|
26339
26342
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
|
|
26340
26343
|
const b = raw;
|
|
@@ -26350,15 +26353,17 @@ var BeatApiSchema = external_exports.preprocess(
|
|
|
26350
26353
|
if (typeof raw !== "object" || raw === null) return raw;
|
|
26351
26354
|
const b = raw;
|
|
26352
26355
|
const id = b["systemId"] || b["projectId"];
|
|
26353
|
-
|
|
26354
|
-
|
|
26356
|
+
const result = { ...b };
|
|
26357
|
+
if (id) result["systemId"] = id;
|
|
26358
|
+
if ((b["systemName"] == null || b["systemName"] === "") && b["projectName"]) result["systemName"] = b["projectName"];
|
|
26359
|
+
return result;
|
|
26355
26360
|
},
|
|
26356
26361
|
external_exports.object({
|
|
26357
26362
|
beatId: external_exports.string(),
|
|
26358
26363
|
systemId: external_exports.string(),
|
|
26359
|
-
/** Source
|
|
26360
|
-
* multi-
|
|
26361
|
-
|
|
26364
|
+
/** Source system's title; attached by the flat /api/beats endpoint for
|
|
26365
|
+
* multi-system (teamspace/org) responses. */
|
|
26366
|
+
systemName: external_exports.string().nullish(),
|
|
26362
26367
|
title: external_exports.string(),
|
|
26363
26368
|
version: external_exports.number().nullish(),
|
|
26364
26369
|
description: external_exports.string().nullish(),
|
|
@@ -26824,13 +26829,13 @@ var NextActionSchema = external_exports.object({
|
|
|
26824
26829
|
var NextActionsResponseSchema = external_exports.object({
|
|
26825
26830
|
actions: external_exports.array(NextActionSchema)
|
|
26826
26831
|
}).passthrough();
|
|
26827
|
-
var SearchResultSchema = external_exports.preprocess(
|
|
26832
|
+
var SearchResultSchema = external_exports.preprocess(normalizeSystemIdAndName, external_exports.object({
|
|
26828
26833
|
entityId: external_exports.string(),
|
|
26829
26834
|
entityType: external_exports.enum(["beat", "note", "revision", "beatVersion", "drop", "project", "system"]),
|
|
26830
26835
|
score: external_exports.number(),
|
|
26831
26836
|
snippet: external_exports.string(),
|
|
26832
26837
|
systemId: external_exports.string().optional(),
|
|
26833
|
-
|
|
26838
|
+
systemName: external_exports.string().optional(),
|
|
26834
26839
|
teamspaceId: external_exports.string().optional()
|
|
26835
26840
|
}).passthrough());
|
|
26836
26841
|
var SearchScopeSchema = external_exports.object({
|
|
@@ -27822,7 +27827,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27822
27827
|
const noteId = await client.getNextNoteId(projectCode, systemId);
|
|
27823
27828
|
note = await client.createNote({
|
|
27824
27829
|
noteId,
|
|
27825
|
-
|
|
27830
|
+
systemId,
|
|
27826
27831
|
beatId,
|
|
27827
27832
|
revisionId,
|
|
27828
27833
|
beatVersionId,
|
|
@@ -28921,7 +28926,7 @@ Systems (${createdProjects.length}):`);
|
|
|
28921
28926
|
const n = await client.createNote({
|
|
28922
28927
|
noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
|
|
28923
28928
|
// overridden server-side
|
|
28924
|
-
|
|
28929
|
+
systemId: project.systemId,
|
|
28925
28930
|
noteType: note.noteType,
|
|
28926
28931
|
content: note.content,
|
|
28927
28932
|
rationale: note.rationale,
|
|
@@ -29581,6 +29586,7 @@ function registerPulseReportTools(server, _ctx, client) {
|
|
|
29581
29586
|
}
|
|
29582
29587
|
|
|
29583
29588
|
// ../../libs/harmonica-services/src/delivery-policy.decide.ts
|
|
29589
|
+
var DELIVERY_STEPS = ["openPullRequest", "requestAgentReview", "watchPullRequest"];
|
|
29584
29590
|
function decideDeliveryStep(resolution, step) {
|
|
29585
29591
|
if (!resolution.resolved) return "ask";
|
|
29586
29592
|
const value = resolution.policy[step];
|
|
@@ -29588,6 +29594,19 @@ function decideDeliveryStep(resolution, step) {
|
|
|
29588
29594
|
if (value === false) return "prohibited";
|
|
29589
29595
|
return "ask";
|
|
29590
29596
|
}
|
|
29597
|
+
function mergeAgentDeliveryPolicy(current, patch) {
|
|
29598
|
+
const merged = { ...current };
|
|
29599
|
+
for (const step of DELIVERY_STEPS) {
|
|
29600
|
+
if (!(step in patch)) continue;
|
|
29601
|
+
const value = patch[step];
|
|
29602
|
+
if (value === null) {
|
|
29603
|
+
delete merged[step];
|
|
29604
|
+
} else if (value === true || value === false) {
|
|
29605
|
+
merged[step] = value;
|
|
29606
|
+
}
|
|
29607
|
+
}
|
|
29608
|
+
return merged;
|
|
29609
|
+
}
|
|
29591
29610
|
|
|
29592
29611
|
// ../../libs/harmonica-services/src/mcp/tools/revision-lifecycle-tools.ts
|
|
29593
29612
|
async function checkDeliveryStep(client, revisionId, step) {
|
|
@@ -30691,7 +30710,7 @@ function formatImportSummary(result, targetTeamspaceId) {
|
|
|
30691
30710
|
lines.push("", `Errors (${result.errors.length}):`);
|
|
30692
30711
|
result.errors.forEach((e) => lines.push(` - ${e}`));
|
|
30693
30712
|
}
|
|
30694
|
-
const hasTeamspaceData = result.
|
|
30713
|
+
const hasTeamspaceData = result.systemTeamspaceId !== void 0 || (result.counts.drops ?? 0) + (result.counts.deliverables ?? 0) > 0;
|
|
30695
30714
|
if (!targetTeamspaceId && hasTeamspaceData) {
|
|
30696
30715
|
lines.push(
|
|
30697
30716
|
"",
|
|
@@ -30965,8 +30984,14 @@ function registerSystemTools(server, ctx, client) {
|
|
|
30965
30984
|
if (Object.keys(nonEmpty).length === 0) {
|
|
30966
30985
|
return { content: [{ type: "text", text: "No updates provided." }], isError: true };
|
|
30967
30986
|
}
|
|
30968
|
-
await
|
|
30987
|
+
const currentSystem = await fetchSystemInOrg(client, systemId, ctx.orgId);
|
|
30969
30988
|
if (nonEmpty["accountId"] === "") delete nonEmpty["accountId"];
|
|
30989
|
+
if (nonEmpty["agentDeliveryPolicy"] && typeof nonEmpty["agentDeliveryPolicy"] === "object") {
|
|
30990
|
+
nonEmpty["agentDeliveryPolicy"] = mergeAgentDeliveryPolicy(
|
|
30991
|
+
currentSystem.agentDeliveryPolicy,
|
|
30992
|
+
nonEmpty["agentDeliveryPolicy"]
|
|
30993
|
+
);
|
|
30994
|
+
}
|
|
30970
30995
|
if (typeof updates.teamspaceId === "string") {
|
|
30971
30996
|
const teamspace = await client.getTeamspace(updates.teamspaceId);
|
|
30972
30997
|
if (!teamspace) {
|
|
@@ -32688,6 +32713,16 @@ function safeParseErrorBody(body) {
|
|
|
32688
32713
|
return void 0;
|
|
32689
32714
|
}
|
|
32690
32715
|
}
|
|
32716
|
+
var ORG_ROLE_PRECEDENCE = ["admin", "member", "client"];
|
|
32717
|
+
function asLegacyMember(m) {
|
|
32718
|
+
const roles = Array.isArray(m["roles"]) ? m["roles"] : [];
|
|
32719
|
+
const rank = (role2) => {
|
|
32720
|
+
const i = ORG_ROLE_PRECEDENCE.indexOf(role2);
|
|
32721
|
+
return i === -1 ? ORG_ROLE_PRECEDENCE.length : i;
|
|
32722
|
+
};
|
|
32723
|
+
const role = [...roles].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b))[0];
|
|
32724
|
+
return { ...m, role: role ?? "" };
|
|
32725
|
+
}
|
|
32691
32726
|
function createHttpClient(config2) {
|
|
32692
32727
|
const url = new URL(config2.apiBaseUrl);
|
|
32693
32728
|
const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
@@ -32802,21 +32837,37 @@ function createHttpClient(config2) {
|
|
|
32802
32837
|
getMembership: async (userId, orgId) => {
|
|
32803
32838
|
return { userId, orgId, role: "member" };
|
|
32804
32839
|
},
|
|
32805
|
-
// Org membership management
|
|
32840
|
+
// Org membership management — the generic member API, shared with the
|
|
32841
|
+
// teamspace, account and system tiers (rev-4551c900). The org-only routes
|
|
32842
|
+
// this used to call were a second implementation over the same Grant rows
|
|
32843
|
+
// that skipped the lockout rule.
|
|
32844
|
+
//
|
|
32845
|
+
// Those routes also set a name, title and responsibility on the User
|
|
32846
|
+
// record. Nothing does that any more: a name is written on first sign-in
|
|
32847
|
+
// from the identity provider, and the fields are gone from the tool
|
|
32848
|
+
// schemas rather than accepted and silently dropped.
|
|
32806
32849
|
listOrgMembers: async (orgId) => {
|
|
32807
|
-
const result = await request("GET", `/api/
|
|
32808
|
-
return result ?? [];
|
|
32850
|
+
const result = await request("GET", `/api/orgs/${encodeURIComponent(orgId)}/members`);
|
|
32851
|
+
return (result?.members ?? []).map(asLegacyMember);
|
|
32809
32852
|
},
|
|
32810
32853
|
addOrgMember: async (orgId, input) => {
|
|
32811
|
-
const result = await request("POST", `/api/
|
|
32812
|
-
|
|
32854
|
+
const result = await request("POST", `/api/orgs/${encodeURIComponent(orgId)}/members`, {
|
|
32855
|
+
userId: input.email.toLowerCase(),
|
|
32856
|
+
roles: [input.role ?? "member"]
|
|
32857
|
+
});
|
|
32858
|
+
return asLegacyMember(result?.member ?? {});
|
|
32813
32859
|
},
|
|
32814
32860
|
updateOrgMember: async (orgId, userId, input) => {
|
|
32815
|
-
|
|
32816
|
-
|
|
32861
|
+
if (!input.role) {
|
|
32862
|
+
const current = await request("GET", `/api/orgs/${encodeURIComponent(orgId)}/members`);
|
|
32863
|
+
const found = (current?.members ?? []).find((m) => String(m["userId"]).toLowerCase() === userId.toLowerCase());
|
|
32864
|
+
return found ? asLegacyMember(found) : void 0;
|
|
32865
|
+
}
|
|
32866
|
+
const result = await request("PATCH", `/api/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, { roles: [input.role] });
|
|
32867
|
+
return asLegacyMember(result?.member ?? {});
|
|
32817
32868
|
},
|
|
32818
32869
|
removeOrgMember: async (orgId, userId) => {
|
|
32819
|
-
await request("DELETE", `/api/
|
|
32870
|
+
await request("DELETE", `/api/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`);
|
|
32820
32871
|
},
|
|
32821
32872
|
// Both renameOrganization and updateOrganization PATCH the same endpoint.
|
|
32822
32873
|
// Server-side routing determines which service runs based on which fields are present.
|
|
@@ -32846,7 +32897,7 @@ function createHttpClient(config2) {
|
|
|
32846
32897
|
try {
|
|
32847
32898
|
const result = await request(
|
|
32848
32899
|
"POST",
|
|
32849
|
-
`/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-
|
|
32900
|
+
`/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-system`,
|
|
32850
32901
|
{ systemId, targetOrgId }
|
|
32851
32902
|
);
|
|
32852
32903
|
const system = result?.system ? {
|
|
@@ -32903,7 +32954,11 @@ function createHttpClient(config2) {
|
|
|
32903
32954
|
const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/facts`);
|
|
32904
32955
|
return result?.data;
|
|
32905
32956
|
},
|
|
32906
|
-
saveSystemFact: (input) =>
|
|
32957
|
+
saveSystemFact: (input) => {
|
|
32958
|
+
const effectiveId = input.systemId ?? input.projectId;
|
|
32959
|
+
if (!effectiveId) throw new Error("saveSystemFact: systemId (or legacy projectId) is required");
|
|
32960
|
+
return request("PUT", `/api/systems/${encodeURIComponent(String(effectiveId))}/facts`, input);
|
|
32961
|
+
},
|
|
32907
32962
|
transitionSystemLifecycleState: async (systemId, targetState, options) => {
|
|
32908
32963
|
return request("POST", `/api/systems/${encodeURIComponent(systemId)}/lifecycle/transitions`, {
|
|
32909
32964
|
targetState,
|
|
@@ -33136,12 +33191,14 @@ function createHttpClient(config2) {
|
|
|
33136
33191
|
return result?.note ?? result;
|
|
33137
33192
|
},
|
|
33138
33193
|
createNote: async (input) => {
|
|
33139
|
-
const
|
|
33194
|
+
const effectiveSystemId = input.systemId;
|
|
33195
|
+
if (!effectiveSystemId) throw new Error("createNote: systemId is required");
|
|
33196
|
+
const result = await request("POST", `/api/systems/${encodeURIComponent(effectiveSystemId)}/notes`, input);
|
|
33140
33197
|
return result?.note ?? result;
|
|
33141
33198
|
},
|
|
33142
33199
|
updateNote: async (noteId, input) => {
|
|
33143
33200
|
const existing = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
|
|
33144
|
-
const systemId = existing?.
|
|
33201
|
+
const systemId = existing?.systemId ?? existing?.note?.systemId;
|
|
33145
33202
|
if (systemId) {
|
|
33146
33203
|
const result2 = await request("PATCH", `/api/systems/${encodeURIComponent(systemId)}/notes/${encodeURIComponent(noteId)}`, input);
|
|
33147
33204
|
return result2?.note ?? result2;
|
|
@@ -33182,20 +33239,20 @@ function createHttpClient(config2) {
|
|
|
33182
33239
|
},
|
|
33183
33240
|
reassignNote: async (noteId, targetBeatId, reason, _actorId, targetRevisionId) => {
|
|
33184
33241
|
const note = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
|
|
33185
|
-
if (!note?.note) return void 0;
|
|
33242
|
+
if (!note?.note?.systemId) return void 0;
|
|
33186
33243
|
const result = await request(
|
|
33187
33244
|
"POST",
|
|
33188
|
-
`/api/systems/${encodeURIComponent(note.note.
|
|
33245
|
+
`/api/systems/${encodeURIComponent(note.note.systemId)}/notes/${encodeURIComponent(noteId)}/reassign`,
|
|
33189
33246
|
{ targetBeatId, targetRevisionId, reason }
|
|
33190
33247
|
);
|
|
33191
33248
|
return result?.note ?? result;
|
|
33192
33249
|
},
|
|
33193
33250
|
removeNote: async (noteId, reason, _actorId) => {
|
|
33194
33251
|
const note = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
|
|
33195
|
-
if (!note?.note) return void 0;
|
|
33252
|
+
if (!note?.note?.systemId) return void 0;
|
|
33196
33253
|
const result = await request(
|
|
33197
33254
|
"POST",
|
|
33198
|
-
`/api/systems/${encodeURIComponent(note.note.
|
|
33255
|
+
`/api/systems/${encodeURIComponent(note.note.systemId)}/notes/${encodeURIComponent(noteId)}/remove`,
|
|
33199
33256
|
{ reason }
|
|
33200
33257
|
);
|
|
33201
33258
|
return result?.note ?? result;
|
|
@@ -34145,7 +34202,9 @@ function createHttpClient(config2) {
|
|
|
34145
34202
|
...parentGroupId !== void 0 && { parentGroupId },
|
|
34146
34203
|
...createdBy !== void 0 && { createdBy }
|
|
34147
34204
|
};
|
|
34148
|
-
const
|
|
34205
|
+
const inputRec = input;
|
|
34206
|
+
const effectiveSystemId = inputRec.systemId ?? input.projectId;
|
|
34207
|
+
const url2 = effectiveSystemId ? `/api/systems/${encodeURIComponent(effectiveSystemId)}/deliverable-groups` : `/api/teamspaces/${encodeURIComponent(input.teamspaceId)}/deliverable-groups`;
|
|
34149
34208
|
const result = await request("POST", url2, body);
|
|
34150
34209
|
if (result === void 0) throw new Error(`Resource not found when creating DeliverableGroup`);
|
|
34151
34210
|
return result;
|
|
@@ -34965,7 +35024,7 @@ function loadConfig() {
|
|
|
34965
35024
|
};
|
|
34966
35025
|
}
|
|
34967
35026
|
async function main() {
|
|
34968
|
-
console.error(`[harmonica-mcp] v${"4.0
|
|
35027
|
+
console.error(`[harmonica-mcp] v${"4.1.0"} starting\u2026`);
|
|
34969
35028
|
const config2 = loadConfig();
|
|
34970
35029
|
const client = createHttpClient({
|
|
34971
35030
|
apiBaseUrl: config2.apiBaseUrl,
|