Skip to content

feat(sdk): a live surface — entries that turn on and off while sessions are connected - #1004

Merged
alexmercerpo merged 8 commits into
feat/m06-result-handlesfrom
feat/m08-live-surface
Nov 2, 2026
Merged

alexmercerpo merged 8 commits into
feat/m06-result-handlesfrom
feat/m08-live-surface

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1003

What and why

Proposal item 2.3 (b): a live tool surface.

availableWhen is decided when the server starts. A server where people turn features on and off while sessions are
live (a settings page that writes a file, a flag service) had to filter the lists in its own hooks, poll the file,
broadcast list_changed itself and refuse calls with its own error, and a call made from a cached list answered
Tool "x" not found with no hint that the list was out of date.

What this PR changes:

  • enabled on @Tool, @Resource, @ResourceTemplate and @Prompt: a boolean, or an EnabledSource
    ({ get(ctx?), subscribe(listener), remedy? }). get answers for a request (it gets the request context);
    subscribe tells FrontMCP the answer may have changed. Validated in each metadata Zod schema: anything else fails at
    the decorator with `enabled` must be a boolean or an EnabledSource ({ get(ctx), subscribe(listener) }), such as fileSignal().
  • fileSignal(path, key, { defaultValue?, pollIntervalMs?, remedy?, logger? }), the built-in source: the boolean
    under key in a JSON file. It watches the file's directory (watchFile from @frontmcp/utils) and reads the file
    again every pollIntervalMs (2000), which is the only check where watching fails. A missing file or no boolean under
    the key means defaultValue (false); content that is not JSON, as a write in progress leaves it (a plain
    writeFile truncates first), keeps the last value read (defaultValue before one was read). Each problem is logged
    once. It calls its listeners only when the value changes. A bad path, key or interval throws InvalidFileSignalError where the entry is declared.
  • Live lists, refusals as stages. Each list flow has a new filterByEnabled stage (tools:list-tools,
    resources:list-resources, resources:list-resource-templates, prompts:list-prompts). tools:call-tool,
    resources:read-resource and prompts:get-prompt have a new checkEnabled stage that refuses an entry that is off
    with FeatureOffError: FEATURE_OFF, JSON-RPC -32003, data: { code, status: 403, entryType, entry, remedy }
    (on tools/call, an isError result with _meta.code, _meta.remedy and a To fix: line). The remedy is the
    source's, else Ask the server's operator to turn on tool "x". A call by an alias and this.callTool() are refused
    too. completion:complete gets checkEnabled as well: an entry that is off completes nothing. resources:subscribe
    refuses a resource that is off in its validateResource stage, as it refuses what resources/read refuses. A source
    that throws counts as off.
  • Coalesced list_changed. A new per-scope LiveSurface (scope.surface) follows the enabled sources of the
    scope's entries (including ones registered later) and, when one fires, emits a registry change, so
    notifications/tools/list_changed (or the resources or prompts one) reaches every connected session, the
    2026-07-28 subscriptions/listen streams and the WebMCP bridge: one per list per 100 ms. scope.surface.listChanged(kind)
    is the same coalesced path for code that changes a list some other way.
  • Surface hash and STALE_TOOL_LIST. The four list results carry _meta['frontmcp/surface'] (SURFACE_META_KEY),
    a 16-character hash of the names (URIs, URI templates) the list shows, the same on every page. The live surface
    remembers, for the last 10 000 connected sessions that listed tools, whether the tool list changed since; the id of
    a 2026-07-28 or stateless request names no session and is not remembered. A new checkSurface stage of
    tools:call-tool (before findTool) answers a client call from such a session to a tool that is not in the current
    list (unknown, or off) with StaleToolListError (STALE_TOOL_LIST, 409, remedy Call tools/list again, then call a tool from the new list.) and sends that session notifications/tools/list_changed. The next call gets the usual
    answer, so a client that never lists again is not stuck. The task runner's run of a task-augmented call is not
    checked: the call that created the task was.
  • Feature flags on EnabledSource. Each flag is a FeatureFlagSource (exported, with featureFlagSource(adapter, ref, options?)): the plugin's gates evaluate through it, and an entry can use one as enabled. FeatureFlagAdapter
    gets an optional subscribe(listener); the Split.io (SDK_UPDATE), LaunchDarkly (update) and Unleash (changed)
    adapters implement it. When the adapter reports a change, the plugin sends the coalesced list_changed for the lists
    that hold an entry with a featureFlag. The flag sources of one adapter share one subscribe() on it, so its SDK
    client gets one listener however many entries use them. The plugin's options, featureFlag metadata and
    FEATURE_FLAG_DISABLED refusal are unchanged; a userIdResolver or attributesResolver that throws still has the
    flag evaluated for an anonymous caller ({}), in the lists and the gates.
  • 2026-07-28 cache scope. A list holding an entry whose enabled is a source, and a resources/read on a server
    with a resource whose enabled is a source, are cacheScope: 'private': the source answers per request, so the
    result can differ by caller.

Different from the plan, and why:

  • "Older" is a version, not a hash comparison. Hashes have no order, and the session's current surface depends on
    hooks (authorities, flags) that only a full listing can run. The live surface counts tool registry changes (a source
    flip, a runtime registration, a remote app's tools) and remembers the count each session listed at; the hash is what
    the client sees.
  • list_changed goes through the registries (notifyListChanged() on the tool, resource and prompt registries)
    instead of NotificationService.broadcastNotification, so 2026-07-28 subscription streams and the WebMCP bridge
    see it too, and the stale-list version moves with it.
  • The feature-flags plugin keeps its hooks for featureFlag metadata (per-app judging, skills, completions and
    its error code stay as they were) and moves the flag evaluation onto FeatureFlagSource. Entries that want the SDK's
    handling use enabled: featureFlagSource(...).
  • STALE_TOOL_LIST is answered once per change. Answering it until the client lists again would leave a client
    that ignores list_changed without the real error.
  • Two small enabling fixes: collectToolMetadata dropped false metadata values (enabled: false never reached the
    tool; resources and prompts already kept them), and the Node sha256/sha256Hex of @frontmcp/utils wrapped their
    input in the global Buffer, which a browser-like run of tools/list (no Buffer) now needs to avoid; Node's hash
    takes strings and typed arrays directly.

User-facing change

  • What changed:
    • New enabled option on @Tool, @Resource, @ResourceTemplate, @Prompt; new EnabledSource, EnabledOption,
      isEnabledSource, enabledOptionSchema, fileSignal(), FileSignalOptions, SURFACE_META_KEY, surfaceHash(),
      LiveSurface (type) and ScopeEntry.surface.
    • New errors FeatureOffError (FEATURE_OFF, 403), StaleToolListError (STALE_TOOL_LIST, 409),
      InvalidFileSignalError (INVALID_FILE_SIGNAL).
    • New stages: filterByEnabled in the four list flows; checkSurface and checkEnabled in tools:call-tool;
      checkEnabled in resources:read-resource, prompts:get-prompt and completion:complete.
    • tools/list, resources/list, resources/templates/list and prompts/list results carry
      _meta['frontmcp/surface'].
    • notifyListChanged() on ToolRegistry, ResourceRegistry, PromptRegistry.
    • @frontmcp/plugin-feature-flags: FeatureFlagSource, featureFlagSource(), FeatureFlagSourceOptions; optional
      FeatureFlagAdapter.subscribe(), implemented by the Split.io, LaunchDarkly and Unleash adapters.
  • Before → after:
    • A flag flip reported by a Split.io, LaunchDarkly or Unleash adapter sent nothing → connected sessions get
      list_changed for the flagged lists.
    • A tools/call from a session whose tool list changed since it listed, to a tool no longer listed, answered
      TOOL_NOT_FOUND → STALE_TOOL_LIST once, with a tools/list_changed to that session.
    • (With enabled) resources/subscribe of a resource or template URI that is off answers FEATURE_OFF, as its
      resources/read does.
    • List results had no _meta → _meta['frontmcp/surface'].
    • Nothing else changes until an entry sets enabled.
  • frontmcp.dev pages affected (if known): the @Tool, @Resource/@ResourceTemplate and @Prompt option
    references (enabled), the tool availability page (new live surface section: fileSignal, FEATURE_OFF,
    list_changed, the surface hash, STALE_TOOL_LIST), the error reference (FEATURE_OFF, STALE_TOOL_LIST), the
    notifications page (coalesced list_changed), the flow stage reference (new stages), and the feature-flags plugin
    page (adapter subscribe, featureFlagSource).
  • Skills catalog entries updated:
    • create-tool/references/availability.md (new "enabled" section and "The surface hash and stale tool lists"),
      decorator-options.md (enabled row, availableWhen + enabled), error-handling.md (FeatureOffError,
      StaleToolListError, remedies), create-tool/SKILL.md (decision tree and reference table),
      skills-manifest.json;
    • frontmcp-development/references/create-resource.md and create-prompt.md (enabled option);
    • frontmcp-development/references/official-plugins.md (feature flags: "A flag flip updates connected clients",
      featureFlagSource), and the plugin's README.md.

Tests

Unit (libs/sdk):

  • surface/__tests__/file-signal.spec.ts: the value under the key; a change calls the listeners and get answers the
    new value; a change that keeps the value calls nothing; an empty or half-written file keeps the last value; a
    missing file, invalid JSON and a non-boolean value answer the default and log once each; a file that appears later; polling where watching fails; unsubscribe stops it; a
    listener that throws is logged and the others keep getting changes; the remedy; invalid path, key or interval.
  • surface/__tests__/live-surface.spec.ts: one list_changed per kind per window; kinds apart; one subscription for
    a shared source; sources of entries added and removed; listChanged(); nothing after stop(); a registry that
    fails to announce the change is logged from the timer, not thrown; a source that cannot be subscribed to; the hash in
    any order; a session stale after a change, current after listing; never stale without a listing or a session;
    refreshSession(); the bounded session memory; only connected sessions remembered (2026-07-28 per-request ids and the
    stateless id push none out).
  • surface/__tests__/live-surface.flows.spec.ts (a connected client): the four lists leave out what is off and follow
    a source; a failing source counts as off; FEATURE_OFF with a remedy by name, by alias and from this.callTool();
    the source's remedy in the To fix: line; reads of a resource and a template URI and a get of a prompt refused with
    status: 403 in data; a subscription to a resource that is off; completion of a prompt that is off; coalesced
    list_changed; STALE_TOOL_LIST then FEATURE_OFF; a stale call to an unknown tool, then TOOL_NOT_FOUND; the task
    runner's run of a task answered as usual; a stale session calling a tool still on; the hash on the four list results
    and the session record; the decorator validation message.
  • transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts: the four lists and resources/read with an
    enabled source are cacheScope: 'private'.
  • libs/utils crypto.spec.ts: Node SHA-256 without a global Buffer.

Unit (plugin-feature-flags):

  • feature-flag.source.spec.ts: the adapter's answer, the fallback chain for an unknown flag and a throwing adapter,
    the caller's context, subscribe following the adapter (and a no-op without), one adapter subscription shared by
    its sources, the remedy.
  • feature-flag.caller-context.spec.ts: a throwing resolver evaluates the lists and the gate anonymously, as before.
  • feature-flag.live-surface.spec.ts (a connected client): one list_changed per flagged list per window on a flip,
    none for resources; the lists follow the flag; enabled: featureFlagSource() refused with FEATURE_OFF;
    featureFlag still FEATURE_FLAG_DISABLED; the adapter is no longer followed once the server is disposed.
  • launchdarkly/unleash/splitio.adapter.spec.ts: subscribe follows the SDK event until stopped, and follows
    nothing before initialize().

E2E:

  • apps/e2e/demo-e2e-notifications/e2e/live-surface.e2e.spec.ts (new server entry src/live-surface.main.ts with a
    fileSignal tool and a settings_set tool that writes the file): flipping the file sends
    notifications/tools/list_changed and tools/list follows it; tools/list carries the hash; a call while off gets
    FEATURE_OFF with the remedy; calling the remedy tool turns it back on; a call from a stale list gets
    STALE_TOOL_LIST and a list_changed, then FEATURE_OFF.
  • apps/e2e/demo-e2e-dts-consumer/fixture/live-surface-must-compile.ts: enabled: true, false, fileSignal(...)
    and a custom EnabledSource on the four decorators, SURFACE_META_KEY, and a @ts-expect-error for
    enabled: 'yes'.

Run:

  • libs/sdk unit suite: 589 suites, 7653 tests passed; libs/utils: 69 suites, 2000 tests passed;
    plugin-feature-flags: 25 suites, 262 tests passed; nx test skills: 249 passed.
  • demo-e2e-notifications: 5 suites, 47 tests passed (5 new); demo-e2e-feature-flags: 4 suites, 33 passed;
    demo-e2e-dts-consumer: 17 passed (1 new).
  • Lint of the touched projects (no new warnings), tsc --noEmit of the SDK library and the plugin library and specs,
    fix-unused-imports, Prettier check.

Summary by CodeRabbit

  • New Features
    • Tools, resources, resource templates, and prompts can now be enabled or disabled while a server is running, using boolean settings, file-based signals, or feature flags.
    • Listings update when availability changes. Disabled entries are omitted, and attempts to use them return an error with guidance when available.
    • Stale tool lists are detected, with guidance to refresh before retrying.
  • Documentation
    • Added guidance on live availability controls, feature-flag updates, and related errors.

… list_changed, a surface hash and STALE_TOOL_LIST

- enabled on @tool, @resource, @ResourceTemplate and @prompt: a boolean or an
  EnabledSource { get(ctx?), subscribe(listener), remedy? }, validated in each
  metadata schema; fileSignal(path, key) follows a JSON file (watch plus poll),
  using its default for a missing or unparsable file, logged once
- filterByEnabled stages in the four list flows; checkEnabled stages in
  tools:call-tool, resources:read-resource, prompts:get-prompt and
  completion:complete refuse an entry that is off with FEATURE_OFF (403 in data)
  and the source's remedy
- a per-scope LiveSurface follows the sources and sends one list_changed per
  list per 100 ms through the registries, to every connected session
- list results carry _meta['frontmcp/surface']; a call from a session whose
  tool list changed since it listed, to a tool no longer listed, answers
  STALE_TOOL_LIST once and sends that session tools/list_changed (bounded
  per-session memory)
- feature flags: FeatureFlagSource and featureFlagSource(); optional
  FeatureFlagAdapter.subscribe(), implemented by the Split.io, LaunchDarkly
  and Unleash adapters, so a flag flip sends list_changed
- a list with an enabled source is cacheScope private under 2026-07-28
- tool metadata keeps false values; Node SHA-256 needs no global Buffer
… and isolate failing surface listeners

- a 2026-07-28 resources/read is cacheScope private when a resource has an
  enabled source, as its lists already are
- a fileSignal listener that throws is logged and no longer stops the checks
  that follow
- a list change the registry fails to announce from the coalescing timer is
  logged instead of thrown
- the e2e features tools declare an outputSchema
…subscribing to a resource that is off, and keep a fileSignal's last value over a half-written file

- LiveSurface remembers a tool listing only for a session registered with the notification service: the per-request
  id of a 2026-07-28 request and the shared stateless id were recorded on every tools/list and pushed real sessions
  out of the 10 000-entry memory.
- checkSurface skips the task runner's run of a task-augmented call; the call that created the task was checked.
- resources:subscribe refuses a resource or template URI whose enabled is off with FEATURE_OFF, as resources/read does.
- fileSignal keeps the last value it read when the file is not valid JSON, as a truncating writer leaves it mid-write,
  instead of flipping to the default and back (two list_changed and a window of FEATURE_OFF).
- Type errors in the live-surface flows spec fixed.
…sources, and evaluate anonymously when a resolver throws

- The FeatureFlagSources of one adapter share one adapter.subscribe(): each source subscribed on its own, so eleven
  flagged entries put eleven listeners on the SDK client and Node warned of an EventEmitter leak.
- A userIdResolver or attributesResolver that throws again has the flag evaluated for an anonymous caller ({}) in the
  list hooks and the gates, as before: the lists threw (tools/list failed) and the gates fell back to the default.
…at/m08-live-surface

# Conflicts:
#	libs/sdk/src/transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts
#	libs/sdk/src/transport/mcp-20260728/result-decorator.ts
#	libs/skills/catalog/create-tool/references/decorator-options.md
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 14:55
@frontegg-david
frontegg-david added this pull request to stack #997 November 2, 2026 14:55
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

This review is too large to run within your organization's remaining usage spending cap. Raise or remove your spending cap in the billing tab, then retry.

Next included review available in 8 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 3 included reviews currently available. Your 48 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 7b8f2c2 and 3bdb615.

Files selected for processing: 69
  • apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/live-surface-must-compile.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
  • apps/e2e/demo-e2e-notifications/e2e/live-surface.e2e.spec.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/index.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/reviews.signal.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/tools/review-list.tool.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/tools/settings-set.tool.ts
  • apps/e2e/demo-e2e-notifications/src/live-surface.main.ts
  • libs/sdk/src/common/entries/scope.entry.ts
  • libs/sdk/src/common/metadata/enabled.metadata.ts
  • libs/sdk/src/common/metadata/index.ts
  • libs/sdk/src/common/metadata/prompt.metadata.ts
  • libs/sdk/src/common/metadata/resource.metadata.ts
  • libs/sdk/src/common/metadata/tool.metadata.ts
  • libs/sdk/src/common/tokens/prompt.tokens.ts
  • libs/sdk/src/common/tokens/resource.tokens.ts
  • libs/sdk/src/common/tokens/tool.tokens.ts
  • libs/sdk/src/completion/flows/complete.flow.ts
  • libs/sdk/src/errors/index.ts
  • libs/sdk/src/errors/mcp.error.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/prompt/flows/get-prompt.flow.ts
  • libs/sdk/src/prompt/flows/prompts-list.flow.ts
  • libs/sdk/src/prompt/prompt.registry.ts
  • libs/sdk/src/resource/flows/read-resource.flow.ts
  • libs/sdk/src/resource/flows/resource-templates-list.flow.ts
  • libs/sdk/src/resource/flows/resources-list.flow.ts
  • libs/sdk/src/resource/flows/subscribe-resource.flow.ts
  • libs/sdk/src/resource/resource.registry.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/sdk/src/surface/__tests__/file-signal.spec.ts
  • libs/sdk/src/surface/__tests__/live-surface.flows.spec.ts
  • libs/sdk/src/surface/__tests__/live-surface.spec.ts
  • libs/sdk/src/surface/entry-enabled.ts
  • libs/sdk/src/surface/file-signal.ts
  • libs/sdk/src/surface/index.ts
  • libs/sdk/src/surface/live-surface.ts
  • libs/sdk/src/tool/flows/call-tool.flow.ts
  • libs/sdk/src/tool/flows/tools-list.flow.ts
  • libs/sdk/src/tool/tool.registry.ts
  • libs/sdk/src/tool/tool.utils.ts
  • libs/sdk/src/transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts
  • libs/sdk/src/transport/mcp-20260728/result-decorator.ts
  • libs/skills/catalog/create-tool/SKILL.md
  • libs/skills/catalog/create-tool/references/availability.md
  • libs/skills/catalog/create-tool/references/decorator-options.md
  • libs/skills/catalog/create-tool/references/error-handling.md
  • libs/skills/catalog/frontmcp-development/references/create-prompt.md
  • libs/skills/catalog/frontmcp-development/references/create-resource.md
  • libs/skills/catalog/frontmcp-development/references/official-plugins.md
  • libs/skills/catalog/skills-manifest.json
  • libs/utils/src/crypto/__tests__/crypto.spec.ts
  • libs/utils/src/crypto/node.ts
  • plugins/plugin-feature-flags/README.md
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.caller-context.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.live-surface.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.plugin.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.source.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/launchdarkly.adapter.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/splitio.adapter.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/unleash.adapter.spec.ts
  • plugins/plugin-feature-flags/src/adapters/feature-flag-adapter.interface.ts
  • plugins/plugin-feature-flags/src/adapters/launchdarkly.adapter.ts
  • plugins/plugin-feature-flags/src/adapters/splitio.adapter.ts
  • plugins/plugin-feature-flags/src/adapters/unleash.adapter.ts
  • plugins/plugin-feature-flags/src/feature-flag.plugin.ts
  • plugins/plugin-feature-flags/src/feature-flag.source.ts
  • plugins/plugin-feature-flags/src/index.ts

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between c60a526 and 7b8f2c2.

Files selected for processing: 69
  • apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/live-surface-must-compile.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
  • apps/e2e/demo-e2e-notifications/e2e/live-surface.e2e.spec.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/index.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/reviews.signal.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/tools/review-list.tool.ts
  • apps/e2e/demo-e2e-notifications/src/apps/features/tools/settings-set.tool.ts
  • apps/e2e/demo-e2e-notifications/src/live-surface.main.ts
  • libs/sdk/src/common/entries/scope.entry.ts
  • libs/sdk/src/common/metadata/enabled.metadata.ts
  • libs/sdk/src/common/metadata/index.ts
  • libs/sdk/src/common/metadata/prompt.metadata.ts
  • libs/sdk/src/common/metadata/resource.metadata.ts
  • libs/sdk/src/common/metadata/tool.metadata.ts
  • libs/sdk/src/common/tokens/prompt.tokens.ts
  • libs/sdk/src/common/tokens/resource.tokens.ts
  • libs/sdk/src/common/tokens/tool.tokens.ts
  • libs/sdk/src/completion/flows/complete.flow.ts
  • libs/sdk/src/errors/index.ts
  • libs/sdk/src/errors/mcp.error.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/prompt/flows/get-prompt.flow.ts
  • libs/sdk/src/prompt/flows/prompts-list.flow.ts
  • libs/sdk/src/prompt/prompt.registry.ts
  • libs/sdk/src/resource/flows/read-resource.flow.ts
  • libs/sdk/src/resource/flows/resource-templates-list.flow.ts
  • libs/sdk/src/resource/flows/resources-list.flow.ts
  • libs/sdk/src/resource/flows/subscribe-resource.flow.ts
  • libs/sdk/src/resource/resource.registry.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/sdk/src/surface/__tests__/file-signal.spec.ts
  • libs/sdk/src/surface/__tests__/live-surface.flows.spec.ts
  • libs/sdk/src/surface/__tests__/live-surface.spec.ts
  • libs/sdk/src/surface/entry-enabled.ts
  • libs/sdk/src/surface/file-signal.ts
  • libs/sdk/src/surface/index.ts
  • libs/sdk/src/surface/live-surface.ts
  • libs/sdk/src/tool/flows/call-tool.flow.ts
  • libs/sdk/src/tool/flows/tools-list.flow.ts
  • libs/sdk/src/tool/tool.registry.ts
  • libs/sdk/src/tool/tool.utils.ts
  • libs/sdk/src/transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts
  • libs/sdk/src/transport/mcp-20260728/result-decorator.ts
  • libs/skills/catalog/create-tool/SKILL.md
  • libs/skills/catalog/create-tool/references/availability.md
  • libs/skills/catalog/create-tool/references/decorator-options.md
  • libs/skills/catalog/create-tool/references/error-handling.md
  • libs/skills/catalog/frontmcp-development/references/create-prompt.md
  • libs/skills/catalog/frontmcp-development/references/create-resource.md
  • libs/skills/catalog/frontmcp-development/references/official-plugins.md
  • libs/skills/catalog/skills-manifest.json
  • libs/utils/src/crypto/__tests__/crypto.spec.ts
  • libs/utils/src/crypto/node.ts
  • plugins/plugin-feature-flags/README.md
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.caller-context.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.live-surface.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.plugin.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/feature-flag.source.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/launchdarkly.adapter.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/splitio.adapter.spec.ts
  • plugins/plugin-feature-flags/src/__tests__/unleash.adapter.spec.ts
  • plugins/plugin-feature-flags/src/adapters/feature-flag-adapter.interface.ts
  • plugins/plugin-feature-flags/src/adapters/launchdarkly.adapter.ts
  • plugins/plugin-feature-flags/src/adapters/splitio.adapter.ts
  • plugins/plugin-feature-flags/src/adapters/unleash.adapter.ts
  • plugins/plugin-feature-flags/src/feature-flag.plugin.ts
  • plugins/plugin-feature-flags/src/feature-flag.source.ts
  • plugins/plugin-feature-flags/src/index.ts

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Walkthrough

The SDK adds live enablement for tools, resources, resource templates, and prompts. It adds file-backed and feature-flag sources, list-change notifications, surface hashes, and stale-tool-list handling. New tests, examples, and documentation cover these additions.

Changes: Live Entry Enablement

Layer / File(s) Summary
Enablement contract and file signals
libs/sdk/src/common/metadata/*, libs/sdk/src/common/tokens/*, libs/sdk/src/surface/entry-enabled.ts, libs/sdk/src/surface/file-signal.ts, libs/sdk/src/surface/__tests__/file-signal.spec.ts, libs/sdk/src/surface/index.ts, libs/sdk/src/index.ts
Metadata schemas accept boolean or EnabledSource values. The SDK adds helpers to evaluate enabled entries and a file-backed source with configurable polling, defaults, and remedies.
Live-surface lifecycle and notifications
libs/sdk/src/surface/live-surface.ts, libs/sdk/src/surface/__tests__/live-surface.spec.ts, libs/sdk/src/scope/*, libs/sdk/src/common/entries/scope.entry.ts, libs/sdk/src/{tool,resource,prompt}/*.registry.ts, libs/utils/src/crypto/*
Scopes start and stop LiveSurface. It tracks source and registry changes, sends coalesced list-change notifications, computes surface hashes, and records tool-list versions for sessions.
Entry filtering, request checks, and list responses
libs/sdk/src/{tool,resource,prompt,completion}/flows/*, libs/sdk/src/{tool,resource,prompt}/*.registry.ts, libs/sdk/src/errors/*, libs/sdk/src/transport/mcp-20260728/*, libs/sdk/src/surface/__tests__/live-surface.flows.spec.ts, libs/skills/catalog/create-tool/*, libs/skills/catalog/frontmcp-development/references/{create-prompt.md,create-resource.md}, libs/skills/catalog/skills-manifest.json
List flows filter disabled entries and return surface hashes. Request flows check enabled state; tool calls also check stale lists. Tests and SDK guidance cover filtering, refusals, notifications, and cache behavior.
Feature-flag sources and adapter subscriptions
plugins/plugin-feature-flags/src/*, plugins/plugin-feature-flags/README.md, plugins/plugin-feature-flags/src/__tests__/*, libs/skills/catalog/frontmcp-development/references/official-plugins.md
Feature-flag sources evaluate flags with caller context and share adapter subscriptions. The LaunchDarkly, Split.io, and Unleash adapters expose subscriptions, and the plugin signals changes to affected lists.
Consumer and live-session examples
apps/e2e/demo-e2e-dts-consumer/*, apps/e2e/demo-e2e-notifications/*
The declaration fixture checks the new types. The notification demo tests file-driven listing changes, remedies, surface metadata, and stale-tool-list responses.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FeatureFlagAdapter
  participant FeatureFlagPlugin
  participant LiveSurface
  participant Client
  participant ToolsListFlow
  participant CallToolFlow
  FeatureFlagAdapter->>FeatureFlagPlugin: report a flag change
  FeatureFlagPlugin->>LiveSurface: signal affected list changes
  LiveSurface-->>Client: send list_changed notification
  Client->>ToolsListFlow: request tools/list
  ToolsListFlow-->>Client: return enabled tools and surface hash
  Client->>CallToolFlow: call a tool from an earlier list
  CallToolFlow->>LiveSurface: check recorded tool-list version
  CallToolFlow-->>Client: return STALE_TOOL_LIST when the list is stale
Loading

Suggested reviewers: alexmercerpo

Merge Risk: Minimal · previous review 7b8f2

The live-surface changes have no established merge-blocking issue in the supplied evidence; merge after normal checks.

Pre-merge checks: Passed 4 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Docstring Coverage Inconclusive Docstring coverage is 42.86% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: live surfaces with entries that can turn on and off during connected sessions.
Description check Passed The description is complete. It explains the motivation, user-facing API and behavior changes, affected documentation, updated skills, extensive tests, and validation checks.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 42.86% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 14 functions across 50 files. (19 skipped: 10 unsupported, 9 over the file limit.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-02T15:43:26.260Z
Commit: f19bd750

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@alexmercerpo
alexmercerpo merged commit 91bbe39 into main Nov 2, 2026
46 checks passed
@alexmercerpo
alexmercerpo deleted the feat/m08-live-surface branch November 2, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants