@frontmcp/plugin-feature-flags 0.0.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 ADDED
@@ -0,0 +1,634 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var __decorateClass = (decorators, target, key, kind) => {
29
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
30
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
31
+ if (decorator = decorators[i])
32
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
33
+ if (kind && result) __defProp(target, key, result);
34
+ return result;
35
+ };
36
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
+
38
+ // plugins/plugin-feature-flags/src/adapters/splitio.adapter.ts
39
+ var splitio_adapter_exports = {};
40
+ __export(splitio_adapter_exports, {
41
+ SplitioFeatureFlagAdapter: () => SplitioFeatureFlagAdapter
42
+ });
43
+ var SplitioFeatureFlagAdapter;
44
+ var init_splitio_adapter = __esm({
45
+ "plugins/plugin-feature-flags/src/adapters/splitio.adapter.ts"() {
46
+ "use strict";
47
+ SplitioFeatureFlagAdapter = class {
48
+ config;
49
+ factory;
50
+ client;
51
+ constructor(config) {
52
+ this.config = config;
53
+ }
54
+ async initialize() {
55
+ let SplitFactory;
56
+ try {
57
+ ({ SplitFactory } = __require("@splitsoftware/splitio"));
58
+ } catch {
59
+ throw new Error("Split.io SDK not found. Install it: npm install @splitsoftware/splitio");
60
+ }
61
+ this.factory = SplitFactory({
62
+ core: {
63
+ authorizationKey: this.config.apiKey
64
+ }
65
+ });
66
+ this.client = this.factory.client();
67
+ await this.client.ready();
68
+ }
69
+ async isEnabled(flagKey, context) {
70
+ if (!this.client) throw new Error("SplitioFeatureFlagAdapter not initialized");
71
+ const key = context.userId ?? context.sessionId ?? "anonymous";
72
+ const treatment = this.client.getTreatment(key, flagKey, context.attributes);
73
+ return treatment === "on";
74
+ }
75
+ async getVariant(flagKey, context) {
76
+ if (!this.client) throw new Error("SplitioFeatureFlagAdapter not initialized");
77
+ const key = context.userId ?? context.sessionId ?? "anonymous";
78
+ const treatment = this.client.getTreatment(key, flagKey, context.attributes);
79
+ return {
80
+ name: treatment,
81
+ value: treatment,
82
+ enabled: treatment === "on"
83
+ };
84
+ }
85
+ async evaluateFlags(flagKeys, context) {
86
+ const results = /* @__PURE__ */ new Map();
87
+ for (const key of flagKeys) {
88
+ results.set(key, await this.isEnabled(key, context));
89
+ }
90
+ return results;
91
+ }
92
+ async destroy() {
93
+ if (this.client) {
94
+ await this.client.destroy();
95
+ }
96
+ this.client = void 0;
97
+ this.factory = void 0;
98
+ }
99
+ };
100
+ }
101
+ });
102
+
103
+ // plugins/plugin-feature-flags/src/adapters/launchdarkly.adapter.ts
104
+ var launchdarkly_adapter_exports = {};
105
+ __export(launchdarkly_adapter_exports, {
106
+ LaunchDarklyFeatureFlagAdapter: () => LaunchDarklyFeatureFlagAdapter
107
+ });
108
+ var LaunchDarklyFeatureFlagAdapter;
109
+ var init_launchdarkly_adapter = __esm({
110
+ "plugins/plugin-feature-flags/src/adapters/launchdarkly.adapter.ts"() {
111
+ "use strict";
112
+ LaunchDarklyFeatureFlagAdapter = class {
113
+ config;
114
+ client;
115
+ constructor(config) {
116
+ this.config = config;
117
+ }
118
+ async initialize() {
119
+ let init;
120
+ try {
121
+ ({ init } = __require("@launchdarkly/node-server-sdk"));
122
+ } catch {
123
+ throw new Error("LaunchDarkly SDK not found. Install it: npm install @launchdarkly/node-server-sdk");
124
+ }
125
+ this.client = init(this.config.sdkKey);
126
+ await this.client.waitForInitialization();
127
+ }
128
+ buildLDContext(context) {
129
+ return {
130
+ kind: "user",
131
+ key: context.userId ?? context.sessionId ?? "anonymous",
132
+ ...context.attributes ?? {}
133
+ };
134
+ }
135
+ async isEnabled(flagKey, context) {
136
+ if (!this.client) throw new Error("LaunchDarklyFeatureFlagAdapter not initialized");
137
+ const ldContext = this.buildLDContext(context);
138
+ return this.client.variation(flagKey, ldContext, false);
139
+ }
140
+ async getVariant(flagKey, context) {
141
+ if (!this.client) throw new Error("LaunchDarklyFeatureFlagAdapter not initialized");
142
+ const ldContext = this.buildLDContext(context);
143
+ const detail = await this.client.variationDetail(flagKey, ldContext, false);
144
+ const value = detail.value;
145
+ return {
146
+ name: String(value),
147
+ value,
148
+ enabled: Boolean(value)
149
+ };
150
+ }
151
+ async evaluateFlags(flagKeys, context) {
152
+ const results = /* @__PURE__ */ new Map();
153
+ for (const key of flagKeys) {
154
+ results.set(key, await this.isEnabled(key, context));
155
+ }
156
+ return results;
157
+ }
158
+ async destroy() {
159
+ if (this.client) {
160
+ await this.client.close();
161
+ }
162
+ this.client = void 0;
163
+ }
164
+ };
165
+ }
166
+ });
167
+
168
+ // plugins/plugin-feature-flags/src/adapters/unleash.adapter.ts
169
+ var unleash_adapter_exports = {};
170
+ __export(unleash_adapter_exports, {
171
+ UnleashFeatureFlagAdapter: () => UnleashFeatureFlagAdapter
172
+ });
173
+ var UnleashFeatureFlagAdapter;
174
+ var init_unleash_adapter = __esm({
175
+ "plugins/plugin-feature-flags/src/adapters/unleash.adapter.ts"() {
176
+ "use strict";
177
+ UnleashFeatureFlagAdapter = class {
178
+ config;
179
+ client;
180
+ constructor(config) {
181
+ this.config = config;
182
+ }
183
+ async initialize() {
184
+ let Unleash;
185
+ try {
186
+ ({ Unleash } = __require("unleash-client"));
187
+ } catch {
188
+ throw new Error("Unleash SDK not found. Install it: npm install unleash-client");
189
+ }
190
+ const options = {
191
+ url: this.config.url,
192
+ appName: this.config.appName
193
+ };
194
+ if (this.config.apiKey) {
195
+ options["customHeaders"] = { Authorization: this.config.apiKey };
196
+ }
197
+ this.client = new Unleash(options);
198
+ await this.client.start();
199
+ }
200
+ buildUnleashContext(context) {
201
+ return {
202
+ userId: context.userId,
203
+ sessionId: context.sessionId,
204
+ properties: context.attributes ?? {}
205
+ };
206
+ }
207
+ async isEnabled(flagKey, context) {
208
+ if (!this.client) throw new Error("UnleashFeatureFlagAdapter not initialized");
209
+ const unleashCtx = this.buildUnleashContext(context);
210
+ return this.client.isEnabled(flagKey, unleashCtx);
211
+ }
212
+ async getVariant(flagKey, context) {
213
+ if (!this.client) throw new Error("UnleashFeatureFlagAdapter not initialized");
214
+ const unleashCtx = this.buildUnleashContext(context);
215
+ const variant = this.client.getVariant(flagKey, unleashCtx);
216
+ return {
217
+ name: variant.name ?? "disabled",
218
+ value: variant.payload?.value ?? variant.name,
219
+ enabled: variant.enabled ?? false
220
+ };
221
+ }
222
+ async evaluateFlags(flagKeys, context) {
223
+ const results = /* @__PURE__ */ new Map();
224
+ for (const key of flagKeys) {
225
+ results.set(key, await this.isEnabled(key, context));
226
+ }
227
+ return results;
228
+ }
229
+ async destroy() {
230
+ if (this.client) {
231
+ this.client.destroy();
232
+ }
233
+ this.client = void 0;
234
+ }
235
+ };
236
+ }
237
+ });
238
+
239
+ // plugins/plugin-feature-flags/src/feature-flag.plugin.ts
240
+ import {
241
+ DynamicPlugin,
242
+ FlowHooksOf,
243
+ FRONTMCP_CONTEXT,
244
+ ListToolsHook,
245
+ ListResourcesHook,
246
+ Plugin,
247
+ ProviderScope as ProviderScope2,
248
+ ToolHook
249
+ } from "@frontmcp/sdk";
250
+
251
+ // plugins/plugin-feature-flags/src/feature-flag.symbols.ts
252
+ var FeatureFlagAdapterToken = /* @__PURE__ */ Symbol(
253
+ "plugin:feature-flags:adapter"
254
+ );
255
+ var FeatureFlagConfigToken = /* @__PURE__ */ Symbol(
256
+ "plugin:feature-flags:config"
257
+ );
258
+ var FeatureFlagAccessorToken = /* @__PURE__ */ Symbol(
259
+ "plugin:feature-flags:accessor"
260
+ );
261
+
262
+ // plugins/plugin-feature-flags/src/adapters/static.adapter.ts
263
+ var StaticFeatureFlagAdapter = class {
264
+ flags;
265
+ constructor(flags) {
266
+ this.flags = { ...flags };
267
+ }
268
+ async initialize() {
269
+ }
270
+ async isEnabled(flagKey, _context) {
271
+ const flag = this.flags[flagKey];
272
+ if (flag === void 0) return false;
273
+ if (typeof flag === "boolean") return flag;
274
+ return flag.enabled;
275
+ }
276
+ async getVariant(flagKey, _context) {
277
+ const flag = this.flags[flagKey];
278
+ if (flag === void 0) {
279
+ return { name: "off", value: void 0, enabled: false };
280
+ }
281
+ if (typeof flag === "boolean") {
282
+ return { name: flag ? "on" : "off", value: flag, enabled: flag };
283
+ }
284
+ return { ...flag };
285
+ }
286
+ async evaluateFlags(flagKeys, context) {
287
+ const results = /* @__PURE__ */ new Map();
288
+ for (const key of flagKeys) {
289
+ results.set(key, await this.isEnabled(key, context));
290
+ }
291
+ return results;
292
+ }
293
+ async destroy() {
294
+ }
295
+ };
296
+
297
+ // plugins/plugin-feature-flags/src/providers/feature-flag-accessor.provider.ts
298
+ import { Provider, ProviderScope } from "@frontmcp/sdk";
299
+ var FeatureFlagAccessor = class {
300
+ adapter;
301
+ ctx;
302
+ config;
303
+ cache = /* @__PURE__ */ new Map();
304
+ constructor(adapter, ctx, config) {
305
+ this.adapter = adapter;
306
+ this.ctx = ctx;
307
+ this.config = config;
308
+ }
309
+ /**
310
+ * Check if a feature flag is enabled.
311
+ */
312
+ async isEnabled(flagKey, defaultValue) {
313
+ const cacheStrategy = this.config.cacheStrategy ?? "none";
314
+ const cacheTtlMs = this.config.cacheTtlMs ?? 3e4;
315
+ if (cacheStrategy !== "none") {
316
+ const cached = this.cache.get(flagKey);
317
+ if (cached && Date.now() < cached.expiresAt) {
318
+ return cached.value;
319
+ }
320
+ }
321
+ const context = this.buildContext();
322
+ let result;
323
+ try {
324
+ result = await this.adapter.isEnabled(flagKey, context);
325
+ } catch {
326
+ result = defaultValue ?? this.config.defaultValue ?? false;
327
+ }
328
+ if (cacheStrategy !== "none") {
329
+ this.cache.set(flagKey, { value: result, expiresAt: Date.now() + cacheTtlMs });
330
+ }
331
+ return result;
332
+ }
333
+ /**
334
+ * Get a feature flag variant (for multi-variate flags).
335
+ */
336
+ async getVariant(flagKey) {
337
+ const context = this.buildContext();
338
+ return this.adapter.getVariant(flagKey, context);
339
+ }
340
+ /**
341
+ * Batch evaluate multiple flags at once.
342
+ */
343
+ async evaluateFlags(flagKeys) {
344
+ const context = this.buildContext();
345
+ return this.adapter.evaluateFlags(flagKeys, context);
346
+ }
347
+ /**
348
+ * Resolve a FeatureFlagRef (string or object) to a boolean.
349
+ */
350
+ async resolveRef(ref) {
351
+ if (typeof ref === "string") {
352
+ return this.isEnabled(ref);
353
+ }
354
+ return this.isEnabled(ref.key, ref.defaultValue);
355
+ }
356
+ /**
357
+ * Build the FeatureFlagContext from the current FrontMcpContext.
358
+ */
359
+ buildContext() {
360
+ const userId = this.config.userIdResolver ? this.config.userIdResolver(this.ctx) : this.ctx.authInfo?.extra?.["sub"] ?? this.ctx.authInfo?.extra?.["userId"] ?? this.ctx.authInfo?.clientId;
361
+ const attributes = this.config.attributesResolver ? this.config.attributesResolver(this.ctx) : {};
362
+ return {
363
+ userId: userId ?? void 0,
364
+ sessionId: this.ctx.sessionId,
365
+ attributes
366
+ };
367
+ }
368
+ };
369
+ FeatureFlagAccessor = __decorateClass([
370
+ Provider({
371
+ name: "provider:feature-flags:accessor",
372
+ description: "Context-scoped accessor for feature flag evaluation",
373
+ scope: ProviderScope.CONTEXT
374
+ })
375
+ ], FeatureFlagAccessor);
376
+ function createFeatureFlagAccessor(adapter, ctx, config) {
377
+ return new FeatureFlagAccessor(adapter, ctx, config);
378
+ }
379
+
380
+ // plugins/plugin-feature-flags/src/feature-flag.plugin.ts
381
+ var ListPromptsHook = FlowHooksOf("prompts:list-prompts");
382
+ var SearchSkillsHook = FlowHooksOf("skills:search");
383
+ var FeatureFlagPlugin = class extends DynamicPlugin {
384
+ options;
385
+ constructor(options) {
386
+ super();
387
+ this.options = options;
388
+ }
389
+ async filterListTools(flowCtx) {
390
+ const { tools } = flowCtx.state;
391
+ if (!tools || tools.length === 0) return;
392
+ const flaggedTools = this.collectFlagRefs(tools, (item) => item.tool.metadata?.featureFlag);
393
+ if (flaggedTools.size === 0) return;
394
+ const adapter = this.get(FeatureFlagAdapterToken);
395
+ const flagResults = await this.batchEvaluateRefs(adapter, flaggedTools);
396
+ const filtered = tools.filter((item) => {
397
+ const ref = item.tool.metadata?.featureFlag;
398
+ if (!ref) return true;
399
+ return this.isRefEnabled(ref, flagResults);
400
+ });
401
+ flowCtx.state.set("tools", filtered);
402
+ }
403
+ async filterListResources(flowCtx) {
404
+ const { resources } = flowCtx.state;
405
+ if (!resources || resources.length === 0) return;
406
+ const flaggedResources = this.collectFlagRefs(resources, (item) => item.resource.metadata?.featureFlag);
407
+ if (flaggedResources.size === 0) return;
408
+ const adapter = this.get(FeatureFlagAdapterToken);
409
+ const flagResults = await this.batchEvaluateRefs(adapter, flaggedResources);
410
+ const filtered = resources.filter((item) => {
411
+ const ref = item.resource.metadata?.featureFlag;
412
+ if (!ref) return true;
413
+ return this.isRefEnabled(ref, flagResults);
414
+ });
415
+ flowCtx.state.set("resources", filtered);
416
+ }
417
+ async filterListPrompts(flowCtx) {
418
+ const { prompts } = flowCtx.state;
419
+ if (!prompts || prompts.length === 0) return;
420
+ const flaggedPrompts = this.collectFlagRefs(prompts, (item) => item.prompt.metadata?.featureFlag);
421
+ if (flaggedPrompts.size === 0) return;
422
+ const adapter = this.get(FeatureFlagAdapterToken);
423
+ const flagResults = await this.batchEvaluateRefs(adapter, flaggedPrompts);
424
+ const filtered = prompts.filter((item) => {
425
+ const ref = item.prompt.metadata?.featureFlag;
426
+ if (!ref) return true;
427
+ return this.isRefEnabled(ref, flagResults);
428
+ });
429
+ flowCtx.state.set("prompts", filtered);
430
+ }
431
+ async filterSearchSkills(flowCtx) {
432
+ const { results } = flowCtx.state;
433
+ if (!results || results.length === 0) return;
434
+ const flaggedSkills = this.collectFlagRefs(results, (item) => item.metadata?.featureFlag);
435
+ if (flaggedSkills.size === 0) return;
436
+ const adapter = this.get(FeatureFlagAdapterToken);
437
+ const flagResults = await this.batchEvaluateRefs(adapter, flaggedSkills);
438
+ const filtered = results.filter((item) => {
439
+ const ref = item.metadata?.featureFlag;
440
+ if (!ref) return true;
441
+ return this.isRefEnabled(ref, flagResults);
442
+ });
443
+ flowCtx.state.set("results", filtered);
444
+ }
445
+ async gateToolExecution(flowCtx) {
446
+ const { tool } = flowCtx.state;
447
+ if (!tool) return;
448
+ const ref = tool.metadata?.featureFlag;
449
+ if (!ref) return;
450
+ const adapter = this.get(FeatureFlagAdapterToken);
451
+ const key = typeof ref === "string" ? ref : ref.key;
452
+ const defaultValue = typeof ref === "object" ? ref.defaultValue ?? false : false;
453
+ let enabled;
454
+ try {
455
+ enabled = await adapter.isEnabled(key, {});
456
+ } catch {
457
+ enabled = defaultValue;
458
+ }
459
+ if (!enabled) {
460
+ throw new Error(`Tool "${tool.metadata.name}" is disabled by feature flag "${key}"`);
461
+ }
462
+ }
463
+ // ─────────────────────────────────────────────────────────────────────────
464
+ // Private Helpers
465
+ // ─────────────────────────────────────────────────────────────────────────
466
+ /**
467
+ * Collect unique flag keys from items that have a featureFlag metadata.
468
+ */
469
+ collectFlagRefs(items, getRef) {
470
+ const refs = /* @__PURE__ */ new Map();
471
+ for (const item of items) {
472
+ const ref = getRef(item);
473
+ if (ref) {
474
+ const key = typeof ref === "string" ? ref : ref.key;
475
+ if (!refs.has(key)) {
476
+ refs.set(key, ref);
477
+ }
478
+ }
479
+ }
480
+ return refs;
481
+ }
482
+ /**
483
+ * Batch evaluate all collected flag refs via the adapter.
484
+ */
485
+ async batchEvaluateRefs(adapter, refs) {
486
+ const keys = Array.from(refs.keys());
487
+ return adapter.evaluateFlags(keys, {});
488
+ }
489
+ /**
490
+ * Determine if a feature flag ref is enabled given adapter results.
491
+ * For object-style refs, `defaultValue` acts as a fallback when the adapter
492
+ * returns false (i.e., the flag is unknown to the adapter).
493
+ */
494
+ isRefEnabled(ref, flagResults) {
495
+ const key = typeof ref === "string" ? ref : ref.key;
496
+ const adapterResult = flagResults.get(key);
497
+ const defaultValue = typeof ref === "object" ? ref.defaultValue ?? false : false;
498
+ if (adapterResult === true) return true;
499
+ return defaultValue;
500
+ }
501
+ };
502
+ /**
503
+ * Dynamic providers based on plugin options.
504
+ */
505
+ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
506
+ const providers = [];
507
+ switch (options.adapter) {
508
+ case "static":
509
+ providers.push({
510
+ name: "feature-flags:adapter:static",
511
+ provide: FeatureFlagAdapterToken,
512
+ useValue: new StaticFeatureFlagAdapter(options.flags)
513
+ });
514
+ break;
515
+ case "splitio":
516
+ providers.push({
517
+ name: "feature-flags:adapter:splitio",
518
+ provide: FeatureFlagAdapterToken,
519
+ inject: () => [],
520
+ useFactory: async () => {
521
+ const { SplitioFeatureFlagAdapter: SplitioFeatureFlagAdapter2 } = (init_splitio_adapter(), __toCommonJS(splitio_adapter_exports));
522
+ const adapter = new SplitioFeatureFlagAdapter2(options.config);
523
+ await adapter.initialize();
524
+ return adapter;
525
+ }
526
+ });
527
+ break;
528
+ case "launchdarkly":
529
+ providers.push({
530
+ name: "feature-flags:adapter:launchdarkly",
531
+ provide: FeatureFlagAdapterToken,
532
+ inject: () => [],
533
+ useFactory: async () => {
534
+ const { LaunchDarklyFeatureFlagAdapter: LaunchDarklyFeatureFlagAdapter2 } = (init_launchdarkly_adapter(), __toCommonJS(launchdarkly_adapter_exports));
535
+ const adapter = new LaunchDarklyFeatureFlagAdapter2(options.config);
536
+ await adapter.initialize();
537
+ return adapter;
538
+ }
539
+ });
540
+ break;
541
+ case "unleash":
542
+ providers.push({
543
+ name: "feature-flags:adapter:unleash",
544
+ provide: FeatureFlagAdapterToken,
545
+ inject: () => [],
546
+ useFactory: async () => {
547
+ const { UnleashFeatureFlagAdapter: UnleashFeatureFlagAdapter2 } = (init_unleash_adapter(), __toCommonJS(unleash_adapter_exports));
548
+ const adapter = new UnleashFeatureFlagAdapter2(options.config);
549
+ await adapter.initialize();
550
+ return adapter;
551
+ }
552
+ });
553
+ break;
554
+ case "custom":
555
+ providers.push({
556
+ name: "feature-flags:adapter:custom",
557
+ provide: FeatureFlagAdapterToken,
558
+ useValue: options.adapterInstance
559
+ });
560
+ break;
561
+ }
562
+ providers.push({
563
+ name: "feature-flags:config",
564
+ provide: FeatureFlagConfigToken,
565
+ useValue: options
566
+ });
567
+ providers.push({
568
+ name: "feature-flags:accessor",
569
+ provide: FeatureFlagAccessorToken,
570
+ scope: ProviderScope2.CONTEXT,
571
+ inject: () => [FeatureFlagAdapterToken, FRONTMCP_CONTEXT, FeatureFlagConfigToken],
572
+ useFactory: (adapter, ctx, cfg) => createFeatureFlagAccessor(adapter, ctx, cfg)
573
+ });
574
+ return providers;
575
+ });
576
+ __decorateClass([
577
+ ListToolsHook.Did("findTools", { priority: 50 })
578
+ ], FeatureFlagPlugin.prototype, "filterListTools", 1);
579
+ __decorateClass([
580
+ ListResourcesHook.Did("findResources", { priority: 50 })
581
+ ], FeatureFlagPlugin.prototype, "filterListResources", 1);
582
+ __decorateClass([
583
+ ListPromptsHook.Did("findPrompts", { priority: 50 })
584
+ ], FeatureFlagPlugin.prototype, "filterListPrompts", 1);
585
+ __decorateClass([
586
+ SearchSkillsHook.Did("search", { priority: 50 })
587
+ ], FeatureFlagPlugin.prototype, "filterSearchSkills", 1);
588
+ __decorateClass([
589
+ ToolHook.Will("execute", { priority: 50 })
590
+ ], FeatureFlagPlugin.prototype, "gateToolExecution", 1);
591
+ FeatureFlagPlugin = __decorateClass([
592
+ Plugin({
593
+ name: "feature-flags",
594
+ description: "Feature flag-based capability filtering for MCP",
595
+ providers: [],
596
+ contextExtensions: [
597
+ {
598
+ property: "featureFlags",
599
+ token: FeatureFlagAccessorToken,
600
+ errorMessage: "FeatureFlagPlugin is not installed. Add FeatureFlagPlugin.init() to your plugins array."
601
+ }
602
+ ]
603
+ })
604
+ ], FeatureFlagPlugin);
605
+
606
+ // plugins/plugin-feature-flags/src/index.ts
607
+ init_splitio_adapter();
608
+ init_launchdarkly_adapter();
609
+ init_unleash_adapter();
610
+
611
+ // plugins/plugin-feature-flags/src/feature-flag.context-extension.ts
612
+ function getFeatureFlags(ctx) {
613
+ return ctx.get(FeatureFlagAccessorToken);
614
+ }
615
+ function tryGetFeatureFlags(ctx) {
616
+ if (typeof ctx.tryGet === "function") {
617
+ return ctx.tryGet(FeatureFlagAccessorToken);
618
+ }
619
+ return void 0;
620
+ }
621
+ export {
622
+ FeatureFlagAccessor,
623
+ FeatureFlagAccessorToken,
624
+ FeatureFlagAdapterToken,
625
+ FeatureFlagConfigToken,
626
+ FeatureFlagPlugin,
627
+ LaunchDarklyFeatureFlagAdapter,
628
+ SplitioFeatureFlagAdapter,
629
+ StaticFeatureFlagAdapter,
630
+ UnleashFeatureFlagAdapter,
631
+ FeatureFlagPlugin as default,
632
+ getFeatureFlags,
633
+ tryGetFeatureFlags
634
+ };
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@frontmcp/plugin-feature-flags",
3
+ "version": "1.0.0",
4
+ "description": "Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags",
5
+ "author": "AgentFront <info@agentfront.dev>",
6
+ "license": "Apache-2.0",
7
+ "keywords": [
8
+ "mcp",
9
+ "feature-flags",
10
+ "split",
11
+ "launchdarkly",
12
+ "unleash",
13
+ "plugin",
14
+ "frontmcp",
15
+ "agentfront"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/agentfront/frontmcp.git",
20
+ "directory": "plugins/plugin-feature-flags"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/agentfront/frontmcp/issues"
24
+ },
25
+ "homepage": "https://github.com/agentfront/frontmcp/blob/main/plugins/plugin-feature-flags/README.md",
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "type": "module",
31
+ "main": "../index.js",
32
+ "module": "./index.mjs",
33
+ "types": "../index.d.ts",
34
+ "sideEffects": false,
35
+ "exports": {
36
+ "./package.json": "../package.json",
37
+ ".": {
38
+ "require": {
39
+ "types": "../index.d.ts",
40
+ "default": "../index.js"
41
+ },
42
+ "import": {
43
+ "types": "../index.d.ts",
44
+ "default": "./index.mjs"
45
+ }
46
+ }
47
+ },
48
+ "dependencies": {
49
+ "@frontmcp/sdk": "1.0.0",
50
+ "@frontmcp/utils": "1.0.0",
51
+ "zod": "^4.0.0"
52
+ },
53
+ "peerDependencies": {
54
+ "@splitsoftware/splitio": "^10.0.0 || ^11.0.0",
55
+ "@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
56
+ "unleash-client": "^5.0.0 || ^6.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@splitsoftware/splitio": {
60
+ "optional": true
61
+ },
62
+ "@launchdarkly/node-server-sdk": {
63
+ "optional": true
64
+ },
65
+ "unleash-client": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "devDependencies": {
70
+ "reflect-metadata": "^0.2.2"
71
+ }
72
+ }