Skip to main content
ESM Packages let you dynamically load npm packages at runtime and register their tools, resources, and prompts into your FrontMCP server. Unlike Remote Apps which proxy requests to external MCP servers over HTTP, ESM packages are fetched from a CDN, cached locally, and executed in-process — giving you the same performance as local apps with the flexibility of npm distribution.

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

The 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:
Individual apps can override the gateway loader via the 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:
When a new version matching the semver range is detected:
  1. The new bundle is fetched and cached
  2. All tools, resources, and prompts from the old version are unregistered
  3. The new manifest is registered
  4. MCP clients receive tools/list_changed and resources/list_changed notifications
In production, use conservative polling intervals (5+ minutes) to avoid excessive registry traffic. The default interval is 5 minutes (300,000ms).

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)
Each cached package is stored as a hashed directory containing the bundle file (.mjs or .cjs) and a meta.json with version, etag, and timestamp.

Cache TTL

Configure how long cached bundles remain valid:
Default TTL is 24 hours (86,400,000ms). After expiry, the next load triggers a fresh fetch.

Browser Mode

In browser environments, only the in-memory cache is used. Bundles are evaluated via Blob + URL.createObjectURL and stored in a Map. See Browser Compatibility for details.

Private Registry Authentication

Never commit tokens directly in source code. Use tokenEnvVar to reference environment variables instead.
The Bearer token is sent in the 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 namespace to avoid naming conflicts between packages
  • Use tokenEnvVar instead of inline tokens for private registries
  • Set autoUpdate with conservative intervals in production (5+ minutes)
  • Use cacheTTL to balance freshness vs. startup speed
  • Test packages locally before deploying to production
Don’t:
  • 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 @App classes 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 packages

Apps

Full guide to local and remote app configuration

ESM Errors

Error classes for ESM loading, caching, and authentication