> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sources

> Where bundles come from — static (filesystem), npm (pinned package), or saas-pull (HTTPS with JWT, polling, cache fallback).

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

| Source | Refresh | Production-ready | Use when |
| - | - | - | - |
| `static` | `fs.watch` (optional) | ✅ for self-hosted | Bundle lives on the FrontMCP server's filesystem; you control deployments |
| `npm` | Server redeploy only | ✅ | Bundle ships as an npm package pinned in your `package.json` |
| `saas` | Boot pull + interval polling | ✅ when paired with signing | Bundle is produced by an external service (FrontMCP Cloud or your own analyzer) |

## `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).

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
SkilledOpenApiPlugin.init({
  source: {
    type: 'static',
    path: path.resolve(__dirname, '../bundle.json'),
    watch: true,
  },
  // ...
})
```

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.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
SkilledOpenApiPlugin.init({
  source: {
    type: 'npm',
    packageName: '@acme/frontmcp-billing-bundle',
    exportName: 'frontmcpBundle',  // optional; default is the package's default export
  },
  // ...
})
```

The package's exported value must conform to the `ResolvedBundle` shape; the plugin parses it through the same Zod validator the static source uses.

`verifyProvenance` defaults to `true`, and npm provenance verification is not implemented yet, so with `true` the source refuses to load the package (it is never imported) and logs why. Set `verifyProvenance: false` to load it without a provenance check.

<Warning>
  With `verifyProvenance: false` the package is imported, and its code runs, before the bundle is parsed. Bundle signing (the `integrity` envelope, `requireSignature` + `trustedKeys`) protects the bundle's content, not the import: a compromised package runs its code even if its bundle then fails verification. Only load a package you trust, pin its exact version in `package.json`, and install it from a lockfile with integrity hashes.
</Warning>

## `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.

Before every pull the pinned `authToken` is verified against the SaaS: signed by a key served at `jwksUrl` (fetched without the token, redirects refused), `iss` equal to `expectedIssuer`, `aud` including `expectedAudience`, not expired. A token that fails is refused with `[saas-source] pull token rejected: …`: nothing is pulled, and the cached bundle is not used instead. The JWKS being unreachable, or serving no usable signing key (no RSA, EC or OKP public key for signatures), counts as an ordinary pull failure, so the cache fallback still applies.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
SkilledOpenApiPlugin.init({
  source: {
    type: 'saas',
    endpoint: 'https://cloud.frontmcp.dev/v1/bundles/acme-prod',
    authToken: process.env.FRONTMCP_CLOUD_TOKEN!,
    expectedAudience: 'acme:prod',                 // RFC 8707 aud claim must match
    expectedIssuer: 'https://cloud.frontmcp.dev',
    jwksUrl: 'https://cloud.frontmcp.dev/.well-known/jwks.json',
    pollIntervalMs: 300_000,                       // 5 min default
  },
  bundleCacheDir: '.frontmcp/skilled-openapi',     // last-good bundle persisted here
  // ...
})
```

### 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. A rejected `authToken` is not a pull failure: the cache is not used, and the source reports an error.
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.

### Redirects

The pull carries `authToken` as a bearer token, so it never follows a redirect: `endpoint` must serve the bundle directly. A 3xx (or a browser runtime's status-0 `opaqueredirect`) fails the pull like any other pull error, with the boot fallback described above.

### 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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
SkilledOpenApiPlugin.init({
  source: { /* ... */ },
  sourceConflictPolicy: 'static-wins',  // default: locally pinned beats remote
})
```

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`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.