Specs and editions
tc39-mcp ships with parsed snapshots of every release tag the upstream tc39/ecma262 and tc39/ecma402 repos cut, plus the working drafts on main. Aliases like latest rebind across releases (and across specs) so callers don't have to think about it.
Every tool accepts two orthogonal arguments: spec (one of "262" / "402") and edition. The parsed-spec cache is keyed on the pair, so { spec: "262", edition: "es2025" } and { spec: "262", edition: "latest" } share one in-memory copy.
ECMA-262 editions
| Edition | tc39/ecma262 ref | Approximate published date |
|---|---|---|
es2016 | tag es2016 | June 2016 |
es2017 | tag es2017 | June 2017 |
es2018 | tag es2018 | June 2018 |
es2019 | tag es2019 | June 2019 |
es2020 | tag es2020 | June 2020 |
es2021 | tag es2021 | June 2021 |
es2022 | tag es2022 | June 2022 |
es2023 | tag es2023 | June 2023 |
es2024 | tag es2024 | June 2024 |
es2025 | tag es2025 | June 2025 |
main | branch main | rolling draft |
ECMA-402 editions
tc39/ecma402 doesn't tag annual releases the way tc39/ecma262 does. The only refs the upstream repo publishes are a small set of esYYYY-candidate-* tags (release candidates that go to ECMA GA) plus main. We expose the most recent candidate plus main; the rest of the time the right answer is main, which tracks the current draft.
| Edition | tc39/ecma402 ref | Notes |
|---|---|---|
es2025-candidate | tag es2025-candidate-2025-04-01 | 12th-edition release candidate |
main | branch main | rolling draft |
The asymmetry vs ECMA-262 is honest: upstream simply doesn't publish annual esYYYY final tags for 402. If you need a specific pinned revision, use the candidate; otherwise stay on main.
Aliases
| Alias | Resolves to (262) | Resolves to (402) | Stability |
|---|---|---|---|
latest | current stable release (es2025) | main | rebinds when next ES tags (262); stable on 402 |
draft | main | main | tracks upstream HEAD |
next | main | main | synonym for draft |
Aliases are resolved by resolveEdition(spec, e) in src/editions.ts. latest is spec-aware: on 262 it points at the most recent stable annual release, on 402 it points at main because there is no annual final tag.
Why the 262 floor is es2016
The earliest tag in tc39/ecma262 is es2016. There is no upstream support for older editions:
- ES5 (2009) / ES5.1 (2011) predate the GitHub repo and the modern ecmarkup HTML format entirely. They exist as ECMA standards documents (Word / PDF) at https://ecma-international.org. Supporting them here would require a different parser pipeline and scraping a non-canonical source. Out of scope.
- ES6 / ES2015 was authored in
tc39/ecma262(it's what motivated the move to GitHub + ecmarkup), but the repo doesn't carry anes2015ores6git tag. You could pin a commit close to the ES2015 publication date (June 2015) by hand if you needed it, but that's an unofficial pin, not an upstream-blessed one.
If you genuinely need older editions, file an issue describing the use case — we'll either provide a hand-pin recipe or be honest that the data isn't structured enough for this server's contract.
Adding the next ES release (ECMA-262)
When tc39/ecma262 cuts the next release tag (e.g. es2026 in June 2026):
// src/editions.ts
export const RELEASED_262_EDITIONS = [
...existing...,
"es2026", // ← add
] as const;
export const LATEST_262_RELEASE: Released262Edition = "es2026"; // ← bumpThen:
npm run fetch-spec # picks up es2026 via the default $EDITIONS_262
npm run parse # writes build/spec-262-es2026.json
npm test # confirm nothing brokeNo other code changes are required. Every tool's schema picks up the new value through EDITION_VALUES; the latest alias on spec: "262" automatically rebinds; spec.diff between any two editions just works.
For deployment, ship the new build/spec-262-es2026.json to wherever your deployment reads from (R2, KV, baked-in, etc.) and the hosted server serves it without code change.
Adding a new ECMA-402 candidate
When tc39/ecma402 cuts a new esYYYY-candidate-* tag:
// src/editions.ts
export const RELEASED_402_EDITIONS = [
...existing...,
"es2026-candidate", // ← add
] as const;
// also list it in CONCRETE_EDITIONS belowThen:
npm run fetch-spec # picks up the new candidate via $EDITIONS_402
npm run parse # writes build/spec-402-es2026-candidate.json
npm testfetch-spec.sh maps each upstream esYYYY-candidate-* tag to a short local name (esYYYY-candidate); if upstream cuts multiple candidates for one edition you'll want to pick which one your short name maps to.
Tracking specific SHAs
Each parsed JSON carries a pin: { spec, edition, sha } field recording the exact upstream commit. clause.get doesn't surface this to the client by default, but it's available via loadSpec(spec, edition).pin in library code if you need to embed reproducibility metadata in your own output.
For citation use cases, the SHA of any (spec, edition) is recorded in vendor/PINNED.txt after npm run fetch-spec:
fetched: 2026-05-30T10:48:45Z
ecma262 es2016 ref: es2016
ecma262 es2016 SHA: b154ce84698377ab53fe88c889633263607f4423
ecma262 es2017 ref: es2017
ecma262 es2017 SHA: 7301daf5ab1f0959b203c2e63ecccb21fe13d5e5
...
ecma262 es2025 ref: es2025
ecma262 es2025 SHA: 84b38ad852ff426795fa29cebc06949027336c64
ecma262 main ref: main
ecma262 main SHA: <upstream HEAD at fetch time>
ecma402 es2025-candidate ref: es2025-candidate-2025-04-01
ecma402 es2025-candidate SHA: <pinned candidate SHA>
ecma402 main ref: main
ecma402 main SHA: <upstream HEAD at fetch time>Drift between fetches
The main branches move; everything else is stable. If you fetch and re-parse periodically, the es2016 – es2025 (262) and es2025-candidate (402) parses will be bit-identical run-to-run, but spec-262-main.json and spec-402-main.json will reflect whatever's upstream at fetch time. Pin main's SHA explicitly if you need reproducibility against the draft.