@codazen/harmonica-mcp 5.2.0 → 5.3.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 +270 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21287,6 +21287,23 @@ watchPullRequest:
|
|
|
21287
21287
|
|
|
21288
21288
|
Re-read the policy before each step rather than caching it, so a human revoking
|
|
21289
21289
|
a toggle mid-flight takes effect on your next action.`;
|
|
21290
|
+
var WORK_ITEM_LIFECYCLE_INSTRUCTIONS = `WORK ITEM LIFECYCLE (CHANNEL-INDEPENDENT)
|
|
21291
|
+
|
|
21292
|
+
When you perform substantive work for a known Work Item, keep that Work Item's
|
|
21293
|
+
status accurate even when the work happens outside a Harmonica Session.
|
|
21294
|
+
|
|
21295
|
+
- Before starting, get the Work Item and transition it from \`not_started\` to
|
|
21296
|
+
\`in_progress\` with \`transition_work_item_status\`. Resume a \`blocked\` item
|
|
21297
|
+
only when its blocker has cleared. Do not reopen a \`done\` item unless new
|
|
21298
|
+
work genuinely belongs to that same item.
|
|
21299
|
+
- When its requested outcome is actually complete, transition it to \`done\`.
|
|
21300
|
+
If work cannot proceed, transition it to \`blocked\`. A conversation, task,
|
|
21301
|
+
or Session ending is not evidence that every associated Work Item is done.
|
|
21302
|
+
- Manage multiple Work Items independently. Leave untouched items unchanged;
|
|
21303
|
+
completing or blocking one must not change the others. A Session association
|
|
21304
|
+
is useful lineage, but it is never required in order to update status.
|
|
21305
|
+
- If the status tool is unavailable or fails, say plainly that the Work Item
|
|
21306
|
+
status was not updated rather than implying that it was.`;
|
|
21290
21307
|
async function createMcpServer(ctx, client, registerTools, registerResources) {
|
|
21291
21308
|
let guidelines;
|
|
21292
21309
|
try {
|
|
@@ -21294,7 +21311,11 @@ async function createMcpServer(ctx, client, registerTools, registerResources) {
|
|
|
21294
21311
|
} catch (err) {
|
|
21295
21312
|
console.warn("[MCP] Failed to load beat guidelines for server instructions:", err);
|
|
21296
21313
|
}
|
|
21297
|
-
const instructions = [
|
|
21314
|
+
const instructions = [
|
|
21315
|
+
guidelines,
|
|
21316
|
+
DELIVERY_LATITUDE_INSTRUCTIONS,
|
|
21317
|
+
WORK_ITEM_LIFECYCLE_INSTRUCTIONS
|
|
21318
|
+
].filter(Boolean).join("\n\n");
|
|
21298
21319
|
const server = new McpServer({
|
|
21299
21320
|
name: "harmonica",
|
|
21300
21321
|
version: "0.1.0"
|
|
@@ -22429,9 +22450,9 @@ var CHECK_MAX_SCORES = {
|
|
|
22429
22450
|
var BeatQualityGateError = class extends Error {
|
|
22430
22451
|
code = "beat_quality_gate_failed";
|
|
22431
22452
|
failure;
|
|
22432
|
-
constructor(
|
|
22433
|
-
super(
|
|
22434
|
-
this.failure =
|
|
22453
|
+
constructor(failure2) {
|
|
22454
|
+
super(failure2.message);
|
|
22455
|
+
this.failure = failure2;
|
|
22435
22456
|
}
|
|
22436
22457
|
};
|
|
22437
22458
|
var PlanVersionsIdempotencyError = class extends Error {
|
|
@@ -25746,6 +25767,7 @@ var NotificationSchema2 = external_exports.object({
|
|
|
25746
25767
|
systemId: external_exports.string(),
|
|
25747
25768
|
beatId: external_exports.string().optional(),
|
|
25748
25769
|
revisionId: external_exports.string().optional(),
|
|
25770
|
+
workItemIds: external_exports.array(external_exports.string()).optional(),
|
|
25749
25771
|
noteId: external_exports.string().optional(),
|
|
25750
25772
|
checkId: external_exports.string().optional(),
|
|
25751
25773
|
actorType: external_exports.enum(["user", "system"]),
|
|
@@ -25941,6 +25963,7 @@ var StartSessionRequestBodySchema = external_exports.object({
|
|
|
25941
25963
|
systemId: external_exports.string().min(1),
|
|
25942
25964
|
message: external_exports.string().min(1),
|
|
25943
25965
|
revisionId: external_exports.string().min(1).optional(),
|
|
25966
|
+
workItemIds: external_exports.array(external_exports.string().min(1)).max(50).optional(),
|
|
25944
25967
|
/** Caller's own Session id — nests the new Session under it (B-231). */
|
|
25945
25968
|
parentSessionId: external_exports.string().min(1).optional(),
|
|
25946
25969
|
enableWebSearch: external_exports.boolean().optional()
|
|
@@ -26010,7 +26033,8 @@ var WORK_ITEM_EFFORT_SIZES = [
|
|
|
26010
26033
|
"S",
|
|
26011
26034
|
"M",
|
|
26012
26035
|
"L",
|
|
26013
|
-
"XL"
|
|
26036
|
+
"XL",
|
|
26037
|
+
"XXL"
|
|
26014
26038
|
];
|
|
26015
26039
|
var WorkItemEffortSizeSchema = external_exports.enum(WORK_ITEM_EFFORT_SIZES);
|
|
26016
26040
|
var HumanAssigneeApiSchema = external_exports.object({
|
|
@@ -26150,9 +26174,24 @@ var WORK_ORDER_SIZES = [
|
|
|
26150
26174
|
"S",
|
|
26151
26175
|
"M",
|
|
26152
26176
|
"L",
|
|
26153
|
-
"XL"
|
|
26177
|
+
"XL",
|
|
26178
|
+
"XXL"
|
|
26154
26179
|
];
|
|
26155
26180
|
var WorkOrderSizeApiSchema = external_exports.enum(WORK_ORDER_SIZES);
|
|
26181
|
+
var WORK_ORDER_SIZE_VALUE_UNITS = {
|
|
26182
|
+
XS: 4,
|
|
26183
|
+
// half a person-day
|
|
26184
|
+
S: 8,
|
|
26185
|
+
// one person-day
|
|
26186
|
+
M: 24,
|
|
26187
|
+
// three person-days
|
|
26188
|
+
L: 40,
|
|
26189
|
+
// one person-week
|
|
26190
|
+
XL: 80,
|
|
26191
|
+
// two person-weeks
|
|
26192
|
+
XXL: 160
|
|
26193
|
+
// four person-weeks
|
|
26194
|
+
};
|
|
26156
26195
|
var WORK_REQUEST_ORIGINS = [
|
|
26157
26196
|
"client",
|
|
26158
26197
|
"internal"
|
|
@@ -26195,6 +26234,9 @@ var WorkOrderApiSchema = external_exports.object({
|
|
|
26195
26234
|
needsDecisionReason: NeedsDecisionReasonApiSchema.optional(),
|
|
26196
26235
|
/** Absent when the Order arrived before it was sized, which the composition counts. */
|
|
26197
26236
|
size: WorkOrderSizeApiSchema.optional(),
|
|
26237
|
+
archived: external_exports.boolean().optional(),
|
|
26238
|
+
archivedAt: external_exports.string().optional(),
|
|
26239
|
+
archivedReason: external_exports.string().optional(),
|
|
26198
26240
|
createdBy: external_exports.string().optional(),
|
|
26199
26241
|
version: external_exports.number().optional(),
|
|
26200
26242
|
createdAt: external_exports.string(),
|
|
@@ -26245,6 +26287,30 @@ var AccountApiSchema = external_exports.object({
|
|
|
26245
26287
|
updatedAt: external_exports.string()
|
|
26246
26288
|
}).passthrough();
|
|
26247
26289
|
var AccountsResponseSchema = external_exports.object({ accounts: external_exports.array(AccountApiSchema) }).passthrough();
|
|
26290
|
+
var ManagedRelationshipStatusApiSchema = external_exports.enum(["active", "suspended", "revoked"]);
|
|
26291
|
+
var ManagedRelationshipApiSchema = external_exports.object({
|
|
26292
|
+
relationshipId: external_exports.string(),
|
|
26293
|
+
agencyOrgId: external_exports.string(),
|
|
26294
|
+
customerOrgId: external_exports.string(),
|
|
26295
|
+
agencyAccountId: external_exports.string(),
|
|
26296
|
+
accountIds: external_exports.array(external_exports.string()),
|
|
26297
|
+
status: ManagedRelationshipStatusApiSchema,
|
|
26298
|
+
createdBy: external_exports.string(),
|
|
26299
|
+
statusChangedBy: external_exports.string().nullish(),
|
|
26300
|
+
statusChangedAt: external_exports.string().nullish(),
|
|
26301
|
+
suspendedBy: external_exports.string().nullish(),
|
|
26302
|
+
suspendedAt: external_exports.string().nullish(),
|
|
26303
|
+
reactivatedBy: external_exports.string().nullish(),
|
|
26304
|
+
reactivatedAt: external_exports.string().nullish(),
|
|
26305
|
+
revokedBy: external_exports.string().nullish(),
|
|
26306
|
+
revokedAt: external_exports.string().nullish(),
|
|
26307
|
+
version: external_exports.number(),
|
|
26308
|
+
createdAt: external_exports.string(),
|
|
26309
|
+
updatedAt: external_exports.string()
|
|
26310
|
+
}).passthrough();
|
|
26311
|
+
var ManagedRelationshipsResponseSchema = external_exports.object({
|
|
26312
|
+
relationships: external_exports.array(ManagedRelationshipApiSchema)
|
|
26313
|
+
}).passthrough();
|
|
26248
26314
|
var AccountSystemApiSchema = external_exports.object({
|
|
26249
26315
|
systemId: external_exports.string(),
|
|
26250
26316
|
accountId: external_exports.string(),
|
|
@@ -26982,6 +27048,100 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
26982
27048
|
);
|
|
26983
27049
|
}
|
|
26984
27050
|
|
|
27051
|
+
// ../../libs/harmonica-services/src/mcp/tools/managed-relationship-tools.ts
|
|
27052
|
+
function formatRelationship(relationship) {
|
|
27053
|
+
if (!relationship) return "Managed relationship not found.";
|
|
27054
|
+
return [
|
|
27055
|
+
`ID: ${relationship.relationshipId}`,
|
|
27056
|
+
`Agency Organization: ${relationship.agencyOrgId}`,
|
|
27057
|
+
`Customer Organization: ${relationship.customerOrgId}`,
|
|
27058
|
+
`Agency Account: ${relationship.agencyAccountId}`,
|
|
27059
|
+
`Customer Accounts: ${relationship.accountIds.join(", ")}`,
|
|
27060
|
+
`Status: ${relationship.status}`
|
|
27061
|
+
].join("\n");
|
|
27062
|
+
}
|
|
27063
|
+
function failure(action, error2) {
|
|
27064
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
27065
|
+
return {
|
|
27066
|
+
content: [{ type: "text", text: `Failed to ${action}: ${message}` }],
|
|
27067
|
+
isError: true
|
|
27068
|
+
};
|
|
27069
|
+
}
|
|
27070
|
+
function registerManagedRelationshipTools(server, _ctx, client) {
|
|
27071
|
+
server.tool(
|
|
27072
|
+
"create_managed_relationship",
|
|
27073
|
+
"Connect an agency Organization to a separately owned customer Organization and grant the agency access only to the named customer Accounts. Both Organizations, the agency commercial Account, and every customer Account must already exist. Phase 1 is administrator-provisioned; no approval request is sent.",
|
|
27074
|
+
{
|
|
27075
|
+
agencyOrgId: external_exports.string().describe("The managing agency Organization ID"),
|
|
27076
|
+
customerOrgId: external_exports.string().describe("The customer-owned Organization ID"),
|
|
27077
|
+
agencyAccountId: external_exports.string().describe("The agency-owned commercial Account for this customer"),
|
|
27078
|
+
accountIds: external_exports.array(external_exports.string()).min(1).max(10).describe("Customer-owned Account IDs delegated to the agency")
|
|
27079
|
+
},
|
|
27080
|
+
async (input) => {
|
|
27081
|
+
try {
|
|
27082
|
+
const relationship = await client.createManagedRelationship(input);
|
|
27083
|
+
return { content: [{ type: "text", text: `Created managed relationship.
|
|
27084
|
+
${formatRelationship(relationship)}` }] };
|
|
27085
|
+
} catch (error2) {
|
|
27086
|
+
return failure("create managed relationship", error2);
|
|
27087
|
+
}
|
|
27088
|
+
}
|
|
27089
|
+
);
|
|
27090
|
+
server.tool(
|
|
27091
|
+
"list_managed_relationships",
|
|
27092
|
+
"List customer Organizations and Account scopes managed by an agency Organization.",
|
|
27093
|
+
{ agencyOrgId: external_exports.string().describe("The managing agency Organization ID") },
|
|
27094
|
+
async ({ agencyOrgId }) => {
|
|
27095
|
+
try {
|
|
27096
|
+
const relationships = await client.listManagedRelationships(agencyOrgId);
|
|
27097
|
+
const text = relationships.length === 0 ? "No managed customer organizations found." : relationships.map(formatRelationship).join("\n\n");
|
|
27098
|
+
return { content: [{ type: "text", text }] };
|
|
27099
|
+
} catch (error2) {
|
|
27100
|
+
return failure("list managed relationships", error2);
|
|
27101
|
+
}
|
|
27102
|
+
}
|
|
27103
|
+
);
|
|
27104
|
+
server.tool(
|
|
27105
|
+
"get_managed_relationship",
|
|
27106
|
+
"Get one managed relationship and its explicitly delegated customer Account IDs.",
|
|
27107
|
+
{ relationshipId: external_exports.string().describe("The managed relationship ID") },
|
|
27108
|
+
async ({ relationshipId }) => {
|
|
27109
|
+
try {
|
|
27110
|
+
return {
|
|
27111
|
+
content: [{
|
|
27112
|
+
type: "text",
|
|
27113
|
+
text: formatRelationship(await client.getManagedRelationship(relationshipId))
|
|
27114
|
+
}]
|
|
27115
|
+
};
|
|
27116
|
+
} catch (error2) {
|
|
27117
|
+
return failure("get managed relationship", error2);
|
|
27118
|
+
}
|
|
27119
|
+
}
|
|
27120
|
+
);
|
|
27121
|
+
server.tool(
|
|
27122
|
+
"set_managed_relationship_status",
|
|
27123
|
+
"Suspend, reactivate, or permanently revoke a managed relationship. Revoked relationships cannot be reactivated.",
|
|
27124
|
+
{
|
|
27125
|
+
relationshipId: external_exports.string().describe("The managed relationship ID"),
|
|
27126
|
+
status: external_exports.enum(["active", "suspended", "revoked"]).describe("The target status")
|
|
27127
|
+
},
|
|
27128
|
+
async ({ relationshipId, status }) => {
|
|
27129
|
+
try {
|
|
27130
|
+
const relationship = await client.setManagedRelationshipStatus(relationshipId, status);
|
|
27131
|
+
if (!relationship) {
|
|
27132
|
+
return {
|
|
27133
|
+
content: [{ type: "text", text: "Managed relationship not found." }],
|
|
27134
|
+
isError: true
|
|
27135
|
+
};
|
|
27136
|
+
}
|
|
27137
|
+
return { content: [{ type: "text", text: formatRelationship(relationship) }] };
|
|
27138
|
+
} catch (error2) {
|
|
27139
|
+
return failure("update managed relationship", error2);
|
|
27140
|
+
}
|
|
27141
|
+
}
|
|
27142
|
+
);
|
|
27143
|
+
}
|
|
27144
|
+
|
|
26985
27145
|
// ../../libs/harmonica-services/src/mcp/tools/membership-tools.ts
|
|
26986
27146
|
var orgIdArg = external_exports.string().describe("The organization ID \u2014 see list_organizations");
|
|
26987
27147
|
function registerMembershipTools(server, ctx, client) {
|
|
@@ -28352,22 +28512,16 @@ ${formatTree(tree.root)}` }] };
|
|
|
28352
28512
|
}
|
|
28353
28513
|
let scopeSystemId = systemId;
|
|
28354
28514
|
if (!scopeSystemId) {
|
|
28355
|
-
let bindings;
|
|
28356
28515
|
let bindingsFetchFailed = false;
|
|
28357
28516
|
try {
|
|
28358
|
-
bindings = await client.getNotebookScopeBindings(notebookId);
|
|
28517
|
+
const bindings = await client.getNotebookScopeBindings(notebookId);
|
|
28359
28518
|
scopeSystemId = bindings?.find((b) => b.scopeType === "system")?.scopeId;
|
|
28360
28519
|
} catch {
|
|
28361
28520
|
bindingsFetchFailed = true;
|
|
28362
28521
|
}
|
|
28363
28522
|
if (!scopeSystemId) {
|
|
28364
|
-
|
|
28365
|
-
|
|
28366
|
-
if (hasLegacyProjectBinding) {
|
|
28367
|
-
return { content: [{ type: "text", text: `Notebook "${notebookId}" has only a legacy 'project' scope binding \u2014 record may need B-247 migration re-check.` }], isError: true };
|
|
28368
|
-
}
|
|
28369
|
-
}
|
|
28370
|
-
return { content: [{ type: "text", text: "No system scope: pass systemId or bind the notebook to a system first." }], isError: true };
|
|
28523
|
+
const msg = bindingsFetchFailed ? "Failed to look up notebook scope binding \u2014 this may be transient. Pass an explicit systemId to proceed." : "No system scope: pass systemId or bind the notebook to a system first.";
|
|
28524
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
28371
28525
|
}
|
|
28372
28526
|
}
|
|
28373
28527
|
let tree;
|
|
@@ -28471,10 +28625,9 @@ ${formatTree(tree.root)}` }] };
|
|
|
28471
28625
|
return { content: [{ type: "text", text: `Element "${elementId}" is not a live collection \u2014 its configuration cannot be edited here.` }], isError: true };
|
|
28472
28626
|
}
|
|
28473
28627
|
const base = existing.query;
|
|
28474
|
-
const normalizedScope = systemId ? `system:${systemId}` : base.scope
|
|
28475
|
-
|
|
28476
|
-
|
|
28477
|
-
return { content: [{ type: "text", text: `Stored scope '${base.scope}' has no system id \u2014 pass an explicit systemId to re-bind this collection.` }], isError: true };
|
|
28628
|
+
const normalizedScope = systemId ? `system:${systemId}` : base.scope;
|
|
28629
|
+
if (!normalizedScope.startsWith("system:") || normalizedScope.length <= "system:".length) {
|
|
28630
|
+
return { content: [{ type: "text", text: `Stored scope '${base.scope}' is not a valid system scope \u2014 pass an explicit systemId to re-bind this collection.` }], isError: true };
|
|
28478
28631
|
}
|
|
28479
28632
|
const mergedQuery = {
|
|
28480
28633
|
source: "notes",
|
|
@@ -30764,19 +30917,16 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30764
30917
|
);
|
|
30765
30918
|
}
|
|
30766
30919
|
function normalizeSnapshot(raw) {
|
|
30767
|
-
const anyRaw = raw;
|
|
30768
30920
|
return {
|
|
30769
30921
|
...raw,
|
|
30770
|
-
|
|
30771
|
-
system: raw.system ?? anyRaw["project"],
|
|
30922
|
+
system: raw.system,
|
|
30772
30923
|
beats: raw.beats ?? [],
|
|
30773
30924
|
beatIndex: raw.beatIndex ?? [],
|
|
30774
30925
|
proposals: raw.proposals ?? [],
|
|
30775
30926
|
proposalIndex: raw.proposalIndex ?? [],
|
|
30776
30927
|
revisions: raw.revisions ?? [],
|
|
30777
30928
|
revisionIndex: raw.revisionIndex ?? [],
|
|
30778
|
-
|
|
30779
|
-
systemRevisionIndex: raw.systemRevisionIndex ?? anyRaw["projectRevisionIndex"] ?? [],
|
|
30929
|
+
systemRevisionIndex: raw.systemRevisionIndex ?? [],
|
|
30780
30930
|
activities: raw.activities ?? [],
|
|
30781
30931
|
activityIndex: raw.activityIndex ?? [],
|
|
30782
30932
|
notes: raw.notes ?? [],
|
|
@@ -30789,8 +30939,7 @@ function normalizeSnapshot(raw) {
|
|
|
30789
30939
|
promptLogs: raw.promptLogs ?? [],
|
|
30790
30940
|
beatVersions: raw.beatVersions ?? [],
|
|
30791
30941
|
beatBeatVersionIndex: raw.beatBeatVersionIndex ?? [],
|
|
30792
|
-
|
|
30793
|
-
systemBeatVersionIndex: raw.systemBeatVersionIndex ?? anyRaw["projectBeatVersionIndex"] ?? [],
|
|
30942
|
+
systemBeatVersionIndex: raw.systemBeatVersionIndex ?? [],
|
|
30794
30943
|
beatVersionDropAssignments: raw.beatVersionDropAssignments ?? [],
|
|
30795
30944
|
drops: raw.drops ?? [],
|
|
30796
30945
|
accountDropIndex: raw.accountDropIndex ?? [],
|
|
@@ -30886,8 +31035,11 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30886
31035
|
if (typeof obj["version"] !== "number" || obj["version"] <= 0) {
|
|
30887
31036
|
throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
|
|
30888
31037
|
}
|
|
30889
|
-
const
|
|
30890
|
-
if (typeof
|
|
31038
|
+
const system = obj["system"];
|
|
31039
|
+
if (typeof system !== "object" || system === null || typeof system["systemId"] !== "string") {
|
|
31040
|
+
if (typeof obj["project"] === "object" && obj["project"] !== null) {
|
|
31041
|
+
throw new Error(`Snapshot${sourceHint} uses the v1-v3 'project' field format, which is no longer supported. Export a fresh snapshot from your environment and re-import.`);
|
|
31042
|
+
}
|
|
30891
31043
|
throw new Error(`Snapshot${sourceHint} is missing 'system.systemId' \u2014 it may not be a valid project snapshot.`);
|
|
30892
31044
|
}
|
|
30893
31045
|
}
|
|
@@ -31497,7 +31649,7 @@ ${t.acceptanceCriteria}` : null,
|
|
|
31497
31649
|
};
|
|
31498
31650
|
server.tool(
|
|
31499
31651
|
"create_track",
|
|
31500
|
-
'Create a Track under a Movement. Use for scoped initiatives \u2014 client SOWs, internal phases, engagement containers \u2014 that collect
|
|
31652
|
+
'Create a Track under a Movement. Use for scoped initiatives \u2014 client SOWs, internal phases, engagement containers \u2014 that collect work items and Drops. Do NOT use for software capabilities the product provides to end users (those are Beats). Recognition cues: "this engagement", "this SOW", "this phase of work", "the client project", "this track" \u2192 Track.',
|
|
31501
31653
|
createTrackSchema,
|
|
31502
31654
|
createTrackHandler
|
|
31503
31655
|
);
|
|
@@ -31937,24 +32089,47 @@ function registerValueVelocityTools(server, ctx, client) {
|
|
|
31937
32089
|
);
|
|
31938
32090
|
}
|
|
31939
32091
|
|
|
31940
|
-
// ../../libs/harmonica-services/src/
|
|
31941
|
-
var
|
|
31942
|
-
|
|
31943
|
-
|
|
31944
|
-
|
|
32092
|
+
// ../../libs/harmonica-services/src/tool-schemas/sizing.ts
|
|
32093
|
+
var WORK_ORDER_SIZE_ENUM = [
|
|
32094
|
+
"XS",
|
|
32095
|
+
"S",
|
|
32096
|
+
"M",
|
|
32097
|
+
"L",
|
|
32098
|
+
"XL",
|
|
32099
|
+
"XXL"
|
|
32100
|
+
];
|
|
31945
32101
|
var WORK_ITEM_EFFORT_SIZE_ENUM = [
|
|
31946
32102
|
"XS",
|
|
31947
32103
|
"S",
|
|
31948
32104
|
"M",
|
|
31949
32105
|
"L",
|
|
31950
|
-
"XL"
|
|
32106
|
+
"XL",
|
|
32107
|
+
"XXL"
|
|
31951
32108
|
];
|
|
32109
|
+
var SIZE_LABELS = {
|
|
32110
|
+
XS: "half a person-day",
|
|
32111
|
+
S: "one person-day",
|
|
32112
|
+
M: "three person-days",
|
|
32113
|
+
L: "one person-week",
|
|
32114
|
+
XL: "two person-weeks",
|
|
32115
|
+
XXL: "four person-weeks"
|
|
32116
|
+
};
|
|
32117
|
+
var SIZE_TABLE = WORK_ORDER_SIZE_ENUM.map(
|
|
32118
|
+
(size) => `${size}=${SIZE_LABELS[size]} (${WORK_ORDER_SIZE_VALUE_UNITS[size]} VU)`
|
|
32119
|
+
).join(", ");
|
|
32120
|
+
var SIZE_BASIS = `Estimate the effort with NO AI assistance \u2014 the fully-human person-hours. Do NOT size what it will actually take with AI. Person-hours, not calendar time: two people for a week is XL, not L. ${SIZE_TABLE}.`;
|
|
32121
|
+
|
|
32122
|
+
// ../../libs/harmonica-services/src/mcp/tools/work-item-tools.ts
|
|
32123
|
+
var WORK_ITEM_STATUS_ENUM = ["not_started", "in_progress", "blocked", "done"];
|
|
32124
|
+
function formatOwner(owner) {
|
|
32125
|
+
return owner ? `${owner.name} <${owner.email}>` : "unassigned";
|
|
32126
|
+
}
|
|
31952
32127
|
function registerWorkItemTools(server, _ctx, client) {
|
|
31953
32128
|
server.tool(
|
|
31954
32129
|
"list_track_work_items",
|
|
31955
|
-
'List all work items under a Track
|
|
32130
|
+
'List all work items under a Track. A work item is an owned, statused unit of operational work. Recognition cues: "what work is on this track", "list the work items" \u2192 list_track_work_items.',
|
|
31956
32131
|
{
|
|
31957
|
-
trackId: external_exports.string().describe("The Track
|
|
32132
|
+
trackId: external_exports.string().describe("The Track ID")
|
|
31958
32133
|
},
|
|
31959
32134
|
async ({ trackId }) => {
|
|
31960
32135
|
const workItems = await client.listTrackWorkItems(trackId);
|
|
@@ -32000,15 +32175,15 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32000
32175
|
);
|
|
32001
32176
|
server.tool(
|
|
32002
32177
|
"create_work_item",
|
|
32003
|
-
'Create a work item under a Track
|
|
32178
|
+
'Create a work item under a Track. A work item is an owned, statused unit of operational work \u2014 it is NOT a Beat. Starts in "not_started" status. Optionally link it to a Beat Version for capability lineage (v2, N-4E09-6574) \u2014 the link is one-directional and never required. Optionally link it to a Work Order it delivers part of; leaving it unattached is a supported state, not a gap to fill (N-4E09-7160). Optionally link it to a Bar it is planned into; leaving it unplanned is likewise a real state, not a gap (B-298).',
|
|
32004
32179
|
{
|
|
32005
|
-
trackId: external_exports.string().describe("The Track
|
|
32180
|
+
trackId: external_exports.string().describe("The Track ID this work item belongs to"),
|
|
32006
32181
|
title: external_exports.string().min(1).max(300).describe("Work item title"),
|
|
32007
32182
|
ownerName: external_exports.string().min(1).optional().describe("Optional owner display name (requires ownerEmail too)"),
|
|
32008
32183
|
ownerEmail: external_exports.string().email().optional().describe("Optional owner email (requires ownerName too). A work item may be left unowned."),
|
|
32009
32184
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("Optional committed estimate in hours"),
|
|
32010
|
-
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).optional().describe(
|
|
32011
|
-
valueUnits: external_exports.number().nonnegative().optional().describe(
|
|
32185
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).optional().describe(`Optional rough t-shirt effort size \u2014 seeds valueUnits. ${SIZE_BASIS}`),
|
|
32186
|
+
valueUnits: external_exports.number().nonnegative().optional().describe(`Optional Value Units \u2014 fully-human person-hours. Defaults from effortSize; pass only to override. ${SIZE_BASIS}`),
|
|
32012
32187
|
costPerValueUnit: external_exports.number().nonnegative().optional().describe("Optional price per Value Unit"),
|
|
32013
32188
|
createdBy: external_exports.string().optional().describe("Optional creator identity for audit"),
|
|
32014
32189
|
beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist."),
|
|
@@ -32056,7 +32231,9 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32056
32231
|
unassignOwner: external_exports.boolean().optional().describe("Pass true to clear the owner, leaving the work item unowned. Cannot be combined with ownerName/ownerEmail."),
|
|
32057
32232
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("New committed estimate in hours"),
|
|
32058
32233
|
actualHours: external_exports.number().nonnegative().optional().describe("New actual hours logged"),
|
|
32059
|
-
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe(
|
|
32234
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe(
|
|
32235
|
+
`Rough t-shirt effort size. Pass null to clear; omit to leave untouched. ${SIZE_BASIS}`
|
|
32236
|
+
),
|
|
32060
32237
|
valueUnits: external_exports.number().nonnegative().nullable().optional().describe("Value Units (commercial lens). Pass null to clear; omit to leave untouched."),
|
|
32061
32238
|
costPerValueUnit: external_exports.number().nonnegative().nullable().optional().describe("Price per Value Unit. Pass null to clear; omit to leave untouched."),
|
|
32062
32239
|
beatVersionId: external_exports.string().min(1).nullable().optional().describe("Link to a Beat Version for capability lineage. Pass null to unlink; omit to leave the current link untouched."),
|
|
@@ -32117,10 +32294,10 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32117
32294
|
);
|
|
32118
32295
|
server.tool(
|
|
32119
32296
|
"move_work_item",
|
|
32120
|
-
"Move a work item to a different Track
|
|
32297
|
+
"Move a work item to a different Track. Re-derives its teamspace/org scope from the new Track.",
|
|
32121
32298
|
{
|
|
32122
32299
|
workItemId: external_exports.string().describe("The work item ID"),
|
|
32123
|
-
newTrackId: external_exports.string().describe("The destination Track
|
|
32300
|
+
newTrackId: external_exports.string().describe("The destination Track ID. Must exist.")
|
|
32124
32301
|
},
|
|
32125
32302
|
async ({ workItemId, newTrackId }) => {
|
|
32126
32303
|
const wi = await client.moveWorkItem(workItemId, newTrackId);
|
|
@@ -32163,13 +32340,12 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32163
32340
|
}
|
|
32164
32341
|
|
|
32165
32342
|
// ../../libs/harmonica-services/src/tool-schemas/work-order.ts
|
|
32166
|
-
var WORK_ORDER_SIZE_ENUM = ["XS", "S", "M", "L", "XL"];
|
|
32167
32343
|
var create_work_order = {
|
|
32168
32344
|
workRequestId: external_exports.string().min(1).describe("The Work Request this order was split from \u2014 immutable lineage"),
|
|
32169
32345
|
title: external_exports.string().min(1).max(300).describe("Work order title"),
|
|
32170
32346
|
description: external_exports.string().max(1e4).optional().describe("Optional detail beyond the title"),
|
|
32171
32347
|
size: external_exports.enum(WORK_ORDER_SIZE_ENUM).optional().describe(
|
|
32172
|
-
"Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them."
|
|
32348
|
+
"Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them. " + SIZE_BASIS
|
|
32173
32349
|
),
|
|
32174
32350
|
createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
|
|
32175
32351
|
};
|
|
@@ -32192,13 +32368,6 @@ var NEEDS_DECISION_REASON_ENUM = [
|
|
|
32192
32368
|
"exceeds_capacity",
|
|
32193
32369
|
"incoherent_with_decision"
|
|
32194
32370
|
];
|
|
32195
|
-
var WORK_ORDER_SIZE_ENUM2 = [
|
|
32196
|
-
"XS",
|
|
32197
|
-
"S",
|
|
32198
|
-
"M",
|
|
32199
|
-
"L",
|
|
32200
|
-
"XL"
|
|
32201
|
-
];
|
|
32202
32371
|
function oneLine(value) {
|
|
32203
32372
|
return value.replace(/[\r\n]+/g, " ");
|
|
32204
32373
|
}
|
|
@@ -32214,14 +32383,14 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32214
32383
|
// exactly the caller this alias exists to greet. Requiring it would answer
|
|
32215
32384
|
// the migrated caller with a schema error instead of the migration message.
|
|
32216
32385
|
{
|
|
32217
|
-
trackId: external_exports.string().optional().describe("Ignored \u2014 the Track
|
|
32386
|
+
trackId: external_exports.string().optional().describe("Ignored \u2014 the Track ID is no longer meaningful for Order scoping")
|
|
32218
32387
|
},
|
|
32219
32388
|
async () => {
|
|
32220
32389
|
return {
|
|
32221
32390
|
content: [
|
|
32222
32391
|
{
|
|
32223
32392
|
type: "text",
|
|
32224
|
-
text: "list_track_work_orders is deprecated. Use list_movement_work_orders(movementId) to fetch all Orders for an engagement \u2014 Orders are now scoped to their Movement, not their Track. If you have only a Track id, its Movement is on the Track itself:
|
|
32393
|
+
text: "list_track_work_orders is deprecated. Use list_movement_work_orders(movementId) to fetch all Orders for an engagement \u2014 Orders are now scoped to their Movement, not their Track. If you have only a Track id, its Movement is on the Track itself: get_track(trackId) returns movementId."
|
|
32225
32394
|
}
|
|
32226
32395
|
],
|
|
32227
32396
|
// The caller asked for a list and is getting none. A result without
|
|
@@ -32306,7 +32475,9 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32306
32475
|
workOrderId: external_exports.string().describe("The Work Order ID"),
|
|
32307
32476
|
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
32308
32477
|
description: external_exports.string().max(1e4).nullable().optional().describe("New description. Pass null to clear; omit to leave untouched."),
|
|
32309
|
-
size: external_exports.enum(
|
|
32478
|
+
size: external_exports.enum(WORK_ORDER_SIZE_ENUM).nullable().optional().describe(
|
|
32479
|
+
"New t-shirt size. Pass null to return it to unsized; omit to leave untouched. " + SIZE_BASIS
|
|
32480
|
+
)
|
|
32310
32481
|
},
|
|
32311
32482
|
async ({ workOrderId, title, description, size }) => {
|
|
32312
32483
|
if (title === void 0 && description === void 0 && size === void 0) {
|
|
@@ -32336,7 +32507,7 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32336
32507
|
// the callers who have already stopped sending these.
|
|
32337
32508
|
{
|
|
32338
32509
|
workOrderId: external_exports.string().optional().describe("Ignored \u2014 a Work Order is no longer moveable between Tracks"),
|
|
32339
|
-
newTrackId: external_exports.string().optional().describe("Ignored \u2014 a Track
|
|
32510
|
+
newTrackId: external_exports.string().optional().describe("Ignored \u2014 a Track is no longer a valid parent for Orders")
|
|
32340
32511
|
},
|
|
32341
32512
|
async () => {
|
|
32342
32513
|
return {
|
|
@@ -32538,6 +32709,14 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32538
32709
|
);
|
|
32539
32710
|
}
|
|
32540
32711
|
|
|
32712
|
+
// ../../libs/harmonica-services/src/work-item-ids.ts
|
|
32713
|
+
function normalizeWorkItemIds(value) {
|
|
32714
|
+
if (!Array.isArray(value)) return [];
|
|
32715
|
+
return [...new Set(
|
|
32716
|
+
value.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
|
|
32717
|
+
)];
|
|
32718
|
+
}
|
|
32719
|
+
|
|
32541
32720
|
// ../../libs/harmonica-services/src/mcp/tools/workflow-tools.ts
|
|
32542
32721
|
var TASK_STATUSES = ["pending", "running", "completed", "failed"];
|
|
32543
32722
|
function registerWorkflowTools(server, ctx, client) {
|
|
@@ -32728,17 +32907,19 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
32728
32907
|
systemId: external_exports.string().min(1).describe("The system ID \u2014 the trusted scope the session is rooted at"),
|
|
32729
32908
|
message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
|
|
32730
32909
|
revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions."),
|
|
32910
|
+
workItemIds: external_exports.array(external_exports.string().min(1)).max(50).optional().describe("Optional Work Items associated with this session at kickoff. Each moves to in_progress when execution starts; the agent manages each item outcome independently."),
|
|
32731
32911
|
parentSessionId: external_exports.string().min(1).optional().describe("Your current Session ID (sess-<uuid>). Pass it when you are kicking off this session as part of your own work \u2014 the new session then nests under yours in the session tree and can report back to you. Omit for a standalone session.")
|
|
32732
32912
|
},
|
|
32733
|
-
async ({ systemId, message, revisionId, parentSessionId }) => {
|
|
32913
|
+
async ({ systemId, message, revisionId, workItemIds, parentSessionId }) => {
|
|
32734
32914
|
try {
|
|
32735
32915
|
await assertSystemExists(client, systemId);
|
|
32736
32916
|
const task = await client.startSession(
|
|
32737
32917
|
systemId,
|
|
32738
32918
|
message,
|
|
32739
32919
|
{ name: ctx.user.name, email: ctx.user.email },
|
|
32740
|
-
revisionId?.trim() || parentSessionId?.trim() ? {
|
|
32920
|
+
revisionId?.trim() || workItemIds?.length || parentSessionId?.trim() ? {
|
|
32741
32921
|
...revisionId?.trim() && { revisionId: revisionId.trim() },
|
|
32922
|
+
...workItemIds?.length && { workItemIds: normalizeWorkItemIds(workItemIds) },
|
|
32742
32923
|
...parentSessionId?.trim() && { parentSessionId: parentSessionId.trim() }
|
|
32743
32924
|
} : void 0
|
|
32744
32925
|
);
|
|
@@ -32966,6 +33147,7 @@ var TOOL_PROFILES = Object.freeze({
|
|
|
32966
33147
|
registerOrganizationTools,
|
|
32967
33148
|
registerTeamspaceTools,
|
|
32968
33149
|
registerMembershipTools,
|
|
33150
|
+
registerManagedRelationshipTools,
|
|
32969
33151
|
registerSystemTools,
|
|
32970
33152
|
registerSystemLifecycleTools,
|
|
32971
33153
|
allow(registerBeatVersionTools, ["relocate_beat_version"])
|
|
@@ -33045,6 +33227,7 @@ function registerAllTools(server, ctx, client, profile) {
|
|
|
33045
33227
|
registerValueVelocityTools(server, ctx, client);
|
|
33046
33228
|
registerNotebookTools(server, ctx, client);
|
|
33047
33229
|
registerAccountTools(server, ctx, client);
|
|
33230
|
+
registerManagedRelationshipTools(server, ctx, client);
|
|
33048
33231
|
registerMovementTools(server, ctx, client);
|
|
33049
33232
|
registerExternalMappingTools(server, ctx, client);
|
|
33050
33233
|
registerWorkItemTools(server, ctx, client);
|
|
@@ -34387,6 +34570,7 @@ function createHttpClient(config2) {
|
|
|
34387
34570
|
// Agent sessions
|
|
34388
34571
|
startSession: async (systemId, message, triggeredBy, options) => {
|
|
34389
34572
|
const trimmedRevisionId = options?.revisionId?.trim();
|
|
34573
|
+
const workItemIds = normalizeWorkItemIds(options?.workItemIds);
|
|
34390
34574
|
const trimmedParentSessionId = options?.parentSessionId?.trim();
|
|
34391
34575
|
const result = await request(
|
|
34392
34576
|
"POST",
|
|
@@ -34396,6 +34580,7 @@ function createHttpClient(config2) {
|
|
|
34396
34580
|
message,
|
|
34397
34581
|
...triggeredBy && { triggeredBy },
|
|
34398
34582
|
...trimmedRevisionId && { revisionId: trimmedRevisionId },
|
|
34583
|
+
...workItemIds?.length && { workItemIds },
|
|
34399
34584
|
...trimmedParentSessionId && { parentSessionId: trimmedParentSessionId }
|
|
34400
34585
|
}
|
|
34401
34586
|
);
|
|
@@ -35034,6 +35219,31 @@ function createHttpClient(config2) {
|
|
|
35034
35219
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/movements`);
|
|
35035
35220
|
return res?.movements ?? [];
|
|
35036
35221
|
},
|
|
35222
|
+
createManagedRelationship: async (input) => {
|
|
35223
|
+
const { agencyOrgId, ...body } = input;
|
|
35224
|
+
const result = await request(
|
|
35225
|
+
"POST",
|
|
35226
|
+
`/api/organizations/${encodeURIComponent(agencyOrgId)}/managed-relationships`,
|
|
35227
|
+
body
|
|
35228
|
+
);
|
|
35229
|
+
if (result === void 0) throw new Error("Resource not found when creating managed relationship");
|
|
35230
|
+
return result;
|
|
35231
|
+
},
|
|
35232
|
+
getManagedRelationship: async (relationshipId) => request("GET", `/api/managed-relationships/${encodeURIComponent(relationshipId)}`),
|
|
35233
|
+
listManagedRelationships: async (agencyOrgId) => {
|
|
35234
|
+
const result = await request(
|
|
35235
|
+
"GET",
|
|
35236
|
+
`/api/organizations/${encodeURIComponent(agencyOrgId)}/managed-relationships`
|
|
35237
|
+
);
|
|
35238
|
+
return result?.relationships ?? [];
|
|
35239
|
+
},
|
|
35240
|
+
setManagedRelationshipStatus: async (relationshipId, status) => {
|
|
35241
|
+
return request(
|
|
35242
|
+
"PATCH",
|
|
35243
|
+
`/api/managed-relationships/${encodeURIComponent(relationshipId)}/status`,
|
|
35244
|
+
{ status }
|
|
35245
|
+
);
|
|
35246
|
+
},
|
|
35037
35247
|
listChildAccounts: async (accountId) => {
|
|
35038
35248
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/children`);
|
|
35039
35249
|
return res?.accounts ?? [];
|
|
@@ -35553,7 +35763,7 @@ function loadConfig() {
|
|
|
35553
35763
|
};
|
|
35554
35764
|
}
|
|
35555
35765
|
async function main() {
|
|
35556
|
-
console.error(`[harmonica-mcp] v${"5.
|
|
35766
|
+
console.error(`[harmonica-mcp] v${"5.3.0"} starting\u2026`);
|
|
35557
35767
|
const config2 = loadConfig();
|
|
35558
35768
|
const client = createHttpClient({
|
|
35559
35769
|
apiBaseUrl: config2.apiBaseUrl,
|