Why ESM Packages?
Zero Build Step
Load community or internal packages at runtime without bundling them into your server build
Auto-Updates
Background version polling with semver-aware hot-reload — tools update without restarts
Private Registries
Token-based authentication for private npm registries and custom CDN endpoints
Cross-Platform
Works in Node.js (disk + memory cache) and browser environments (memory-only cache)
Quick Start
App.esm() method creates an app entry that loads the npm package @acme/mcp-tools (any version matching ^1.0.0) at server startup. Its tools, resources, and prompts are automatically registered.
How It Works
1
Parse Specifier
App.esm('@acme/tools@^1.0.0') parses the npm package specifier into scope, name, and semver range.2
Resolve Version
The npm registry is queried to resolve the semver range (e.g.,
^1.0.0) to a concrete version (e.g., 1.2.3).3
Check Cache
The two-tier cache is checked — first in-memory, then disk. On a hit, the cached bundle is used directly.
4
Fetch Bundle
On a cache miss, the bundle is fetched from the esm.sh CDN (or a custom loader URL) and stored in both cache tiers.
5
Evaluate Module
The bundle is evaluated (ESM via native
import(), CJS via bridge wrapper) and the default export is extracted.6
Normalize Manifest
The export is normalized into a
FrontMcpPackageManifest — supporting plain objects, decorated classes, or named exports.7
Register Primitives
Tools, resources, and prompts from the manifest are registered into standard registries with full hook and lifecycle support.
App.esm() API
Parameters
string
required
npm package specifier in the format
@scope/name@range or name@range.
The range defaults to latest if omitted.Options
string
Override the auto-derived app name (defaults to the package’s full name).
string
Prefix for all tools, resources, and prompts from this package. For example,
namespace: 'acme' turns a tool named echo into acme:echo.string
Human-readable description for the app entry.
boolean | 'includeInParent'
default:"false"
Isolation mode.
true creates a separate scope; 'includeInParent' lists it under the parent while keeping isolation.PackageLoader
Per-app loader override. Takes precedence over the gateway-level
loader. See Gateway-Level Loader for fields.{ enabled: boolean; intervalMs?: number }
Enable background version polling. When a new version matching the semver range is published, the package is automatically reloaded. Default interval: 300,000ms (5 minutes).
number
Local cache time-to-live in milliseconds. Default: 86,400,000ms (24 hours).
Record<string, string>
Import map overrides for ESM resolution. Maps package names to alternative URLs.
AppFilterConfig
Include/exclude filter for selectively importing primitives. See Per-Primitive Loading for details.
Examples
Package Specifier Format
Supported semver ranges include
^1.0.0, ~1.0.0, >=1.0.0 <2.0.0, exact versions like 1.2.3, and dist-tags like latest, next, beta.
Gateway-Level Loader
Set a default loader at the server level that applies to all npm apps:loader option in App.esm().
PackageLoader Fields
When
url is set but registryUrl is not, both the registry API and bundle downloads use url. When registryUrl is also set, the registry uses registryUrl while bundles use url.Auto-Update & Hot-Reload
Enable background version polling to automatically reload packages when new versions are published:- The new bundle is fetched and cached
- All tools, resources, and prompts from the old version are unregistered
- The new manifest is registered
- MCP clients receive
tools/list_changedandresources/list_changednotifications
CLI Update
You can also check for and apply updates via the CLI:Caching
ESM packages use a two-tier cache for fast startup and offline resilience:Cache Locations (Node.js)
- Project mode:
node_modules/.cache/frontmcp-esm/(relative to project root) - CLI mode:
~/.frontmcp/esm-cache/(user home directory)
.mjs or .cjs) and a meta.json with version, etag, and timestamp.
Cache TTL
Configure how long cached bundles remain valid:Browser Mode
In browser environments, only the in-memory cache is used. Bundles are evaluated viaBlob + URL.createObjectURL and stored in a Map. See Browser Compatibility for details.
Private Registry Authentication
Authorization header for both registry API calls (version resolution) and bundle fetching.
Comparison: ESM vs Remote vs Local Apps
Error Handling
ESM loading can fail at several stages. FrontMCP provides specific error classes for each:
See the ESM Errors reference for full details.
Best Practices
Do:- Use
namespaceto avoid naming conflicts between packages - Use
tokenEnvVarinstead of inline tokens for private registries - Set
autoUpdatewith conservative intervals in production (5+ minutes) - Use
cacheTTLto balance freshness vs. startup speed - Test packages locally before deploying to production
- Commit registry tokens in source code
- Use very short polling intervals in production (avoids registry rate limits)
- Load untrusted packages without reviewing their manifest and code
- Rely on ESM packages for plugins or adapters (use local
@Appclasses instead)
Next Steps
Publishing ESM Packages
Create and publish npm packages loadable by FrontMCP servers
CLI Reference
Use
frontmcp package esm-update to manage ESM packagesApps
Full guide to local and remote app configuration
ESM Errors
Error classes for ESM loading, caching, and authentication