Skip to main content
Glama

Find a phrase in the scanned text of every archive

search_inside
Read-onlyIdempotent

Search inside texts across Internet Archive, Library of Congress, and other archives simultaneously to locate where a phrase appears, with page numbers and source-labelled excerpts.

Instructions

Search the machine-read text of every archive this server reads that holds any, at the same time, and get one list of where a phrase was printed. The archives behind this tool hold different material, so the list is additive: it is the places a phrase appears in each corpus, put together, rather than the same question answered twice. 'per_source' names what each corpus is. A match carries 'page_number', which is a real leaf on an archive whose index holds one and null on an archive whose index holds none. That null is the index having no leaf, never a page this server dropped, and no page is ever invented. A match also carries 'excerpt_kind'. 'passage' is the text around the words that matched. 'page_opening' is the start of the page, sent because the machine-read text that came back stops before the searched words appear, so it does not carry the match. The notes say how many excerpts are openings. Every count is that archive's own and counts something of its own: documents in one place, leaves in another. They are never added together, and there is no total across archives. Rows are interleaved one archive at a time. Nothing ranks them against each other and nothing orders them by date, because a year is measured on different things in each archive. Matches whose excerpt carries the searched words are placed before matches whose excerpt is a 'page_opening' and carries them nowhere. That rests on what each row states about its own excerpt rather than on any score, no match is ever dropped for it, and the interleaving holds inside each of the two groups. The archives read the words given in different ways, and 'per_source' says which each one does. An index that answers only where every word appears returns nothing for a question written as a sentence, even on a work it holds several copies of; an index that scores the words instead answers such a question with the pages it ranks highest, which can carry only some of them. Either way it is the words: a character that is neither a letter nor a digit is no word to an index, and 'non_word_characters' lists any the query carried. Shorter and differently spelled wordings are therefore derived from the query and asked for their union, which costs nothing extra when the words as asked already answer. Every wording sent is named in 'per_source' with what it returned, every match carries the wording that returned it in 'found_by_query', and 'fan_out' turns the derivation off. Use search_items for a work by its title, its creator or its subject: this tool reads the text on the pages and knows nothing of a catalogue, so a title given here finds every book that happens to mention it and misses the book itself. Answers take several seconds, because one of the archives publishes a request ceiling this server keeps to. A slow answer is the pacing, not a stall.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pageNoWhich page of matches, from 1. Each archive is paged separately.
limitNoMatches to take from each archive, so one archive cannot fill the whole list. The text block shows about six matches; the rest of what comes back is in the structured output.
queryYesWords, or a phrase in double quotes such as '"call me ishmael"'.
fan_outNoWhether to derive shorter and differently spelled wordings from the query and ask each archive for the union of what they return. A question written as a sentence returns nothing where every word given has to appear, and the rows an index scores highest where it does not. An archive is asked a derived wording only when the words as asked did not return as many rows as 'limit', so a query that works costs one request. Set false to send exactly the words given. 'per_source' names every wording, sent or not, and each match names the one that returned it.
sourcesNoArchives to ask, by id. Left out, every archive that reads machine-read text is asked, which is the point of this tool.
max_excerpt_charsNoBudget for one passage. Read it together with 'max_excerpts_per_match': the size of the answer is the product of the two, the limit, and the number of archives.
max_excerpts_per_matchNoPassages to keep per match. A long work matches in several places, and the later ones rarely say anything the first did not.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
hitsYes
pageYes
notesYes
orderYesHow the list was built, in words.
queryYes
hit_countYesMatches in this answer, across every archive. It is a count of what came back, never a total of what exists.
per_sourceYes
queries_runYesRequests this server sent for this answer, counting every wording on every archive. Each archive's own wordings are in 'per_source'.
excerpt_kindsYesHow many excerpts here are of each kind.
non_word_charactersYesCharacters in the query that are neither letters nor digits. These indexes answer on words, so a match here can carry none of them, and 'requires_every_word' covers the words that were given rather than these.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed21 schema fields changedv1.2.0
    • addedInput schema / properties / fan_out
      Added value: +{
      +  "default": true,
      +  "description": "Whether to derive shorter and differently spelled wordings from the query and ask each archive for the union of what they return. A question written as a sentence returns nothing where every word given has to appear, and the rows an index scores highest where it does not. An archive is asked a derived wording only when the words as asked did not return as many rows as 'limit', so a query that works costs one request. Set false to send exactly the words given. 'per_source' names every wording, sent or not, and each match names the one that returned it.",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / sources / items / enum
      Previous value: -[
      -  "archive",
      -  "loc"
      -]New value: +[
      +  "archive",
      +  "loc",
      +  "bnf"
      +]
    • addedOutput schema / properties / hits / items / properties / found_by_derivation
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "How that wording was arrived at from the question, in words."
      +}
    • addedOutput schema / properties / hits / items / properties / found_by_query
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "The wording this archive was given that returned this row. It is the query as you wrote it unless a further wording was derived, in which case the row answers that wording's words and not the rest of the question."
      +}
    • changedOutput schema / properties / hits / items / required
      Previous value: -[
      -  "id",
      -  "source",
      -  "source_name",
      -  "identifier",
      -  "title",
      -  "creator",
      -  "year",
      -  "page_number",
      -  "excerpts",
      -  "excerpt_kind",
      -  "source_url",
      -  "matched_file",
      -  "inside_container",
      -  "published_on",
      -  "publication"
      -]New value: +[
      +  "id",
      +  "source",
      +  "source_name",
      +  "identifier",
      +  "title",
      +  "creator",
      +  "year",
      +  "page_number",
      +  "excerpts",
      +  "excerpt_kind",
      +  "source_url",
      +  "matched_file",
      +  "inside_container",
      +  "published_on",
      +  "publication",
      +  "found_by_query",
      +  "found_by_derivation"
      +]
    • addedOutput schema / properties / non_word_characters
      Added value: +{
      +  "description": "Characters in the query that are neither letters nor digits. These indexes answer on words, so a match here can carry none of them, and 'requires_every_word' covers the words that were given rather than these.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / per_source / items / properties / attribution
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "What to say when repeating what this archive contributed, as this archive states it for this answer. An archive whose licence asks for the date its metadata was retrieved carries that date here. Null on an archive that was never asked."
      +}
    • changedOutput schema / properties / per_source / items / properties / corpus / description
      Previous value: -"What body of material this archive's full-text index reads."New value: +"What body of material this archive's full-text index reads. Null where this answer did not read it."
    • addedOutput schema / properties / per_source / items / properties / filters_dropped
      Added value: +{
      +  "description": "Narrowings you asked for that this archive never received, because its catalogue cannot apply them. Its rows were not narrowed by them, and a row from it that happens to satisfy one is a coincidence rather than a filter. Empty when it received every narrowing asked for.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "because": {
      +        "description": "Why this archive never received it.",
      +        "type": "string"
      +      },
      +      "filter": {
      +        "description": "The narrowing, named as the argument that carries it.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "filter",
      +      "because"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / per_source / items / properties / queries
      Added value: +{
      +  "description": "Every wording derived for this archive, in the order they were tried, with what each one returned and why any was withheld. Retyping one of them reproduces its rows by hand. Empty on a call that carries no query.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "added": {
      +        "anyOf": [
      +          {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Rows this wording contributed that an earlier one had not already returned. Null when it was not sent, or did not answer."
      +      },
      +      "count": {
      +        "anyOf": [
      +          {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Rows the archive returned for this wording. Zero is that wording finding nothing, which is a statement about the wording. Null when it was not sent, or did not answer."
      +      },
      +      "derivation": {
      +        "description": "How this wording was arrived at from the question, in words.",
      +        "type": "string"
      +      },
      +      "error": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "properties": {
      +              "code": {
      +                "type": "string"
      +              },
      +              "hint": {
      +                "type": "string"
      +              },
      +              "message": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "code",
      +              "message"
      +            ],
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Why this wording did not answer. Null when it did, or was never sent."
      +      },
      +      "not_run_because": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Why it was withheld. Null when it was sent."
      +      },
      +      "query": {
      +        "description": "The words this archive was given, exactly as they were sent.",
      +        "type": "string"
      +      },
      +      "ran": {
      +        "description": "Whether it was sent. False means it was derived and withheld.",
      +        "type": "boolean"
      +      }
      +    },
      +    "required": [
      +      "query",
      +      "derivation",
      +      "ran",
      +      "count",
      +      "added",
      +      "not_run_because",
      +      "error"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / per_source / items / properties / requires_every_word
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "boolean"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Whether the index this answer put the words to answers only where every word given appears. It covers the words: a character that is neither a letter nor a digit is not a word to an index, and those are listed in 'non_word_characters' instead. False means the index ranks the words and answers with what it scores highest, so one of its rows can carry only some of them. Null on an archive that was not asked."
      +}
    • addedOutput schema / properties / per_source / items / properties / row_describes
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "What one row from this archive is: a copy it holds, a record in a catalogue, or a work as an entity whose editions are records of their own. Rows carry the same fields and describe different kinds of thing."
      +}
    • addedOutput schema / properties / per_source / items / properties / searches_on
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "The fields this archive matched the query against. The archives read different ones, so the same words are not the same question everywhere, and a name given to an index over titles alone comes back as the works written about that person."
      +}
    • removedOutput schema / properties / per_source / items / properties / skipped / anyOf
      Removed value: -[
      -  {
      -    "maximum": 9007199254740991,
      -    "minimum": -9007199254740991,
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • changedOutput schema / properties / per_source / items / properties / skipped / description
      Previous value: -"Rows this archive sent in a shape the server could not read, and left out. Null on an answer served from a cache that kept the rows and not the count of what was dropped building them."New value: +"Rows this archive sent in a shape the server could not read, and left out of this answer. Always a count, so it reads the same way on every answer. Rows served out of a cache were counted the same way when they were first read, and 'cached' marks an answer whose count can be short of a drop nobody kept a record of."
    • addedOutput schema / properties / per_source / items / properties / skipped / maximum
      Added value: +9007199254740991
    • addedOutput schema / properties / per_source / items / properties / skipped / minimum
      Added value: +-9007199254740991
    • addedOutput schema / properties / per_source / items / properties / skipped / type
      Added value: +"integer"
    • changedOutput schema / properties / per_source / items / required
      Previous value: -[
      -  "source",
      -  "name",
      -  "status",
      -  "stage",
      -  "absent_because",
      -  "count",
      -  "reported_total",
      -  "reported_total_means",
      -  "skipped",
      -  "more_on_this_archive",
      -  "ordered_on",
      -  "media_type_asked",
      -  "year_means",
      -  "publishes_page_number",
      -  "corpus",
      -  "cached",
      -  "error"
      -]New value: +[
      +  "source",
      +  "name",
      +  "status",
      +  "stage",
      +  "absent_because",
      +  "count",
      +  "reported_total",
      +  "reported_total_means",
      +  "skipped",
      +  "more_on_this_archive",
      +  "ordered_on",
      +  "media_type_asked",
      +  "attribution",
      +  "searches_on",
      +  "row_describes",
      +  "filters_dropped",
      +  "queries",
      +  "year_means",
      +  "publishes_page_number",
      +  "corpus",
      +  "requires_every_word",
      +  "cached",
      +  "error"
      +]
    • addedOutput schema / properties / queries_run
      Added value: +{
      +  "description": "Requests this server sent for this answer, counting every wording on every archive. Each archive's own wordings are in 'per_source'.",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "query",
      -  "page",
      -  "hits",
      -  "hit_count",
      -  "per_source",
      -  "order",
      -  "excerpt_kinds",
      -  "notes"
      -]New value: +[
      +  "query",
      +  "page",
      +  "hits",
      +  "hit_count",
      +  "per_source",
      +  "queries_run",
      +  "non_word_characters",
      +  "order",
      +  "excerpt_kinds",
      +  "notes"
      +]
  2. First observedv1.0.0

TDQS

A5/5.0
Behavior5/5

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

Discloses extensive behavioral context beyond the annotations: results are additive across archives with no total, page_number is null for archives without a leaf index, excerpt_kind distinguishes passages from page openings, ordering groups matches by excerpt type without ranking, and no match is ever dropped. Also explains the meaning of non_word_characters and the fan_out derivation. No contradiction with the readOnly/openWorld/idempotent annotations.

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?

Although lengthy, every sentence contributes unique value—covering result semantics, ordering, parameter behavior, and performance. The description is front-loaded with the core purpose and then systematically adds detail. No redundant restatement of schema or annotations.

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?

The description is exceptionally complete given the tool's complexity: it explains output semantics (per_source, page_number, excerpt_kind, found_by_query), inter-archive behavior, parameter interactions, edge cases like derived wordings, and performance expectations. The presence of an output schema does not reduce the need for this context, and it delivers.

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 100%, but the description adds significant meaning beyond the schema: it clarifies that 'fan_out' enables derived wordings and that 'per_source' names every wording sent, each match carries 'found_by_query', and 'max_excerpt_chars' and 'max_excerpts_per_match' multiply to form answer size. It also explains the query syntax and behavior for sentence queries.

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?

Opens with a specific verb+resource+scope statement: 'Search the machine-read text of every archive this server reads... and get one list of where a phrase was printed.' It clearly distinguishes from siblings by explicitly telling the user to 'Use search_items for a work by its title, its creator or its subject' because this tool reads page text and knows nothing of a catalogue.

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

Usage Guidelines5/5

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

Provides explicit when-to-use and when-not-to-use guidance, naming the alternative tool (search_items) and explaining why a title query would fail here. Also details nuanced usage rules: how fan_out behaves, how results are combined across archives, and that answers take several seconds due to a request ceiling.

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

Deploy Server

Other Tools