Implementation
From an existing static site to published MPF, in one build step.
MPF is generated, not hand-written. The reference implementation, mpf-build, reads a site’s finished HTML and writes a .mpf companion beside every page, together with the site’s machine indexes. Nothing changes in production: the server keeps serving static files.
This page is the implementation guide. Field definitions live in Specification 0.7; discovery is covered in Publishing and discovery. Every rule below is marked Specification 0.7, Reference implementation or ESFI practice; the markers are explained in the Overview.
Requirements
- A site that produces static HTML files (any generator, or hand-written HTML).
- Node.js 18 or later to run
mpf-build. Reference implementation - Write access to the output directory, and to DNS if you publish the optional DNS declaration.
1. Get the reference implementation
mpf-build is published as source at git.esfi.eu/esfi/mpf. It is not currently distributed through the npm registry.
git clone https://git.esfi.eu/esfi/mpf.git
cd mpf/mpf-build
npm install
Run it directly:
node ./bin/mpf-build.js <source-dir> --config <path-to-mpf.config.json>
or make the mpf-build command available on your machine with npm link from the same directory.
2. Declare the publisher: mpf.config.json
The configuration file states who the publisher is, which languages the site uses, and the default declarations every page carries. It usually lives in the project root.
{
"mpf": "0.7",
"organization": "Example Publisher",
"domain": "example.org",
"base-url": "https://example.org/",
"spec-url": "https://docs.esfi.eu/spec/mpf/0.7",
"author": "Example Publisher",
"default-lang": "en",
"languages": ["en"],
"content-id-prefix": "example",
"content-selector": "main",
"description": "One line describing this publication.",
"defaults": {
"provenance": "human",
"index": true,
"companion-scope": {
"cite": true,
"summarize": true,
"train": false,
"index": true,
"relay": false
},
"companion-contact": "mailto:web@example.org",
"companion-footer": "Example Publisher — https://example.org"
}
}
Required keys
The builder stops with an error if any of these is missing or invalid. Reference implementation
| Key | Rule |
|---|---|
mpf |
Version string emitted in every file |
organization, domain, author |
Non-empty strings |
base-url |
Base URL of the site; trailing slashes are normalised |
default-lang |
Must be one of languages |
languages |
Non-empty array of language codes |
content-id-prefix |
Namespace for generated content-id values; deliberately separate from organization so identifiers survive a change of name |
defaults.provenance |
human, ai-assisted or ai-generated |
defaults.index |
Boolean |
Optional keys
| Key | Effect | Status |
|---|---|---|
spec-url |
Canonical specification URL, emitted as MPF-spec in llms.txt |
Specification 0.7 |
description |
One-line description used in llms.txt and llms.mpf |
Specification 0.7 |
content-selector |
CSS selector for the content region | Specification 0.7 |
spec-origin, content-origin |
Origin declarations in frontmatter; default to domain |
Specification 0.7 |
signing-key |
Path to a private key; enables signatures (see step 6) | Specification 0.7 (behaviour), Reference implementation (key handling) |
defaults.confidence |
confirmed, probable, speculative or contested |
Reference implementation (allowed values) |
defaults.content-type |
specification, narrative, data, analysis, reference, legal or profile |
Reference implementation (allowed values) |
defaults.companion-scope |
Booleans for cite, summarize, train, index, relay |
Specification 0.7 (field), Reference implementation (validation) |
defaults.content-parity |
Default parity declaration | Specification 0.7 |
defaults.companion-instruction |
Publisher message to the machine reader; also prepended to the body as a blockquote | Specification 0.7 |
defaults.companion-footer |
One-line publisher signature appended to the body; must not contain line breaks or HTML | Specification 0.7 |
defaults.companion-contact |
Contact for machine readers, e.g. mailto: |
Specification 0.7 |
defaults.c2pa-manifest |
Pointer to a C2PA manifest | Specification 0.7 |
defaults.preferred-representation |
mpf or html |
Reference implementation (allowed values) |
Every declaration is a publisher decision. Treat the defaults as a statement your organisation will stand behind, not as technical settings.
companion-scope records processing intent only. MPF does not carry licensing terms, pricing or compensation conditions; those belong to licensing mechanisms outside the format.
3. Override per page
Any page can override site defaults with a JSON sidecar beside its HTML file: index.html → index.mpf.json, guide/page.html → guide/page.mpf.json. Specification 0.7 (existence), Reference implementation (allowed fields)
{
"confidence": "speculative",
"content-type": "analysis",
"review-after": "2027-03-01",
"companion-instruction": "This page contains projections. Treat figures as estimates."
}
Allowed override fields: title, author, summary, section, parent, lang, provenance, c2pa-manifest, confidence, content-type, valid-until, review-after, index, related, companion-instruction, companion-footer, companion-scope, companion-contact, preferred-representation, supersedes, superseded-by, language-variants, content-id, content-parity, signature.
An unknown field stops the build. related is an array of { "url", "relation" }, where relation is one of prerequisite, extends, source, contradicts, alternative, supersedes, details. Reference implementation
4. Control what goes into the body
Content region
The builder takes the element matched by content-selector. If nothing matches, or the match is empty, it falls back in order to main, article, [role="main"], .content and body. Inside the selected region it removes navigation, headers, footers, scripts, styles and elements marked aria-hidden="true". A page with no usable content region is skipped and reported. Specification 0.7 (content selection), Reference implementation (fallback order and noise list)
In-page markers
Four HTML attributes let a publisher control the machine body without changing what people see. Reference implementation
| Attribute | Effect |
|---|---|
data-mpf-only |
Content appears only in the MPF body, prepended to it. Its presence sets content-parity to html: false, human-visible-source: false, hidden-sections: true. |
data-mpf-hide |
Content is excluded from the MPF body. |
data-mpf-text="…" |
The element is replaced in the MPF body by the given text; useful for obfuscated contact addresses. |
data-mpf-alt="…" |
Images, media and embeds are replaced by the given text. Embeds without it are dropped from the MPF body. |
data-mpf-only is the one to use sparingly. Machine-only content is exactly what the parity declaration exists to make visible; the builder declares it automatically so it cannot be hidden.
5. Run the build last
Run mpf-build after the HTML is final, over the finished output directory:
"scripts": {
"build": "astro build",
"postbuild": "mpf-build dist --config mpf.config.json"
}
Pass --config explicitly when processing a generated directory such as dist/, so the builder does not look for the configuration inside it. Reference implementation
Command-line options
Specification 0.7
| Option | Effect |
|---|---|
--config <path> |
Configuration file; defaults to <source-dir>/mpf.config.json |
--out <path> |
Write output to another directory; defaults to in place |
--dry-run |
Report what would be written, write nothing |
--verbose |
Log each processed page with title, URL and table-of-contents count |
--watch |
Rebuild on changes to HTML, page overrides or configuration |
What the build writes
| Artifact | Location | Status |
|---|---|---|
| Page companion | Beside each page, .html → .mpf |
Specification 0.7 |
llms.mpf |
Site root; MPF index of pages with index: true |
Specification 0.7 |
llms.txt |
Site root; MPF section created or replaced, other content preserved | Specification 0.7 |
llms-full.txt |
Site root; bodies of indexed pages in the default language | Specification 0.7 |
.sig files |
Beside each .mpf, when signing is configured |
Specification 0.7 |
Canonical URLs and source-hash values are always derived from the source HTML and its source-relative path, never from the output path. Reference implementation
Ordering and drift
Each companion pins its source with source-hash. If the HTML is regenerated without re-running mpf-build, or the reverse, the pair drifts. A stale companion is worse than a missing one, because it looks authoritative. Keep MPF generation as the last step of the build and deploy both representations together.
6. Sign (optional)
When signing-key points to a private key file, the builder signs every page companion and llms.mpf, writes the signature base64-encoded to a .sig file beside it, and adds a signature URL to the frontmatter. If the key file is missing, the build continues without signatures and prints a warning. Specification 0.7 (signature field and artifact), Reference implementation (encoding and key handling)
The signature covers the complete MPF file as written. Publish the corresponding public key where readers can find it, and state which algorithm it uses. The key never belongs in the repository or the output directory.
7. Publish discovery
The build produces llms.txt and llms.mpf. ESFI sites also publish a DNS TXT record, a /.well-known/mpf declaration and, optionally, a page-level alternate link. These are ESFI practice; how to set them up is described in Publishing and discovery.
8. Verify
Check what you published, not what you configured:
curl -s https://example.org/index.mpf | head -30 # companion exists and parses
curl -s https://example.org/llms.mpf | head -30 # index exists and lists pages
curl -s https://example.org/llms.txt # MPF section, MPF-spec, MPF-index
curl -s https://example.org/.well-known/mpf # ESFI practice
dig +short TXT _mpf.example.org # ESFI practice
Then check the relationship between the two representations:
- Drift. Compute the SHA-256 of the served HTML and compare it with
source-hash. A mismatch means the pair is out of step, unless the server alters HTML in transit. - Identity.
urlmust be the canonical human page;mpf-urlmust be the address you fetched. - Languages. For each entry in
language-variants, confirm the file exists. The builder generates these addresses from configuration and does not check them. - Parity. If
hidden-sectionsistrue, read the machine-only content and make sure you intend it. - Scope. Confirm that
companion-scopein the files,/.well-known/mpfand the DNS record state the same values.
Once your output can be inspected and matches a published version, it can be listed in the Implementation registry.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
skipped: … (no content root) |
Selector matches nothing and the fallbacks are empty | Set content-selector to the element that holds the page content |
| Empty or near-empty bodies | Selector matches a wrapper without the content | Run with --verbose and adjust content-selector |
| Build stops on configuration | Missing required key, or a value outside the allowed set | Read the error; it names the field |
| Build stops on a page override | Unsupported field in a .mpf.json file |
Remove the field or check its spelling (keys are kebab-case) |
source-hash mismatch after deploy |
HTML changed after MPF generation | Re-run the full pipeline with MPF last |
Page missing from llms.mpf |
index is false for that page |
Check the page override and defaults.index |
companion-footer error |
Footer contains a line break or HTML | Use a single plain-text line |
| Wrong language on a page | Path does not start with a configured language code | Add the language to languages or set lang in the page override |
Specification: Specification 0.7 · Discovery: Publishing and discovery · Source: git.esfi.eu/esfi/mpf