
Plugs Claude into three Library of Congress APIs: the main digital collections search (with format, date, subject, and location filters), Chronicling America's newspaper archive with full OCR text extraction, and the LC Subject Headings authority file. You get six tools that cover discovery, full metadata retrieval, and newspaper page text fetching. The newspaper tools are the standout piece. They return OCR excerpts inline during search, then pull complete ALTO XML text for individual pages, which is useful for historical research or citation work that needs the actual digitized words, not just catalog records. Also exposes a resource template for stable item references.
Search LOC digital collections, browse Chronicling America newspapers with full OCR text, and look up LC Subject Headings via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://libofcongress.caseyjhand.com/mcp
Library of Congress digital collections, Chronicling America newspaper archives, and LC Subject Headings (LCSH) authority data. Search items and newspaper pages, retrieve full item metadata and OCR text, resolve LCSH subject terms, and browse curated collections from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
libofcongress_search_items | Search LOC digital collections by keyword with format, date range, subject, location, and collection filters. |
libofcongress_get_item | Retrieve full metadata for a specific LOC digital item — imprint, contributors, subjects, rights, formats, its digital resources, and a paged list of its pages. |
libofcongress_search_newspapers | Search historical newspaper pages in the Chronicling America corpus, with an excerpt of each page's opening OCR text. |
libofcongress_get_newspaper_page | Retrieve the full text and metadata of one page — a newspaper page, or a book or manuscript page from libofcongress_get_item — or only the passages around a search term. |
libofcongress_search_subjects | Search Library of Congress Subject Headings (LCSH) by keyword. |
libofcongress_browse_collections | List and browse LOC curated digital collections, optionally filtered by keyword. |
| Resource | Description |
|---|---|
libofcongress://item/{+item_id} | LOC digital item metadata by ID — stable URI for injecting item context into agent conversations. |
All resource data is also reachable via libofcongress_get_item. Use libofcongress_search_items to discover item IDs first.
libofcongress_search_items toolphoto, map, newspaper, manuscript, audio, film, book, notated-music), inclusive year range (date_start/date_end), subject (an LCSH heading from libofcongress_search_subjects or a facet term, split at -- into lowercase terms with dates dropped and ANDed — United States--History--Civil War, 1861-1865 filters on united states + history + civil war), location, and collection_slug (from libofcongress_browse_collections, mutually exclusive with format); up to 100 results per page — a larger limit (up to 1000) is served as a 100-result page with effectiveLimit and a noticedate is the date as LOC catalogs it (c1915 Feb. 19.), not the padded YYYY-01-01 sort key LOC uses for photos; where the catalog holds only a coded value (18950000, 185u, uuuu), the row's readable date (1895, 185?) stands in, or date is absentis_item — true for catalog items whose id resolves via libofcongress_get_item, records keyed by an lccn.loc.gov permalink included (their id is the bare LCCN), false for collections, exhibit/guide pages, newspaper pages, catalog.loc.gov records, and hdl.loc.gov finding aids, whose url should be opened insteadinclude_facets: true adds subject_facets and location_facets — LOC's top 10 subject and location terms across all matches, each with its item count, the values the subject and location filters accept. Off by default, since the facet block adds LOC response time to every searchlibofcongress_get_item toolid, simple or multi-segment (newspaper issues: sn95047246/1935-09-05/ed-1), or a loc.gov item link (https://www.loc.gov/item/2017760320/, http:// or without www., item/2017760320), read as its bare ID, or an LCCN permalink (https://lccn.loc.gov/2005567251, http:// or without a scheme), read as its LCCN in normalized form (lccn.loc.gov/2005-568294 as 2005568294, sn 83030214 as sn83030214); any other URL — a /resource/ page URL, another lccn.loc.gov path, another host — is rejected as invalid_item_id before any request. Returns the imprint (place, publisher, and date as cataloged: New York : Werner School Book Company, c1900.), contributors with their roles, LCSH subject_headings as cataloged (Transportation--Oklahoma--Maps), subject_terms (the lowercase facet terms the search subject filter accepts), notes, summary, languages, locations, rights, physical description, call number, former IDs, formats, and access_restricteddate is the date as LOC catalogs it (1936 Apr., 1909-1911.), not the padded sort key LOC keeps for photos, and is absent when the record holds only a coded value (uuuu, ||||, 0w0 d1804 d1806)rights_information is plain text: where LOC sends its rights statement as HTML, the tags are dropped and each link's target is kept as an absolute URL (Copyright and other Restrictions (https://www.loc.gov/legal/)). Catalog text from www.loc.gov arrives with its HTML character references (’, …) decoded, on every tool; page text from libofcongress_get_newspaper_page is returned as the text service sends it, since that text is not HTML-escapedresources lists each digital resource with its page count, representative image, pdf, and audio (MP3); pages lists a window of the item's pages — each a page_url and has_text — 50 at a time by default (page_offset, page_limit, page_total, next_page_offset; at most 100 per call — a larger page_limit, up to 1000, is served as 100 with effectivePageLimit and a notice). A page with text reads through libofcongress_get_newspaper_page; a blank page can still show has_text: true (LOC lists a placeholder text file for it), and reads as empty text with a notice. include_files: true adds each listed page's download URLs (JPEG sizes, JP2, TIFF, text)related_items carries related record IDs or URLs in full; fields absent upstream are omitted rather than filledlibofcongress_search_newspapers tooldate_start/date_end take an integer year or a YYYY, YYYY-MM, or YYYY-MM-DD string), US state (full name or postal code, matched against the state LOC indexes each title under), and newspaper title (the exact newspaper_title of a search result); up to 100 results per page — a larger limit (up to 1000) is served as a 100-result page with effectiveLimit and a noticestates (every state LOC indexes the title under), and the url that libofcongress_get_newspaper_page takes verbatimmatch to libofcongress_get_newspaper_page for the passages around itlibofcongress_get_newspaper_page toolurl from a libofcongress_search_newspapers result, or a pages[].page_url from libofcongress_get_item — newspaper, book, and manuscript pages alike. Also reads the same /resource/ URL over http:// or without www., a newspaper page path ending /ed-N/seq-M/, and a Chronicling America page link (chroniclingamerica.loc.gov/lccn/<lccn>/<date>/ed-N/seq-M/ or www.loc.gov/chroniclingamerica/lccn/…), each as its https://www.loc.gov/resource/…/?sp=M page; the output page_url is that URL, with only the sp query parameter kept. Rejected as invalid_page_url before any request: a Chronicling America link that names no single page (an issue link without seq-M, an /ocr/ view, a file link), any other host, a path with a dot segment or percent-escape, and a non-numeric spocr_text — in the text output, a fenced code block holding the same text, so OCR noise never reads as Markdown — with the issue's newspaper_title, date, place_of_publication, states, edition, the page's sequence, and the page count segment_count; ocr_available: false marks an image-only page, and a notice says why a page with text came back empty — a text service that did not answer (retry), a body that could not be read as text (retrying will not help), or a blank page. LOC's blank-page placeholder ([NO TEXT AVAILABLE FOR THIS PAGE] as the whole text) is returned as empty ocr_textitem_title is the title of the item the page belongs to, on every page type — the book or manuscript title (the text output's heading for those pages), or a newspaper issue's title with place and date beside the unchanged newspaper_titleitem_id names the item the page belongs to — the newspaper issue (sn83030214/1912-04-16/ed-1), book, or manuscript volume (mm77093930); pass it to libofcongress_get_item to list every page with its page_url and has_text, to follow an article onto a later page or read a book page by page. It comes from the page response LOC already sends, so it costs no extra requestmatch returns the passages around a term instead of the whole page — pass the query you searched with. Space-separated words are separate terms and a double-quoted span is one phrase; each matches case-insensitively as literal text, with line breaks read as spaces. matches holds up to 10 passages, about 300 characters either side of each occurrence (overlapping ones merged up to 1,200 characters, never overlapping, in page order; when more would be needed, the rarest terms' occurrences are chosen first, so sinking of the titanic shows the Titanic rather than ten passages of "the"), match_count counts every occurrence, and ocr_text is empty; a notice says when nothing matched or some occurrences fall outside the passages, which the whole page (no match) holds. On a 50,241-character page of the 16 April 1912 New-York Tribune, match: "titanic" returns 2 passages of about 600 characters each — about 3 KB across both response surfaces instead of 104 KBlibofcongress_search_subjects toollimit, up to 1000, returns up to 50 with a notice); returns the authorized LCSH label and stable LOC uri, ranked by relevance with an exact match for the query first — pass the label verbatim as libofcongress_search_items's subject filter, since LCSH uses standardized wording ("World War, 1939-1945", "Aerial photography")aerial photog) falls back to the headings that begin with it; when more headings match than the call returns, the response says how manylibofcongress_browse_collections toollimit (up to 1000) is served as a 100-collection page with effectiveLimit and a noticeslug for libofcongress_search_items's collection_slug (taken from the loc.gov route, not derivable from the title); item_count is approximate and omitted when LOC doesn't provide itlibofcongress://item/{+item_id} resourcelibofcongress_get_item, as application/json, with its default page window (the first 50 pages, no files); next_page_offset marks where libofcongress_get_item's page_offset continuesitem_id comes from a libofcongress_search_items result's id field, or from libofcongress_get_item; it is checked like the tool's item_id, so a URL or path that is not an item ID is rejected as invalid_item_id before any requestlibofcongress://item/sn95047246/1935-09-05/ed-1); percent-encoded slashes (%2F) also resolveBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Library of Congress-specific:
www.loc.gov: concurrent calls start in arrival order, at least LOC_REQUEST_DELAY_MS apart (default 3100ms, ~19 req/min), and no 60-second window holds more than LOC's documented 20 starts, whatever the delay is set to. A call the queue cannot start within its time budget fails at once as server_busy, with the seconds to wait in retryAfterwww.loc.gov answers with a Cloudflare challenge (cf-mitigated: challenge) or an HTML page instead of JSON — LOC's documented CAPTCHA response under heavy load — the call fails as upstream_unavailable with retryAfter: 60, and further www.loc.gov requests, queued ones included, are refused for 60 seconds without being sent. libofcongress_search_subjects (id.loc.gov) is unaffectedrequest_timed_out with a recovery hint before a client's 60-second request timeout. Network drops retry with backoff while budget remains; the 429 rate-limit path is never retried, since a retry would deepen LOC's 1-hour blockupstream_unavailable with a retry hint, on every tool and the item resource. The message names the upstream and any HTTP status, never a URL, host, or transport error textAgent-friendly output:
notice field with recovery hints — echoes the applied filters and suggests how to broadentotal, page, pages, has_next), capped at LOC's ~100,000-item retrieval ceiling, with a notice disclosing how to page past itocr_available and is_item discriminator fields let callers branch on data availability without parsing textA public instance is available at https://libofcongress.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"libofcongress-mcp-server": {
"type": "streamable-http",
"url": "https://libofcongress.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"libofcongress-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/libofcongress-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_SESSION_MODE": "stateless",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"libofcongress-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/libofcongress-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_SESSION_MODE": "stateless",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"libofcongress-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "MCP_SESSION_MODE=stateless",
"ghcr.io/cyanheads/libofcongress-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
LOC_USER_AGENT for polite access.git clone https://github.com/cyanheads/libofcongress-mcp-server.git
cd libofcongress-mcp-server
bun install
cp .env.example .env
# edit .env if you want to set LOC_USER_AGENT or LOC_REQUEST_DELAY_MS
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
| Variable | Description | Default |
|---|---|---|
LOC_USER_AGENT | User-Agent header sent with LOC API requests. LOC recommends a descriptive value for polite access. | libofcongress-mcp-server/0.5.1 |
LOC_REQUEST_DELAY_MS | Delay in milliseconds between LOC API requests to stay under the 20 req/min rate limit. | 3100 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_SESSION_MODE | HTTP session mode. This server is explicitly stateless. | stateless |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t libofcongress-mcp-server .
docker run --rm -p 3010:3010 libofcongress-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/libofcongress-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, resource, and initializes services. |
src/config | Server-specific environment variable parsing (LOC_USER_AGENT, LOC_REQUEST_DELAY_MS). |
src/mcp-server/tools | Tool definitions (*.tool.ts) — six LOC tools. |
src/mcp-server/resources | Resource definitions — libofcongress://item/{+item_id}. |
src/services/loc-api | LocApiService wrapping www.loc.gov — search, item fetch, page text, collection browser. |
src/services/lc-linked-data | LcLinkedDataService wrapping id.loc.gov — LCSH subject heading suggest. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
LOC_USER_AGENTdefault: libofcongress-mcp-server/0.2.8User-Agent header sent with LOC API requests. LOC recommends a descriptive value for polite access.
LOC_REQUEST_DELAY_MSdefault: 3100Delay in milliseconds between LOC API requests to stay under the 20 req/min rate limit.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.