Skip to main content
Glama
nestordemeure

gallica-mcp

Gallica MCP Server

MCP server for Gallica, the digital library of the Bibliothèque nationale de France (BnF). Search and access OCR text from millions of digitized documents:

  • search_gallica: Text search with boolean operators (AND, OR, NOT), exact phrase matching with quotes, and parentheses for grouping. Returns paginated results (50 docs/page) with metadata.

  • get_snippets: Retrieves text excerpts showing where search terms appear within a specific document. Includes page numbers for each snippet.

  • advanced_search_gallica: Search with filters for creators (authors), document types, date ranges, language, title, subject heading, publisher, holding library, and a minimum OCR quality score. All filter parameters are optional.

  • download_text: Downloads OCR text for a range of a document's pages, given its ARK identifier. Caches each page locally for fast repeated access.

The search functions convert your inputs into CQL (Contextual Query Language) queries that are sent to Gallica's SRU API.

There are two ways to use it: an MCP server for clients that speak MCP, and a gallica CLI for agents driven through a shell. Both share one client, one cache and one set of behaviours. The CLI is what the bundled gallica-search skill uses, and it exposes every filter unconditionally rather than hiding them behind an install flag.

Installation

Install the code

uv sync

Install the CLI

uv tool install .        # puts `gallica` on your PATH

Install to MCP CLIs

Installs to Claude Code, Codex CLI, and Gemini CLI:

# Basic installation (search_gallica + download_text tools only)
uv run gallica-mcp-install

# With advanced search enabled (adds advanced_search_gallica tool)
uv run gallica-mcp-install --enable-advanced-search

Verify the installation:

claude mcp list   # For Claude Code
codex mcp list    # For Codex CLI
gemini mcp list   # For Gemini CLI

Related MCP server: Gallica/BnF MCP Server

Usage

CLI

gallica search '"Tour Eiffel"'                                 # first page, best matches first
gallica search '"Tour Eiffel"' --type monographie --from-year 1889 --to-year 1900
gallica search '"Tour Eiffel"' --min-ocr-quality 99            # only legibly scanned documents
gallica search '"Tour Eiffel"' --publisher Hachette            # 287 results, not 622,818
gallica search --subject 'Tour Eiffel (Paris)'                  # catalogue subject heading, no text query
gallica search --library 'departement Arts du spectacle'        # one holding collection, browsed whole
gallica search 'Exposition universelle' --from-year 1889 --to-year 1890 --sort date_asc --pages all
gallica snippets 'ark:/12148/bpt6k1910270z' '"Tour Eiffel"'    # where it appears, with PAG_ page ids
gallica get 'ark:/12148/bpt6k1910270z' --pages 2-3             # cached OCR text path

Search returns documents without snippets, so the workflow is search → snippets to judge a document cheaply → get only the pages worth reading. Boolean operators must be UPPERCASE. Add --json for machine-readable output.

snippets reports page identifiers like PAG_30, and get --pages accepts that form verbatim, so a page reference carries from one command to the next without translation.

Results are ordered by relevance, and that matters more than it sounds. Gallica's text index ranks rather than filters: a phrase search reports a long tail of loosely related documents, so "Tour Eiffel" claims 622,818 results while only the first page or two are actually about it. Treat the total as a ranking depth, not a count of matches. --sort date_asc/date_desc are available for chronological work, but they are worth using only on a query narrowed by filters until its total is plausible — on a broad query they bury the good material.

Filters

search takes --creator and --type (both repeatable, ORed), --from-year, --to-year, --language, --title, --subject, --publisher, --library, --min-ocr-quality, plus --include-restricted and --fuzzy. All are optional, and each is ANDed onto the query.

Four are worth singling out, because they behave unlike the text search and each carries an edge:

Flag

Selects on

Worth knowing

--subject

BnF catalogue subject heading, in French

A strict filter, so the reported total becomes a true count. But headings belong to catalogue records, so it returns zero periodical issues — never combine it with a newspaper sweep

--min-ocr-quality

OCR quality score, 0–100

The most broadly useful filter here. Any value above 0 excludes material with no OCR at all, such as engravings and image-only scans

--publisher

Publisher as printed on the item

The field also carries the place of publication in parentheses (E. Voisin (Paris)), so a city name matches that place. It is not a place-of-publication filter, and Gallica has no index that is

--library

Holding institution, from the record's provenance string

Reaches a partner collection or a BnF department whole, with no text query at all. Values are catalogue strings, so take them from a result's source field; accents may be omitted

Filters repair the result count. The six-figure totals come from the text index alone; metadata filters intersect it strictly, so a filtered query reports a number that means something. That is why narrowing beats paging deeper — --pages all is reasonable on a filtered query and reckless on a bare one.

A note on what is deliberately absent: Gallica documents a dewey index that only resolves at single-digit granularity, an sdewey index that returns nothing, and no place-of-publication index at all. Its SRU explain operation currently answers HTTP 500, so the index list cannot be discovered from the service itself. Flags exist only for facets verified against the live API.

Downloads are cached in $XDG_CACHE_HOME/gallica-mcp (override with --cache-dir or GALLICA_CACHE_DIR). The cache location does not depend on the working directory, so the CLI can be run from anywhere.

Rate limits

None of these numbers are published by BnF. Its stated policy for the Gallica APIs is open access "except in case of abusive usage" (api.bnf.fr); the one published figure covers the IIIF image API, which this client does not use. Everything below is either community practice or measured against the live service, so treat it as an observation with a date on it rather than a contract — and see Re-deriving these if the client starts getting refused where it used to work.

There are two separate budgets, and both are shared across processes via lock files in the cache directory, so parallel invocations draw on one allowance rather than each getting its own.

Search and snippets are paced at one request every 3 seconds, following established Gallica clients such as bnfimage and bnf_downloader, which treat that as the point above which BnF starts reading traffic as malicious. Community practice, not measurement. Override with GALLICA_MIN_REQUEST_INTERVAL.

OCR download is metered separately and much more tightly, because Gallica serves it one page per request from RequestDigitalElement. Measured 2026-07-29 from a single residential IP:

Pacing between requests

Successes before HTTP 429

3s

5

5s

4

Roughly 120 seconds of quiet restored the allowance. Slower pacing bought fewer requests, not more — which is the interesting part: it rules out a rate limit, because the server is counting requests in a window rather than measuring the gap between them. So the client models it as a token bucket: a burst of 4 pages refilling at one per 25 seconds, set one step under the observed cliff. Override with GALLICA_OCR_BURST and GALLICA_OCR_REFILL_SECONDS.

The 120s recovery figure is from probing rather than from the server; the client does not read a Retry-After header, and whether one is sent was not checked.

One further behaviour, same session: sustained overdraw stops producing 429s and starts stalling. After repeated refusals the endpoint simply stops answering and the connection times out. The client reports a timeout there as a block rather than a network fault, because retrying it is exactly wrong.

In practice: a handful of pages found via snippets costs seconds, an 8-page newspaper issue about two minutes, and a 200-page book over an hour. get declines documents longer than 20 pages unless you pass --pages (or --pages all to mean it). Pages are cached individually, so a download interrupted by the rate limit resumes rather than restarts.

Re-deriving these

If downloads start failing at counts these defaults should allow, the ceiling has moved and the measurement is worth redoing. It costs about ten minutes:

  1. Leave Gallica alone for several minutes, so the bucket is full and you are measuring the limit rather than your own recent traffic.

  2. Request consecutive ALTO pages of one public-domain document at a fixed spacing, recording the status of each: https://gallica.bnf.fr/RequestDigitalElement?O=<id>&E=ALTO&Deb=<n>. Stop at the first 429 — that count is the burst.

  3. Repeat at a different spacing. If the count does not rise with the gap, it is still a bucket and only the capacity changed.

  4. Wait, and probe single requests to find how long recovery takes. Divide by the burst for the refill rate.

Set GALLICA_OCR_BURST one below the smallest observed failure point, and update the table above with the new date. Do not run this while doing real research — it deliberately ends in a block, and a stalled endpoint takes a good while to come back.

MCP server

Run the server directly:

uv run gallica-mcp

Test with MCP Inspector:

uv run fastmcp dev src/gallica_mcp/server.py

Available Tools

3 tools
download_textA

Download OCR text from a Gallica document and save to cache in plain text format.

Args: identifier: Gallica ARK identifier (e.g., 'ark:/12148/bpt6k5619759j')

Returns: Path to the cached text file (as string)

IMPORTANT: The downloaded files are VERY LARGE (typically 100KB-1MB+ of text). DO NOT attempt to read the entire file into context. Use read tools with offset/limit parameters to read specific portions. Reading the full file will waste tokens and may cause performance issues.

Example: path = download_text("ark:/12148/bpt6k5619759j")

ParametersJSON Schema
NameRequiredDescriptionDefault
identifierYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It discloses that files are very large (100KB-1MB+), warns about token usage and performance, and explains the tool saves to cache and returns a file path. This is comprehensive behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with sections (Args, Returns, Important, Example), is concise, and each sentence adds value. There is no wasted text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one parameter and output schema present (context indicates true), the description covers purpose, usage, behavioral warnings, and an example. It is complete and self-contained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description adds significant meaning by explaining the identifier parameter as a Gallica ARK identifier with an example. This compensates for the lack of schema detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool downloads OCR text from a Gallica document and saves to cache in plain text format. It distinguishes from siblings like get_snippets and search_gallica by its specific action of downloading full text.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description includes a strong warning about large files, advising against reading the entire file and recommending use of read tools with offset/limit. This provides clear usage guidance, though it doesn't explicitly state when not to use the tool or suggest alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_snippetsA

Fetch text snippets showing where search terms appear in a Gallica document.

Uses the ContentSearch API to find and return text excerpts with page numbers. This is useful for locating specific content within a document after searching.

Args: identifier: Gallica ARK identifier (e.g., 'ark:/12148/bpt6k5619759j') query: Search terms to find in the document. Supports the same syntax as search_gallica: - Simple text: "Houdini" or "magic tricks" - Exact phrases: '"Harry Houdini"' - Boolean operators: "magic AND illusion", "Houdini OR Houdin" - Complex queries: '("Harry Houdini" OR "Jean Houdin") AND escape'

Returns: Dictionary containing: - identifier: The document ARK identifier - query: The search query used - snippets: List of text excerpts with: - text: Snippet text showing search terms in context - page: Page identifier (e.g., "PAG_200" for page 200)

Examples: # Get snippets for a specific document get_snippets("ark:/12148/bpt6k5619759j", "Houdini")

# Find exact phrase occurrences
get_snippets("ark:/12148/bpt6k5619759j", '"Harry Houdini"')

# Complex query
get_snippets("ark:/12148/bpt6k5619759j", "magic AND (illusion OR escape)")
ParametersJSON Schema
NameRequiredDescriptionDefault
identifierYes
queryYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses the underlying API (ContentSearch), specifies output structure with snippets and page numbers, and does not suggest any destructive behavior. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is well-structured with a clear purpose sentence, usage context, parameter docs, return structure, and examples. Slightly verbose for a simple tool, but each sentence adds value. Front-loaded with purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description thoroughly explains the return dictionary. It covers purpose, usage context, parameter details, return format, and examples. No gaps for a tool of this complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, but the description adds extensive meaning for both parameters: identifier is explained with an example ARK, query is described with full syntax and examples including exact phrases and Boolean operators. This far exceeds the schema's minimal definition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description explicitly states the tool fetches text snippets for search terms in a Gallica document, using a specific verb and resource. It distinguishes from siblings by mentioning it is useful after searching (complementing search_gallica) and its output includes page numbers, unlike download_text which likely returns full text.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Description provides context that the tool is for locating specific content after searching, and details query syntax matching search_gallica. However, it does not explicitly state when to use alternatives like download_text or when not to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_gallicaA

Search Gallica for documents matching a text query.

Searches across OCR content with support for boolean operators and exact phrases. Returns paginated results with metadata. Uses exact matching by default.

Note: To get text snippets showing where your search terms appear within documents, use the get_snippets tool with the document identifier and query.

Args: query: Text to search in OCR content. Supports CQL query syntax: - Simple text: "Houdini" or "magic tricks" (all words must appear, any order) - Exact phrases: '"Harry Houdini"' (use double quotes for exact phrase) - AND operator: "magic AND illusion" (both terms must appear - MUST BE UPPERCASE) - OR operator: "Houdini OR Houdin" (either term can appear - MUST BE UPPERCASE) - NOT operator: "magic NOT card" (first term yes, second term no - MUST BE UPPERCASE) - Parentheses: "(Houdini OR Houdin) AND escape" (group operations for precedence) - Combine all: '"Harry Houdini" AND (escape OR illusion) NOT death'

    Boolean operators (AND, OR, NOT) MUST be UPPERCASE.
    The query is converted to CQL format automatically and searches OCR text content.

page: Page number for pagination, 1-indexed (default: 1)

Returns: Dictionary containing: - page: Current page number - total_results: Total number of matching documents - total_pages: Total number of pages available - documents: List of documents with: - identifier: ARK identifier (use with download_text or get_snippets) - title: Document title - url: URL to view document on gallica.bnf.fr - creators: List of authors/creators - date: Publication date (if available) - type: Document type (e.g., monographie, périodique) - language: Language code (if available)

Examples: # Simple search search_gallica(query="Houdini")

# Exact phrase
search_gallica(query='"Harry Houdini"')

# Boolean operators
search_gallica(query="magic AND illusion")
search_gallica(query="Houdini OR Houdin")
search_gallica(query="magic NOT card")

# Complex query
search_gallica(query='("Harry Houdini" OR "Jean Houdin") AND (escape OR illusion)')
ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
pageNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It discloses that the tool searches OCR content, supports boolean operators and exact phrases, returns paginated results, and uses exact matching by default. It also details the return format and query syntax. It does not mention rate limits, authentication, or performance considerations, but covers core behavior well.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (Args, Returns, Examples). It is longer than average but justified by the complexity of the query syntax and the need for comprehensive documentation. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (CQL syntax, pagination, multiple output fields) and no output schema, the description is remarkably complete. It explains the return dictionary structure with all fields, provides multiple usage examples, and mentions sibling tools for related functionality.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema has only 2 parameters with 0% coverage. The description provides extensive semantics for the query parameter, including CQL query syntax, operators, and examples. The page parameter is briefly explained with default and indexing. This adds substantial meaning beyond the minimal schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Search Gallica for documents matching a text query,' specifying the verb (search), resource (Gallica documents), and scope (matching a text query). It distinguishes from siblings by referencing get_snippets for snippet extraction and implying download_text for downloading.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly directs when to use get_snippets for text snippets, providing an alternative. However, it lacks an explicit 'when not to use' statement or mentions of prerequisites, though the context is clear enough for adequate decision-making.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observeddownload_text
    • First observedget_snippets
    • First observedsearch_gallica

TDQS

A4.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: search_gallica finds documents, get_snippets extracts text passages from a document, and download_text retrieves the full OCR text. There is no functional overlap.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (search_gallica, get_snippets, download_text), making the API predictable and easy to understand.

Tool Count4/5

With 3 tools, the set is minimal yet sufficient for core document search and text retrieval workflows. It could be expanded with metadata or navigation tools, but the current count is reasonable for this scope.

Completeness4/5

The tools cover the essential operations of searching Gallica, locating content within documents, and downloading full text. Minor gaps exist (e.g., no standalone document metadata retrieval), but the core use cases are well-supported.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model-Client-Protocol server that enables users to search the Gallica digital library of the National Library of France and generate structured sequential research reports with formatted citations and relevant images.
    12
    -
  • F
    license
    B
    quality
    D
    maintenance
    Enables access to the Gallica digital library of the Bibliothèque nationale de France (BnF) with search capabilities across titles, authors, subjects, and dates, plus retrieval of document metadata, IIIF images, and OCR text content.
    12
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables searching documents in Gallica, the digital library of the Bibliothèque nationale de France, and generating structured research reports with citations and images.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables searching and retrieving records from the Europeana digital collection, including metadata such as titles, creators, dates, and images, via two MCP tools: search and record.
    6 npm
    MIT