smeet666

mcp-libraryofcongress

Community smeet666
Updated

MCP server for the Library of Congress. Search inside scanned newspapers, browse the catalogue, read records. No API key.

mcp-libraryofcongress

npmCIlicense

An MCP server for the Library of Congress. Search thetext scanned off digitised American newspaper pages, search the catalogue bykind of thing, read one record, and list the collections a curator built. No APIkey, no account, no configuration.

(Version française plus bas / French version below)

Quickstart

One-click install

Install in CursorInstall in VS Code

Claude Code

claude mcp add libraryofcongress -- npx -y mcp-libraryofcongress

Claude Desktop, Cursor, and any client using the standard config format

{
  "mcpServers": {
    "libraryofcongress": {
      "command": "npx",
      "args": ["-y", "mcp-libraryofcongress"]
    }
  }
}

Node 20 or later.

Bundle, without npm

Download mcp-libraryofcongress-<version>.mcpb fromthe latest releaseand open it. A client that supports MCP bundles installs it on its own, with nonpm and no configuration file to edit.

Tools

Tool What it does Key parameters
search_newspapers Finds a phrase in the text of scanned newspaper pages. query, location, publication, year_from, limit
search_items Searches one catalogue: books, photos, maps, audio, more. query, media_type, subject, location, year_from
get_item Reads one record, section by section. identifier, sections, offset
list_collections The digital collections, with the filter each one takes. limit, page, max_description_chars

The server is read-only. It uploads nothing and writes nothing back.

Searching inside the newspapers is the point

A catalogue search reads titles, creators and descriptions. search_newspapersreads what optical character recognition took off millions of scanned pages ofAmerican newspapers, so it answers a question nothing else here can: whichissue printed this phrase. A match comes back with the paper, the date, theleaf of the issue, the state it was published in, and an address that opens thatleaf with the query applied.

Put a phrase in double quotes to match it whole. Without quotes the words arematched separately, which finds far more.

The corpus spans every state and a century and a half, so a bare phrase reachesa great deal that a question did not ask for. Three arguments narrow it:location keeps to papers published in one state, publication to a singlepaper, and year_from with year_to to a span of years. Each takes the wordingthe Library itself uses, which every match carries: state on a row is whatlocation expects, and publication on a row is what publication expects.Asking search_items with media_type: "newspapers" lists the papersthemselves, and a title there is the wording too.

search_newspapers(
  query: '"ellis island" immigration',
  location: "new york",
  publication: "new-york tribune (new york [n.y.]) 1866-1924",
  year_from: 1900, year_to: 1910
)

A filter the corpus does not recognise matches nothing, and an empty answerwould read as the Library holding no such page. The search is asked againwithout the narrowing instead, and a note says what was set aside.

Three things it will not pretend to know

total counts pages, and it pages. It is the number of newspaper leavesthat match, and it is not a count of how many times the words occur. Ask forpage 2, 3 and so on rather than treating the first answer as the whole of it.

An excerpt is sometimes the opening of the page. The Library returns thebeginning of a page's text with each row rather than the whole page, so thesearched words are often further down than that text reaches. words_locatedsays which of the two happened: true and the passage is centred on the words,false and it is the opening of the leaf. Follow source_url for the rest.

Scanned text is machine-read. Excerpts carry the misreadings that come withit. Quote them as scanned text and link the page.

One catalogue per kind of thing

search_items requires media_type, because the Library keeps a separatecatalogue for each kind of thing: books, photos, maps, audio,film-and-videos, manuscripts, notated-music, newspapers. There is noaddress that asks all of them at once, and one title can exist in several.

Narrowing is typed rather than free text: year_from, year_to, subject,location, language, collection, online_only, sort. A filter thatmatches nothing is set aside, the search is asked again without it, and theanswer names what was dropped, so a narrowing that spelled a subject the Librarywords differently is reported as a spelling that found nothing rather than asthe Library holding nothing on it.

By default only material with a digitised copy comes back. online_only: falsetakes in the records the Library holds on a shelf alone.

Reading a record, and finding a corpus

get_item takes one identifier. An identifier can carry slashes: a singlenewspaper issue is named by its paper, its date and its edition together, as insn83045462/1929-02-03/ed-1. Sections are opt-in, basic, citations,resources and full_metadata, because the served copies of a scan and thefull field list are each larger than the record they describe. A longdescription paginates by character offset and resumes at a line boundary: whennext_offset is not null, call again with offset set to it.

list_collections shows the bodies of material a curator chose, described andpublished together, so a caller can see what is there before searching. Each rowcarries collection_filter, which is the wording search_items takes as itscollection argument.

What the answers claim

A failure is never an empty result. A refused request is invalid_input, anunreadable answer is parse_failure, and only a genuinely empty record is anabsence. Silence about a failure becomes "there is none" in the mouth of amodel, which is a false statement about the world.

A null is never printed as a value. A record with no stated terms of usereports null and says that silence is not permission.

A count is named for what it counts. The site reports the number of resultsand the number of pages under names that read alike, and one is the othermultiplied by the page size. Only the count of results is ever published as atotal.

Every answer carries a link back, and the notes that qualify an answer reachthe text block, so a client that renders only text still reads them. Textpublished by someone else cannot imitate this server's own lines.

Rights

Metadata from the Library of Congress is in the public domain. The material itdescribes is not always: rights vary per deposit and are often unstated. Arecord reports the terms it carries, and a record carrying none is not a recordgranting permission. Credit the Library of Congress and link what you use.

Configuration

Every variable is optional. Set them in the env block of your MCP client.

Variable Default Purpose
LOC_USER_AGENT (none) Identify your own client. The project's identifier is appended, so the Library can reach a human.
LOC_MIN_INTERVAL_MS 6000 Minimum gap between requests. Values below 3000 ms are refused, as are values above 60000.
LOC_TIMEOUT_MS 30000 Per-request deadline for the catalogue, the records and the collections. Accepted between 1000 and 120000.
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.
LOC_MAX_RETRIES 3 Retries on rate limiting and transient errors, up to 8.
LOC_CACHE_TTL_MS 900000 In-memory cache lifetime. 0 turns it off.
LOC_CACHE_MAX_ENTRIES 200 In-memory cache size, up to 5000.
LOC_LOG_LEVEL error silent, error, info or debug. Logs go to stderr.

A value outside its range is refused with a line on stderr and the defaultstands, so a typo in one variable takes away no tool.

How this server treats the Library

The Library publishes two ceilings: twenty requests a minute for the JSONresponses and ten a minute across the site. This server takes the lower of thetwo, which is one request every six seconds, and holds that floor whether thesetting arrives from the environment or from a configuration object handed tothe published client. Answers therefore take a few seconds; a repeated questionis served from memory. It sends one request at a time, caches what it reads, andidentifies itself with an address a human can be reached at. A caller may saywho they are; that address is appended rather than replaced.

The site's robots file disallows /search for every client and asks for fiveseconds between requests. No address this server builds reaches that path, andthe spacing it keeps is wider than the one asked for. The site returns linksinto /search inside its facet blocks, and those links are read as labelsrather than followed.

Using the client on its own

The layer that talks to the site imports nothing from the protocol and ispublished separately, with the pacing, the cache and the error taxonomyattached.

import { LocClient } from "mcp-libraryofcongress/client";

const client = new LocClient();
const { data } = await client.searchNewspapers('"cure for influenza"', 5, 1, {
  maxChars: 300,
  maxCount: 2,
});
console.log(data.paging.resultCount, data.hits[0]?.sourceUrl);

Troubleshooting

rate_limited. The site asked this client to slow down, or this server wasasked for more than its pacing allows. It says nothing about whether the Libraryholds what you asked for. Wait and ask again.

parse_failure. A response arrived in a shape this server cannot read,which includes a long answer cut off in transit. It usually means a routechanged. Pleaseopen an issue withthe arguments you used.

not_found. The site answered, and holds nothing at that address.

An empty catalogue search. Check media_type: one title exists acrossseveral kinds of thing, and each is a catalogue of its own. A phrase printedinside a newspaper belongs in search_newspapers.

Development

npm install
npm test                 # unit tests, no network
npm run typecheck
npm run build
LOC_LIVE=1 npm run test:live   # one request per route against the real site
npm run inspector        # explore the tools in the MCP Inspector

Fixtures are generated rather than captured: npm run build:fixtures writes acorpus of invented titles and passages, so tests are deterministic and noLibrary content lives in this repository. Anything touching time runs on a fakeclock pinned to a fixed instant.

The access layer under src/loc does not import the MCP SDK and is publishedseparately as mcp-libraryofcongress/client, usable as a plain library.

Contributing

Bugs, questions and ideas all belong inthe issue tracker.Pull requests are welcome; please open an issue first so we can agree on whatthe right answer is before you write it. CONTRIBUTING.md hasthe detail, and SECURITY.md covers anything exploitable.

Support

Free, and it stays free. If it saved you some time, you canbuy me a coffee.

License

MIT. See LICENSE. The licence covers this source code only, not thematerial retrieved through it, which carries whatever terms its depositorattached, and often none at all.

This is an unofficial project, with no affiliation to or endorsement by theLibrary of Congress.

mcp-libraryofcongress (français)

Un serveur MCP pour la Library of Congress. Cherchezune phrase dans le texte des pages de journaux américains numérisées,parcourez le catalogue par type de document, lisez une fiche, et listez lescollections construites par un conservateur. Sans clé d'API, sans compte, sansconfiguration.

Démarrage rapide

Installation en un clic

Install in CursorInstall in VS Code

Claude Code

claude mcp add libraryofcongress -- npx -y mcp-libraryofcongress

Claude Desktop, Cursor, et tout client utilisant le format standard

{
  "mcpServers": {
    "libraryofcongress": {
      "command": "npx",
      "args": ["-y", "mcp-libraryofcongress"]
    }
  }
}

Node 20 ou plus récent.

Bundle, sans npm

Téléchargez mcp-libraryofcongress-<version>.mcpb depuisla dernière releaseet ouvrez-le. Un client compatible l'installe seul, sans npm ni fichier deconfiguration à modifier.

Outils

Outil Rôle Paramètres principaux
search_newspapers Trouve une phrase dans le texte des pages de journaux numérisées. query, location, publication, year_from, limit
search_items Cherche un catalogue : livres, photos, cartes, sons, et le reste. query, media_type, subject, location, year_from
get_item Lit une fiche, section par section. identifier, sections, offset
list_collections Les collections numériques, avec le filtre que chacune accepte. limit, page, max_description_chars

Le serveur est en lecture seule. Il ne téléverse rien et n'écrit rien.

Chercher dans les journaux est le cœur du sujet

Une recherche de catalogue lit les titres, les auteurs et les descriptions.search_newspapers lit ce que la reconnaissance de caractères a tiré demillions de pages de journaux américains numérisées, et répond donc à unequestion qu'aucun autre outil ici ne sait traiter : quel numéro a imprimé cettephrase. Une correspondance revient avec le journal, la date, le feuillet dunuméro, l'État de publication, et une adresse qui ouvre ce feuillet avec larequête appliquée.

Mettez une phrase entre guillemets doubles pour la faire correspondre en entier.Sans guillemets, les mots sont cherchés séparément, ce qui trouve bien plus.

Le corpus couvre tous les États et un siècle et demi : une phrase seule ramènedonc beaucoup de choses que la question ne demandait pas. Trois arguments laresserrent : location limite aux journaux publiés dans un État, publicationà un seul titre, et year_from avec year_to à une plage d'années. Chacunprend la formulation de la Bibliothèque, que chaque correspondance porte : lechamp state d'une ligne est ce qu'attend location, et son champpublication est ce qu'attend publication. search_items avecmedia_type: "newspapers" liste les journaux eux-mêmes, et un titre y est aussila formulation attendue.

search_newspapers(
  query: '"ellis island" immigration',
  location: "new york",
  publication: "new-york tribune (new york [n.y.]) 1866-1924",
  year_from: 1900, year_to: 1910
)

Un filtre que le corpus ne reconnaît pas ne correspond à rien, et une réponsevide se lirait comme une Bibliothèque ne détenant aucune page de ce genre. Larecherche est donc relancée sans le resserrement, et une note dit ce qui a étéécarté.

Trois choses qu'il refuse de prétendre savoir

total compte des pages, et il se pagine. C'est le nombre de feuillets quicorrespondent, pas un nombre d'occurrences. Demandez la page 2, la page 3, plutôtque de prendre la première réponse pour la totalité.

Un extrait est parfois le début de la page. La Library renvoie le début dutexte d'une page avec chaque ligne de résultat plutôt que la page entière, sibien que les mots cherchés se trouvent souvent plus bas que ce texte ne va.words_located indique lequel des deux cas s'est produit : vrai et le passageest centré sur les mots, faux et c'est le début du feuillet. Suivez source_urlpour la suite.

Le texte numérisé est lu par une machine. Les extraits en portent lesfautes. Citez-les comme tels et suivez le lien.

Un catalogue par type de document

search_items exige media_type, car la Library tient un catalogue distinctpour chaque type de document : books, photos, maps, audio,film-and-videos, manuscripts, notated-music, newspapers. Aucune adressene les interroge tous à la fois, et un même titre peut exister dans plusieurs.

Le filtrage est typé plutôt que textuel : year_from, year_to, subject,location, language, collection, online_only, sort. Un filtre qui necorrespond à rien est mis de côté, la recherche est relancée sans lui, et laréponse nomme ce qui a été écarté : une orthographe différente de celle de laLibrary est ainsi signalée comme telle, et non comme un fonds vide.

Par défaut, seuls les documents disposant d'une copie numérisée reviennent.online_only: false inclut les fiches que la Library ne conserve qu'en rayon.

Lire une fiche, et trouver un corpus

get_item prend un identifiant. Un identifiant peut contenir des barresobliques : un numéro de journal se nomme par son titre, sa date et son éditionréunis, comme sn83045462/1929-02-03/ed-1. Les sections sont facultatives,basic, citations, resources et full_metadata, car les copies servies d'unscan et la liste complète des champs pèsent chacune plus lourd que la fichequ'elles décrivent. Une description longue se pagine par décalage de caractèreset reprend à une fin de ligne : quand next_offset n'est pas nul, rappelezl'outil avec offset réglé sur cette valeur.

list_collections montre les ensembles qu'un conservateur a choisis, décrits etpubliés ensemble, pour voir ce qui existe avant de chercher. Chaque ligne portecollection_filter, la formulation exacte que search_items accepte dans sonargument collection.

Ce que les réponses affirment

Un échec n'est jamais un résultat vide. Une requête refusée estinvalid_input, une réponse illisible est parse_failure, et seule une ficheréellement vide est une absence. Taire un échec revient à faire dire « il n'y ena pas » à un modèle, ce qui est une affirmation fausse sur le monde.

Un vide n'est jamais imprimé comme une valeur. Une fiche sans conditionsd'usage renvoie null et précise que ce silence n'est pas une autorisation.

Un compteur porte le nom de ce qu'il compte. Le site publie le nombre derésultats et le nombre de pages sous des noms qui se ressemblent, et l'un estl'autre multiplié par la taille de page. Seul le nombre de résultats est publiécomme total.

Chaque réponse porte son lien, et les notes qui la nuancent atteignent lebloc de texte, pour qu'un client qui n'affiche que du texte les lise aussi. Untexte publié par un tiers ne peut pas imiter les lignes propres au serveur.

Droits

Les métadonnées de la Library of Congress sont dans le domaine public. Lesdocuments décrits ne le sont pas toujours : les droits varient selon le dépôt etsont souvent tus. Une fiche indique les conditions qu'elle porte, et une fichequi n'en porte aucune n'accorde rien. Créditez la Library of Congress et liez ceque vous utilisez.

Configuration

Toutes les variables sont optionnelles, à déclarer dans le bloc env de votreclient.

Variable Défaut Rôle
LOC_USER_AGENT (aucun) Identifiez votre client. L'identifiant du projet est ajouté, pour que la Library puisse joindre une personne.
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.
LOC_TIMEOUT_MS 30000 Délai par requête pour le catalogue, les fiches et les collections. Accepté entre 1000 et 120000.
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.
LOC_MAX_RETRIES 3 Tentatives en cas de limitation ou d'erreur passagère, jusqu'à 8.
LOC_CACHE_TTL_MS 900000 Durée de vie du cache mémoire. 0 le désactive.
LOC_CACHE_MAX_ENTRIES 200 Taille du cache mémoire, jusqu'à 5000.
LOC_LOG_LEVEL error silent, error, info ou debug. Sortie sur stderr.

Une valeur hors bornes est refusée avec une ligne sur stderr et le défauts'applique : une faute de frappe dans une variable ne retire aucun outil.

Ce que ce serveur doit à la Library

La Library publie deux plafonds : vingt requêtes par minute pour les réponsesJSON, et dix par minute sur l'ensemble du site. Ce serveur retient le plus basdes deux, soit une requête toutes les six secondes, et tient ce plancher que leréglage vienne de l'environnement ou d'un objet de configuration passé au clientpublié. Les réponses prennent donc quelques secondes ; une question répétée estservie depuis la mémoire. Il n'envoie qu'une requête à la fois, met en cache cequ'il lit, et s'identifie avec une adresse où joindre une personne. Un appelantpeut dire qui il est ; cette adresse est ajoutée, pas remplacée.

Le fichier robots du site interdit /search à tout client et demande cinqsecondes entre les requêtes. Aucune adresse construite ici n'atteint ce chemin,et l'écart tenu est plus large que celui demandé. Le site renvoie des liens vers/search dans ses blocs de facettes : ils sont lus comme des étiquettes, jamaissuivis.

Utiliser le client seul

La couche qui parle au site n'importe rien du protocole et est publiéeséparément, avec la cadence, le cache et la taxonomie d'erreurs.

import { LocClient } from "mcp-libraryofcongress/client";

const client = new LocClient();
const { data } = await client.searchNewspapers('"cure for influenza"', 5, 1, {
  maxChars: 300,
  maxCount: 2,
});
console.log(data.paging.resultCount, data.hits[0]?.sourceUrl);

Dépannage

rate_limited. Le site demande à ce client de ralentir, ou ce serveur a étésollicité au-delà de sa cadence. Cela ne dit rien de ce que la Library conserve.Attendez et redemandez.

parse_failure. Une réponse est arrivée dans une forme illisible pour ceserveur, ce qui inclut une réponse longue coupée en transit. En général, uneroute a changé. Mercid'ouvrir une issueavec les arguments utilisés.

not_found. Le site a répondu, et ne conserve rien à cette adresse.

Une recherche catalogue vide. Vérifiez media_type : un même titre existesous plusieurs types de document, et chacun est un catalogue à part. Une phraseimprimée dans un journal relève de search_newspapers.

Développement

npm install
npm test                 # tests unitaires, sans réseau
npm run typecheck
npm run build
LOC_LIVE=1 npm run test:live   # une requête par route sur le vrai site
npm run inspector        # explorer les outils dans le MCP Inspector

Les fixtures sont générées, pas capturées : npm run build:fixtures écrit uncorpus de titres et de passages inventés, ce qui rend les tests déterministes etévite de stocker du contenu de la Library dans ce dépôt. Tout ce qui touche autemps tourne sur une horloge figée à un instant fixe.

La couche d'accès sous src/loc n'importe pas le SDK MCP et est publiéeséparément sous mcp-libraryofcongress/client, utilisable comme bibliothèque.

Contribuer

Bugs, questions et idées vont dansle suivi d'issues.Les pull requests sont bienvenues ; ouvrez d'abord une issue pour qu'ons'accorde sur la bonne réponse avant que vous n'écriviez le code.

Soutenir

Gratuit, et ça le reste. Si ça vous a fait gagner du temps, vous pouvezm'offrir un café.

Licence

MIT, voir LICENSE. La licence couvre uniquement ce code source, pasles documents récupérés par son intermédiaire, qui portent les conditions queleur déposant y a attachées, et souvent aucune.

Projet non officiel, sans affiliation à la Library of Congress ni approbation desa part.

MCP Server · Populars

MCP Server · New

    drakulavich

    Kesha Voice Kit

    Give your tools a voice — speech to text and back, 25 languages, up to ~19× faster than Whisper. On your machine.

    Community drakulavich
    lobu-ai

    Lobu — Open-source backend for AI teammates

    Open-source control plane and runtime for organisational agents: shared company context, isolated execution, approvals and MCP.

    Community lobu-ai
    minipuft

    Claude Prompts MCP Server

    Wolfflow: Model Context Protocol (MCP) server for reusable prompt templates, multi-step workflow chains, and quality gates. Compose agentic workflows with an operator syntax; export as native skills to Claude Code, Cursor, OpenCode, and Gemini CLI.

    Community minipuft
    docmancer

    Docmancer

    Find out what your coding agents already know. Docmancer indexes the memory, rules, and instructions Claude Code, Codex, Cursor, and Gemini wrote on your machine, then carries the durable parts to every agent. Local-first, MIT.

    Community docmancer
    lineai-intelligence

    codelogic-mcp-server

    An MCP Server to utilize Codelogic's rich software dependency data in your AI programming assistant.