@frontmcp/plugin-feature-flags 1.8.6 → 1.9.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/esm/index.mjs +71 -8
- package/esm/package.json +2 -2
- package/feature-flag.errors.d.ts +26 -0
- package/feature-flag.errors.d.ts.map +1 -0
- package/feature-flag.plugin.d.ts.map +1 -1
- package/feature-flag.types.d.ts +5 -0
- package/feature-flag.types.d.ts.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +90 -28
- package/package.json +2 -2
- package/providers/feature-flag-accessor.provider.d.ts +12 -0
- package/providers/feature-flag-accessor.provider.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
1
4
|
var __defProp = Object.defineProperty;
|
|
2
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -318,6 +321,27 @@ function buildFeatureFlagContext(ctx, config) {
|
|
|
318
321
|
};
|
|
319
322
|
}
|
|
320
323
|
|
|
324
|
+
// plugins/plugin-feature-flags/src/feature-flag.errors.ts
|
|
325
|
+
import { PublicMcpError } from "@frontmcp/sdk";
|
|
326
|
+
var FeatureFlagDisabledError = class extends PublicMcpError {
|
|
327
|
+
constructor(kind, entryName, flagKey) {
|
|
328
|
+
super(`${kind} "${entryName}" is disabled by feature flag "${flagKey}"`, "FEATURE_FLAG_DISABLED", 403);
|
|
329
|
+
this.kind = kind;
|
|
330
|
+
this.entryName = entryName;
|
|
331
|
+
this.flagKey = flagKey;
|
|
332
|
+
}
|
|
333
|
+
kind;
|
|
334
|
+
entryName;
|
|
335
|
+
flagKey;
|
|
336
|
+
name = "FeatureFlagDisabledError";
|
|
337
|
+
};
|
|
338
|
+
var FeatureFlagConfigurationError = class extends Error {
|
|
339
|
+
constructor(message) {
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "FeatureFlagConfigurationError";
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
321
345
|
// plugins/plugin-feature-flags/src/feature-flag.symbols.ts
|
|
322
346
|
var FeatureFlagAdapterToken = /* @__PURE__ */ Symbol(
|
|
323
347
|
"plugin:feature-flags:adapter"
|
|
@@ -335,6 +359,7 @@ var FeatureFlagAccessor = class {
|
|
|
335
359
|
adapter;
|
|
336
360
|
ctx;
|
|
337
361
|
config;
|
|
362
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
338
363
|
cache = /* @__PURE__ */ new Map();
|
|
339
364
|
constructor(adapter, ctx, config) {
|
|
340
365
|
this.adapter = adapter;
|
|
@@ -343,27 +368,41 @@ var FeatureFlagAccessor = class {
|
|
|
343
368
|
}
|
|
344
369
|
/**
|
|
345
370
|
* Check if a feature flag is enabled.
|
|
371
|
+
*
|
|
372
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
373
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
374
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
375
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
346
376
|
*/
|
|
347
377
|
async isEnabled(flagKey, defaultValue) {
|
|
378
|
+
const answer = await this.evaluate(flagKey);
|
|
379
|
+
return answer ?? defaultValue ?? this.config.defaultValue ?? false;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
383
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
384
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
385
|
+
*/
|
|
386
|
+
async evaluate(flagKey) {
|
|
348
387
|
const cacheStrategy = this.config.cacheStrategy ?? "none";
|
|
349
388
|
const cacheTtlMs = this.config.cacheTtlMs ?? 3e4;
|
|
350
389
|
if (cacheStrategy !== "none") {
|
|
351
390
|
const cached = this.cache.get(flagKey);
|
|
352
391
|
if (cached && Date.now() < cached.expiresAt) {
|
|
353
|
-
return cached.
|
|
392
|
+
return cached.answer;
|
|
354
393
|
}
|
|
355
394
|
}
|
|
356
|
-
|
|
357
|
-
let result;
|
|
395
|
+
let answer;
|
|
358
396
|
try {
|
|
359
|
-
|
|
397
|
+
const results = await this.adapter.evaluateFlags([flagKey], this.buildContext());
|
|
398
|
+
answer = results.has(flagKey) ? results.get(flagKey) === true : void 0;
|
|
360
399
|
} catch {
|
|
361
|
-
|
|
400
|
+
return void 0;
|
|
362
401
|
}
|
|
363
402
|
if (cacheStrategy !== "none") {
|
|
364
|
-
this.cache.set(flagKey, {
|
|
403
|
+
this.cache.set(flagKey, { answer, expiresAt: Date.now() + cacheTtlMs });
|
|
365
404
|
}
|
|
366
|
-
return
|
|
405
|
+
return answer;
|
|
367
406
|
}
|
|
368
407
|
/**
|
|
369
408
|
* Get a feature flag variant (for multi-variate flags).
|
|
@@ -412,6 +451,22 @@ var ReadResourceHook = FlowHooksOf("resources:read-resource");
|
|
|
412
451
|
var GetPromptHook = FlowHooksOf("prompts:get-prompt");
|
|
413
452
|
var CompleteHook = FlowHooksOf("completion:complete");
|
|
414
453
|
var FilterSkillsHook = FlowHooksOf("skills:filter");
|
|
454
|
+
var SUPPORTED_ADAPTERS = ["static", "splitio", "launchdarkly", "unleash", "custom"];
|
|
455
|
+
var REQUIRED_ADAPTER_METHODS = ["isEnabled", "getVariant", "evaluateFlags"];
|
|
456
|
+
function assertCustomAdapter(adapterInstance) {
|
|
457
|
+
let problem;
|
|
458
|
+
if (typeof adapterInstance !== "object" || adapterInstance === null) {
|
|
459
|
+
problem = `got ${adapterInstance === void 0 ? "undefined" : JSON.stringify(adapterInstance)}`;
|
|
460
|
+
} else {
|
|
461
|
+
const instance = adapterInstance;
|
|
462
|
+
const missing = REQUIRED_ADAPTER_METHODS.filter((method) => typeof instance[method] !== "function");
|
|
463
|
+
if (missing.length > 0) problem = `missing ${missing.map((m) => `${m}()`).join(", ")}`;
|
|
464
|
+
}
|
|
465
|
+
if (problem === void 0) return;
|
|
466
|
+
throw new FeatureFlagConfigurationError(
|
|
467
|
+
`FeatureFlagPlugin.init({ adapter: 'custom' }) requires an \`adapterInstance\` option: an object implementing FeatureFlagAdapter (${REQUIRED_ADAPTER_METHODS.map((m) => `${m}()`).join(", ")}); ${problem}.`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
415
470
|
var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
416
471
|
options;
|
|
417
472
|
constructor(options) {
|
|
@@ -531,7 +586,7 @@ var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
|
531
586
|
enabled = defaultValue;
|
|
532
587
|
}
|
|
533
588
|
if (!enabled) {
|
|
534
|
-
throw new
|
|
589
|
+
throw new FeatureFlagDisabledError(kind, metadata?.name, key);
|
|
535
590
|
}
|
|
536
591
|
}
|
|
537
592
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -614,6 +669,11 @@ var FeatureFlagPlugin = class extends DynamicPlugin {
|
|
|
614
669
|
*/
|
|
615
670
|
__publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
616
671
|
const providers = [];
|
|
672
|
+
if (!SUPPORTED_ADAPTERS.includes(options?.adapter)) {
|
|
673
|
+
throw new FeatureFlagConfigurationError(
|
|
674
|
+
`FeatureFlagPlugin.init() requires an \`adapter\` option, got ${JSON.stringify(options?.adapter)}. Supported adapters: ${SUPPORTED_ADAPTERS.map((a) => `"${a}"`).join(", ")}.`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
617
677
|
switch (options.adapter) {
|
|
618
678
|
case "static":
|
|
619
679
|
providers.push({
|
|
@@ -662,6 +722,7 @@ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
|
662
722
|
});
|
|
663
723
|
break;
|
|
664
724
|
case "custom":
|
|
725
|
+
assertCustomAdapter(options.adapterInstance);
|
|
665
726
|
providers.push({
|
|
666
727
|
name: "feature-flags:adapter:custom",
|
|
667
728
|
provide: FeatureFlagAdapterToken,
|
|
@@ -747,6 +808,8 @@ export {
|
|
|
747
808
|
FeatureFlagAccessorToken,
|
|
748
809
|
FeatureFlagAdapterToken,
|
|
749
810
|
FeatureFlagConfigToken,
|
|
811
|
+
FeatureFlagConfigurationError,
|
|
812
|
+
FeatureFlagDisabledError,
|
|
750
813
|
FeatureFlagPlugin,
|
|
751
814
|
LaunchDarklyFeatureFlagAdapter,
|
|
752
815
|
SplitioFeatureFlagAdapter,
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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.
|
|
52
|
+
"@frontmcp/sdk": "1.9.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes for the feature-flag plugin.
|
|
3
|
+
*
|
|
4
|
+
* A refusal is an answer for the caller, so it is a public MCP error whose message reaches the
|
|
5
|
+
* client as written, in every environment, instead of being wrapped as an internal server error.
|
|
6
|
+
*
|
|
7
|
+
* @module @frontmcp/plugin-feature-flags
|
|
8
|
+
*/
|
|
9
|
+
import { PublicMcpError } from '@frontmcp/sdk';
|
|
10
|
+
/**
|
|
11
|
+
* Raised when a tool, resource, prompt or agent is called while its feature flag is off.
|
|
12
|
+
*/
|
|
13
|
+
export declare class FeatureFlagDisabledError extends PublicMcpError {
|
|
14
|
+
readonly kind: string;
|
|
15
|
+
readonly entryName: string | undefined;
|
|
16
|
+
readonly flagKey: string;
|
|
17
|
+
readonly name = "FeatureFlagDisabledError";
|
|
18
|
+
constructor(kind: string, entryName: string | undefined, flagKey: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Raised at startup when `FeatureFlagPlugin.init()` is given no usable adapter.
|
|
22
|
+
*/
|
|
23
|
+
export declare class FeatureFlagConfigurationError extends Error {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=feature-flag.errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-flag.errors.d.ts","sourceRoot":"","sources":["../src/feature-flag.errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,cAAc;aAIxC,IAAI,EAAE,MAAM;aACZ,SAAS,EAAE,MAAM,GAAG,SAAS;aAC7B,OAAO,EAAE,MAAM;IALjC,SAAkB,IAAI,8BAA8B;gBAGlC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,MAAM;CAIlC;AAED;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAYb,KAAK,SAAS,EAGd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"feature-flag.plugin.d.ts","sourceRoot":"","sources":["../src/feature-flag.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAYb,KAAK,SAAS,EAGd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAOvB,OAAO,KAAK,EAEV,wBAAwB,EACxB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAwC9B;;;;;;;;;;;;;;;;;;GAkBG;AAeH,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,CAkGzF;IAMF;;;OAGG;IAEG,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC;IAqB5D;;OAEG;IAEG,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,0BAA0B,CAAC;IAoBxE;;;;OAIG;IAEG,2BAA2B,CAAC,OAAO,EAAE,SAAS,CAAC,mCAAmC,CAAC;IAoBzF;;OAEG;IAEG,iBAAiB,CAAC,OAAO,EAAE,GAAG;IAsBpC;;;;;;;;OAQG;IAEG,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC;IAsBtD;;;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;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;YACW,iBAAiB;IAQ/B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAWrB"}
|
package/feature-flag.types.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export type UserIdResolver = (ctx: FrontMcpContext) => string | undefined;
|
|
|
27
27
|
export type AttributesResolver = (ctx: FrontMcpContext) => Record<string, unknown>;
|
|
28
28
|
/** Shared base options for all adapter configurations. */
|
|
29
29
|
interface FeatureFlagBaseOptions {
|
|
30
|
+
/**
|
|
31
|
+
* What `this.featureFlags.isEnabled()` answers when the adapter throws or has no answer for the
|
|
32
|
+
* flag and the call passes no `defaultValue` of its own. Gates use their ref's `defaultValue`.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
30
35
|
defaultValue?: boolean;
|
|
31
36
|
cacheStrategy?: FeatureFlagCacheStrategy;
|
|
32
37
|
cacheTtlMs?: number;
|
|
@@ -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.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* @packageDocumentation
|
|
23
23
|
*/
|
|
24
24
|
export { default, default as FeatureFlagPlugin } from './feature-flag.plugin';
|
|
25
|
+
export { FeatureFlagConfigurationError, FeatureFlagDisabledError } from './feature-flag.errors';
|
|
25
26
|
export { FeatureFlagAdapterToken, FeatureFlagConfigToken, FeatureFlagAccessorToken } from './feature-flag.symbols';
|
|
26
27
|
export type { FeatureFlagContext, FeatureFlagVariant, FeatureFlagRef, FeatureFlagCacheStrategy, UserIdResolver, AttributesResolver, FeatureFlagPluginOptions, FeatureFlagPluginOptionsInput, StaticFeatureFlagPluginOptions, SplitioFeatureFlagPluginOptions, LaunchDarklyFeatureFlagPluginOptions, UnleashFeatureFlagPluginOptions, CustomFeatureFlagPluginOptions, } from './feature-flag.types';
|
|
27
28
|
export type { FeatureFlagAdapter } from './adapters';
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG9E,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGnH,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,+BAA+B,EAC/B,oCAAoC,EACpC,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAKjF,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG9E,OAAO,EAAE,6BAA6B,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGhG,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGnH,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,+BAA+B,EAC/B,oCAAoC,EACpC,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAKjF,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC"}
|
package/index.js
CHANGED
|
@@ -243,6 +243,8 @@ __export(index_exports, {
|
|
|
243
243
|
FeatureFlagAccessorToken: () => FeatureFlagAccessorToken,
|
|
244
244
|
FeatureFlagAdapterToken: () => FeatureFlagAdapterToken,
|
|
245
245
|
FeatureFlagConfigToken: () => FeatureFlagConfigToken,
|
|
246
|
+
FeatureFlagConfigurationError: () => FeatureFlagConfigurationError,
|
|
247
|
+
FeatureFlagDisabledError: () => FeatureFlagDisabledError,
|
|
246
248
|
FeatureFlagPlugin: () => FeatureFlagPlugin,
|
|
247
249
|
LaunchDarklyFeatureFlagAdapter: () => LaunchDarklyFeatureFlagAdapter,
|
|
248
250
|
SplitioFeatureFlagAdapter: () => SplitioFeatureFlagAdapter,
|
|
@@ -255,7 +257,7 @@ __export(index_exports, {
|
|
|
255
257
|
module.exports = __toCommonJS(index_exports);
|
|
256
258
|
|
|
257
259
|
// plugins/plugin-feature-flags/src/feature-flag.plugin.ts
|
|
258
|
-
var
|
|
260
|
+
var import_sdk4 = require("@frontmcp/sdk");
|
|
259
261
|
|
|
260
262
|
// plugins/plugin-feature-flags/src/adapters/static.adapter.ts
|
|
261
263
|
var StaticFeatureFlagAdapter = class {
|
|
@@ -318,6 +320,27 @@ function buildFeatureFlagContext(ctx, config) {
|
|
|
318
320
|
};
|
|
319
321
|
}
|
|
320
322
|
|
|
323
|
+
// plugins/plugin-feature-flags/src/feature-flag.errors.ts
|
|
324
|
+
var import_sdk2 = require("@frontmcp/sdk");
|
|
325
|
+
var FeatureFlagDisabledError = class extends import_sdk2.PublicMcpError {
|
|
326
|
+
constructor(kind, entryName, flagKey) {
|
|
327
|
+
super(`${kind} "${entryName}" is disabled by feature flag "${flagKey}"`, "FEATURE_FLAG_DISABLED", 403);
|
|
328
|
+
this.kind = kind;
|
|
329
|
+
this.entryName = entryName;
|
|
330
|
+
this.flagKey = flagKey;
|
|
331
|
+
}
|
|
332
|
+
kind;
|
|
333
|
+
entryName;
|
|
334
|
+
flagKey;
|
|
335
|
+
name = "FeatureFlagDisabledError";
|
|
336
|
+
};
|
|
337
|
+
var FeatureFlagConfigurationError = class extends Error {
|
|
338
|
+
constructor(message) {
|
|
339
|
+
super(message);
|
|
340
|
+
this.name = "FeatureFlagConfigurationError";
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
321
344
|
// plugins/plugin-feature-flags/src/feature-flag.symbols.ts
|
|
322
345
|
var FeatureFlagAdapterToken = /* @__PURE__ */ Symbol(
|
|
323
346
|
"plugin:feature-flags:adapter"
|
|
@@ -330,11 +353,12 @@ var FeatureFlagAccessorToken = /* @__PURE__ */ Symbol(
|
|
|
330
353
|
);
|
|
331
354
|
|
|
332
355
|
// plugins/plugin-feature-flags/src/providers/feature-flag-accessor.provider.ts
|
|
333
|
-
var
|
|
356
|
+
var import_sdk3 = require("@frontmcp/sdk");
|
|
334
357
|
var FeatureFlagAccessor = class {
|
|
335
358
|
adapter;
|
|
336
359
|
ctx;
|
|
337
360
|
config;
|
|
361
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
338
362
|
cache = /* @__PURE__ */ new Map();
|
|
339
363
|
constructor(adapter, ctx, config) {
|
|
340
364
|
this.adapter = adapter;
|
|
@@ -343,27 +367,41 @@ var FeatureFlagAccessor = class {
|
|
|
343
367
|
}
|
|
344
368
|
/**
|
|
345
369
|
* Check if a feature flag is enabled.
|
|
370
|
+
*
|
|
371
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
372
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
373
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
374
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
346
375
|
*/
|
|
347
376
|
async isEnabled(flagKey, defaultValue) {
|
|
377
|
+
const answer = await this.evaluate(flagKey);
|
|
378
|
+
return answer ?? defaultValue ?? this.config.defaultValue ?? false;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
382
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
383
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
384
|
+
*/
|
|
385
|
+
async evaluate(flagKey) {
|
|
348
386
|
const cacheStrategy = this.config.cacheStrategy ?? "none";
|
|
349
387
|
const cacheTtlMs = this.config.cacheTtlMs ?? 3e4;
|
|
350
388
|
if (cacheStrategy !== "none") {
|
|
351
389
|
const cached = this.cache.get(flagKey);
|
|
352
390
|
if (cached && Date.now() < cached.expiresAt) {
|
|
353
|
-
return cached.
|
|
391
|
+
return cached.answer;
|
|
354
392
|
}
|
|
355
393
|
}
|
|
356
|
-
|
|
357
|
-
let result;
|
|
394
|
+
let answer;
|
|
358
395
|
try {
|
|
359
|
-
|
|
396
|
+
const results = await this.adapter.evaluateFlags([flagKey], this.buildContext());
|
|
397
|
+
answer = results.has(flagKey) ? results.get(flagKey) === true : void 0;
|
|
360
398
|
} catch {
|
|
361
|
-
|
|
399
|
+
return void 0;
|
|
362
400
|
}
|
|
363
401
|
if (cacheStrategy !== "none") {
|
|
364
|
-
this.cache.set(flagKey, {
|
|
402
|
+
this.cache.set(flagKey, { answer, expiresAt: Date.now() + cacheTtlMs });
|
|
365
403
|
}
|
|
366
|
-
return
|
|
404
|
+
return answer;
|
|
367
405
|
}
|
|
368
406
|
/**
|
|
369
407
|
* Get a feature flag variant (for multi-variate flags).
|
|
@@ -396,10 +434,10 @@ var FeatureFlagAccessor = class {
|
|
|
396
434
|
}
|
|
397
435
|
};
|
|
398
436
|
FeatureFlagAccessor = __decorateClass([
|
|
399
|
-
(0,
|
|
437
|
+
(0, import_sdk3.Provider)({
|
|
400
438
|
name: "provider:feature-flags:accessor",
|
|
401
439
|
description: "Context-scoped accessor for feature flag evaluation",
|
|
402
|
-
scope:
|
|
440
|
+
scope: import_sdk3.ProviderScope.CONTEXT
|
|
403
441
|
})
|
|
404
442
|
], FeatureFlagAccessor);
|
|
405
443
|
function createFeatureFlagAccessor(adapter, ctx, config) {
|
|
@@ -407,12 +445,28 @@ function createFeatureFlagAccessor(adapter, ctx, config) {
|
|
|
407
445
|
}
|
|
408
446
|
|
|
409
447
|
// plugins/plugin-feature-flags/src/feature-flag.plugin.ts
|
|
410
|
-
var ListPromptsHook = (0,
|
|
411
|
-
var ReadResourceHook = (0,
|
|
412
|
-
var GetPromptHook = (0,
|
|
413
|
-
var CompleteHook = (0,
|
|
414
|
-
var FilterSkillsHook = (0,
|
|
415
|
-
var
|
|
448
|
+
var ListPromptsHook = (0, import_sdk4.FlowHooksOf)("prompts:list-prompts");
|
|
449
|
+
var ReadResourceHook = (0, import_sdk4.FlowHooksOf)("resources:read-resource");
|
|
450
|
+
var GetPromptHook = (0, import_sdk4.FlowHooksOf)("prompts:get-prompt");
|
|
451
|
+
var CompleteHook = (0, import_sdk4.FlowHooksOf)("completion:complete");
|
|
452
|
+
var FilterSkillsHook = (0, import_sdk4.FlowHooksOf)("skills:filter");
|
|
453
|
+
var SUPPORTED_ADAPTERS = ["static", "splitio", "launchdarkly", "unleash", "custom"];
|
|
454
|
+
var REQUIRED_ADAPTER_METHODS = ["isEnabled", "getVariant", "evaluateFlags"];
|
|
455
|
+
function assertCustomAdapter(adapterInstance) {
|
|
456
|
+
let problem;
|
|
457
|
+
if (typeof adapterInstance !== "object" || adapterInstance === null) {
|
|
458
|
+
problem = `got ${adapterInstance === void 0 ? "undefined" : JSON.stringify(adapterInstance)}`;
|
|
459
|
+
} else {
|
|
460
|
+
const instance = adapterInstance;
|
|
461
|
+
const missing = REQUIRED_ADAPTER_METHODS.filter((method) => typeof instance[method] !== "function");
|
|
462
|
+
if (missing.length > 0) problem = `missing ${missing.map((m) => `${m}()`).join(", ")}`;
|
|
463
|
+
}
|
|
464
|
+
if (problem === void 0) return;
|
|
465
|
+
throw new FeatureFlagConfigurationError(
|
|
466
|
+
`FeatureFlagPlugin.init({ adapter: 'custom' }) requires an \`adapterInstance\` option: an object implementing FeatureFlagAdapter (${REQUIRED_ADAPTER_METHODS.map((m) => `${m}()`).join(", ")}); ${problem}.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
var FeatureFlagPlugin = class extends import_sdk4.DynamicPlugin {
|
|
416
470
|
options;
|
|
417
471
|
constructor(options) {
|
|
418
472
|
super();
|
|
@@ -531,7 +585,7 @@ var FeatureFlagPlugin = class extends import_sdk3.DynamicPlugin {
|
|
|
531
585
|
enabled = defaultValue;
|
|
532
586
|
}
|
|
533
587
|
if (!enabled) {
|
|
534
|
-
throw new
|
|
588
|
+
throw new FeatureFlagDisabledError(kind, metadata?.name, key);
|
|
535
589
|
}
|
|
536
590
|
}
|
|
537
591
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -549,13 +603,13 @@ var FeatureFlagPlugin = class extends import_sdk3.DynamicPlugin {
|
|
|
549
603
|
judgedItems(items, entryOf) {
|
|
550
604
|
let scope;
|
|
551
605
|
try {
|
|
552
|
-
scope = this.get(
|
|
606
|
+
scope = this.get(import_sdk4.ScopeEntry);
|
|
553
607
|
} catch {
|
|
554
608
|
scope = void 0;
|
|
555
609
|
}
|
|
556
610
|
if (!scope?.hooks) return new Set(items);
|
|
557
611
|
const judgingScope = scope;
|
|
558
|
-
return new Set(items.filter((item) => (0,
|
|
612
|
+
return new Set(items.filter((item) => (0, import_sdk4.isEntryGatedBy)(judgingScope, entryOf(item), this)));
|
|
559
613
|
}
|
|
560
614
|
/**
|
|
561
615
|
* Collect unique flag keys from items that have a featureFlag metadata.
|
|
@@ -590,7 +644,7 @@ var FeatureFlagPlugin = class extends import_sdk3.DynamicPlugin {
|
|
|
590
644
|
*/
|
|
591
645
|
currentFlagContext() {
|
|
592
646
|
try {
|
|
593
|
-
const ctx = this.get(
|
|
647
|
+
const ctx = this.get(import_sdk4.FrontMcpContextStorage)?.getStore();
|
|
594
648
|
if (!ctx) return {};
|
|
595
649
|
return buildFeatureFlagContext(ctx, this.options);
|
|
596
650
|
} catch {
|
|
@@ -614,6 +668,11 @@ var FeatureFlagPlugin = class extends import_sdk3.DynamicPlugin {
|
|
|
614
668
|
*/
|
|
615
669
|
__publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
616
670
|
const providers = [];
|
|
671
|
+
if (!SUPPORTED_ADAPTERS.includes(options?.adapter)) {
|
|
672
|
+
throw new FeatureFlagConfigurationError(
|
|
673
|
+
`FeatureFlagPlugin.init() requires an \`adapter\` option, got ${JSON.stringify(options?.adapter)}. Supported adapters: ${SUPPORTED_ADAPTERS.map((a) => `"${a}"`).join(", ")}.`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
617
676
|
switch (options.adapter) {
|
|
618
677
|
case "static":
|
|
619
678
|
providers.push({
|
|
@@ -662,6 +721,7 @@ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
|
662
721
|
});
|
|
663
722
|
break;
|
|
664
723
|
case "custom":
|
|
724
|
+
assertCustomAdapter(options.adapterInstance);
|
|
665
725
|
providers.push({
|
|
666
726
|
name: "feature-flags:adapter:custom",
|
|
667
727
|
provide: FeatureFlagAdapterToken,
|
|
@@ -677,20 +737,20 @@ __publicField(FeatureFlagPlugin, "dynamicProviders", (options) => {
|
|
|
677
737
|
providers.push({
|
|
678
738
|
name: "feature-flags:accessor",
|
|
679
739
|
provide: FeatureFlagAccessorToken,
|
|
680
|
-
scope:
|
|
681
|
-
inject: () => [FeatureFlagAdapterToken,
|
|
740
|
+
scope: import_sdk4.ProviderScope.CONTEXT,
|
|
741
|
+
inject: () => [FeatureFlagAdapterToken, import_sdk4.FRONTMCP_CONTEXT, FeatureFlagConfigToken],
|
|
682
742
|
useFactory: (adapter, ctx, cfg) => createFeatureFlagAccessor(adapter, ctx, cfg)
|
|
683
743
|
});
|
|
684
744
|
return providers;
|
|
685
745
|
});
|
|
686
746
|
__decorateClass([
|
|
687
|
-
|
|
747
|
+
import_sdk4.ListToolsHook.Did("findTools", { priority: 50 })
|
|
688
748
|
], FeatureFlagPlugin.prototype, "filterListTools", 1);
|
|
689
749
|
__decorateClass([
|
|
690
|
-
|
|
750
|
+
import_sdk4.ListResourcesHook.Did("findResources", { priority: 50 })
|
|
691
751
|
], FeatureFlagPlugin.prototype, "filterListResources", 1);
|
|
692
752
|
__decorateClass([
|
|
693
|
-
|
|
753
|
+
import_sdk4.ListResourceTemplatesHook.Did("findTemplates", { priority: 50 })
|
|
694
754
|
], FeatureFlagPlugin.prototype, "filterListResourceTemplates", 1);
|
|
695
755
|
__decorateClass([
|
|
696
756
|
ListPromptsHook.Did("findPrompts", { priority: 50 })
|
|
@@ -699,7 +759,7 @@ __decorateClass([
|
|
|
699
759
|
FilterSkillsHook.Did("filterSkills", { priority: 50, appliesTo: "uncovered-apps" })
|
|
700
760
|
], FeatureFlagPlugin.prototype, "filterSkills", 1);
|
|
701
761
|
__decorateClass([
|
|
702
|
-
|
|
762
|
+
import_sdk4.ToolHook.Will("execute", { priority: 50, appliesTo: "uncovered-apps" })
|
|
703
763
|
], FeatureFlagPlugin.prototype, "gateToolExecution", 1);
|
|
704
764
|
__decorateClass([
|
|
705
765
|
ReadResourceHook.Will("execute", { priority: 50, appliesTo: "uncovered-apps" })
|
|
@@ -711,7 +771,7 @@ __decorateClass([
|
|
|
711
771
|
CompleteHook.Will("complete", { priority: 50, appliesTo: "uncovered-apps" })
|
|
712
772
|
], FeatureFlagPlugin.prototype, "gateCompletion", 1);
|
|
713
773
|
FeatureFlagPlugin = __decorateClass([
|
|
714
|
-
(0,
|
|
774
|
+
(0, import_sdk4.Plugin)({
|
|
715
775
|
name: "feature-flags",
|
|
716
776
|
description: "Feature flag-based capability filtering for MCP",
|
|
717
777
|
// A server where an entry declares `featureFlag` and none of these gates reaches it refuses to start.
|
|
@@ -748,6 +808,8 @@ function tryGetFeatureFlags(ctx) {
|
|
|
748
808
|
FeatureFlagAccessorToken,
|
|
749
809
|
FeatureFlagAdapterToken,
|
|
750
810
|
FeatureFlagConfigToken,
|
|
811
|
+
FeatureFlagConfigurationError,
|
|
812
|
+
FeatureFlagDisabledError,
|
|
751
813
|
FeatureFlagPlugin,
|
|
752
814
|
LaunchDarklyFeatureFlagAdapter,
|
|
753
815
|
SplitioFeatureFlagAdapter,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-feature-flags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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.
|
|
52
|
+
"@frontmcp/sdk": "1.9.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@launchdarkly/node-server-sdk": "^9.0.0 || ^10.0.0",
|
|
@@ -9,12 +9,24 @@ export declare class FeatureFlagAccessor {
|
|
|
9
9
|
private readonly adapter;
|
|
10
10
|
private readonly ctx;
|
|
11
11
|
private readonly config;
|
|
12
|
+
/** The adapter's answers by flag key: `undefined` when it has none (an unknown flag). */
|
|
12
13
|
private readonly cache;
|
|
13
14
|
constructor(adapter: FeatureFlagAdapter, ctx: FrontMcpContext, config: FeatureFlagPluginOptions);
|
|
14
15
|
/**
|
|
15
16
|
* Check if a feature flag is enabled.
|
|
17
|
+
*
|
|
18
|
+
* `defaultValue` (else the plugin's `defaultValue`, else `false`) is the answer when the adapter
|
|
19
|
+
* throws or has no answer for the flag: a key the static adapter was not given, or one a custom
|
|
20
|
+
* adapter's `evaluateFlags()` omits. A flag the adapter answers keeps its answer, `false` included.
|
|
21
|
+
* It used to apply only when the adapter threw, so `isEnabled('unknown', true)` was `false` (#678).
|
|
16
22
|
*/
|
|
17
23
|
isEnabled(flagKey: string, defaultValue?: boolean): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The adapter's answer for one flag, or `undefined` when it has none or throws. Asked through
|
|
26
|
+
* `evaluateFlags()`, as the execution gates ask, which is how an adapter says it does not know a
|
|
27
|
+
* flag; `isEnabled()` answers `false` for it. A successful answer is cached per `cacheStrategy`.
|
|
28
|
+
*/
|
|
29
|
+
private evaluate;
|
|
18
30
|
/**
|
|
19
31
|
* Get a feature flag variant (for multi-variate flags).
|
|
20
32
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/feature-flag-accessor.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2B,MAAM,eAAe,CAAC;AAEzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,OAAO,KAAK,EAEV,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,qBAKa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"feature-flag-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/feature-flag-accessor.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2B,MAAM,eAAe,CAAC;AAEzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,OAAO,KAAK,EAEV,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,qBAKa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyE;gBAEnF,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,wBAAwB;IAM/F;;;;;;;OAOG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1E;;;;OAIG;YACW,QAAQ;IA0BtB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAK9D;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAKtE;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAOvD;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,kBAAkB,EAC3B,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,wBAAwB,GAC/B,mBAAmB,CAErB"}
|