@frontmcp/plugin-feature-flags 1.8.0 → 1.8.1
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/esm/index.mjs +37 -10
- package/esm/package.json +2 -2
- package/feature-flag.plugin.d.ts +22 -4
- package/feature-flag.plugin.d.ts.map +1 -1
- package/feature-flag.types.d.ts +3 -0
- package/feature-flag.types.d.ts.map +1 -1
- package/index.js +36 -10
- package/package.json +2 -2
package/esm/index.mjs
CHANGED
|
@@ -248,6 +248,7 @@ import {
|
|
|
248
248
|
FRONTMCP_CONTEXT,
|
|
249
249
|
FrontMcpContextStorage,
|
|
250
250
|
ListResourcesHook,
|
|
251
|
+
ListResourceTemplatesHook,
|
|
251
252
|
ListToolsHook,
|
|
252
253
|
Plugin,
|
|
253
254
|
ProviderScope as ProviderScope2,
|
|
@@ -396,9 +397,10 @@ function createFeatureFlagAccessor(adapter, ctx, config) {
|
|
|
396
397
|
|
|
397
398
|
// plugins/plugin-feature-flags/src/feature-flag.plugin.ts
|
|
398
399
|
var ListPromptsHook = FlowHooksOf("prompts:list-prompts");
|
|
399
|
-
var SearchSkillsHook = FlowHooksOf("skills:search");
|
|
400
400
|
var ReadResourceHook = FlowHooksOf("resources:read-resource");
|
|
401
401
|
var GetPromptHook = FlowHooksOf("prompts:get-prompt");
|
|
402
|
+
var CompleteHook = FlowHooksOf("completion:complete");
|
|
403
|
+
var FilterSkillsHook = FlowHooksOf("skills:filter");
|
|
402
404
|
var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
403
405
|
options;
|
|
404
406
|
constructor(options) {
|
|
@@ -433,6 +435,20 @@ var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
|
433
435
|
});
|
|
434
436
|
flowCtx.state.set("resources", filtered);
|
|
435
437
|
}
|
|
438
|
+
async filterListResourceTemplates(flowCtx) {
|
|
439
|
+
const { templates } = flowCtx.state;
|
|
440
|
+
if (!templates || templates.length === 0) return;
|
|
441
|
+
const flaggedTemplates = this.collectFlagRefs(templates, (item) => item.template.metadata.featureFlag);
|
|
442
|
+
if (flaggedTemplates.size === 0) return;
|
|
443
|
+
const adapter = this.get(FeatureFlagAdapterToken);
|
|
444
|
+
const flagResults = await this.batchEvaluateRefs(adapter, flaggedTemplates);
|
|
445
|
+
const filtered = templates.filter((item) => {
|
|
446
|
+
const ref = item.template.metadata.featureFlag;
|
|
447
|
+
if (!ref) return true;
|
|
448
|
+
return this.isRefEnabled(ref, flagResults);
|
|
449
|
+
});
|
|
450
|
+
flowCtx.state.set("templates", filtered);
|
|
451
|
+
}
|
|
436
452
|
async filterListPrompts(flowCtx) {
|
|
437
453
|
const { prompts } = flowCtx.state;
|
|
438
454
|
if (!prompts || prompts.length === 0) return;
|
|
@@ -447,19 +463,19 @@ var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
|
447
463
|
});
|
|
448
464
|
flowCtx.state.set("prompts", filtered);
|
|
449
465
|
}
|
|
450
|
-
async
|
|
451
|
-
const {
|
|
452
|
-
if (!
|
|
453
|
-
const flaggedSkills = this.collectFlagRefs(
|
|
466
|
+
async filterSkills(flowCtx) {
|
|
467
|
+
const { skills } = flowCtx.state;
|
|
468
|
+
if (!skills || skills.length === 0) return;
|
|
469
|
+
const flaggedSkills = this.collectFlagRefs(skills, (skill) => skill.metadata.featureFlag);
|
|
454
470
|
if (flaggedSkills.size === 0) return;
|
|
455
471
|
const adapter = this.get(FeatureFlagAdapterToken);
|
|
456
472
|
const flagResults = await this.batchEvaluateRefs(adapter, flaggedSkills);
|
|
457
|
-
const filtered =
|
|
458
|
-
const ref =
|
|
473
|
+
const filtered = skills.filter((skill) => {
|
|
474
|
+
const ref = skill.metadata.featureFlag;
|
|
459
475
|
if (!ref) return true;
|
|
460
476
|
return this.isRefEnabled(ref, flagResults);
|
|
461
477
|
});
|
|
462
|
-
flowCtx.state.set("
|
|
478
|
+
flowCtx.state.set("skills", filtered);
|
|
463
479
|
}
|
|
464
480
|
async gateToolExecution(flowCtx) {
|
|
465
481
|
await this.gateEntryExecution("Tool", flowCtx.state.tool);
|
|
@@ -470,6 +486,11 @@ var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
|
470
486
|
async gatePromptGet(flowCtx) {
|
|
471
487
|
await this.gateEntryExecution("Prompt", flowCtx.state.prompt);
|
|
472
488
|
}
|
|
489
|
+
async gateCompletion(flowCtx) {
|
|
490
|
+
const { prompt, resource } = flowCtx.state;
|
|
491
|
+
if (prompt) await this.gateEntryExecution("Prompt", prompt);
|
|
492
|
+
if (resource) await this.gateEntryExecution("Resource", resource);
|
|
493
|
+
}
|
|
473
494
|
/**
|
|
474
495
|
* Shared execution gate for tools, resources and prompts.
|
|
475
496
|
*
|
|
@@ -630,12 +651,15 @@ __decorateClass([
|
|
|
630
651
|
__decorateClass([
|
|
631
652
|
ListResourcesHook.Did("findResources", { priority: 50 })
|
|
632
653
|
], FeatureFlagPlugin.prototype, "filterListResources", 1);
|
|
654
|
+
__decorateClass([
|
|
655
|
+
ListResourceTemplatesHook.Did("findTemplates", { priority: 50 })
|
|
656
|
+
], FeatureFlagPlugin.prototype, "filterListResourceTemplates", 1);
|
|
633
657
|
__decorateClass([
|
|
634
658
|
ListPromptsHook.Did("findPrompts", { priority: 50 })
|
|
635
659
|
], FeatureFlagPlugin.prototype, "filterListPrompts", 1);
|
|
636
660
|
__decorateClass([
|
|
637
|
-
|
|
638
|
-
], FeatureFlagPlugin.prototype, "
|
|
661
|
+
FilterSkillsHook.Did("filterSkills", { priority: 50 })
|
|
662
|
+
], FeatureFlagPlugin.prototype, "filterSkills", 1);
|
|
639
663
|
__decorateClass([
|
|
640
664
|
ToolHook.Will("execute", { priority: 50 })
|
|
641
665
|
], FeatureFlagPlugin.prototype, "gateToolExecution", 1);
|
|
@@ -645,6 +669,9 @@ __decorateClass([
|
|
|
645
669
|
__decorateClass([
|
|
646
670
|
GetPromptHook.Will("execute", { priority: 50 })
|
|
647
671
|
], FeatureFlagPlugin.prototype, "gatePromptGet", 1);
|
|
672
|
+
__decorateClass([
|
|
673
|
+
CompleteHook.Will("complete", { priority: 50 })
|
|
674
|
+
], FeatureFlagPlugin.prototype, "gateCompletion", 1);
|
|
648
675
|
FeatureFlagPlugin = __decorateClass([
|
|
649
676
|
Plugin({
|
|
650
677
|
name: "feature-flags",
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.8.
|
|
52
|
+
"@frontmcp/sdk": "1.8.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|
package/feature-flag.plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DynamicPlugin, FlowCtxOf, ProviderType } from '@frontmcp/sdk';
|
|
1
|
+
import { DynamicPlugin, type FlowCtxOf, type ProviderType } from '@frontmcp/sdk';
|
|
2
2
|
import type { FeatureFlagPluginOptions, FeatureFlagPluginOptionsInput } from './feature-flag.types';
|
|
3
3
|
/**
|
|
4
4
|
* FeatureFlagPlugin - Dynamic capability gating for FrontMCP.
|
|
@@ -35,15 +35,26 @@ export default class FeatureFlagPlugin extends DynamicPlugin<FeatureFlagPluginOp
|
|
|
35
35
|
* Filter resources from list_resources based on feature flags.
|
|
36
36
|
*/
|
|
37
37
|
filterListResources(flowCtx: FlowCtxOf<'resources:list-resources'>): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Filter resource templates from resources/templates/list based on feature flags.
|
|
40
|
+
*
|
|
41
|
+
* GHSA-gf7p-j3hr-h5h4: templates were listed even when their flag was off.
|
|
42
|
+
*/
|
|
43
|
+
filterListResourceTemplates(flowCtx: FlowCtxOf<'resources:list-resource-templates'>): Promise<void>;
|
|
38
44
|
/**
|
|
39
45
|
* Filter prompts from list_prompts based on feature flags.
|
|
40
46
|
*/
|
|
41
47
|
filterListPrompts(flowCtx: any): Promise<void>;
|
|
42
48
|
/**
|
|
43
|
-
* Filter skills
|
|
44
|
-
*
|
|
49
|
+
* Filter skills in the `skills:filter` flow, which every skill surface runs: MCP
|
|
50
|
+
* `skills/search`, `skills/list` and `skills/load`, SEP-2640 `skill://` resources, and the HTTP
|
|
51
|
+
* `/skills`, `/llm.txt` and `/llm_full.txt` endpoints. A skill removed here is absent from
|
|
52
|
+
* listings and not found when named.
|
|
53
|
+
*
|
|
54
|
+
* GHSA-gf7p-j3hr-h5h4: the only skill hook sat on `skills:search`, which the `skills/search`
|
|
55
|
+
* handler never ran and whose results carry no `featureFlag`, so no skill surface was gated.
|
|
45
56
|
*/
|
|
46
|
-
|
|
57
|
+
filterSkills(flowCtx: FlowCtxOf<'skills:filter'>): Promise<void>;
|
|
47
58
|
/**
|
|
48
59
|
* Execution gate: block direct tool/call when the tool's feature flag is off.
|
|
49
60
|
* This prevents bypassing the list filter via direct tool invocation.
|
|
@@ -64,6 +75,13 @@ export default class FeatureFlagPlugin extends DynamicPlugin<FeatureFlagPluginOp
|
|
|
64
75
|
* retrievable by name.
|
|
65
76
|
*/
|
|
66
77
|
gatePromptGet(flowCtx: any): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Execution gate: block completion/complete for a prompt or resource whose feature flag is off.
|
|
80
|
+
*
|
|
81
|
+
* GHSA-gf7p-j3hr-h5h4: completion looked the entry up and ran its argument completers without
|
|
82
|
+
* a gate, so a disabled resource template still suggested its values.
|
|
83
|
+
*/
|
|
84
|
+
gateCompletion(flowCtx: any): Promise<void>;
|
|
67
85
|
/**
|
|
68
86
|
* Shared execution gate for tools, resources and prompts.
|
|
69
87
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,
|
|
1
|
+
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAUb,KAAK,SAAS,EACd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAMvB,OAAO,KAAK,EAEV,wBAAwB,EACxB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAa9B;;;;;;;;;;;;;;;;;;GAkBG;AAaH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,aAAa,CAAC,wBAAwB,EAAE,6BAA6B,CAAC;IACnH,OAAO,EAAE,wBAAwB,CAAC;gBAEtB,OAAO,EAAE,6BAA6B;IAKlD;;OAEG;IACH,OAAgB,gBAAgB,GAAI,SAAS,6BAA6B,KAAG,YAAY,EAAE,CA0FzF;IAMF;;;OAGG;IAEG,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC;IAmB5D;;OAEG;IAEG,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,0BAA0B,CAAC;IAmBxE;;;;OAIG;IAEG,2BAA2B,CAAC,OAAO,EAAE,SAAS,CAAC,mCAAmC,CAAC;IAmBzF;;OAEG;IAEG,iBAAiB,CAAC,OAAO,EAAE,GAAG;IAmBpC;;;;;;;;OAQG;IAEG,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC;IAmBtD;;;OAGG;IAEG,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,iBAAiB,CAAC;IAI7D;;;;;;OAMG;IAEG,gBAAgB,CAAC,OAAO,EAAE,GAAG;IAInC;;;;;OAKG;IAEG,aAAa,CAAC,OAAO,EAAE,GAAG;IAIhC;;;;;OAKG;IAEG,cAAc,CAAC,OAAO,EAAE,GAAG;IAMjC;;;;;OAKG;YACW,kBAAkB;IA+BhC;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;YACW,iBAAiB;IAQ/B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAWrB"}
|
package/feature-flag.types.d.ts
CHANGED
|
@@ -77,6 +77,9 @@ declare global {
|
|
|
77
77
|
interface ExtendFrontMcpResourceMetadata {
|
|
78
78
|
featureFlag?: FeatureFlagRef;
|
|
79
79
|
}
|
|
80
|
+
interface ExtendFrontMcpResourceTemplateMetadata {
|
|
81
|
+
featureFlag?: FeatureFlagRef;
|
|
82
|
+
}
|
|
80
83
|
interface ExtendFrontMcpPromptMetadata {
|
|
81
84
|
featureFlag?: FeatureFlagRef;
|
|
82
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.types.d.ts","sourceRoot":"","sources":["../src/feature-flag.types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"feature-flag.types.d.ts","sourceRoot":"","sources":["../src/feature-flag.types.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -399,9 +399,10 @@ function createFeatureFlagAccessor(adapter, ctx, config) {
|
|
|
399
399
|
|
|
400
400
|
// plugins/plugin-feature-flags/src/feature-flag.plugin.ts
|
|
401
401
|
var ListPromptsHook = (0, import_sdk2.FlowHooksOf)("prompts:list-prompts");
|
|
402
|
-
var SearchSkillsHook = (0, import_sdk2.FlowHooksOf)("skills:search");
|
|
403
402
|
var ReadResourceHook = (0, import_sdk2.FlowHooksOf)("resources:read-resource");
|
|
404
403
|
var GetPromptHook = (0, import_sdk2.FlowHooksOf)("prompts:get-prompt");
|
|
404
|
+
var CompleteHook = (0, import_sdk2.FlowHooksOf)("completion:complete");
|
|
405
|
+
var FilterSkillsHook = (0, import_sdk2.FlowHooksOf)("skills:filter");
|
|
405
406
|
var FeatureFlagPlugin = class extends import_sdk2.DynamicPlugin {
|
|
406
407
|
options;
|
|
407
408
|
constructor(options) {
|
|
@@ -436,6 +437,20 @@ var FeatureFlagPlugin = class extends import_sdk2.DynamicPlugin {
|
|
|
436
437
|
});
|
|
437
438
|
flowCtx.state.set("resources", filtered);
|
|
438
439
|
}
|
|
440
|
+
async filterListResourceTemplates(flowCtx) {
|
|
441
|
+
const { templates } = flowCtx.state;
|
|
442
|
+
if (!templates || templates.length === 0) return;
|
|
443
|
+
const flaggedTemplates = this.collectFlagRefs(templates, (item) => item.template.metadata.featureFlag);
|
|
444
|
+
if (flaggedTemplates.size === 0) return;
|
|
445
|
+
const adapter = this.get(FeatureFlagAdapterToken);
|
|
446
|
+
const flagResults = await this.batchEvaluateRefs(adapter, flaggedTemplates);
|
|
447
|
+
const filtered = templates.filter((item) => {
|
|
448
|
+
const ref = item.template.metadata.featureFlag;
|
|
449
|
+
if (!ref) return true;
|
|
450
|
+
return this.isRefEnabled(ref, flagResults);
|
|
451
|
+
});
|
|
452
|
+
flowCtx.state.set("templates", filtered);
|
|
453
|
+
}
|
|
439
454
|
async filterListPrompts(flowCtx) {
|
|
440
455
|
const { prompts } = flowCtx.state;
|
|
441
456
|
if (!prompts || prompts.length === 0) return;
|
|
@@ -450,19 +465,19 @@ var FeatureFlagPlugin = class extends import_sdk2.DynamicPlugin {
|
|
|
450
465
|
});
|
|
451
466
|
flowCtx.state.set("prompts", filtered);
|
|
452
467
|
}
|
|
453
|
-
async
|
|
454
|
-
const {
|
|
455
|
-
if (!
|
|
456
|
-
const flaggedSkills = this.collectFlagRefs(
|
|
468
|
+
async filterSkills(flowCtx) {
|
|
469
|
+
const { skills } = flowCtx.state;
|
|
470
|
+
if (!skills || skills.length === 0) return;
|
|
471
|
+
const flaggedSkills = this.collectFlagRefs(skills, (skill) => skill.metadata.featureFlag);
|
|
457
472
|
if (flaggedSkills.size === 0) return;
|
|
458
473
|
const adapter = this.get(FeatureFlagAdapterToken);
|
|
459
474
|
const flagResults = await this.batchEvaluateRefs(adapter, flaggedSkills);
|
|
460
|
-
const filtered =
|
|
461
|
-
const ref =
|
|
475
|
+
const filtered = skills.filter((skill) => {
|
|
476
|
+
const ref = skill.metadata.featureFlag;
|
|
462
477
|
if (!ref) return true;
|
|
463
478
|
return this.isRefEnabled(ref, flagResults);
|
|
464
479
|
});
|
|
465
|
-
flowCtx.state.set("
|
|
480
|
+
flowCtx.state.set("skills", filtered);
|
|
466
481
|
}
|
|
467
482
|
async gateToolExecution(flowCtx) {
|
|
468
483
|
await this.gateEntryExecution("Tool", flowCtx.state.tool);
|
|
@@ -473,6 +488,11 @@ var FeatureFlagPlugin = class extends import_sdk2.DynamicPlugin {
|
|
|
473
488
|
async gatePromptGet(flowCtx) {
|
|
474
489
|
await this.gateEntryExecution("Prompt", flowCtx.state.prompt);
|
|
475
490
|
}
|
|
491
|
+
async gateCompletion(flowCtx) {
|
|
492
|
+
const { prompt, resource } = flowCtx.state;
|
|
493
|
+
if (prompt) await this.gateEntryExecution("Prompt", prompt);
|
|
494
|
+
if (resource) await this.gateEntryExecution("Resource", resource);
|
|
495
|
+
}
|
|
476
496
|
/**
|
|
477
497
|
* Shared execution gate for tools, resources and prompts.
|
|
478
498
|
*
|
|
@@ -633,12 +653,15 @@ __decorateClass([
|
|
|
633
653
|
__decorateClass([
|
|
634
654
|
import_sdk2.ListResourcesHook.Did("findResources", { priority: 50 })
|
|
635
655
|
], FeatureFlagPlugin.prototype, "filterListResources", 1);
|
|
656
|
+
__decorateClass([
|
|
657
|
+
import_sdk2.ListResourceTemplatesHook.Did("findTemplates", { priority: 50 })
|
|
658
|
+
], FeatureFlagPlugin.prototype, "filterListResourceTemplates", 1);
|
|
636
659
|
__decorateClass([
|
|
637
660
|
ListPromptsHook.Did("findPrompts", { priority: 50 })
|
|
638
661
|
], FeatureFlagPlugin.prototype, "filterListPrompts", 1);
|
|
639
662
|
__decorateClass([
|
|
640
|
-
|
|
641
|
-
], FeatureFlagPlugin.prototype, "
|
|
663
|
+
FilterSkillsHook.Did("filterSkills", { priority: 50 })
|
|
664
|
+
], FeatureFlagPlugin.prototype, "filterSkills", 1);
|
|
642
665
|
__decorateClass([
|
|
643
666
|
import_sdk2.ToolHook.Will("execute", { priority: 50 })
|
|
644
667
|
], FeatureFlagPlugin.prototype, "gateToolExecution", 1);
|
|
@@ -648,6 +671,9 @@ __decorateClass([
|
|
|
648
671
|
__decorateClass([
|
|
649
672
|
GetPromptHook.Will("execute", { priority: 50 })
|
|
650
673
|
], FeatureFlagPlugin.prototype, "gatePromptGet", 1);
|
|
674
|
+
__decorateClass([
|
|
675
|
+
CompleteHook.Will("complete", { priority: 50 })
|
|
676
|
+
], FeatureFlagPlugin.prototype, "gateCompletion", 1);
|
|
651
677
|
FeatureFlagPlugin = __decorateClass([
|
|
652
678
|
(0, import_sdk2.Plugin)({
|
|
653
679
|
name: "feature-flags",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.8.
|
|
52
|
+
"@frontmcp/sdk": "1.8.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|