ES module imports

Complete reference for the --allow-external-modules flag, supported import specifier forms, and the modules policy.

Server flag

--allow-external-modules

Property Value
Type boolean flag (no value argument)
Default false
Source server/src/cli.rs

Enables resolution and fetching of external ES modules. When absent (the default) any import specifier that resolves to an external URL — npm:, jsr:, https://, or http:// — is rejected at the resolve step with an error of the form:

External module imports are disabled. Cannot import npm package 'npm:uuid'.
Start the server with --allow-external-modules to enable.

No network traffic is attempted. Relative specifiers (./foo.js) are not affected by this flag.

Supported import specifier forms

Specifier form allow_external gate Resolved URL
npm:<package>[@version][/path] yes https://esm.sh/<package>[@version][/path]
jsr:<scope>/<package>[@version][/path] yes https://esm.sh/jsr/<scope>/<package>[@version][/path]
https://<host>/… yes used as-is
http://<host>/… yes used as-is
./relative or ../relative no resolved against referrer URL

Only https and http schemes are loadable. Attempts to load from any other scheme (e.g. file://) fail with:

Cannot load module '<url>': only https/http modules are supported

TypeScript modules

If the final response URL (after redirects) has a path ending in .ts or .tsx, the response body is transpiled with the same swc-based transpiler used for inline code before being evaluated as JavaScript.

Fetch timeouts

Parameter Value
Connect timeout 10 seconds
Total request timeout 30 seconds

These values are compile-time constants (MODULE_FETCH_CONNECT_TIMEOUT, MODULE_FETCH_TIMEOUT in engine/module_loader.rs).

Modules policy

Entrypoint

data.mcp.modules.allow

Configuration

Supplied via --policies-json under the modules key:

{
  "modules": {
    "policies": [
      { "url": "file:///path/to/policies/modules.rego" }
    ]
  }
}

Or with a remote OPA server:

{
  "modules": {
    "policies": [
      { "url": "http://opa:8181", "policy_path": "mcp/modules" }
    ]
  }
}

Multiple entries in policies form a chain; all must return true for the import to be allowed.

Policy input shape

The input document passed to the policy on every module load:

Field Type Description
specifier string Fully resolved URL of the module being loaded
specifier_type string "npm", "jsr", or "url" (see derivation below)
resolved_url string Same as specifier
url_parsed.scheme string URL scheme, e.g. "https"
url_parsed.host string Host of the resolved URL, e.g. "esm.sh"
url_parsed.path string Path component, e.g. "/lodash-es"

specifier_type derivation

Condition (evaluated against resolved URL string) specifier_type
URL contains "esm.sh/jsr/" "jsr"
URL contains "esm.sh/" (but not "esm.sh/jsr/") "npm"
All other HTTP/HTTPS URLs "url"

Example policy input

{
  "specifier": "https://esm.sh/lodash-es",
  "specifier_type": "npm",
  "resolved_url": "https://esm.sh/lodash-es",
  "url_parsed": {
    "scheme": "https",
    "host": "esm.sh",
    "path": "/lodash-es"
  }
}

Policy evaluation timing

The policy is evaluated inside the load phase, after allow_external has been confirmed true but before the HTTP GET request is sent. A false or undefined result from the policy entrypoint returns the following error to the executing code:

Module import denied by policy: '<url>' is not allowed by the module policy

Example policy

See policies/modules.rego in the repository for a complete allowlist example covering npm:, jsr:, URL hosts, and wildcard host suffixes.

Interaction with --allow-external-modules

The allow_external flag and the modules policy are independent controls:

--allow-external-modules policy configured Result
absent (false) any all external imports blocked at resolve
present absent all resolved URLs are loaded without policy check
present present URLs are loaded only if policy returns true

See also