rfc-extract
v0.3.0
Published
Turn an IETF RFC into a markdown knowledge base
Maintainers
Readme
rfc-extract
Turns an IETF RFC into a folder of markdown that an agent can grep and a human can read.
rfc-extract 9421~/.local/share/rfc-kb/rfc9421/
README.md title, abstract, source tier, navigation
meta.json number, date, authors, sha256, tier, extraction date
sections/ one file per top-level section, with frontmatter
requirements.md every BCP 14 paragraph, grouped by section
requirements.json the same records, machine-readable
abnf/ grammar this RFC defines
examples/ one file per code block, typed extension
references.md normative and informative, with citing sections
iana.md what the RFC asks IANA to register
errata.md published errata with status
notes/ yours, never touched by the toolThe derived views are the useful part. requirements.json is the conformance checklist you would
otherwise build by hand, every example is a file you can pipe into a test, and each paragraph keeps
the RFC's own anchor, so req-2.1-17 locates its own source in the published document.
To let an agent read the result over MCP, see rfc-mcp.
Install
Needs Node 22 or newer.
npm install -g rfc-extractOr without installing:
npx rfc-extract 9421From a clone:
git clone https://github.com/cozminu/rfc-tools.git
cd rfc-tools
pnpm install
pnpm build
node packages/rfc-extract/dist/cli.js 9421CLI
rfc-extract <rfc-number | rfcNNNN | url> [options]All three of 9421, rfc9421, and https://www.rfc-editor.org/rfc/rfc9421.html resolve to the
same RFC.
| Flag | Effect |
| --- | --- |
| --out <dir> | output root, default ~/.local/share/rfc-kb (see below) |
| --no-errata | skip the errata dump |
| --refresh | ignore the HTTP cache |
| --json | machine-readable run summary on stdout |
| --help | print usage |
| Exit code | Meaning | | --- | --- | | 0 | success | | 1 | unsupported source tier (a legacy RFC) | | 2 | network or fetch failure, or bad arguments | | 3 | parse failure | | 4 | emit failure |
The KB is rebuilt in place and is deterministic for the same input, so a committed KB shows only
real changes in git. notes/ survives every re-run.
Where things go
A knowledge base is user-level data, not a property of the directory you ran from, so both defaults live under your home directory and are shared by every project.
| What | Default | Overrides, in order |
| --- | --- | --- |
| the KB | ~/.local/share/rfc-kb | --out, then $RFC_KB_ROOT, then $XDG_DATA_HOME/rfc-kb |
| fetched source | ~/.cache/rfc-extract | $RFC_CACHE_DIR, then $XDG_CACHE_HOME/rfc-extract |
Pass --out ./kb if you want a KB committed alongside a project instead.
Source is cached with conditional GETs, so re-runs are cheap and mostly offline. The cache is disposable; deleting it costs you one refetch.
Search
To find an RFC number when you don't know it:
rfc-extract search message signatures9421 HTTP Message Signatures
PROPOSED STANDARD, IETF, httpbis, February 2024
This document describes a mechanism for creating, encoding, and verifying
digital signatures or message authentication codes over components of an
1 of 1 results. Index confirmed 2026-08-03T09:14:22.104Z.| Flag | Effect |
| --- | --- |
| --status <s> | filter by current status |
| --stream <s> | filter by stream (IETF, IAB, IRTF, Independent, Legacy) |
| --wg <w> | filter by working group acronym |
| --since <year> | published in or after <year>, inclusive |
| --until <year> | published in or before <year>, inclusive |
| --current | drop RFCs that have been obsoleted |
| --limit <n> | maximum hits to print, default 20 |
| --refresh | ignore the HTTP cache |
| --json | machine-readable result on stdout |
Search reads the RFC Editor's rfc-index.xml through the same cache as everything else, so it
revalidates on every run and falls back to the copy on disk when you're offline. Matching is
lexical: terms are weighted by whether they land in the title, keywords, authors or abstract, and
every term has to match. It is not semantic, so "signing" won't find "signatures" and a query
naming a concept will do worse than one naming words that appear in the title. Zero results exits 0.
API
One entry point, for rfc-mcp and anyone else who wants the pipeline in process:
import { extractRfc, type ExtractOptions, type RunSummary } from "rfc-extract";
const summary: RunSummary = await extractRfc("9421");Without an out it writes to the same default the CLI uses. Pass one to put it elsewhere.
defaultKbRoot(), defaultCacheDir(), and expandTilde() are exported too, so a front end can
resolve the same paths without duplicating the rules. Requirement and SourceTier are exported as
types. Nothing else is public, and the exports map blocks deep imports.
What it extracts
Requirements are paragraph-scoped: one record per paragraph containing at least one BCP 14 keyword, not one per keyword. Sentence-splitting RFC prose is where these tools mangle quotes ("e.g.", "Section 2.1.", "[RFC8941]"), so a dense paragraph yields one record listing several keywords and no requirement is ever truncated. The RFC 2119/8174 boilerplate paragraph is excluded.
ABNF comes from sourcecode type="abnf" where it exists. Untyped artwork gets sniffed and
marked as heuristic in a comment header, never merged as if it were authoritative. grammar.abnf is
what the RFC defines, not a complete parseable grammar: RFC 9421 has one block because it imports
the rest from RFC 8941.
Examples are every non-ABNF code block, one file each, extension from the declared type
(http-message becomes .http, json becomes .json, untyped becomes .txt).
IANA is the IANA Considerations section with its tables pulled out, plus a link to the live registry. It's what the RFC asks for, not a registry fetch, and the file says so.
Errata come from the RFC Editor's full JSON dump, filtered by number, because there's no per-RFC
endpoint. First fetch is chunky (around 11 MB) and then cached. Each entry keeps its status, since
Verified and Rejected mean very different things. If the fetch fails, the run warns and continues,
and errata.md says errata were unavailable rather than implying there are none.
IDs derive from the RFC's own paragraph numbers (pn="section-2.1-12" becomes req-2.1-12).
Published RFCs are immutable, so the same input always produces the same ID, and the ID points at
its own source.
Source tiers
Three tiers. meta.json and the generated README always record which one produced the KB.
| Tier | Source | Status |
| --- | --- | --- |
| rfcxml | /rfc/rfcNNNN.xml | full fidelity, preferred |
| html-modern | rendered HTML with <section> and class="bcp14", roughly 2019 onward | near parity |
| html-legacy | pre-2019 text inside <pre> | not supported, exits 1 |
Legacy RFCs are refused on purpose. That markup carries no section structure and no requirement
markers, and a half-parsed 1999 RFC that looks confident is worse than a refusal. A .xml returning
404 isn't an error, it's the fallback to HTML.
Limitations
- Pre-2019 RFCs aren't supported. RFC 2616 exits 1 and points you at the
.txt. - Normative references are listed, not followed. Extracting a referenced RFC is a separate run.
- Search is literal. There's no embedding or semantic index.
- IANA output is what the RFC specifies, not the current state of the live registry.
- Internet-Drafts and non-rfc-editor sources aren't handled.
Format
The emitted tree is documented in docs/kb-format.md. Unstable until
1.0.
Changelog
CHANGELOG.md, at the repo root, covers both packages.
License
MIT, for this code.
Not for what it emits. RFCs are copyrighted, and a knowledge base rearranges an RFC's text across a
few hundred files, which makes it a derivative work rather than the full unmodified copy that the
IETF Trust Legal Provisions plainly permit you to
redistribute. Every emitted KB carries the source RFC's copyright notice in its README.md and
meta.json. abnf/ and examples/ are code components under Section 4 of the TLP, licensed
Revised BSD, and each of those directories gets a LICENSE.
Extracting an RFC to read it is not the case those terms restrict. Publishing the result is a different question, and this tool doesn't answer it for you.
rfc-extract is an independent tool. It is not affiliated with, endorsed by, or sponsored by the
IETF, the IETF Trust, or the RFC Editor.
