Connect to the corpus
The Watermark Directory exposes a Model Context Protocol
endpoint at https://watermark.directory/api/mcp. Connect Claude Desktop or any MCP-compatible client to search
and interrogate the investigative record directly.
Claude Desktop setup
Add the following block to your claude_desktop_config.json (find it at
~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"watermark-directory": {
"url": "https://watermark.directory/api/mcp",
"type": "http"
}
}
} Restart Claude Desktop after saving. The watermark-directory server will appear in the MCP server list. No API key is required for the public tier.
Available tools
The public tier exposes 9 tools. All return evidentiary-tagged results — see evidentiary vocabulary below.
| Tool | Description | Required params | Example call |
|---|---|---|---|
search_corpus | Hybrid search across the whole corpus — the discovery entrypoint. Ranking fuses semantic (vector) similarity with BM25 keyword scoring via reciprocal-rank-fusion, degrading to keyword-only when query embeddings are unavailable. Use to FIND relevant items across every feed (records, documents, timeline, entities, …); narrow with a `filters` bag over indexed fields (site, feed, source_kind, verified, date_from/date_to, confidence, county, agency, permit_number, document_type, entity, project/campus — all AND-combined) so unrelated feeds don't crowd the results. Prefer a facet over a keyword when you have one: `filters.permit_number:"2PH00006"` finds every action filed under that permit including its modifications, where the same string in `query` merely ranks. This is NOT the way to pull one known document — use get_document for that. Returns ranked evidence cards (id, title, site, collection, date, source_kind, score, snippet, estimated_tokens, verified, tier, tier_reason, citation) — NO full record text by default; pass a hit's id to get_document to fetch its projected fields. Every card carries a structured `citation` (document_id, source, page/pages, source_url, evidence, and a paste-ready `label`), so you can CITE A HIT WITHOUT FETCHING IT — a follow-up get_document is for the record's fields, not for its provenance. Absent citation fields mean the source carries no such value; none of it is inferred, and a card's snippet is a window over the record's flattened fields, so it is never offered as a verbatim quote (use search_passages for that). Each hit is tiered by evidence role so you don't treat every match as equal: `tier` is `direct` (top-relevance-band primary evidence — records/documents/timeline/meetings that answer the query), `corroborating` (relevant supporting material — a secondary entity/person/place view, or primary evidence below the top band), or `background` (definitional/derived context — glossary concepts, or a weak-relevance match); `tier_reason` says why. The tier is an evidence-grounded heuristic (evidence class + score band), never score alone — a glossary hit is never `direct`. A filing's versions (e.g. a permit's final + draft + fact sheet) collapse to the canonical member by default — pass deduplicate:"none" to see every version, or version_policy to tune which superseded versions survive. Size knobs: response_mode (ids_only|compact|snippets|full — ids_only omits the tier; full reproduces the whole record, ~18–24k tokens/hit, opt-in), limit/max_results, snippet_tokens, max_tokens, cursor. | query |
|
search_passages | Page-level excerpt search over PUBLISHED source PDFs — returns the exact supporting page(s) with a citation, not a whole record. Use when you need the verbatim passage behind a claim (a permit condition, a board vote, a dollar figure) plus a page cite — especially for PDFs, where one relevant page shouldn't require pulling the full extracted document. This is the deeper peer of search_corpus: search_corpus finds WHICH item is relevant; search_passages finds WHICH PAGE says it. Ranking fuses semantic (vector) similarity with BM25, degrading to keyword-only when query embeddings are unavailable. Scoped to the public-publish allowlist, so it covers only documents whose bytes are publicly served — not the whole corpus. Narrow to specific documents with document_ids (the document_id / rel from search_corpus or get_documents). Returns page excerpts (id, document_id, page, section, title, text, method, score, citation). This is the one tool whose `citation.quote` is populated — on a `method: "pdf_text"` hit the excerpt IS the document's own text layer, so it is genuinely verbatim (a bounded lead excerpt; the hit's `text` carries the full page). The text is the PDF text layer verbatim — for scanned pages that is garbled OCR, so treat it as a locator for the cited page, not a transcription; open the page itself with get_document. ALWAYS read `method` before quoting: a handful of pages come from source PDFs whose font mapping is broken, and those carry either an `ocr` read (this platform's, not the document's) or, where even that failed, a `pdf_text_damaged` partial that is a locator only and must never be quoted. By default pages from a byte-identical duplicate document are collapsed to the canonical copy (deduplicate:"none" to disable); draft/final page variants are always kept distinct. Size knobs: max_results, max_tokens, max_tokens_per_result (trims the excerpt), cursor, intent. | query |
|
get_timeline | Dated events for a site (permits, filings, meetings, transactions), oldest-first. Use to build a chronology or find what happened in a window; filter by since/until/category. Returns event records directly (date, category, title, parties, detail, citation) — every row carries the same structured `citation` object the search tools return, so an event is citable as it stands. A terminal read, not a discovery index; to open the document behind an event, take its parties/title into search_corpus or get_document. Size knobs: max_results, max_tokens, max_tokens_per_result (sheds detail/parties first), cursor, intent. | — |
|
get_entities | Entity graph for a site — parties, companies, people, parcels and their roles/relationships. Use to resolve who is involved or enumerate the parcels/companies on the record; filter by type. Returns entity nodes directly (key, display, kind, roles, parcels, addresses, sources, signals). Not a document fetch — take an entity's name into search_corpus to find the documents behind it. Size knobs: max_results, max_tokens, max_tokens_per_result (sheds variants/addresses/signals/parcels/roles first), cursor, intent. | — |
|
get_hypotheses | Per-site boom-origin hypotheses joined to their signal assessments — the investigation's open theses and how the evidence scores against each. Use to see the analytic frame or what is being tested; filter by site. Returns each hypothesis (claim, thesis, status, signals) with its assessments (tag, group, citations); assessments_total flags a budget-shrunk list. Not a document fetch — follow a signal's citation via search_corpus/get_document. Size knobs: max_results, max_tokens, max_tokens_per_result, cursor, intent. | — |
|
get_documents | Lists source-document COLLECTIONS and their file entries (metadata only — rel, name, media_type, published, available), by collection. Use to browse what documents exist or find a document's id; filter by collection. Returns collection cards with entry_count + entries, NO document bodies or extracted fields. Then fetch: pass an entry's rel to get_document for its extraction fields + citation. Contrast get_document (one document, projected) and search_corpus (hybrid search across every feed). Size knobs: max_results, max_tokens, max_tokens_per_result (caps the entries list), cursor, intent. | — |
|
get_document | Fetch ONE document you already have an id for, with field/section projection — the targeted peer of get_documents (which only lists collections). Use it to pull a specific document's evidence AFTER discovery; for discovery itself use search_corpus or get_documents, and note this does no corpus search. Addressed by its `collection/rel` file path (e.g. recorder/bistrozzi-deeds/202508130008300.pdf) OR the joined extraction-record id (e.g. recorder/202508130008300.deed.yaml); ids returned by search_corpus work directly. Returns the document's metadata joined to its extraction record — structured `fields` and a structured `citation` (the same object the search tools return: document_id, source, page/pages, source_url, evidence, and a paste-ready `label`) — bounded by max_tokens, projected by fields/sections. IMPORTANT: the bundle carries document metadata + record `fields`, NOT the raw source-document body text. `fields`/`sections` projection operates over those extracted fields; there is no per-page body-text projection here — use search_passages to retrieve a published PDF's page text with a page cite. `include_source_text` returns the record's flattened extraction text, not scanned page text. | document_id |
|
get_facts | Retrieve normalized (subject, predicate, value, unit, status) FACTS — the numbers a site's provenanced feeds already carry (economics, energy, water/cooling, air, facility power), flattened into one queryable table so a fact question is a tiny retrieval + arithmetic instead of a whole-record pull. Use it to look up or compute over specific quantities (e.g. genset_count × genset_rating → backup MW; county employment; demand_share_pct); filter by subject, predicate, and/or `fact_category` (economics | energy | facility-power | water | air | platform — the grouping over the source feeds; `feed` takes one exact source instead). `subject` matches flexibly (case-insensitive, over the `<kind>:<id>` key + human label + kind — e.g. "Allen County", "facility", "air-scenario"); `predicate` takes one name or a list of the exact snake_case field names. Returns compact tuples by default (subject, predicate, value, unit, status, low/high band); status is the evidence tag (verified|inference|reference|open). NOT a document fetch and NOT search — for the record behind a fact, take its subject into search_corpus/get_document. Pass include_evidence=true to attach each fact's provenance — both the raw `evidence` block (source, source_kind, page, citation, verified) and the same structured `citation` object the other tools return; note page is null/absent where the source carries none — never invented, and for most facts the ONLY provenance is a free-text string, which rides in `citation.note` and becomes its label. Size knobs: max_results, max_tokens, max_tokens_per_result (sheds evidence then the band), cursor, intent. | — |
|
aggregate_facts | Compute a deterministic GROUPED TOTAL over the facts feed server-side — sum / count / mean / product — so you never pull every row just to total something. Returns one row per group with the value, unit, a human-readable `derivation` (e.g. "114 × 2.75 MW"), a `confidence`, a `caveat`, and the `evidence_ids` (the <subject>/<predicate> handles) that fed it. `metric` is either a registered recipe (backup_generation_capacity_mw = genset_count × genset_rating; facility_draw_mw = it_load × PUE) or the generic grammar sum:<predicate> | mean:<predicate> | count:<predicate> | product:<a>,<b> (e.g. "sum:annual_avg_employment"). Call it with NO metric to list the registered metrics (discovery). `group_by` partitions the total: project/subject (per facility/county/scenario — the default), kind (subject_kind), feed, or all/site (one whole-site total). A product is computed per subject then summed up to a coarser group. Optionally pre-filter inputs by `subject` (flexible match, like get_facts), `status`, and `fact_category`/`feed` — the same category gate get_facts uses, so a total and the tuples behind it are always taken over the same rows. Status/confidence take the weakest input; a product is never reported stronger than inference (a derivation is not a document). For the raw tuples behind a total, use get_facts with the same subject/predicate. | — |
|
Rate limits and budget
| Tier | Rate limit | Daily output budget | Auth |
|---|---|---|---|
| Public | 60 requests / hour per IP | 100k tokens / day (shared) | None — open access |
| Keyed | Higher limits | 500k tokens / day (per key) | Authorization: Bearer <token> |
To request a keyed account, submit a tip or correction and mention your research use case. Keyed access is granted to journalists, researchers, and public-interest investigators.
Evidentiary vocabulary
Every result from the corpus carries an evidence tag. These tags encode how strongly the claim is grounded in the documentary record:
[verified]- Directly confirmed in a primary source document — a permit, a recorded deed, meeting minutes, a regulatory filing. The document is in the corpus and the claim is traceable to a specific page.
[inference]- Logically derived from verified facts but not explicitly stated in a source. A reasonable conclusion the record supports, not a claim a document makes directly.
[reference]- Sourced from a secondary or historical reference (published books, surveys, news) rather than a primary instrument. Accurate but one step removed from the primary record.
[open]- A question or gap the record has not yet resolved — an area where the corpus is incomplete or where the evidence is insufficient to draw a conclusion.
When a model queries this corpus, it should surface these tags in its reasoning and not assert
[inference] claims with the same confidence as [verified] ones.
Auto-discovery
The endpoint publishes a /.well-known/mcp.json discovery document. MCP clients that support auto-discovery can find the server without manual configuration.