App.esm(). By the end, you’ll have a published package with tools, resources, and prompts that any FrontMCP server can consume.
Prerequisites
- Node.js 24+ and npm
- An npm account (or access to a private registry)
- Familiarity with FrontMCP tools, resources, and prompts
The Package Manifest Contract
Every ESM package must export aFrontMcpPackageManifest — an object declaring what the package provides:
Step 1: Scaffold the Package
Step 2: Create Tools
FrontMCP ESM packages support two styles for defining tools:Plain Object Contract
When using plain objects, each tool must have:| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tool name |
description | string | No | Human-readable description |
inputSchema | object | No | JSON Schema for input validation |
execute | function | Yes | (input) => Promise<CallToolResult> |
execute function receives the parsed input and must return a CallToolResult with a content array.
Step 3: Create Resources & Prompts
Resource Contract
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Resource name |
description | string | No | Human-readable description |
uri | string | Yes | Resource URI (e.g., my-tools://status) |
mimeType | string | No | MIME type of the resource content |
read | function | Yes | () => Promise<ReadResourceResult> |
Prompt Contract
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Prompt name |
description | string | No | Human-readable description |
arguments | array | No | Array of { name, description?, required? } |
execute | function | Yes | (args) => Promise<GetPromptResult> |
Step 4: Export the Manifest
The package must export aFrontMcpPackageManifest. Three export formats are supported:
Step 5: Configure package.json
Declare
@frontmcp/sdk and zod as peer dependencies, not regular dependencies. The consuming server provides these at runtime. This keeps bundle sizes small and avoids version conflicts.Step 6: Build & Publish
Step 7: Consume with App.esm()
Once published, any FrontMCP server can load your package:acme:echo, acme:add, and acme:status at startup.
Manifest Normalization
How FrontMCP normalizes your package export
How FrontMCP normalizes your package export
FrontMCP’s
normalizeEsmExport() function tries three paths in order:- Default export as manifest — Checks if
module.defaulthasnameandversionfields. If so, validates against the Zod schema. - Default export as decorated class — Checks if
module.defaultis a class withfrontmcp:typereflect-metadata. If so, extracts the@FrontMcpconfiguration. - Named exports — Scans the module for named exports matching manifest primitive keys (
tools,prompts,resources, etc.) and assembles them into a manifest.
EsmManifestInvalidError is thrown.Best Practices
Use descriptive tool names and descriptions
Use descriptive tool names and descriptions
Tools loaded from ESM packages are often namespaced (e.g.,
acme:echo). Include clear descriptions so AI models understand what each tool does without seeing the source code.Declare peer dependencies, don't bundle them
Declare peer dependencies, don't bundle them
@frontmcp/sdk and zod should be peer dependencies. Bundling them causes version conflicts and inflates package size.Include both ESM and CJS outputs
Include both ESM and CJS outputs
Use
tsup or a similar tool to produce both .mjs and .cjs outputs. FrontMCP handles both formats, but dual output maximizes compatibility.Follow semver strictly
Follow semver strictly
ESM consumers use semver ranges (
^1.0.0, ~2.1.0). Breaking changes should bump the major version. New features bump minor. Bug fixes bump patch.Test locally before publishing
Test locally before publishing
Set up a local ESM server or use
App.esm() with a file path during development to verify your manifest is correct before publishing.Troubleshooting
Package loads but no tools are registered
Package loads but no tools are registered
Verify your default export has
name, version, and a tools array. Check that each tool object has at least name and execute fields. Enable debug logging on the server to see manifest normalization output.Version resolution fails
Version resolution fails
Check that the package is published and accessible. For private registries, verify the
token or tokenEnvVar is set correctly. Try npm view @your/package versions to confirm the version exists.Module evaluation fails
Module evaluation fails
Ensure your package doesn’t import Node.js-only modules (
fs, crypto, path) at the top level if it needs to work in browsers. Use dynamic imports for platform-specific code.Browser environment errors
Browser environment errors
In browser environments, avoid any file system operations. The ESM cache is memory-only. Modules are evaluated via
Blob URLs, so ensure your code doesn’t rely on __filename or __dirname.Related
ESM Packages Reference
Full reference for App.esm(), caching, auth, and hot-reload
ESM Errors
Error classes for loading, caching, and authentication