mcp-libraryofcongress 1.0.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ First stable release. The tool names, the argument names and the shape of the
6
+ structured output are settled and will not change without a major version.
7
+
8
+ Four tools over the Library of Congress, with no API key and no account.
9
+
10
+ - `search_newspapers` reads the text optical recognition took off millions of
11
+ scanned pages of American newspapers, so it answers a question no catalogue
12
+ can: which issue printed this phrase. A match names the paper, the date, the
13
+ leaf and the state, and links the leaf with the query applied. Narrowing is
14
+ typed here too: `location` for the state a paper was published in,
15
+ `publication` for one paper, `year_from` and `year_to` for a span of years.
16
+ - `search_items` searches one catalogue at a time, because the Library keeps a
17
+ separate catalogue per kind of thing and `media_type` names which one.
18
+ Narrowing is typed: years, subject, location, language and collection.
19
+ - `get_item` reads one record section by section, and paginates a long
20
+ description by character offset, resuming at a line boundary.
21
+ - `list_collections` shows the corpora a curator built, each row carrying the
22
+ exact wording `search_items` takes as its `collection` filter.
23
+
24
+ Four things this release is careful about.
25
+
26
+ A failure is never reported as an empty result. A refused request is
27
+ `invalid_input`, an unreadable answer is `parse_failure`, and only a genuinely
28
+ empty record is an absence. Reading rows alone turns "I could not ask" into
29
+ "there is none of it".
30
+
31
+ An excerpt states where it came from. The Library returns the opening of a
32
+ page's text with each search row rather than the whole page, so the searched
33
+ words often sit further down. `words_located` says which of the two happened for
34
+ every match, rather than letting the opening of a leaf pass for the passage that
35
+ matched.
36
+
37
+ Each route is given the deadline it needs. The full-text newspaper corpus takes
38
+ tens of seconds to answer where the catalogue takes a few, so
39
+ `LOC_NEWSPAPER_TIMEOUT_MS` governs that one route and `LOC_TIMEOUT_MS` the rest.
40
+ A query holding two quoted phrases is an ordinary question of that corpus and it
41
+ gets an answer rather than a deadline.
42
+
43
+ A count is named for what it counts. The site publishes the number of results
44
+ and the number of pages under names that read alike, and one is the other
45
+ multiplied by the page size. Only the count of results is ever reported as a
46
+ total.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Damien Bouissou
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.
package/README.md ADDED
@@ -0,0 +1,555 @@
1
+ # mcp-libraryofcongress
2
+
3
+ [![npm](https://img.shields.io/npm/v/mcp-libraryofcongress.svg)](https://www.npmjs.com/package/mcp-libraryofcongress)
4
+ [![CI](https://github.com/smeet666/mcp-libraryofcongress/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-libraryofcongress/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/mcp-libraryofcongress.svg)](LICENSE)
6
+ [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-libraryofcongress/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-libraryofcongress)
7
+
8
+ An MCP server for the [Library of Congress](https://www.loc.gov). **Search the
9
+ text scanned off digitised American newspaper pages**, search the catalogue by
10
+ kind of thing, read one record, and list the collections a curator built. No API
11
+ key, no account, no configuration.
12
+
13
+ _(Version française plus bas / French version below)_
14
+
15
+ ## Quickstart
16
+
17
+ **One-click install**
18
+
19
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=libraryofcongress&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1saWJyYXJ5b2Zjb25ncmVzcyJdfQ%3D%3D)
20
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=libraryofcongress&config=%7B%22name%22%3A%22libraryofcongress%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-libraryofcongress%22%5D%7D)
21
+
22
+ **Claude Code**
23
+
24
+ ```bash
25
+ claude mcp add libraryofcongress -- npx -y mcp-libraryofcongress
26
+ ```
27
+
28
+ **Claude Desktop, Cursor, and any client using the standard config format**
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "libraryofcongress": {
34
+ "command": "npx",
35
+ "args": ["-y", "mcp-libraryofcongress"]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Node 20 or later.
42
+
43
+ **Bundle, without npm**
44
+
45
+ Download `mcp-libraryofcongress-<version>.mcpb` from
46
+ [the latest release](https://github.com/smeet666/mcp-libraryofcongress/releases/latest)
47
+ and open it. A client that supports MCP bundles installs it on its own, with no
48
+ npm and no configuration file to edit.
49
+
50
+ ## Tools
51
+
52
+ | Tool | What it does | Key parameters |
53
+ | ------------------- | --------------------------------------------------------- | --------------------------------------------------------- |
54
+ | `search_newspapers` | Finds a phrase in the text of scanned newspaper pages. | `query`, `location`, `publication`, `year_from`, `limit` |
55
+ | `search_items` | Searches one catalogue: books, photos, maps, audio, more. | `query`, `media_type`, `subject`, `location`, `year_from` |
56
+ | `get_item` | Reads one record, section by section. | `identifier`, `sections`, `offset` |
57
+ | `list_collections` | The digital collections, with the filter each one takes. | `limit`, `page`, `max_description_chars` |
58
+
59
+ The server is **read-only**. It uploads nothing and writes nothing back.
60
+
61
+ ## Searching inside the newspapers is the point
62
+
63
+ A catalogue search reads titles, creators and descriptions. `search_newspapers`
64
+ reads what optical character recognition took off millions of scanned pages of
65
+ American newspapers, so it answers a question nothing else here can: _which
66
+ issue printed this phrase_. A match comes back with the paper, the date, the
67
+ leaf of the issue, the state it was published in, and an address that opens that
68
+ leaf with the query applied.
69
+
70
+ Put a phrase in double quotes to match it whole. Without quotes the words are
71
+ matched separately, which finds far more.
72
+
73
+ The corpus spans every state and a century and a half, so a bare phrase reaches
74
+ a great deal that a question did not ask for. Three arguments narrow it:
75
+ `location` keeps to papers published in one state, `publication` to a single
76
+ paper, and `year_from` with `year_to` to a span of years. Each takes the wording
77
+ the Library itself uses, which every match carries: `state` on a row is what
78
+ `location` expects, and `publication` on a row is what `publication` expects.
79
+ Asking `search_items` with `media_type: "newspapers"` lists the papers
80
+ themselves, and a title there is the wording too.
81
+
82
+ ```
83
+ search_newspapers(
84
+ query: '"ellis island" immigration',
85
+ location: "new york",
86
+ publication: "new-york tribune (new york [n.y.]) 1866-1924",
87
+ year_from: 1900, year_to: 1910
88
+ )
89
+ ```
90
+
91
+ A filter the corpus does not recognise matches nothing, and an empty answer
92
+ would read as the Library holding no such page. The search is asked again
93
+ without the narrowing instead, and a note says what was set aside.
94
+
95
+ ### Three things it will not pretend to know
96
+
97
+ **`total` counts pages, and it pages.** It is the number of newspaper leaves
98
+ that match, and it is not a count of how many times the words occur. Ask for
99
+ page 2, 3 and so on rather than treating the first answer as the whole of it.
100
+
101
+ **An excerpt is sometimes the opening of the page.** The Library returns the
102
+ beginning of a page's text with each row rather than the whole page, so the
103
+ searched words are often further down than that text reaches. `words_located`
104
+ says which of the two happened: true and the passage is centred on the words,
105
+ false and it is the opening of the leaf. Follow `source_url` for the rest.
106
+
107
+ **Scanned text is machine-read.** Excerpts carry the misreadings that come with
108
+ it. Quote them as scanned text and link the page.
109
+
110
+ ## One catalogue per kind of thing
111
+
112
+ `search_items` requires `media_type`, because the Library keeps a separate
113
+ catalogue for each kind of thing: `books`, `photos`, `maps`, `audio`,
114
+ `film-and-videos`, `manuscripts`, `notated-music`, `newspapers`. There is no
115
+ address that asks all of them at once, and one title can exist in several.
116
+
117
+ Narrowing is typed rather than free text: `year_from`, `year_to`, `subject`,
118
+ `location`, `language`, `collection`, `online_only`, `sort`. A filter that
119
+ matches nothing is set aside, the search is asked again without it, and the
120
+ answer names what was dropped, so a narrowing that spelled a subject the Library
121
+ words differently is reported as a spelling that found nothing rather than as
122
+ the Library holding nothing on it.
123
+
124
+ By default only material with a digitised copy comes back. `online_only: false`
125
+ takes in the records the Library holds on a shelf alone.
126
+
127
+ ## Reading a record, and finding a corpus
128
+
129
+ `get_item` takes one identifier. An identifier can carry slashes: a single
130
+ newspaper issue is named by its paper, its date and its edition together, as in
131
+ `sn83045462/1929-02-03/ed-1`. Sections are opt-in, `basic`, `citations`,
132
+ `resources` and `full_metadata`, because the served copies of a scan and the
133
+ full field list are each larger than the record they describe. A long
134
+ description paginates by character offset and resumes at a line boundary: when
135
+ `next_offset` is not null, call again with `offset` set to it.
136
+
137
+ `list_collections` shows the bodies of material a curator chose, described and
138
+ published together, so a caller can see what is there before searching. Each row
139
+ carries `collection_filter`, which is the wording `search_items` takes as its
140
+ `collection` argument.
141
+
142
+ ## What the answers claim
143
+
144
+ **A failure is never an empty result.** A refused request is `invalid_input`, an
145
+ unreadable answer is `parse_failure`, and only a genuinely empty record is an
146
+ absence. Silence about a failure becomes "there is none" in the mouth of a
147
+ model, which is a false statement about the world.
148
+
149
+ **A null is never printed as a value.** A record with no stated terms of use
150
+ reports null and says that silence is not permission.
151
+
152
+ **A count is named for what it counts.** The site reports the number of results
153
+ and the number of pages under names that read alike, and one is the other
154
+ multiplied by the page size. Only the count of results is ever published as a
155
+ total.
156
+
157
+ **Every answer carries a link back**, and the notes that qualify an answer reach
158
+ the text block, so a client that renders only text still reads them. Text
159
+ published by someone else cannot imitate this server's own lines.
160
+
161
+ ## Rights
162
+
163
+ Metadata from the Library of Congress is in the public domain. The material it
164
+ describes is not always: rights vary per deposit and are often unstated. A
165
+ record reports the terms it carries, and a record carrying none is not a record
166
+ granting permission. Credit the Library of Congress and link what you use.
167
+
168
+ ## Configuration
169
+
170
+ Every variable is optional. Set them in the `env` block of your MCP client.
171
+
172
+ | Variable | Default | Purpose |
173
+ | -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `LOC_USER_AGENT` | _(none)_ | Identify your own client. The project's identifier is appended, so the Library can reach a human. |
175
+ | `LOC_MIN_INTERVAL_MS` | `6000` | Minimum gap between requests. Values below 3000 ms are refused, as are values above 60000. |
176
+ | `LOC_TIMEOUT_MS` | `30000` | Per-request deadline for the catalogue, the records and the collections. Accepted between 1000 and 120000. |
177
+ | `LOC_NEWSPAPER_TIMEOUT_MS` | `90000` | Per-request deadline for `search_newspapers`, which reads the text of millions of pages and answers in tens of seconds. Accepted between 1000 and 300000. |
178
+ | `LOC_MAX_RETRIES` | `3` | Retries on rate limiting and transient errors, up to 8. |
179
+ | `LOC_CACHE_TTL_MS` | `900000` | In-memory cache lifetime. `0` turns it off. |
180
+ | `LOC_CACHE_MAX_ENTRIES` | `200` | In-memory cache size, up to 5000. |
181
+ | `LOC_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`. Logs go to stderr. |
182
+
183
+ A value outside its range is refused with a line on stderr and the default
184
+ stands, so a typo in one variable takes away no tool.
185
+
186
+ ## How this server treats the Library
187
+
188
+ The Library publishes two ceilings: twenty requests a minute for the JSON
189
+ responses and ten a minute across the site. This server takes the lower of the
190
+ two, which is one request every six seconds, and holds that floor whether the
191
+ setting arrives from the environment or from a configuration object handed to
192
+ the published client. Answers therefore take a few seconds; a repeated question
193
+ is served from memory. It sends one request at a time, caches what it reads, and
194
+ identifies itself with an address a human can be reached at. A caller may say
195
+ who they are; that address is appended rather than replaced.
196
+
197
+ The site's robots file disallows `/search` for every client and asks for five
198
+ seconds between requests. No address this server builds reaches that path, and
199
+ the spacing it keeps is wider than the one asked for. The site returns links
200
+ into `/search` inside its facet blocks, and those links are read as labels
201
+ rather than followed.
202
+
203
+ ## Using the client on its own
204
+
205
+ The layer that talks to the site imports nothing from the protocol and is
206
+ published separately, with the pacing, the cache and the error taxonomy
207
+ attached.
208
+
209
+ ```ts
210
+ import { LocClient } from "mcp-libraryofcongress/client";
211
+
212
+ const client = new LocClient();
213
+ const { data } = await client.searchNewspapers('"cure for influenza"', 5, 1, {
214
+ maxChars: 300,
215
+ maxCount: 2,
216
+ });
217
+ console.log(data.paging.resultCount, data.hits[0]?.sourceUrl);
218
+ ```
219
+
220
+ ## Troubleshooting
221
+
222
+ **`rate_limited`.** The site asked this client to slow down, or this server was
223
+ asked for more than its pacing allows. It says nothing about whether the Library
224
+ holds what you asked for. Wait and ask again.
225
+
226
+ **`parse_failure`.** A response arrived in a shape this server cannot read,
227
+ which includes a long answer cut off in transit. It usually means a route
228
+ changed. Please
229
+ [open an issue](https://github.com/smeet666/mcp-libraryofcongress/issues) with
230
+ the arguments you used.
231
+
232
+ **`not_found`.** The site answered, and holds nothing at that address.
233
+
234
+ **An empty catalogue search.** Check `media_type`: one title exists across
235
+ several kinds of thing, and each is a catalogue of its own. A phrase printed
236
+ inside a newspaper belongs in `search_newspapers`.
237
+
238
+ ## Development
239
+
240
+ ```bash
241
+ npm install
242
+ npm test # unit tests, no network
243
+ npm run typecheck
244
+ npm run build
245
+ LOC_LIVE=1 npm run test:live # one request per route against the real site
246
+ npm run inspector # explore the tools in the MCP Inspector
247
+ ```
248
+
249
+ Fixtures are generated rather than captured: `npm run build:fixtures` writes a
250
+ corpus of invented titles and passages, so tests are deterministic and no
251
+ Library content lives in this repository. Anything touching time runs on a fake
252
+ clock pinned to a fixed instant.
253
+
254
+ The access layer under `src/loc` does not import the MCP SDK and is published
255
+ separately as `mcp-libraryofcongress/client`, usable as a plain library.
256
+
257
+ ## Contributing
258
+
259
+ Bugs, questions and ideas all belong in
260
+ [the issue tracker](https://github.com/smeet666/mcp-libraryofcongress/issues).
261
+ Pull requests are welcome; please open an issue first so we can agree on what
262
+ the right answer is before you write it. [CONTRIBUTING.md](CONTRIBUTING.md) has
263
+ the detail, and [SECURITY.md](SECURITY.md) covers anything exploitable.
264
+
265
+ ## Support
266
+
267
+ Free, and it stays free. If it saved you some time, you can
268
+ [buy me a coffee](https://buymeacoffee.com/smeet666).
269
+
270
+ ## License
271
+
272
+ MIT. See [LICENSE](LICENSE). The licence covers this source code only, not the
273
+ material retrieved through it, which carries whatever terms its depositor
274
+ attached, and often none at all.
275
+
276
+ This is an unofficial project, with no affiliation to or endorsement by the
277
+ Library of Congress.
278
+
279
+ ---
280
+
281
+ # mcp-libraryofcongress (français)
282
+
283
+ Un serveur MCP pour la [Library of Congress](https://www.loc.gov). **Cherchez
284
+ une phrase dans le texte des pages de journaux américains numérisées**,
285
+ parcourez le catalogue par type de document, lisez une fiche, et listez les
286
+ collections construites par un conservateur. Sans clé d'API, sans compte, sans
287
+ configuration.
288
+
289
+ ## Démarrage rapide
290
+
291
+ **Installation en un clic**
292
+
293
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=libraryofcongress&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1saWJyYXJ5b2Zjb25ncmVzcyJdfQ%3D%3D)
294
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=libraryofcongress&config=%7B%22name%22%3A%22libraryofcongress%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-libraryofcongress%22%5D%7D)
295
+
296
+ **Claude Code**
297
+
298
+ ```bash
299
+ claude mcp add libraryofcongress -- npx -y mcp-libraryofcongress
300
+ ```
301
+
302
+ **Claude Desktop, Cursor, et tout client utilisant le format standard**
303
+
304
+ ```json
305
+ {
306
+ "mcpServers": {
307
+ "libraryofcongress": {
308
+ "command": "npx",
309
+ "args": ["-y", "mcp-libraryofcongress"]
310
+ }
311
+ }
312
+ }
313
+ ```
314
+
315
+ Node 20 ou plus récent.
316
+
317
+ **Bundle, sans npm**
318
+
319
+ Téléchargez `mcp-libraryofcongress-<version>.mcpb` depuis
320
+ [la dernière release](https://github.com/smeet666/mcp-libraryofcongress/releases/latest)
321
+ et ouvrez-le. Un client compatible l'installe seul, sans npm ni fichier de
322
+ configuration à modifier.
323
+
324
+ ## Outils
325
+
326
+ | Outil | Rôle | Paramètres principaux |
327
+ | ------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |
328
+ | `search_newspapers` | Trouve une phrase dans le texte des pages de journaux numérisées. | `query`, `location`, `publication`, `year_from`, `limit` |
329
+ | `search_items` | Cherche un catalogue : livres, photos, cartes, sons, et le reste. | `query`, `media_type`, `subject`, `location`, `year_from` |
330
+ | `get_item` | Lit une fiche, section par section. | `identifier`, `sections`, `offset` |
331
+ | `list_collections` | Les collections numériques, avec le filtre que chacune accepte. | `limit`, `page`, `max_description_chars` |
332
+
333
+ Le serveur est **en lecture seule**. Il ne téléverse rien et n'écrit rien.
334
+
335
+ ## Chercher dans les journaux est le cœur du sujet
336
+
337
+ Une recherche de catalogue lit les titres, les auteurs et les descriptions.
338
+ `search_newspapers` lit ce que la reconnaissance de caractères a tiré de
339
+ millions de pages de journaux américains numérisées, et répond donc à une
340
+ question qu'aucun autre outil ici ne sait traiter : _quel numéro a imprimé cette
341
+ phrase_. Une correspondance revient avec le journal, la date, le feuillet du
342
+ numéro, l'État de publication, et une adresse qui ouvre ce feuillet avec la
343
+ requête appliquée.
344
+
345
+ Mettez une phrase entre guillemets doubles pour la faire correspondre en entier.
346
+ Sans guillemets, les mots sont cherchés séparément, ce qui trouve bien plus.
347
+
348
+ Le corpus couvre tous les États et un siècle et demi : une phrase seule ramène
349
+ donc beaucoup de choses que la question ne demandait pas. Trois arguments la
350
+ resserrent : `location` limite aux journaux publiés dans un État, `publication`
351
+ à un seul titre, et `year_from` avec `year_to` à une plage d'années. Chacun
352
+ prend la formulation de la Bibliothèque, que chaque correspondance porte : le
353
+ champ `state` d'une ligne est ce qu'attend `location`, et son champ
354
+ `publication` est ce qu'attend `publication`. `search_items` avec
355
+ `media_type: "newspapers"` liste les journaux eux-mêmes, et un titre y est aussi
356
+ la formulation attendue.
357
+
358
+ ```
359
+ search_newspapers(
360
+ query: '"ellis island" immigration',
361
+ location: "new york",
362
+ publication: "new-york tribune (new york [n.y.]) 1866-1924",
363
+ year_from: 1900, year_to: 1910
364
+ )
365
+ ```
366
+
367
+ Un filtre que le corpus ne reconnaît pas ne correspond à rien, et une réponse
368
+ vide se lirait comme une Bibliothèque ne détenant aucune page de ce genre. La
369
+ recherche est donc relancée sans le resserrement, et une note dit ce qui a été
370
+ écarté.
371
+
372
+ ### Trois choses qu'il refuse de prétendre savoir
373
+
374
+ **`total` compte des pages, et il se pagine.** C'est le nombre de feuillets qui
375
+ correspondent, pas un nombre d'occurrences. Demandez la page 2, la page 3, plutôt
376
+ que de prendre la première réponse pour la totalité.
377
+
378
+ **Un extrait est parfois le début de la page.** La Library renvoie le début du
379
+ texte d'une page avec chaque ligne de résultat plutôt que la page entière, si
380
+ bien que les mots cherchés se trouvent souvent plus bas que ce texte ne va.
381
+ `words_located` indique lequel des deux cas s'est produit : vrai et le passage
382
+ est centré sur les mots, faux et c'est le début du feuillet. Suivez `source_url`
383
+ pour la suite.
384
+
385
+ **Le texte numérisé est lu par une machine.** Les extraits en portent les
386
+ fautes. Citez-les comme tels et suivez le lien.
387
+
388
+ ## Un catalogue par type de document
389
+
390
+ `search_items` exige `media_type`, car la Library tient un catalogue distinct
391
+ pour chaque type de document : `books`, `photos`, `maps`, `audio`,
392
+ `film-and-videos`, `manuscripts`, `notated-music`, `newspapers`. Aucune adresse
393
+ ne les interroge tous à la fois, et un même titre peut exister dans plusieurs.
394
+
395
+ Le filtrage est typé plutôt que textuel : `year_from`, `year_to`, `subject`,
396
+ `location`, `language`, `collection`, `online_only`, `sort`. Un filtre qui ne
397
+ correspond à rien est mis de côté, la recherche est relancée sans lui, et la
398
+ réponse nomme ce qui a été écarté : une orthographe différente de celle de la
399
+ Library est ainsi signalée comme telle, et non comme un fonds vide.
400
+
401
+ Par défaut, seuls les documents disposant d'une copie numérisée reviennent.
402
+ `online_only: false` inclut les fiches que la Library ne conserve qu'en rayon.
403
+
404
+ ## Lire une fiche, et trouver un corpus
405
+
406
+ `get_item` prend un identifiant. Un identifiant peut contenir des barres
407
+ obliques : un numéro de journal se nomme par son titre, sa date et son édition
408
+ réunis, comme `sn83045462/1929-02-03/ed-1`. Les sections sont facultatives,
409
+ `basic`, `citations`, `resources` et `full_metadata`, car les copies servies d'un
410
+ scan et la liste complète des champs pèsent chacune plus lourd que la fiche
411
+ qu'elles décrivent. Une description longue se pagine par décalage de caractères
412
+ et reprend à une fin de ligne : quand `next_offset` n'est pas nul, rappelez
413
+ l'outil avec `offset` réglé sur cette valeur.
414
+
415
+ `list_collections` montre les ensembles qu'un conservateur a choisis, décrits et
416
+ publiés ensemble, pour voir ce qui existe avant de chercher. Chaque ligne porte
417
+ `collection_filter`, la formulation exacte que `search_items` accepte dans son
418
+ argument `collection`.
419
+
420
+ ## Ce que les réponses affirment
421
+
422
+ **Un échec n'est jamais un résultat vide.** Une requête refusée est
423
+ `invalid_input`, une réponse illisible est `parse_failure`, et seule une fiche
424
+ réellement vide est une absence. Taire un échec revient à faire dire « il n'y en
425
+ a pas » à un modèle, ce qui est une affirmation fausse sur le monde.
426
+
427
+ **Un vide n'est jamais imprimé comme une valeur.** Une fiche sans conditions
428
+ d'usage renvoie null et précise que ce silence n'est pas une autorisation.
429
+
430
+ **Un compteur porte le nom de ce qu'il compte.** Le site publie le nombre de
431
+ résultats et le nombre de pages sous des noms qui se ressemblent, et l'un est
432
+ l'autre multiplié par la taille de page. Seul le nombre de résultats est publié
433
+ comme total.
434
+
435
+ **Chaque réponse porte son lien**, et les notes qui la nuancent atteignent le
436
+ bloc de texte, pour qu'un client qui n'affiche que du texte les lise aussi. Un
437
+ texte publié par un tiers ne peut pas imiter les lignes propres au serveur.
438
+
439
+ ## Droits
440
+
441
+ Les métadonnées de la Library of Congress sont dans le domaine public. Les
442
+ documents décrits ne le sont pas toujours : les droits varient selon le dépôt et
443
+ sont souvent tus. Une fiche indique les conditions qu'elle porte, et une fiche
444
+ qui n'en porte aucune n'accorde rien. Créditez la Library of Congress et liez ce
445
+ que vous utilisez.
446
+
447
+ ## Configuration
448
+
449
+ Toutes les variables sont optionnelles, à déclarer dans le bloc `env` de votre
450
+ client.
451
+
452
+ | Variable | Défaut | Rôle |
453
+ | -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
454
+ | `LOC_USER_AGENT` | _(aucun)_ | Identifiez votre client. L'identifiant du projet est ajouté, pour que la Library puisse joindre une personne. |
455
+ | `LOC_MIN_INTERVAL_MS` | `6000` | Écart minimal entre requêtes. En dessous de 3000 ms et au-dessus de 60000 ms, la valeur est refusée. |
456
+ | `LOC_TIMEOUT_MS` | `30000` | Délai par requête pour le catalogue, les fiches et les collections. Accepté entre 1000 et 120000. |
457
+ | `LOC_NEWSPAPER_TIMEOUT_MS` | `90000` | Délai par requête pour `search_newspapers`, qui lit le texte de millions de pages et répond en dizaines de secondes. Accepté entre 1000 et 300000. |
458
+ | `LOC_MAX_RETRIES` | `3` | Tentatives en cas de limitation ou d'erreur passagère, jusqu'à 8. |
459
+ | `LOC_CACHE_TTL_MS` | `900000` | Durée de vie du cache mémoire. `0` le désactive. |
460
+ | `LOC_CACHE_MAX_ENTRIES` | `200` | Taille du cache mémoire, jusqu'à 5000. |
461
+ | `LOC_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`. Sortie sur stderr. |
462
+
463
+ Une valeur hors bornes est refusée avec une ligne sur stderr et le défaut
464
+ s'applique : une faute de frappe dans une variable ne retire aucun outil.
465
+
466
+ ## Ce que ce serveur doit à la Library
467
+
468
+ La Library publie deux plafonds : vingt requêtes par minute pour les réponses
469
+ JSON, et dix par minute sur l'ensemble du site. Ce serveur retient le plus bas
470
+ des deux, soit une requête toutes les six secondes, et tient ce plancher que le
471
+ réglage vienne de l'environnement ou d'un objet de configuration passé au client
472
+ publié. Les réponses prennent donc quelques secondes ; une question répétée est
473
+ servie depuis la mémoire. Il n'envoie qu'une requête à la fois, met en cache ce
474
+ qu'il lit, et s'identifie avec une adresse où joindre une personne. Un appelant
475
+ peut dire qui il est ; cette adresse est ajoutée, pas remplacée.
476
+
477
+ Le fichier robots du site interdit `/search` à tout client et demande cinq
478
+ secondes entre les requêtes. Aucune adresse construite ici n'atteint ce chemin,
479
+ et l'écart tenu est plus large que celui demandé. Le site renvoie des liens vers
480
+ `/search` dans ses blocs de facettes : ils sont lus comme des étiquettes, jamais
481
+ suivis.
482
+
483
+ ## Utiliser le client seul
484
+
485
+ La couche qui parle au site n'importe rien du protocole et est publiée
486
+ séparément, avec la cadence, le cache et la taxonomie d'erreurs.
487
+
488
+ ```ts
489
+ import { LocClient } from "mcp-libraryofcongress/client";
490
+
491
+ const client = new LocClient();
492
+ const { data } = await client.searchNewspapers('"cure for influenza"', 5, 1, {
493
+ maxChars: 300,
494
+ maxCount: 2,
495
+ });
496
+ console.log(data.paging.resultCount, data.hits[0]?.sourceUrl);
497
+ ```
498
+
499
+ ## Dépannage
500
+
501
+ **`rate_limited`.** Le site demande à ce client de ralentir, ou ce serveur a été
502
+ sollicité au-delà de sa cadence. Cela ne dit rien de ce que la Library conserve.
503
+ Attendez et redemandez.
504
+
505
+ **`parse_failure`.** Une réponse est arrivée dans une forme illisible pour ce
506
+ serveur, ce qui inclut une réponse longue coupée en transit. En général, une
507
+ route a changé. Merci
508
+ [d'ouvrir une issue](https://github.com/smeet666/mcp-libraryofcongress/issues)
509
+ avec les arguments utilisés.
510
+
511
+ **`not_found`.** Le site a répondu, et ne conserve rien à cette adresse.
512
+
513
+ **Une recherche catalogue vide.** Vérifiez `media_type` : un même titre existe
514
+ sous plusieurs types de document, et chacun est un catalogue à part. Une phrase
515
+ imprimée dans un journal relève de `search_newspapers`.
516
+
517
+ ## Développement
518
+
519
+ ```bash
520
+ npm install
521
+ npm test # tests unitaires, sans réseau
522
+ npm run typecheck
523
+ npm run build
524
+ LOC_LIVE=1 npm run test:live # une requête par route sur le vrai site
525
+ npm run inspector # explorer les outils dans le MCP Inspector
526
+ ```
527
+
528
+ Les fixtures sont générées, pas capturées : `npm run build:fixtures` écrit un
529
+ corpus de titres et de passages inventés, ce qui rend les tests déterministes et
530
+ évite de stocker du contenu de la Library dans ce dépôt. Tout ce qui touche au
531
+ temps tourne sur une horloge figée à un instant fixe.
532
+
533
+ La couche d'accès sous `src/loc` n'importe pas le SDK MCP et est publiée
534
+ séparément sous `mcp-libraryofcongress/client`, utilisable comme bibliothèque.
535
+
536
+ ## Contribuer
537
+
538
+ Bugs, questions et idées vont dans
539
+ [le suivi d'issues](https://github.com/smeet666/mcp-libraryofcongress/issues).
540
+ Les pull requests sont bienvenues ; ouvrez d'abord une issue pour qu'on
541
+ s'accorde sur la bonne réponse avant que vous n'écriviez le code.
542
+
543
+ ## Soutenir
544
+
545
+ Gratuit, et ça le reste. Si ça vous a fait gagner du temps, vous pouvez
546
+ [m'offrir un café](https://buymeacoffee.com/smeet666).
547
+
548
+ ## Licence
549
+
550
+ MIT, voir [LICENSE](LICENSE). La licence couvre uniquement ce code source, pas
551
+ les documents récupérés par son intermédiaire, qui portent les conditions que
552
+ leur déposant y a attachées, et souvent aucune.
553
+
554
+ Projet non officiel, sans affiliation à la Library of Congress ni approbation de
555
+ sa part.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node