@codazen/harmonica-mcp 3.4.0 → 3.5.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 +100 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25241,7 +25241,7 @@ var STAGE_ENUM = external_exports.enum([
|
|
|
25241
25241
|
"beta",
|
|
25242
25242
|
"prod"
|
|
25243
25243
|
]);
|
|
25244
|
-
function registerFeatureFlagTools(server,
|
|
25244
|
+
function registerFeatureFlagTools(server, ctx, client) {
|
|
25245
25245
|
server.tool(
|
|
25246
25246
|
"list_flags",
|
|
25247
25247
|
"List feature flags and their enabled stages. Use check_flag to test a single flag against a specific stage, or get_flag for full detail on one flag.",
|
|
@@ -25307,6 +25307,89 @@ function registerFeatureFlagTools(server, _ctx, client) {
|
|
|
25307
25307
|
}
|
|
25308
25308
|
}
|
|
25309
25309
|
);
|
|
25310
|
+
server.tool(
|
|
25311
|
+
"create_flag",
|
|
25312
|
+
"Create or update a feature flag with the specified enabled stages. Idempotent \u2014 calling with an existing flag name replaces its enabledIn list.",
|
|
25313
|
+
{
|
|
25314
|
+
name: external_exports.string().min(1).describe('Flag name (e.g. "rbac:enforcement")'),
|
|
25315
|
+
enabledIn: external_exports.array(STAGE_ENUM).describe("Stages to enable the flag in"),
|
|
25316
|
+
description: external_exports.string().optional().describe("Human-readable description of what this flag gates"),
|
|
25317
|
+
note: external_exports.string().optional().describe("Reason for this change (recorded in change history)")
|
|
25318
|
+
},
|
|
25319
|
+
async ({ name, enabledIn, description, note }) => {
|
|
25320
|
+
if (!ctx.user.email) {
|
|
25321
|
+
return { content: [{ type: "text", text: "Write operations require an authenticated user with a valid email." }], isError: true };
|
|
25322
|
+
}
|
|
25323
|
+
try {
|
|
25324
|
+
const flag = await client.createFlag(name, enabledIn, { description, note, updatedBy: ctx.user.email });
|
|
25325
|
+
return { content: [{ type: "text", text: formatFlag(flag) }] };
|
|
25326
|
+
} catch (err) {
|
|
25327
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25328
|
+
return { content: [{ type: "text", text: `Failed to create flag: ${message}` }], isError: true };
|
|
25329
|
+
}
|
|
25330
|
+
}
|
|
25331
|
+
);
|
|
25332
|
+
server.tool(
|
|
25333
|
+
"set_flag",
|
|
25334
|
+
"Update which stages a feature flag is enabled in. Replaces the current enabledIn list entirely.",
|
|
25335
|
+
{
|
|
25336
|
+
name: external_exports.string().min(1).describe("Flag name"),
|
|
25337
|
+
enabledIn: external_exports.array(STAGE_ENUM).describe("New set of stages to enable the flag in. Pass [] to disable everywhere without archiving."),
|
|
25338
|
+
note: external_exports.string().optional().describe("Reason for this change (recorded in change history)")
|
|
25339
|
+
},
|
|
25340
|
+
async ({ name, enabledIn, note }) => {
|
|
25341
|
+
if (!ctx.user.email) {
|
|
25342
|
+
return { content: [{ type: "text", text: "Write operations require an authenticated user with a valid email." }], isError: true };
|
|
25343
|
+
}
|
|
25344
|
+
try {
|
|
25345
|
+
const flag = await client.setFlag(name, enabledIn, { note, updatedBy: ctx.user.email });
|
|
25346
|
+
return { content: [{ type: "text", text: formatFlag(flag) }] };
|
|
25347
|
+
} catch (err) {
|
|
25348
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25349
|
+
return { content: [{ type: "text", text: `Failed to set flag: ${message}` }], isError: true };
|
|
25350
|
+
}
|
|
25351
|
+
}
|
|
25352
|
+
);
|
|
25353
|
+
server.tool(
|
|
25354
|
+
"archive_flag",
|
|
25355
|
+
"Archive a feature flag. Clears its enabledIn list and marks it archived so it cannot be re-enabled via set_flag. Use unarchive_flag to restore it.",
|
|
25356
|
+
{
|
|
25357
|
+
name: external_exports.string().min(1).describe("Flag name"),
|
|
25358
|
+
note: external_exports.string().optional().describe("Reason for archiving (recorded in change history)")
|
|
25359
|
+
},
|
|
25360
|
+
async ({ name, note }) => {
|
|
25361
|
+
if (!ctx.user.email) {
|
|
25362
|
+
return { content: [{ type: "text", text: "Write operations require an authenticated user with a valid email." }], isError: true };
|
|
25363
|
+
}
|
|
25364
|
+
try {
|
|
25365
|
+
const flag = await client.archiveFlag(name, { note, updatedBy: ctx.user.email });
|
|
25366
|
+
return { content: [{ type: "text", text: formatFlag(flag) }] };
|
|
25367
|
+
} catch (err) {
|
|
25368
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25369
|
+
return { content: [{ type: "text", text: `Failed to archive flag: ${message}` }], isError: true };
|
|
25370
|
+
}
|
|
25371
|
+
}
|
|
25372
|
+
);
|
|
25373
|
+
server.tool(
|
|
25374
|
+
"unarchive_flag",
|
|
25375
|
+
"Restore an archived feature flag to operable state. The flag is re-enabled with an empty enabledIn list \u2014 use set_flag afterward to enable it in specific stages.",
|
|
25376
|
+
{
|
|
25377
|
+
name: external_exports.string().min(1).describe("Flag name"),
|
|
25378
|
+
note: external_exports.string().optional().describe("Reason for restoring (recorded in change history)")
|
|
25379
|
+
},
|
|
25380
|
+
async ({ name, note }) => {
|
|
25381
|
+
if (!ctx.user.email) {
|
|
25382
|
+
return { content: [{ type: "text", text: "Write operations require an authenticated user with a valid email." }], isError: true };
|
|
25383
|
+
}
|
|
25384
|
+
try {
|
|
25385
|
+
const flag = await client.unarchiveFlag(name, { note, updatedBy: ctx.user.email });
|
|
25386
|
+
return { content: [{ type: "text", text: formatFlag(flag) }] };
|
|
25387
|
+
} catch (err) {
|
|
25388
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25389
|
+
return { content: [{ type: "text", text: `Failed to unarchive flag: ${message}` }], isError: true };
|
|
25390
|
+
}
|
|
25391
|
+
}
|
|
25392
|
+
);
|
|
25310
25393
|
}
|
|
25311
25394
|
function formatFlag(flag, history) {
|
|
25312
25395
|
const archived = flag.archivedAt ? " **(archived)**" : "";
|
|
@@ -25316,7 +25399,8 @@ function formatFlag(flag, history) {
|
|
|
25316
25399
|
`**Scope:** ${flag.scope}`,
|
|
25317
25400
|
`**Enabled in:** ${enabled}`,
|
|
25318
25401
|
flag.description ? `**Description:** ${flag.description}` : null,
|
|
25319
|
-
`**Last updated:** ${flag.updatedAt.slice(0, 10)} by ${flag.updatedBy}
|
|
25402
|
+
`**Last updated:** ${flag.updatedAt.slice(0, 10)} by ${flag.updatedBy}`,
|
|
25403
|
+
flag.archivedAt ? `**Archived:** ${flag.archivedAt.slice(0, 10)} by ${flag.archivedBy}` : null
|
|
25320
25404
|
].filter(Boolean).join("\n");
|
|
25321
25405
|
if (!history?.length) return parts;
|
|
25322
25406
|
const historyLines = history.map((h) => {
|
|
@@ -34270,7 +34354,7 @@ function createHttpClient(config2) {
|
|
|
34270
34354
|
);
|
|
34271
34355
|
return result?.usedBy;
|
|
34272
34356
|
},
|
|
34273
|
-
// Feature flags (B-135 v9) — not available in HTTP transport; the published
|
|
34357
|
+
// Feature flags (B-135 v9/v10) — not available in HTTP transport; the published
|
|
34274
34358
|
// npm package has no DynamoDB credentials.
|
|
34275
34359
|
listFlags: async () => {
|
|
34276
34360
|
throw new Error("Feature flag queries are not available in HTTP transport mode");
|
|
@@ -34280,6 +34364,18 @@ function createHttpClient(config2) {
|
|
|
34280
34364
|
},
|
|
34281
34365
|
checkFlag: async () => {
|
|
34282
34366
|
throw new Error("Feature flag queries are not available in HTTP transport mode");
|
|
34367
|
+
},
|
|
34368
|
+
createFlag: async () => {
|
|
34369
|
+
throw new Error("Feature flag writes are not available in HTTP transport mode");
|
|
34370
|
+
},
|
|
34371
|
+
setFlag: async () => {
|
|
34372
|
+
throw new Error("Feature flag writes are not available in HTTP transport mode");
|
|
34373
|
+
},
|
|
34374
|
+
archiveFlag: async () => {
|
|
34375
|
+
throw new Error("Feature flag writes are not available in HTTP transport mode");
|
|
34376
|
+
},
|
|
34377
|
+
unarchiveFlag: async () => {
|
|
34378
|
+
throw new Error("Feature flag writes are not available in HTTP transport mode");
|
|
34283
34379
|
}
|
|
34284
34380
|
};
|
|
34285
34381
|
return client;
|
|
@@ -34330,7 +34426,7 @@ function loadConfig() {
|
|
|
34330
34426
|
};
|
|
34331
34427
|
}
|
|
34332
34428
|
async function main() {
|
|
34333
|
-
console.error(`[harmonica-mcp] v${"3.
|
|
34429
|
+
console.error(`[harmonica-mcp] v${"3.5.0"} starting\u2026`);
|
|
34334
34430
|
const config2 = loadConfig();
|
|
34335
34431
|
const client = createHttpClient({
|
|
34336
34432
|
apiBaseUrl: config2.apiBaseUrl,
|