Skip to main content
The plugin loads bundles through a SkillBundleSource interface. Three implementations are available; you choose by setting source.type in the plugin options.

static

Loads from a local filesystem path. Optional watch: true re-fetches on file change (debounced 250ms). Falls back to a 2-second poll if fs.watch rejects the file (some platforms reject watch on certain filesystems).
Bundle file extensions:
  • .json → JSON
  • .yaml / .yml → YAML
  • anything else → format sniffed from the first non-whitespace byte

npm

Loads from a published npm package’s default (or named) export. Pinned by your package.json — updates require a server redeploy, which is intentional: the lower-blast-radius distribution mode.
The package’s exported value must conform to the ResolvedBundle shape; the plugin parses it through the same Zod validator the static source uses. Pin the package version in your package.json and sign your bundles via the integrity envelope as your supply-chain controls.

saas

Pulls bundles from a configured HTTPS endpoint with a pinned JWT. Boot pull is mandatory; interval polling refreshes; a last-good cached bundle on disk is the fallback if a fresh pull fails.

Boot semantics

  1. Initial pull runs synchronously when the plugin’s BundleSyncService factory boots.
  2. If the pull succeeds, the bundle is persisted to bundleCacheDir and applied via the sync service.
  3. If the pull fails AND a cached bundle exists, the cache is loaded with a startup warning. The server stays healthy.
  4. If the pull fails AND no cache exists, the source throws — the plugin reports a startup error but does not kill the server.
This prevents a SaaS outage at boot from killing every customer’s MCP server. The trade-off is that a long-broken SaaS will leave the server serving an old bundle indefinitely; monitor your SaaS-side metrics to catch a stalled pipeline.

Polling

Default pollIntervalMs: 300_000 (5 min). Overlapping polls are skipped via a single-flight gate — if a previous poll is still in flight when the next interval fires, the new poll is dropped (not queued).

Source-conflict policy

If multiple sources somehow register the same bundleId (e.g. you wire both static and saas simultaneously), the plugin’s sourceConflictPolicy controls resolution:
Values:
  • 'static-wins' (default): static beats npm beats saas
  • 'last-wins': most recent apply wins (use with caution; race-prone)
  • 'reject': fail to apply when a conflict is detected
The plugin constructs a single source from options.source, so this policy only takes effect when more than one source registers the same bundleId.