rfclib 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. rfclib-0.2.0/.gitignore +20 -0
  2. rfclib-0.2.0/CHANGELOG.md +39 -0
  3. rfclib-0.2.0/LICENSE +21 -0
  4. rfclib-0.2.0/NOTICE +24 -0
  5. rfclib-0.2.0/PKG-INFO +511 -0
  6. rfclib-0.2.0/README.md +465 -0
  7. rfclib-0.2.0/docs/retrieval-design.md +350 -0
  8. rfclib-0.2.0/pyproject.toml +88 -0
  9. rfclib-0.2.0/rfclib/__init__.py +3 -0
  10. rfclib-0.2.0/rfclib/__main__.py +6 -0
  11. rfclib-0.2.0/rfclib/cite.py +204 -0
  12. rfclib-0.2.0/rfclib/cli.py +848 -0
  13. rfclib-0.2.0/rfclib/config.py +119 -0
  14. rfclib-0.2.0/rfclib/errata.py +203 -0
  15. rfclib-0.2.0/rfclib/fsutil.py +82 -0
  16. rfclib-0.2.0/rfclib/graph.py +213 -0
  17. rfclib-0.2.0/rfclib/index/__init__.py +1 -0
  18. rfclib-0.2.0/rfclib/index/chunker.py +268 -0
  19. rfclib-0.2.0/rfclib/index/embed.py +166 -0
  20. rfclib-0.2.0/rfclib/index/hybrid.py +305 -0
  21. rfclib-0.2.0/rfclib/index/lexical.py +223 -0
  22. rfclib-0.2.0/rfclib/index/rfcxml.py +673 -0
  23. rfclib-0.2.0/rfclib/index/search.py +496 -0
  24. rfclib-0.2.0/rfclib/index/txtseg.py +516 -0
  25. rfclib-0.2.0/rfclib/library.py +285 -0
  26. rfclib-0.2.0/rfclib/mcp/__init__.py +1 -0
  27. rfclib-0.2.0/rfclib/mcp/server.py +327 -0
  28. rfclib-0.2.0/rfclib/packs/__init__.py +1 -0
  29. rfclib-0.2.0/rfclib/packs/builder.py +228 -0
  30. rfclib-0.2.0/rfclib/packs/collections/all-current-txt.json +10 -0
  31. rfclib-0.2.0/rfclib/packs/collections/essentials.json +19 -0
  32. rfclib-0.2.0/rfclib/packs/collections/fyi-current.json +10 -0
  33. rfclib-0.2.0/rfclib/packs/collections/standards-current.json +10 -0
  34. rfclib-0.2.0/rfclib/packs/collections/year-2025.json +10 -0
  35. rfclib-0.2.0/rfclib/packs/collections/year-2026.json +10 -0
  36. rfclib-0.2.0/rfclib/packs/collections.py +163 -0
  37. rfclib-0.2.0/rfclib/packs/docsetexport.py +163 -0
  38. rfclib-0.2.0/rfclib/packs/epub.py +159 -0
  39. rfclib-0.2.0/rfclib/packs/export.py +306 -0
  40. rfclib-0.2.0/rfclib/packs/htmlexport.py +82 -0
  41. rfclib-0.2.0/rfclib/packs/serve.py +143 -0
  42. rfclib-0.2.0/rfclib/packs/zimexport.py +171 -0
  43. rfclib-0.2.0/rfclib/query.py +466 -0
  44. rfclib-0.2.0/rfclib/subjects.py +448 -0
  45. rfclib-0.2.0/rfclib/sync/__init__.py +1 -0
  46. rfclib-0.2.0/rfclib/sync/downloader.py +129 -0
  47. rfclib-0.2.0/rfclib/sync/manifest.py +163 -0
  48. rfclib-0.2.0/rfclib/sync/scanner.py +49 -0
  49. rfclib-0.2.0/rfclib/sync/state.py +445 -0
  50. rfclib-0.2.0/rfclib/views.py +1050 -0
  51. rfclib-0.2.0/scripts/make_test_fixture.py +79 -0
  52. rfclib-0.2.0/tests/conftest.py +43 -0
  53. rfclib-0.2.0/tests/fixtures/library/README.md +3 -0
  54. rfclib-0.2.0/tests/fixtures/library/errata.json +1997 -0
  55. rfclib-0.2.0/tests/fixtures/library/meta/rfc-index.xml +695 -0
  56. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc2119.txt +171 -0
  57. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc5234.txt +899 -0
  58. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc7405.txt +227 -0
  59. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc768.txt +174 -0
  60. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc8174.txt +227 -0
  61. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc8651.txt +577 -0
  62. rfclib-0.2.0/tests/fixtures/library/mirror/rfc/rfc8651.xml +973 -0
  63. rfclib-0.2.0/tests/test_features.py +40 -0
  64. rfclib-0.2.0/tests/test_lexical.py +48 -0
  65. rfclib-0.2.0/tests/test_library.py +195 -0
@@ -0,0 +1,20 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ data/
8
+ *.db
9
+ *.db-wal
10
+ *.db-shm
11
+
12
+ export-packs/
13
+ export-formats/
14
+ android-reader/local.properties
15
+ android-reader/.gradle/
16
+ android-reader/build/
17
+ android-reader/app/build/
18
+
19
+ # the reader UI lives on the reader-ui branch; ignore local leftovers
20
+ reader-ui/
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (unreleased)
4
+
5
+ First release intended for PyPI
6
+ (`uvx --from "rfclib[mcp,semantic]" rfclib mcp`).
7
+
8
+ - **Optional features.** The core package (CLI, library, keyword search)
9
+ has four dependencies (~9 MB); the MCP server (`rfclib[mcp]`), semantic
10
+ search (`rfclib[semantic]`) and ZIM export (`rfclib[zim]`) are extras,
11
+ loaded lazily and reported by `rfclib status`. The reader UI and the
12
+ Docker images moved to the `reader-ui` branch.
13
+
14
+ - **Library lifecycle.** Data lives in the per-user platform data directory
15
+ (or `$RFCLIB_HOME`), never the working directory. `rfclib init` sets up
16
+ the catalog (RFC index, errata, subject tree) in seconds; each RFC is
17
+ fetched from rfc-editor.org and indexed the first time it is read;
18
+ `rfclib init --content current|all` prefetches everything. `rfclib status`
19
+ reports what the library holds. `RFCLIB_NETWORK=auto|on-demand|offline`
20
+ controls fetching. The library is portable (relative file paths), safe
21
+ to share between processes, and loads its embedding model offline.
22
+ - **MCP server: nine tools** (`search`, `read`, `outline`, `grep`, `cite`,
23
+ `links`, `info`, `browse`, `errata`), compact plain text, one reference
24
+ grammar (`RFC9000§9.6¶2`) that every tool accepts and every page footer
25
+ emits, prompts (`/rfclib:explain`, `checklist`, `current`), an
26
+ `rfc://{ref}` resource template and completions.
27
+ - **Structure.** RFC 8650+ from RFCXML (exact sections and paragraph ids);
28
+ older RFCs segmented from their own Table of Contents (heading recall
29
+ 59% -> 98%). Section-precise citation links for every era.
30
+ - **Search.** Hybrid BM25 + embeddings + document titles, plus an exact
31
+ identifier index (`preferred_address`, `SETTINGS_INITIAL_WINDOW_SIZE`)
32
+ and acronym-aware title matching. Status-aware: obsolete documents are
33
+ flagged with their current replacement.
34
+ - **Errata** from the RFC Editor bulk API, shown per section.
35
+
36
+ ## 0.1.0
37
+
38
+ Initial local library: RFC Editor sync, SQLite metadata and full-text
39
+ index, CLI, reader UI, content packs.
rfclib-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Capella
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
rfclib-0.2.0/NOTICE ADDED
@@ -0,0 +1,24 @@
1
+ rfclib
2
+ Copyright (c) 2026 Justin Capella. Licensed under the MIT License (LICENSE).
3
+
4
+ RFC content is not part of this software's license
5
+ ----------------------------------------------------
6
+ rfclib downloads documents of the RFC Series and their metadata from the
7
+ RFC Editor (https://www.rfc-editor.org) at the user's request and stores
8
+ them locally. Those documents are published by the IETF Trust under the
9
+ IETF Trust Legal Provisions (https://trustee.ietf.org/license-info), not
10
+ under this software's license. In particular, RFCs may be copied and
11
+ distributed in full but not modified outside the IETF Standards Process;
12
+ code components in documents published on or after November 10, 2008 are
13
+ available under the Revised BSD License. Documents published before 1986
14
+ may carry different terms. See the IETF Trust FAQ
15
+ (https://trustee.ietf.org/about/faq).
16
+
17
+ The test fixtures in tests/fixtures/library are unmodified copies of a few
18
+ RFCs and trimmed copies of RFC Editor metadata, included for offline tests
19
+ under the same terms.
20
+
21
+ Subject taxonomy and errata are fetched from the RFC Editor's published
22
+ pages and API (rfc-editor.org/subjects, rfc-editor.org/api/v1/errata.json).
23
+ The embedding model (minishlab/potion-base-8M) is downloaded from Hugging
24
+ Face under its own license when semantic search is first built.
rfclib-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,511 @@
1
+ Metadata-Version: 2.5
2
+ Name: rfclib
3
+ Version: 0.2.0
4
+ Summary: The RFC series as a local library and MCP server: section-precise search, paging by citation, status, errata and citations
5
+ Project-URL: Homepage, https://github.com/b1tninja/rfclib
6
+ Project-URL: Source, https://github.com/b1tninja/rfclib
7
+ Project-URL: Issues, https://github.com/b1tninja/rfclib/issues
8
+ Project-URL: Changelog, https://github.com/b1tninja/rfclib/blob/master/CHANGELOG.md
9
+ Author-email: Justin Capella <justincapella@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ License-File: NOTICE
13
+ Keywords: agents,ietf,llm,mcp,model-context-protocol,rfc,rfc-editor,search,standards
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Topic :: Internet
26
+ Classifier: Topic :: Software Development :: Documentation
27
+ Classifier: Topic :: Text Processing :: Indexing
28
+ Requires-Python: >=3.12
29
+ Requires-Dist: httpx>=0.27
30
+ Requires-Dist: platformdirs>=4.2
31
+ Requires-Dist: rich>=13
32
+ Requires-Dist: typer>=0.12
33
+ Provides-Extra: all
34
+ Requires-Dist: libzim>=3.7; extra == 'all'
35
+ Requires-Dist: mcp>=1.2; extra == 'all'
36
+ Requires-Dist: model2vec>=0.3; extra == 'all'
37
+ Requires-Dist: sqlite-vec; extra == 'all'
38
+ Provides-Extra: mcp
39
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
40
+ Provides-Extra: semantic
41
+ Requires-Dist: model2vec>=0.3; extra == 'semantic'
42
+ Requires-Dist: sqlite-vec; extra == 'semantic'
43
+ Provides-Extra: zim
44
+ Requires-Dist: libzim>=3.7; extra == 'zim'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # rfclib — Local RFC Library + MCP Server
48
+
49
+ A local, self-maintaining library of **every published RFC** (plus BCP/STD/FYI),
50
+ sourced exclusively from the **official RFC Editor infrastructure**
51
+ ([rfc-editor.org](https://www.rfc-editor.org/) — the authoritative publisher of
52
+ the RFC Series for all five streams, operating under the IETF Administration
53
+ LLC; see RFC 9280, RFC 4714, RFC 7841).
54
+
55
+ Everything lives in **one SQLite file** and is exposed to AI agents over **MCP**,
56
+ and to scripts over a CLI. Fully offline after the initial sync: no API keys,
57
+ no external services; embeddings run locally on CPU.
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ uvx rfclib init # catalog: RFC index, errata, subjects (~1 min)
63
+ claude mcp add rfclib -- uvx --from "rfclib[mcp,semantic]" rfclib mcp
64
+ ```
65
+
66
+ The core package is small (CLI, library, keyword search, ~9 MB with its
67
+ dependencies); heavier features are extras:
68
+
69
+ | Extra | Adds | Size |
70
+ |---|---|---|
71
+ | `rfclib[mcp]` | the MCP server (`rfclib mcp`) | ~29 MB |
72
+ | `rfclib[semantic]` | embedding search fused into hybrid search | ~69 MB |
73
+ | `rfclib[zim]` | Kiwix ZIM export | native wheel |
74
+ | `rfclib[all]` | all of the above | |
75
+
76
+ `rfclib status` shows which are installed; a missing one says how to add it,
77
+ and search without `semantic` uses keywords, identifiers and titles.
78
+
79
+ [`uvx`](https://docs.astral.sh/uv/) fetches rfclib into an isolated, cached
80
+ environment (and a suitable Python if needed); `pip install rfclib` works
81
+ too. RFCs are then fetched from rfc-editor.org the first time they are read;
82
+ `uvx rfclib init --content current` prefetches every current RFC so
83
+ full-text section search covers them all (~10 min, ~1.5 GB).
84
+
85
+ Requirements: Python 3.12+ on Windows, macOS or Linux. Semantic search
86
+ (`rfclib[semantic]`) uses a ~30 MB local embedding model (`minishlab/potion-base-8M`,
87
+ [model2vec](https://github.com/MinishLab/model2vec)) and `sqlite-vec`; on a
88
+ Python whose sqlite3 cannot load extensions, search is keyword-only.
89
+
90
+ ## Sources (first-party only)
91
+
92
+ | What | Source |
93
+ |---|---|
94
+ | Bulk mirror / periodic re-mirror | `rsync.rfc-editor.org::rfcs` (official rsync modules, see [download docs](https://www.rfc-editor.org/series/rfc-download/)) |
95
+ | Master manifest (all docs + obsoletes/updates/alias graph) | `https://www.rfc-editor.org/rfc-index.xml` |
96
+ | Per-document content | `https://www.rfc-editor.org/{rfc,bcp,std,fyi}/<id>.{txt,html,xml,pdf}` |
97
+ | New-publication feed | `https://www.rfc-editor.org/rfcrss.xml` |
98
+ | Errata (bulk, `rfclib init`; per-doc fallback) | `https://www.rfc-editor.org/api/v1/errata.json`, `https://errata.rfc-editor.org/` |
99
+
100
+ Every local file is recorded with source URL + SHA-256 for provenance.
101
+
102
+ ## From source
103
+
104
+ ```bash
105
+ git clone https://github.com/b1tninja/rfclib && cd rfclib
106
+ uv sync --group dev # .venv with rfclib (editable) + pytest
107
+ uv run pytest -q # offline tests, sample library
108
+
109
+ rfclib init # catalog: RFC index, errata, subject tree
110
+ rfclib status # where the library lives, what it holds
111
+ ```
112
+
113
+ That is enough to use the MCP server: status, obsolete chains, document
114
+ search, browsing and errata cover every RFC, and each RFC is fetched from
115
+ rfc-editor.org, segmented and indexed the first time it is read (about a
116
+ second). To make full-text section search cover everything up front:
117
+
118
+ ```bash
119
+ rfclib init --content current # + every current RFC, indexes, vectors (~10 min)
120
+ rfclib init --content all # + obsolete RFCs too
121
+ ```
122
+
123
+ Later updates are incremental (`rfclib sync && rfclib index build && rfclib
124
+ index embed`). The official rsync service still works for a bulk mirror:
125
+ `scripts\mirror.ps1`, then `rfclib scan`.
126
+
127
+ ```powershell
128
+ # Explore
129
+ .venv\Scripts\rfclib.exe hsearch "proving address ownership" # hybrid — recommended
130
+ .venv\Scripts\rfclib.exe search "congestion control" # titles/abstracts
131
+ .venv\Scripts\rfclib.exe search "KeyUpdate" --body # BM25, sections
132
+ .venv\Scripts\rfclib.exe vsearch "proving address ownership" # semantic only
133
+ .venv\Scripts\rfclib.exe toc rfc9000 # outline + line ranges
134
+ .venv\Scripts\rfclib.exe get rfc9000 --start-line 6858 --lines 40
135
+ .venv\Scripts\rfclib.exe rels rfc2616
136
+ .venv\Scripts\rfclib.exe resolve bcp14
137
+ .venv\Scripts\rfclib.exe subjects get quic # curated topic listing
138
+ .venv\Scripts\rfclib.exe status
139
+ ```
140
+
141
+ ## CLI reference
142
+
143
+ | Command | Purpose |
144
+ |---|---|
145
+ | `manifest` | Fetch/parse the official rfc-index.xml into the DB (metadata + relations) |
146
+ | `sync` | Incremental HTTP sync of missing docs (`--include-obsolete`, `--formats txt,html`, `--concurrency`) |
147
+ | `scan` | Register files from an rsync-produced mirror into the DB |
148
+ | `status` | Library + index statistics |
149
+ | `search <q> [--body]` | Titles/abstracts/keywords; `--body` = BM25 over sections (raw FTS5 syntax allowed) |
150
+ | `vsearch <q>` | Semantic (embedding) search over sections — needs `index embed` |
151
+ | `hsearch <q>` | Hybrid BM25 + embeddings + title coverage fused (RRF) — the recommended default |
152
+ | `get <id>` | Print a document (`--start-line N --lines N`, `--fmt txt\|html\|xml`) |
153
+ | `meta <id>` / `rels <id>` / `resolve <id>` | Bibliographic record / relation graph / BCP→RFC |
154
+ | `toc <id>` | Section outline with exact line ranges (feed into `get --start-line`) |
155
+ | `refs [<id>] [--build]` | Citation graph mined from txt: what an RFC cites / cites it, with §-level anchors (`--build` = incremental mine, ~50 s full corpus) |
156
+ | `errata <id> [--refresh]` | Verified-errata overlay from the RFC Editor errata service (on-demand fetch + local cache; `--all` for other statuses) |
157
+ | `subjects sync [--force] [--limit N]` | Mirror the RFC Editor subject taxonomy (~640 pages, one polite crawl) |
158
+ | `subjects list [filter] [--descriptions]` | Show the subject tree (or a filtered slice), parents before children |
159
+ | `subjects get <name> [--limit N]` | Documents inside one subject incl. descendants, newest first |
160
+ | `index build\|embed\|status` | Maintain retrieval indexes (`embed --limit N`, `--model <id>`) |
161
+ | `packs list [--collections-dir D]` | Show collection manifests and resolved doc counts |
162
+ | `packs build <id...> \| --all` | Export `.rfcpack` zips (embedded FTS5 search, `--formats`, `--out`, `--no-search`) |
163
+ | `packs export <id...> \| --all` | Export for third-party readers: EPUB e-books + static HTML mirror + Kiwix ZIM + Dash docset (`--formats epub,html,zim,docset`, `--link-refs none\|internal\|external`) |
164
+ | `packs catalog [--out D]` | Rebuild `catalog.json` (name/size/sha256 per pack) |
165
+ | `packs serve [--dir D] [--port 8100]` | LAN HTTP distribution for the Android reader |
166
+ | `mcp` | Run the MCP server on stdio |
167
+
168
+ All searches accept `--include-obsolete` (obsolete docs are hidden by design —
169
+ an agent reading RFC 2616 in 2026 is a failure mode).
170
+
171
+ ## Retrieval indexes (full-text + semantic)
172
+
173
+ Documents are chunked by section (heuristic parser for RFC txt, exact source
174
+ line ranges kept; long sections split into ~2.4k-char parts with per-part
175
+ line ranges). All three search modes live in the same SQLite file: BM25 via
176
+ FTS5 (`search --body`), KNN via sqlite-vec (`vsearch`), and hybrid
177
+ (`hsearch`). Chunk IDs are content-addressed, so re-chunking preserves
178
+ embeddings for every section whose text didn't change (a chunker tweak
179
+ re-embeds only the diff, e.g. 15k of 298k chunks in ~11 s).
180
+
181
+ `hsearch` fuses up to four lists with diversified, weighted RRF (the fourth,
182
+ an exact-identifier index, joins when the query contains tokens such as
183
+ `preferred_address`, `SETTINGS_INITIAL_WINDOW_SIZE` or `X25519`, weighted by
184
+ how rare they are; see docs/retrieval-design.md): section-level
185
+ BM25 (terms OR'd + proximity-boosted), the embedding KNN list, and a
186
+ document-level list ranked by how much of the query the doc's own **title**
187
+ carries (IDF-weighted term coverage — the spec titled "TCP Congestion
188
+ Control" beats one that merely mentions "NewReno"). Each doc appears once
189
+ per list before fusion, chunks of the same doc corroborate each other, and
190
+ standards-track/BCP docs get a status boost while References/boilerplate
191
+ sections are penalized. Without `index embed` it still works (BM25 + title).
192
+
193
+ Full design rationale, measured trade-offs, and FTS5/chunker gotchas:
194
+ [docs/retrieval-design.md](docs/retrieval-design.md).
195
+
196
+ ### Accuracy loop (regression-test retrieval changes)
197
+
198
+ ```powershell
199
+ python scripts\eval_retrieval.py # golden queries -> expected doc, top-5
200
+ ```
201
+
202
+ A set of hand-picked (query → canonical-RFC) pairs run through `hsearch`.
203
+ Any change to chunking, FTS query shape, or fusion should be built,
204
+ re-embedded, and re-evaluated with this script before shipping
205
+ (currently 10/12 in top-5; slowest cold query ~1.8 s — see
206
+ [docs/retrieval-design.md §5](docs/retrieval-design.md) for the two known-hard misses).
207
+
208
+ ## MCP server
209
+
210
+ Any MCP client launches the server as a subprocess over stdio. With `uvx`
211
+ there is nothing to install first (`--from "rfclib[mcp,semantic]"` brings the
212
+ MCP and semantic-search extras); set `RFCLIB_HOME` in `env` only to use a
213
+ library somewhere other than the default per-user data directory.
214
+
215
+ **Claude Code**
216
+
217
+ ```bash
218
+ claude mcp add rfclib -- uvx --from "rfclib[mcp,semantic]" rfclib mcp
219
+ ```
220
+
221
+ **Claude Desktop** (`claude_desktop_config.json`) and **Cursor**
222
+ (`~/.cursor/mcp.json`):
223
+
224
+ ```json
225
+ {
226
+ "mcpServers": {
227
+ "rfclib": {
228
+ "command": "uvx",
229
+ "args": ["--from", "rfclib[mcp,semantic]", "rfclib", "mcp"]
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ **VS Code** (`.vscode/mcp.json`):
236
+
237
+ ```json
238
+ {
239
+ "servers": {
240
+ "rfclib": {
241
+ "type": "stdio",
242
+ "command": "uvx",
243
+ "args": ["--from", "rfclib[mcp,semantic]", "rfclib", "mcp"]
244
+ }
245
+ }
246
+ }
247
+ ```
248
+
249
+ GUI clients don't always see your shell's `PATH`; if `uvx` isn't found,
250
+ use its absolute path (`which uvx` / `where uvx`). From a source checkout,
251
+ point `command` at the venv's `rfclib` executable instead (this repository's
252
+ `.mcp.json` does that for development).
253
+
254
+ ### Tools (9)
255
+
256
+ One job per tool, all answering in compact plain text; every tool's main
257
+ argument is `ref` (only `search` takes `query`), all addressed by
258
+ **refs**, which are also citations. Every page of `read` ends with
259
+ `next: <ref>` to continue from:
260
+ `RFC9000` (abstract + outline), `RFC9000§9.6` (section incl. subsections),
261
+ `RFC9000§9.6¶2` (paragraph), `RFC9000#table-2` (RFCXML block id),
262
+ `RFC9000:2964-2979` (txt lines); `BCP14`/`STD7` resolve to their RFCs.
263
+ Errors come back as `isError` results with the fix in the message; all
264
+ tools are annotated read-only; `search` and `read` carry
265
+ `_meta["anthropic/alwaysLoad"]` so they stay visible when a client defers
266
+ tools behind tool search.
267
+
268
+ | Tool | Args | What it returns |
269
+ |---|---|---|
270
+ | `search` | `query`, `scope`, `mode`, `subject`, `status`, `limit`, `detail` | Paragraph-precise hits with snippets. `scope=sections` (default) or `documents` (titles/abstracts); `mode=hybrid` (BM25 + embeddings), `exact` (FTS5 syntax) or `semantic` |
271
+ | `read` | `ref`, `max_tokens`, `view`, `lang`, `format` | **The way to read text.** Pages cut at paragraph boundaries, page furniture stripped, `[§9.6¶2]` markers, footer `next:`/`up:`/`after:` refs, status + errata in the header. `view=normative` (MUST/SHOULD statements), `code` (ABNF/figures, `lang=`), `refs`; `format=xml\|html\|json` for raw slices of other renderings |
272
+ | `outline` | `ref`, `depth`, `contains` | One line per section with its line range (depth 2 by default; RFC 9000 ≈ 6k chars) |
273
+ | `grep` | `ref`, `pattern` | Regex inside one document or section; hits labelled with paragraph refs + lines |
274
+ | `cite` | `ref`, `style` | Checkable citation: label, status/obsolescence, anchor URL, errata status, span hash |
275
+ | `links` | `ref`, `direction` | What a section cites / is cited by, section-precise (`RFC9000§8.1 → RFC9002 §6.2 [normative]`), plus reference-list edges; `related` for subject neighbours |
276
+ | `info` | `ref`, `detail` | Status, obsoletes/updates graph with the **current** replacement (RFC2616 → RFC9110–9112), alias members, authors, subjects, errata summary, abstract; `detail=full` = JSON record |
277
+ | `browse` | `subject`, `parent`, `contains`, `recent`, `offset` | RFC Editor subject tree one level at a time, a subject's documents, or the newest RFCs |
278
+ | `errata` | `ref`, `status`, `section` | Verified (default), open or all errata with the corrected text; `RFC791§3.1` narrows to a section |
279
+
280
+ **Prompts** (user-invoked slash commands in Claude Code):
281
+ `/rfclib:explain <ref>`, `/rfclib:checklist <ref>` (normative statements →
282
+ implementation checklist), `/rfclib:current <topic>` (which RFCs are
283
+ current). **Resource template** `rfc://{ref}`: `@rfclib:rfc://RFC9112§6.3`
284
+ attaches a section to a prompt. **Completions** for document ids and
285
+ section numbers.
286
+
287
+ **Agent guidance shipped in the repo** (`.claude/`): the `rfc-research`
288
+ skill (workflow, ref grammar, recipes) and a `PreToolUse` hook
289
+ (`scripts/hooks/guard_mirror.py`) that denies `Read`/`Grep`/`Glob` on
290
+ `data/mirror/**` and points at the MCP call to use instead.
291
+
292
+ Missing documents are fetched on demand from rfc-editor.org.
293
+
294
+ ### Subject views (RFC-Editor organization)
295
+
296
+ The RFC Editor curates a ~640-subject tree (`web > http > http2`,
297
+ `transport > quic`, …) and assigns every document to one or more
298
+ subjects; it is published only as HTML at
299
+ [rfc-editor.org/subjects/](https://www.rfc-editor.org/subjects/).
300
+ `rfclib subjects sync` mirrors it (one polite crawl, a few minutes; HTML
301
+ cached under `meta/subjects/` so re-parses are offline) into `subjects` +
302
+ `doc_subjects` tables. On top of that: the `browse` /
303
+ `links(direction='related')` MCP tools, `search(scope='documents',
304
+ subject=...)`, subject tags in `info`, and the same via CLI
305
+ (`rfclib subjects list|get`). This answers *"what RFCs exist about X"*
306
+ and *"what else is like this RFC"* without a keyword query — the
307
+ citation-graph view (`links`) stays available for normative context.
308
+
309
+ ## Configuration
310
+
311
+ Environment variables (the same in a shell or an MCP client's `env`
312
+ block):
313
+
314
+ - `RFCLIB_HOME`: the library root. Default: the per-user data directory
315
+ (`%LOCALAPPDATA%
316
+ fclib` on Windows, `~/.local/share/rfclib` or
317
+ `$XDG_DATA_HOME/rfclib` on Linux, `~/Library/Application Support/rfclib`
318
+ on macOS). Never the working directory, because MCP clients start
319
+ servers from arbitrary directories. Older versions defaulted to `./data`;
320
+ `rfclib status` points out such a library and how to keep using it.
321
+ - `RFCLIB_NETWORK`: `auto` (default; fetch documents on demand),
322
+ `on-demand`, or `offline` (never touch the network; answer from what
323
+ is local, and say so).
324
+ - `RFCLIB_AUTO_INIT=1`: let the MCP server initialize an empty library's
325
+ catalog in the background on first use (opt-in; off by default).
326
+
327
+ The library is portable: file paths are stored relative to `mirror/`, so a
328
+ library can be moved, copied to another OS, or mounted into a container.
329
+ Several processes can share it (one MCP server per client session plus
330
+ `rfclib sync`): SQLite runs in WAL mode with a busy timeout, downloads are
331
+ written atomically, and bulk writers take a lock file.
332
+
333
+ ```
334
+ <RFCLIB_HOME>/
335
+ rfclib.db one SQLite file: docs, doc_relations, files (sha256 provenance),
336
+ chunks, chunks_fts + docs_fts (FTS5), chunks_vec (sqlite-vec),
337
+ embed_state (content-hashed embed coverage),
338
+ blocks + xrefs (paragraphs and section-level citations),
339
+ seg_quality (how each document was segmented), errata
340
+ meta/ rfc-index.xml (official manifest)
341
+ mirror/ document files in rfc-editor.org layout (rfc/, bcp/, ...)
342
+ models/ the embedding model (~30 MB), loaded offline
343
+ ```
344
+
345
+ ## Query tips
346
+
347
+ - `hsearch`/`search` treat words as OR + proximity — natural phrasing
348
+ works ("does this spec allow sending packets early?"); no operators needed.
349
+ - `search --body` and `search(mode='exact')` pass your query **raw to FTS5**:
350
+ quote phrases (`"KeyUpdate message"`), combine with `AND`/`OR`/`NOT`,
351
+ prefix with `*`. NEAR takes the comma form: `NEAR(tls handshake, 40)`
352
+ (`NEAR(tls handshake / 40)` is a syntax error).
353
+ - Keep version tokens in queries (`TLS 1.3`, `802.11`): they are strong
354
+ discriminators and hybrid search preserves them unstemmed.
355
+ - For a long RFC, `outline` first, then `read('RFC9000§9.6')` and follow
356
+ the `next:` ref — don't page through 8,000-line documents, and never point
357
+ an agent at the mirror files (its file reader truncates them).
358
+ - RFCs since 8650 are indexed from their RFCXML source (`rfclib sync
359
+ --formats xml && rfclib index build`); older RFCs are segmented from
360
+ their own Table of Contents (`rfclib/index/txtseg.py`, quality per doc
361
+ in the `seg_quality` table). Either way you get exact sections,
362
+ paragraph refs, `view=normative`/`code` and section-level `links`.
363
+ - `rfclib errata --sync-all` loads every erratum from the RFC Editor bulk
364
+ API once, so errata status is known offline for every document.
365
+
366
+ ## Troubleshooting
367
+
368
+ | Symptom | Fix |
369
+ |---|---|
370
+ | `vsearch`/`search(mode='semantic')` return nothing | Run `rfclib index embed` (`index status` shows chunk vs embedded counts) |
371
+ | Hybrid results lack conceptual recall | Same — `hsearch` degrades to BM25+title without vectors |
372
+ | Section titles look like ToC junk | Chunker heuristics improved since your index; `rfclib index build && rfclib index embed` (only the diff re-embeds) |
373
+ | `database is locked` | An `index build`/`embed` is running elsewhere; they commit in batches — wait or stop the other run |
374
+ | Expected old/obsolete RFC missing | It's obsoleted and hidden by default: add `--include-obsolete` / `include_obsolete: true` |
375
+ | `sync` too slow / rsync unavailable (Windows) | `sync` is HTTP-based and cross-platform; `scripts\mirror.ps1` shells out to rsync under WSL for the initial bulk pull |
376
+
377
+ ## Content packs & Android reader
378
+
379
+ RFCs are published rolling (~5–25/month, no official batches), so updates are
380
+ distributed as **content packs**: a `.rfcpack` zip holds one curated
381
+ collection — document files + a `pack.json` manifest (per-file sha256) + an
382
+ offline SQLite **FTS5** index (`search.sqlite`) so the phone searches without
383
+ a backend. Collections are tiny JSON manifests (explicit doc ids and/or
384
+ selectors over series/stream/area/WG/year); built-ins:
385
+
386
+ | Collection | Contents |
387
+ |---|---|
388
+ | `essentials` | ~52 hand-picked current core protocols (IP/TCP/DNS/TLS 1.3/QUIC/HTTP/DHCP/email + process docs) |
389
+ | `standards-current` | Every RFC that is a member of a current STD or BCP — the closest thing to an official essential list |
390
+ | `fyi-current` | Current FYI notes |
391
+ | `year-2025` / `year-2026` | Everything published that year (example of the year selector) |
392
+ | `all-current-txt` | The whole current archive (~8.8k docs) |
393
+
394
+ Put your own `<id>.json` in `data/collections/` (or `--collections-dir`) to
395
+ define new ones. `history: true` pulls in the obsoleted lineage too.
396
+
397
+ ```powershell
398
+ .venv\Scripts\rfclib.exe packs list
399
+ .venv\Scripts\rfclib.exe packs build essentials standards-current # -> export-packs/
400
+ .venv\Scripts\rfclib.exe packs serve --dir export-packs --port 8100
401
+ ```
402
+
403
+ `android-reader/` is a dependency-free Android app (Java, mirrors the
404
+ caionline pack-install design): install a `.rfcpack` via the file picker,
405
+ `adb push`, or **Sync** against `packs serve` on your LAN (downloads are
406
+ sha256-verified against `catalog.json`, installs are verified file-by-file
407
+ against `pack.json` before promotion). Browse packs → documents, full-text
408
+ search across all installed packs, and read in the WebView via a loopback
409
+ server — fully offline after sync.
410
+
411
+ ### Third-party export formats (`packs export`)
412
+
413
+ For readers outside this repo (e-book apps, offline browsing), the same
414
+ collections export to standard formats — one RFC .txt becomes one clean
415
+ XHTML chapter: page furniture stripped, ASCII diagrams preserved verbatim
416
+ in `<pre>`, section anchors generated with the same heuristics as the
417
+ search index, and `RFC 9000`/`[RFC9000]` references linkified (internal to
418
+ the set by default; `--link-refs external` adds rfc-editor.org links).
419
+
420
+ ```powershell
421
+ .venv\Scripts\rfclib.exe packs export essentials # -> export-formats/essentials.epub + essentials/
422
+ .venv\Scripts\rfclib.exe packs export standards-current --formats epub
423
+ .venv\Scripts\rfclib.exe packs export standards-current --formats zim # needs: uv sync --extra zim
424
+ .venv\Scripts\rfclib.exe packs export standards-current --formats docset
425
+ ```
426
+
427
+ * **EPUB** (EPUB3, pure stdlib zip): works in ReadEra/Apple Books/Play
428
+ Books; the nav document gives per-document chapters nested with their
429
+ top sections. Reader-side full-text search indexes the chapters.
430
+ * **Dash docset** (`--formats docset`, pure stdlib): `<id>.docset` bundle +
431
+ `.tgz` — drag into Dash (or Zeal/Xcode). The `docSet.dsidx` lookup index
432
+ carries one entry per document plus one per top-level section/appendix
433
+ (`RFC9110: 4.2. HTTP Messaging` → deep-link straight to that anchor).
434
+ * **HTML mirror**: plain folder of pages + `index.html` — greppable,
435
+ Everything/Windows-Search friendly, opens anywhere (the CHM successor,
436
+ without CHM's dead tooling; CHM itself was rejected: no maintained
437
+ Python writer, no Android support).
438
+ * **ZIM (Kiwix)** (`--formats zim`; optional native bindings, `uv sync
439
+ --extra zim` — libzim wheels carry Xapian): one `.zim` per collection
440
+ with a **full-text index + title suggestions built at write time**, so
441
+ Kiwix apps / `kiwix-serve --port 8080 export-formats\standards-current.zim`
442
+ give offline full-text search over the corpus. Entries are flat and
443
+ lowercase (`rfc9000`), the main page is the collection index, and a
444
+ generated 48x48 PNG icon shows in the Kiwix catalogue.
445
+
446
+ Validation: `python scripts\check_export.py` verifies EPUB/HTML structure
447
+ (mimetype placement, OPF/nav well-formedness, anchors, cross-links,
448
+ furniture stripping), `python scripts\check_zim.py` reopens a built
449
+ ZIM with the libzim reader (entry counts, main-page redirect, metadata,
450
+ illustration, anchors/links, and live Xapian suggestion + full-text
451
+ queries), and `python scripts\check_docset.py` validates the docset
452
+ bundle (plist, dsidx rows + every url/anchor resolving, tgz layout) —
453
+ all end-to-end against the `essentials` collection.
454
+
455
+ Search engine compatibility: the packs carry an FTS5 index, but **AOSP's
456
+ system SQLite does not enable FTS5** — `external/sqlite/dist/Android.bp`
457
+ on Android 11 and 16 defines FTS3/FTS4 only, and an API 36 emulator image
458
+ has no `fts5` module (`MATCH` fails with `no such module: fts5`; OEM ROMs
459
+ sometimes add it). `RfcSearch` therefore probes FTS5 once per process and
460
+ falls back to portable LIKE-based AND matching with density ranking when
461
+ the module is absent — correct, though slower and unstemmed. Queries pass
462
+ a stop-word filter: SQLite FTS5 has no native stop-word support (no
463
+ built-in tokenizer option exists, verified through SQLite 3.54), so
464
+ filtering happens at query time and needs no pack rebuild; RFC normative
465
+ keywords (MUST/SHOULD/MAY/CAN) are deliberately kept out of the stop list
466
+ because they carry meaning in this corpus. A future option for indexed
467
+ search on every device is an FTS4 mirror table (FTS4 is guaranteed on
468
+ AOSP) or bundling a native SQLite with FTS5.
469
+
470
+ ```powershell
471
+ cd android-reader
472
+ .\gradlew.bat assembleDebug # APK: app\build\outputs\apk\debug
473
+ .\gradlew.bat test # installer validation unit tests
474
+ adb install app\build\outputs\apk\debug\app-debug.apk
475
+ ```
476
+
477
+ ## Reader UI and Docker images
478
+
479
+ The React reader UI (`rfclib ui`, with its stdlib JSON API) and the Docker
480
+ images that serve it live on the [`reader-ui`](https://github.com/b1tninja/rfclib/tree/reader-ui)
481
+ branch, kept out of the package so it needs no npm toolchain. Check that
482
+ branch out to build or run them.
483
+
484
+ ## Roadmap
485
+
486
+ 1. ✅ Bulk download + incremental sync (rsync bootstrap + HTTP syncer)
487
+ 2. ✅ MCP presentation (search / get / metadata / aliases / recents)
488
+ 3. ✅ Full-text + semantic + hybrid retrieval, golden-set eval harness
489
+ 4. ✅ React + Vite reader UI (`rfclib ui`): JSON API + SPA — now on the
490
+ `reader-ui` branch
491
+ 5. ✅ Cross-references: citation graph mined from txt (normative/
492
+ informative/mention + section-level anchors, `rfclib refs`), exposed
493
+ in CLI and MCP with section deep links
494
+ 6. ✅ Verified-errata overlay from the RFC Editor errata service
495
+ (bulk API + per-doc fallback, `rfclib errata`, MCP `errata`)
496
+ 7. ✅ Content packs (`.rfcpack` + collections + LAN serve) and offline
497
+ Android reader (`android-reader/`) with FTS5 search
498
+ 8. ✅ Third-party export (`packs export`): EPUB3, static HTML mirror, Kiwix
499
+ ZIM (libzim + Xapian full-text index, optional `zim` extra) and Dash
500
+ docset (stdlib dsidx with section deep-links) — all four over the
501
+ shared txt→XHTML render layer
502
+ 9. ✅ Multi-stage Docker workflow serving the reader UI — on the
503
+ `reader-ui` branch
504
+
505
+
506
+ ## License
507
+
508
+ rfclib is MIT-licensed (see [LICENSE](LICENSE)). The RFCs it downloads are
509
+ not: they are published by the IETF Trust under the
510
+ [Trust Legal Provisions](https://trustee.ietf.org/license-info). They may be
511
+ copied and distributed in full but not modified; see [NOTICE](NOTICE).