MCP server: One MCP server.

libofcongress-mcp-server

@cyanheadsMCP server · External

Search LOC digital collections, Chronicling America newspapers (full OCR), and LC Subject Headings.

Government open datav0.5.1Apache-2.0TypeScript
Freeno charge per call

One address for any MCP client. No sign-in needed on this server.

What it does

6 read

Grouped by tool name. At most: only reads or looks up information.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Tools

6 · grouped by tool name · checked 22 Oct 2026

Read · 6Look things up

  • libofcongress_search_items
  • libofcongress_get_item
  • libofcongress_search_newspapers
  • libofcongress_get_newspaper_page
  • libofcongress_search_subjects
  • libofcongress_browse_collections
Try it in the Inspector
README11 min read

@cyanheads/libofcongress-mcp-server

Search LOC digital collections, browse Chronicling America newspapers with full OCR text, and look up LC Subject Headings via MCP. STDIO or Streamable HTTP.

6 Tools • 1 Resource

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Overview

Library of Congress digital collections, Chronicling America historic newspapers, and LC Subject Headings. Search items and newspaper pages, read full metadata and OCR text, resolve subject terms, and browse curated collections.

Tools

ToolDescription
libofcongress_search_itemsSearch LOC digital collections by keyword with format, date range, subject, location, and collection filters.
libofcongress_get_itemRetrieve 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_newspapersSearch historical newspaper pages in the Chronicling America corpus, with an excerpt of each page's opening OCR text.
libofcongress_get_newspaper_pageRetrieve 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_subjectsSearch Library of Congress Subject Headings (LCSH) by keyword.
libofcongress_browse_collectionsList and browse LOC curated digital collections, optionally filtered by keyword.

Resources

ResourceDescription
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.

Capability reference

libofcongress_search_items tool

  • Filters: eight material formats (photo, 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), 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 100 with effectiveLimit and a notice
  • Each result carries date as LOC catalogs it and is_item: true for catalog items whose id resolves via libofcongress_get_item, false for collections, exhibit/guide pages, newspaper pages, catalog.loc.gov records, and hdl.loc.gov finding aids, whose url should be opened instead
  • include_facets: true adds subject_facets and location_facets (LOC's top 10 terms across all matches, each with its item count); off by default since it slows every search. A zero-result notice names the subject terms and location applied

libofcongress_get_item tool

  • Takes a search result's id (simple or multi-segment, e.g. sn95047246/1935-09-05/ed-1), a loc.gov item link, or an lccn.loc.gov permalink; any other URL is rejected as invalid_item_id before any request
  • Returns the imprint, contributors with roles, LCSH subject_headings, subject_terms (the lowercase terms the search subject filter accepts), notes, summary, languages, locations, rights_information as plain text, physical description, call number, former IDs, formats, and access_restricted; fields absent upstream are omitted
  • resources lists each digital resource with its page count, image, pdf, and audio (MP3). pages lists 50 pages 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), each with a page_url and has_text that read through libofcongress_get_newspaper_page. include_files: true adds each page's download URLs

libofcongress_search_newspapers tool

  • Filters: keyword, inclusive date range down to the day (date_start/date_end take an integer year or a YYYY, YYYY-MM, or YYYY-MM-DD string), US state (full name or postal code), 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 100 with effectiveLimit and a notice
  • Each result carries the opening ~500 characters of the page's OCR text, states, and the url that libofcongress_get_newspaper_page takes verbatim
  • The excerpt is the page's first lines, not the text around the match; pass the same query as match to libofcongress_get_newspaper_page for the passages around it

libofcongress_get_newspaper_page tool

  • Takes the url from a libofcongress_search_newspapers result, or a pages[].page_url from libofcongress_get_item (newspaper, book, and manuscript pages alike), plus Chronicling America page links; a link that names no single page, any other host, or a malformed path or sp is rejected as invalid_page_url before any request
  • Returns the page's text as ocr_text with newspaper_title, date, place_of_publication, states, edition, sequence, segment_count, item_title, and item_id (pass it to libofcongress_get_item to list the item's pages). ocr_available: false marks an image-only page, and a notice explains an empty result: the text service did not answer (retry), the body could not be read as text, or the page is blank
  • match returns only the passages around a term — pass the query you searched with. Space-separated words are separate terms and a double-quoted span is one phrase, matched case-insensitively. matches holds up to 10 passages of about 300 characters either side of each occurrence, match_count counts every occurrence, and ocr_text is empty; a notice says when nothing matched or some occurrences fall outside the passages

libofcongress_search_subjects tool

  • Keyword query, up to 50 results per call (default 10; a larger limit, up to 1000, returns up to 50 with a notice); returns the authorized LCSH label and stable LOC uri, exact match first
  • Pass the label verbatim as libofcongress_search_items's subject filter. A partial word (aerial photog) falls back to headings that begin with it

libofcongress_browse_collections tool

  • Optional keyword filter on collection name and description; up to 100 collections per page — a larger limit (up to 1000) is served as a 100-collection page with effectiveLimit and a notice
  • Each collection carries a slug 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 it

libofcongress://item/{+item_id} resource

  • Returns the same record as libofcongress_get_item, as application/json, with the first 50 pages and no files; next_page_offset marks where libofcongress_get_item's page_offset continues
  • item_id comes from a libofcongress_search_items result's id; anything that is not an item ID is rejected as invalid_item_id. Multi-segment newspaper IDs keep their slashes (libofcongress://item/sn95047246/1935-09-05/ed-1)

Features

Built 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:

  • Request queue in front of www.loc.gov: calls start in arrival order, at least LOC_REQUEST_DELAY_MS apart (default 3100ms, ~19 req/min), never exceeding LOC's 20 per minute. A call that cannot start in time fails as server_busy with retryAfter
  • Rate-limit handling: a 429 triggers LOC's 1-hour block, and calls fail without sending anything until it lifts (never retried); a Cloudflare challenge or HTML page instead of JSON fails as upstream_unavailable with retryAfter: 60. libofcongress_search_subjects (id.loc.gov) is unaffected
  • A 50-second budget per tool call covers queue wait, retries, and the OCR fetch, so a slow upstream fails as request_timed_out; other upstream failures (dropped connections, 5xx or 403, empty or truncated bodies) fail as upstream_unavailable with a retry hint
  • Pages past the end of the results or past the retrieval ceiling return an empty result with a notice, not an error
  • Newspaper OCR is surfaced as-is; 19th-century or degraded pages may contain garbled text

Agent-friendly output:

  • Empty results include a notice with recovery hints that echo the applied filters
  • Every search response carries pagination status (total, page, pages, has_next), capped at LOC's ~100,000-item retrieval ceiling
  • ocr_available and is_item let callers branch without parsing text
  • Every typed error carries a recovery hint

Getting started

Public Hosted Instance

A public instance is available at https://libofcongress.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

json
{
  "mcpServers": {
    "libofcongress-mcp-server": {
      "type": "streamable-http",
      "url": "https://libofcongress.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file.

json
{
  "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):

json
{
  "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:

json
{
  "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:

sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+).
  • No API key required — the LOC JSON API and LC Linked Data endpoints are open. LOC recommends a descriptive LOC_USER_AGENT for polite access.

Installation

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/libofcongress-mcp-server.git
  1. Navigate into the directory:
sh
cd libofcongress-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
cp .env.example .env
# edit .env if you want to set LOC_USER_AGENT or LOC_REQUEST_DELAY_MS

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts.

VariableDescriptionDefault
LOC_USER_AGENTUser-Agent header sent with LOC API requests. LOC recommends a descriptive value for polite access.libofcongress-mcp-server/0.5.1
LOC_REQUEST_DELAY_MSDelay in milliseconds between LOC API requests to stay under the 20 req/min rate limit.3100
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for HTTP server.3010
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_SESSION_MODEHTTP session mode. This server is explicitly stateless.stateless
MCP_LOG_LEVELLog level (RFC 5424).info
LOGS_DIRDirectory for log files (Node.js only).<project-root>/logs
LOG_TOOL_FAILURE_PAYLOADSLog 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_TYPEStorage backend.in-memory
OTEL_ENABLEDEnable OpenTelemetry instrumentation (spans, metrics, completion logs).false

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    sh
    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

sh
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.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools, resource, and initializes services.
src/configServer-specific environment variable parsing (LOC_USER_AGENT, LOC_REQUEST_DELAY_MS).
src/mcp-server/toolsTool definitions (*.tool.ts) — six LOC tools.
src/mcp-server/resourcesResource definitions — libofcongress://item/{+item_id}.
src/services/loc-apiLocApiService wrapping www.loc.gov — search, item fetch, page text, collection browser.
src/services/lc-linked-dataLcLinkedDataService wrapping id.loc.gov — LCSH subject heading suggest.
tests/Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools and resources via the arrays in src/index.ts
  • Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields

Contributing

Issues are welcome. Run checks and tests before submitting:

sh
bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Similar modules

Browse Government open data