@frontmcp/plugin-feature-flags

中文文档
1.12.2发布于 2026/11/15Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 5363

Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags

原始使用文档
文档目录12 节
  1. Install
  2. Usage
  3. The featureFlag option
  4. Options
  5. Your own adapter
  6. A flag as enabled
  7. this.featureFlags
  8. When the flag service fails
  9. Docs
  10. Related packages
  11. Contributing
  12. License

@frontmcp/plugin-feature-flags

Turns the tools, resources, resource templates, prompts, skills and agents of a FrontMCP server on and off with feature flags. Give an entry featureFlag: 'bulk-export', and while that flag is off for the caller, the entry is left out of every list and refused when called, so you can ship a capability to some users before everyone. Flags come from fixed values, LaunchDarkly, Split, Unleash or your own adapter, and are evaluated for each caller. Inside your code, this.featureFlags reads a flag.

npm License

Install

npm install @frontmcp/plugin-feature-flags

The plugin runs inside a FrontMCP server, so your app needs @frontmcp/sdk, at the same version as this package, and Node.js 24 or later.

The flag services' SDKs are optional peer dependencies. Install the one for the adapter you use:

adapterInstall
'launchdarkly'@launchdarkly/node-server-sdk (9, 10)
'splitio'@splitsoftware/splitio (10, 11)
'unleash'unleash-client (5, 6)

Without it, the server fails to start with an error such as LaunchDarkly SDK not found. Install it: npm install @launchdarkly/node-server-sdk. The 'static' and 'custom' adapters need nothing else.

Usage

Register the plugin with FeatureFlagPlugin.init(), then gate entries with featureFlag:

import { FeatureFlagPlugin } from '@frontmcp/plugin-feature-flags';
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';

@Tool({
  name: 'bulk_export',
  description: 'Export every ticket as CSV',
  inputSchema: {},
  featureFlag: 'bulk-export',
})
class BulkExportTool extends ToolContext {
  async execute() {
    return { csv: 'id,title\nT-1,Cannot log in' };
  }
}

@Tool({ name: 'search_tickets', description: 'Search support tickets', inputSchema: { query: z.string() } })
class SearchTicketsTool extends ToolContext {
  async execute({ query }: { query: string }) {
    const ranking = (await this.featureFlags.isEnabled('semantic-search', false)) ? 'semantic' : 'keyword';
    return { query, ranking, tickets: ['T-1'] };
  }
}

@App({ id: 'help-desk', name: 'Help Desk', tools: [SearchTicketsTool, BulkExportTool] })
class HelpDeskApp {}

@FrontMcp({
  info: { name: 'help-desk', version: '1.0.0' },
  apps: [HelpDeskApp],
  plugins: [
    FeatureFlagPlugin.init({
      adapter: 'static',
      flags: { 'bulk-export': false, 'semantic-search': true },
    }),
  ],
})
export default class Server {}

Here bulk_export is left out of tools/list, and a call to it is refused with Tool "bulk_export" is disabled by feature flag "bulk-export" and the code FEATURE_FLAG_DISABLED.

Registered on @FrontMcp, the plugin gates every app's entries. Registered on an @App, it gates that app's entries, and those of any app without a feature-flag plugin of its own. If an entry declares featureFlag and no feature-flag plugin reaches it, the server doesn't start.

The featureFlag option

The plugin adds featureFlag to @Tool, @Resource, @ResourceTemplate, @Prompt, @Skill and @Agent:

FormMeaning
'key'Gated by the flag key. When the adapter has no answer for it, the entry is off, unless the plugin's gateDefaultValue is true.
{ key, defaultValue }The same, except that defaultValue decides when the adapter has no answer for the flag, or throws. A flag the adapter reports off stays off.

Options

adapter picks where flags come from, and which other option it needs:

adapterAlso requiredFlags come from
'static'flags: Record<string, boolean | FeatureFlagVariant>The values you give.
'launchdarkly'config: { sdkKey }LaunchDarkly.
'splitio'config: { apiKey }Split.
'unleash'config: { url, appName, apiKey? }Unleash.
'custom'adapterInstance: FeatureFlagAdapterYour adapter.

Every adapter also takes:

OptionTypeDefaultDescription
defaultValuebooleanfalseWhat this.featureFlags.isEnabled() returns, when the call passes no default, for an unknown flag or an error.
gateDefaultValuebooleanfalseWhat lists and gates answer for an unknown flag, or an adapter error, when the entry's featureFlag has no defaultValue. true fails open.
cacheStrategy'session' | 'request' | 'none''none'Caches this.featureFlags.isEnabled() results. Lists and gates never cache.
cacheTtlMsnumber30000How long a cached result lasts, in milliseconds.
userIdResolver(ctx: FrontMcpContext) => string | undefinedthe signed-in userThe userId adapters evaluate flags for. An anonymous caller has none.
attributesResolver(ctx: FrontMcpContext) => Record<string, unknown>{}Targeting attributes, such as a tenant or a plan.

FeatureFlagPlugin.init() without a supported adapter throws a FeatureFlagConfigurationError.

FeatureFlagPlugin.init({
  adapter: 'launchdarkly',
  config: { sdkKey: process.env.LAUNCHDARKLY_SDK_KEY! },
  attributesResolver: (ctx) => ({ plan: ctx.authInfo?.extra?.['plan'] }),
});

Your own adapter

Pass any object that implements FeatureFlagAdapter as adapterInstance. Here flagService stands for your flag service's client:

import { FeatureFlagPlugin, type FeatureFlagAdapter } from '@frontmcp/plugin-feature-flags';

const flagAdapter: FeatureFlagAdapter = {
  async isEnabled(flagKey, context) {
    return (await flagService.isOn(flagKey, context.userId)) === true;
  },
  async getVariant(flagKey, context) {
    const enabled = (await flagService.isOn(flagKey, context.userId)) === true;
    return { name: enabled ? 'on' : 'off', value: enabled, enabled };
  },
  async evaluateFlags(flagKeys, context) {
    const results = new Map<string, boolean>();
    for (const flagKey of flagKeys) {
      const answer = await flagService.isOn(flagKey, context.userId);
      if (answer !== undefined) results.set(flagKey, answer);
    }
    return results;
  },
};

FeatureFlagPlugin.init({ adapter: 'custom', adapterInstance: flagAdapter });
  • evaluateFlags() is what lists, gates and this.featureFlags.isEnabled() call. Leave out a key you know nothing about, so the caller's defaultValue applies; answer false for a flag that's off.
  • getVariant() serves this.featureFlags.getVariant(). isEnabled() is required, though the plugin doesn't call it.
  • The optional initialize() runs when the server starts, and destroy() when it's disposed.
  • The optional subscribe(listener) calls listener when flags may have changed and returns the function that stops it. With it, a flag flip sends connected clients list_changed for the lists your flags gate. The Split.io, LaunchDarkly and Unleash adapters implement it.

The built-in adapters are exported too: StaticFeatureFlagAdapter, LaunchDarklyFeatureFlagAdapter, SplitioFeatureFlagAdapter and UnleashFeatureFlagAdapter.

A flag as enabled

featureFlagSource(adapter, ref) is a flag as an SDK EnabledSource, for enabled on a tool, resource, template or prompt. Give the plugin the same adapter. The SDK then filters the lists, refuses the entry with FEATURE_OFF and the remedy Turn on the feature flag "beta"., and sends list_changed when the adapter reports a change. The sources of one adapter share one listener on its SDK client:

import { FeatureFlagPlugin, featureFlagSource, UnleashFeatureFlagAdapter } from '@frontmcp/plugin-feature-flags';

const flags = new UnleashFeatureFlagAdapter({ url: 'https://unleash.example.com/api', appName: 'my-mcp-server' });

@Tool({ name: 'beta_report', inputSchema: {}, enabled: featureFlagSource(flags, 'beta-reports') })
class BetaReportTool extends ToolContext {}

FeatureFlagPlugin.init({ adapter: 'custom', adapterInstance: flags });

this.featureFlags

this.featureFlags is available in tools, resources and prompts, and evaluates flags for the current caller:

MethodReturnsDescription
isEnabled(flagKey, defaultValue?)Promise<boolean>The flag's value. For an unknown flag, or an adapter error: defaultValue, else the plugin's, else false.
getVariant(flagKey)Promise<FeatureFlagVariant>The flag's variant, { name, value, enabled }. Adapter errors are thrown.
evaluateFlags(flagKeys)Promise<Map<string, boolean>>Several flags in one adapter call. Adapter errors are thrown.
resolveRef(ref)Promise<boolean>The value of a featureFlag ref: a key, or { key, defaultValue }.

this.get(FeatureFlagAccessorToken) returns the same accessor, and this.tryGet(FeatureFlagAccessorToken) returns it or undefined where the plugin isn't installed.

When the flag service fails

WhereWhat happens when the adapter throws
A list, such as tools/listThe ref's defaultValue, else the plugin's gateDefaultValue, else false, with a warning in the log. The rest of the list is served.
A gate, such as tools/callThe ref's defaultValue, else the plugin's gateDefaultValue, else false.
this.featureFlags.isEnabled()The call's defaultValue, else the plugin's defaultValue, else false.
getVariant(), evaluateFlags()The error is thrown to your code.

Up to 1.11.2 a list failed with the adapter's error (-32603), and no default applied.

Docs

Contributing

Source: plugins/plugin-feature-flags. Issues and pull requests are welcome; see CONTRIBUTING.md.

License

Apache-2.0. See LICENSE.

版本与发布记录

@frontmcp/plugin-feature-flags 共发布 98 个版本,首次发布于 2026/03/17;过去 12 个月发布了 98 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.2latest2026/11/151 天前
1.12.12026/11/132 天前
1.12.02026/11/114 天前
1.12.0-rc.1rc2026/11/114 天前
1.11.32026/11/106 天前
1.11.22026/11/096 天前

其他发布通道:beta → 1.1.2-beta.1,rc → 1.12.0-rc.1。

关于 @frontmcp/plugin-feature-flags 的常见问题

@frontmcp/plugin-feature-flags 是什么?

Feature flag plugin for FrontMCP - dynamically gate MCP capabilities behind feature flags

如何安装 @frontmcp/plugin-feature-flags?

在项目目录运行 npm install @frontmcp/plugin-feature-flags,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@frontmcp/sdk(1.12.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@frontmcp/plugin-feature-flags 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@frontmcp/plugin-feature-flags 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@frontmcp/plugin-feature-flags 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@frontmcp/plugin-feature-flags 可以商用吗?

@frontmcp/plugin-feature-flags 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@frontmcp/plugin-feature-flags 的下载量有多少?

根据 npm 官方下载统计,@frontmcp/plugin-feature-flags 在 2026/10/16 – 2026/11/14 这 30 天内被下载 5,363 次,日均约 179 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@frontmcp/plugin-feature-flags 还在维护吗?

最新版本 1.12.2 发布于 2026/11/15(1 天前);至今共发布 98 个版本,其中过去 12 个月发布了 98 个;首次发布于 2026/03/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。