@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 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 filterSearchSkills(flowCtx) {
451
- const { results } = flowCtx.state;
452
- if (!results || results.length === 0) return;
453
- const flaggedSkills = this.collectFlagRefs(results, (item) => item.metadata?.featureFlag);
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 = results.filter((item) => {
458
- const ref = item.metadata?.featureFlag;
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("results", filtered);
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
- SearchSkillsHook.Did("search", { priority: 50 })
638
- ], FeatureFlagPlugin.prototype, "filterSearchSkills", 1);
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.0",
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.0"
52
+ "@frontmcp/sdk": "1.8.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
@@ -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 from skills:search based on feature flags.
44
- * Hooks after search stage, before finalize converts to output.
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
- filterSearchSkills(flowCtx: any): Promise<void>;
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,EACb,SAAS,EAQT,YAAY,EAEb,MAAM,eAAe,CAAC;AAMvB,OAAO,KAAK,EAEV,wBAAwB,EACxB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAW9B;;;;;;;;;;;;;;;;;;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;;OAEG;IAEG,iBAAiB,CAAC,OAAO,EAAE,GAAG;IAmBpC;;;OAGG;IAEG,kBAAkB,CAAC,OAAO,EAAE,GAAG;IAmBrC;;;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;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"}
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"}
@@ -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":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAMrD,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE9E,mDAAmD;AACnD,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEtE,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC;AAE1E,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAMnF,0DAA0D;AAC1D,UAAU,sBAAsB;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAED,uDAAuD;AACvD,MAAM,WAAW,8BAA+B,SAAQ,sBAAsB;IAC5E,OAAO,EAAE,QAAQ,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB,CAAC,CAAC;CACrD;AAED,gCAAgC;AAChC,MAAM,WAAW,+BAAgC,SAAQ,sBAAsB;IAC7E,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,oCAAoC;AACpC,MAAM,WAAW,oCAAqC,SAAQ,sBAAsB;IAClF,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,+BAA+B;AAC/B,MAAM,WAAW,+BAAgC,SAAQ,sBAAsB;IAC7E,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,+DAA+D;AAC/D,MAAM,WAAW,8BAA+B,SAAQ,sBAAsB;IAC5E,OAAO,EAAE,QAAQ,CAAC;IAClB,eAAe,EAAE,OAAO,2CAA2C,EAAE,kBAAkB,CAAC;CACzF;AAED,wCAAwC;AACxC,MAAM,MAAM,wBAAwB,GAChC,8BAA8B,GAC9B,+BAA+B,GAC/B,oCAAoC,GACpC,+BAA+B,GAC/B,8BAA8B,CAAC;AAEnC,oDAAoD;AACpD,MAAM,MAAM,6BAA6B,GAAG,wBAAwB,CAAC;AAMrE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,0BAA0B;QAClC,WAAW,CAAC,EAAE,cAAc,CAAC;KAC9B;IACD,UAAU,8BAA8B;QACtC,WAAW,CAAC,EAAE,cAAc,CAAC;KAC9B;IACD,UAAU,4BAA4B;QACpC,WAAW,CAAC,EAAE,cAAc,CAAC;KAC9B;IACD,UAAU,2BAA2B;QACnC,WAAW,CAAC,EAAE,cAAc,CAAC;KAC9B;CACF"}
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 filterSearchSkills(flowCtx) {
454
- const { results } = flowCtx.state;
455
- if (!results || results.length === 0) return;
456
- const flaggedSkills = this.collectFlagRefs(results, (item) => item.metadata?.featureFlag);
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 = results.filter((item) => {
461
- const ref = item.metadata?.featureFlag;
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("results", filtered);
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
- SearchSkillsHook.Did("search", { priority: 50 })
641
- ], FeatureFlagPlugin.prototype, "filterSearchSkills", 1);
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.0",
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.0"
52
+ "@frontmcp/sdk": "1.8.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",