ancestry-com-mcp
# ancestry-com-mcp
An MCP server that lets an AI assistant do everything you would do on Ancestry.com by hand: search records,
read and edit your trees (people, facts, sources, citations, notes, tags, media), review hints, attach records,
look at DNA matches, and read or send messages.
Ancestry has no public API. This server drives the site's own internal JSON endpoints, reverse-engineered
and documented in [`docs/endpoints.md`](docs/endpoints.md) and [`docs/writes.md`](docs/writes.md).
Companion server: [familysearch-org-mcp](https://github.com/ball2jh/familysearch-org-mcp). Both can be attached at once;
their tools are prefixed `ancestry_` and `familysearch_` so they never collide.
> **Unofficial.** This project is not affiliated with or endorsed by Ancestry. It uses undocumented endpoints
> that may change or break at any time, and automated access may be against Ancestry's terms of service.
> Use it only with your own account, on your own trees, at your own risk.
## How it works
Cloudflare rejects ordinary Node/curl TLS on ancestry.com even with valid cookies, but it accepts a client that
impersonates Chrome's TLS/HTTP2 fingerprint ([impit](https://github.com/apify/impit)) carrying the site's two
login cookies (`ATT`, `SecureATT`). So the server runs **without a browser**: cookies live in `.session.json`
(mode 600, git-ignored) and every request goes straight from Node. A real Chrome is opened only to sign in:
`pnpm login` (or the `ancestry_session` tool's `login` action) launches a dedicated Chrome profile on the sign-in
page, waits for you to log in, saves the cookies, and closes the window again.
Requirements: Node 26+ (runs TypeScript directly), pnpm, Google Chrome.
## Setup
```bash
git clone https://github.com/ball2jh/ancestry-com-mcp
cd ancestry-com-mcp
pnpm install
pnpm login # one-time: opens Chrome to sign in, saves .session.json, closes Chrome
pnpm mcp-test # optional: read-only check of the connection and every read tool
```
Add to Claude Code / Claude Desktop (stdio):
```json
{
"mcpServers": {
"ancestry": {
"command": "node",
"args": ["/absolute/path/to/ancestry-com-mcp/src/server.ts"]
}
}
}
```
If the saved session is missing or expired, tools return an error telling you to run `ancestry_session login`
(it opens Chrome for a one-time sign-in and closes it afterwards). Ancestry's login cookie lasts a long time, so
this is rare; the server also persists any rotated cookies it receives.
## Tools
Tools are grouped by resource and split into a **read** tool and an **edit** tool, so an MCP client can
auto-approve reads and ask before writes based on the tool annotations (`readOnlyHint`, `destructiveHint`,
`idempotentHint`, `openWorldHint`, all set honestly on every tool). Most tools take an `action` parameter; every
parameter is described in the schema, and a missing parameter for an action comes back as an error naming it.
The whole definition set is about 12K tokens (`pnpm mcp-test` prints the exact figure); the extra weight over a
bare schema is the per-parameter documentation, which is what lets the model call these tools correctly the
first time.
| Tool | Actions | Writes? |
|---|---|---|
| `ancestry_session` | status, login | opens Chrome, saves cookies |
| `ancestry_tree` | list, get (with Tree Checker totals), checker (the flagged people: duplicates, no sources, tree-only sources, issues) | no |
| `ancestry_tree_edit` | create, update, set_privacy, import_gedcom, delete (needs `confirmName`) | yes |
| `ancestry_person` | list, find, get, family_view, relationship_to_home, fact_types, issues (duplicate candidates and data issues) | no |
| `ancestry_person_edit` | add, link_existing, update, change_relationship, remove_relationship, delete (needs `displayName`) | yes |
| `ancestry_fact` | add, edit, delete, attach_citation, detach_citation, attach_media, detach_media | yes |
| `ancestry_source` | list_sources, get_citation | no |
| `ancestry_source_edit` | create_source, update_source, create_repository, link_repository, unlink_repository, create_citation, update_citation, remove_citation, add_web_link, remove_web_link | yes |
| `ancestry_notes_tags` | get_notes, list_tags | no |
| `ancestry_notes_tags_edit` | save_notes (replaces the note), append_notes (keeps it), add_tags, remove_tags, create_custom_tag | yes |
| `ancestry_search_records` | single action: the full website search form (names, per-event year/place, relatives, exactness, collection) | no |
| `ancestry_record` | get, image_index (a whole census page, paged and filterable by name/household/fields), catalog (which collections exist for a keyword/place/year), browse (a collection's images by place, for unindexed collections), suggest_places, shoebox_list | no |
| `ancestry_record_edit` | save_to_person (update/alternate/name/relatives like the Review page), shoebox_save, shoebox_remove | yes |
| `ancestry_download` | record_image, media, gedcom | writes a local file, or returns the image inline |
| `ancestry_hint` | summary, list, compare | no |
| `ancestry_hint_set_state` | rejected / deferred / pending | yes (reversible) |
| `ancestry_media` | list, get | no |
| `ancestry_media_edit` | upload, update, set_profile_photo, copy_from_tree, delete | yes |
| `ancestry_dna` | tests, matches (all website filters: cM, parent side, trees, groups, name, unviewed/notes/new, sort), groups, match_detail, ethnicity | no |
| `ancestry_dna_edit` | set_note, add_to_group, remove_from_group | yes |
| `ancestry_messages` | list_conversations, get_messages, member_profile, notifications | no |
| `ancestry_messages_send` | send a message to a member | yes, irreversible |
| `ancestry_raw_request` | any ancestry.com request with the saved session | can |
Files: `ancestry_download` writes record images, media files and GEDCOM exports to a local `outPath`; without
`outPath`, images come back inline (scaled to `maxSide`, default 1600 px) so the assistant can look at them.
`ancestry_media_edit upload` and `ancestry_tree_edit import_gedcom` read from a local `filePath`.
Ids: `treeId` from `ancestry_tree list`; `personId` is the plain numeric id (not the `pid:1030:tree` form);
`assertionId`, `citationId`, `webLinkId` come from `ancestry_person get`; `collectionId` + `recordId` from
`ancestry_search_records` or `ancestry_hint list`; `imageId` from `ancestry_record get`.
Results: JSON, trimmed to what a caller acts on. The heavy reads (`ancestry_person get`/`family_view`, `ancestry_hint list`,
`ancestry_media list`, `ancestry_dna matches`, `ancestry_notes_tags list_tags`, `ancestry_source list_sources`) take a
`responseFormat` of `concise` (default) or `detailed`; concise drops bulky secondary fields such as per-fact citation id
lists, download URLs and field-by-field hint comparisons, and each description states the expected size. A result over 36K characters (about 9K tokens, under the 10K-token warning and 25K-token cap Claude Code applies to tool results; override with
`MCP_MAX_RESULT_CHARS`) is cut at an item boundary so it stays valid JSON, and carries a `_truncated` field with
the count shown, the total, and how to get the rest. Errors, including validation errors and upstream HTTP
failures, come back as tool results with `isError: true` and a message that says what to do next. A 429 from
Ancestry is retried once after its `Retry-After`; if it persists the error says how long to wait.
## Tests
- `pnpm test` — unit tests, no network. Besides the result helpers, the search query builder and the image-index
and checker projections, `test/writes.test.ts` runs the real client against `test/fake-http.ts` (a transport
double that serves declared routes and records every call) and checks the body each replace-style write sends
when given one field. `test/conventions.test.ts` and `test/tools.test.ts` are static checks: no write method
swallows a failed read, every replace-style write merges through `mergeGiven`, every tool action has a handler
and every handler an action, every parameter a description.
- `pnpm mcp-test` — read-only end-to-end run over stdio against your account: checks that every tool has a
title, full annotations and described parameters, calls every read action, fails if any default read comes back
truncated, downloads one image inline and one to disk, and checks that error paths come back as `isError` results. Set `ANCESTRY_TEST_TREE` to choose
the tree; else your first tree is used. Prints definition size and per-call latency.
- `pnpm mcp-write-test` — every write action on a throwaway tree that it creates and deletes afterward
(deletion runs in a `finally`, so a failing step does not leave the tree behind). Also briefly sets and clears
a note on one DNA match that had none.
## Evals
`evals/` holds a small harness for measuring tool-layout changes with real agents rather than intuition:
- `evals/run.ts` runs read-only research tasks (`tasks.json`) or editing tasks on a throwaway tree
(`write-tasks.json`) through headless Claude Code against any server layout described by an `mcp-<name>.json`,
records tool calls, errors, writes, tokens, cost and time, and has an Opus judge grade each answer against a
rubric. `node evals/run.ts --layouts old,new --tasks-file write-tasks.json`. Costs real money; results are gitignored.
- `evals/transcript-stats.py` summarises MCP usage in Claude Code session transcripts: calls, error rates, result
sizes and every distinct error message per tool and action.
- `evals/field-usage.py` measures, per returned field, how often agents actually used the value afterwards. The
concise/detailed split of the read tools was chosen from its output.
## Repository layout
- `src/http.ts` — Chrome-impersonating HTTP client (impit), CSRF tokens, JSONP, throttling, Cloudflare and rate-limit handling
- `src/session.ts` — cookie jar persisted in `.session.json`
- `src/browser.ts` — Chrome bootstrap used only by `login` (sign in, export cookies, close)
- `src/client.ts` — typed client for every endpoint
- `src/result.ts` — tool-result helpers: JSON-safe truncation, inline images, errors, action dispatch
- `src/tools.ts` — MCP tool definitions
- `src/server.ts` — stdio entry point and server instructions
- `scripts/` — `login.ts`, the Chrome launcher, the traffic recorder used during reverse engineering, the two end-to-end tests, and `measure-limits.ts` (field length limits, measured on a throwaway tree)
- `test/` — unit tests and `fake-http.ts`, the transport double they run the client against
- `CLAUDE.md` — the rules for changing this code, one per bug class found in the field
- `evals/` — tool-layout eval harness and transcript analysis scripts
- `test-fixtures/` — tiny image and GEDCOM used by the tests
- `docs/` — research notes and the endpoint contracts
## Not covered
Stories (a separate slide-editor app), tree sharing and invitations, merging duplicate people, and comments. Everything
else visible in the Ancestry UI has a tool; `ancestry_raw_request` covers the rest.
## Caveats
- Ancestry's terms of service restrict automated access. This is a personal tool for your own account and
trees; use it accordingly. Writes are serialized with a small delay to stay gentle.
- `ancestry_search_records` mirrors the website's full form: per-name match level, per-event year tolerance and place scope, exact relatives/keyword, collection focus, and a passthrough for collection-specific fields.
- Ancestry's name parameters are positional (`{given}_{surname}`), so a surname-only search has to send the empty
given slot. Sending the bare surname returns people whose *given* name matches, which reads like a real result
— `test/search.test.ts` and the surname assertion in `pnpm mcp-test` guard against that regressing.
- Replace-style writes refill what the caller left out. Several of Ancestry's save forms are full replaces
(person quick-edit, fact save, source, media), so a call that gives only one field would blank the others.
Every such write in `client.ts` reads the current values first and merges the given fields in; an explicit
empty string still clears a field, and a read that fails refuses the write with a message naming what it would
have blanked. Where the endpoint keeps fields it is not sent (tree basic info, tree privacy, citation) only the
given fields are sent, never empty defaults. `scripts/mcp-write-test.ts` has a partial-edit check for each.
Any new write must be classified the same way before it ships.
- Free-text fields have ceilings that Ancestry mostly enforces by silently truncating on save. The tools reject
over-long values instead, and carry the limit as `maxLength` in their schema: fact description 1024, source
title 256, citation detail 256, media title 128, web link title 64 (HTTP 500 above it), custom tag name 64.
Person notes, citation transcription and otherInfo, media description and source note took 16k without
complaint. Re-measure with `node scripts/measure-limits.ts`, which probes a throwaway tree and deletes it.
- Ancestry's image index (`index-panel-data`) returns every record on an image in one response and ignores
paging parameters, and each record lists every indexed column of the collection even when empty (a 50-line
census page is 120–190K characters raw). `ancestry_record image_index` drops the empty columns and pages,
filters (name, household, record ids) and projects fields client-side; `test/image-index.test.ts` covers that.
- The Tree Checker list has no total count; `ancestry_tree checker` reports `hasMore` from a full page.
Duplicate pairs come from Ancestry's per-person check, one request per flagged person. There is no endpoint to
merge or dismiss a pair (the guessed ones are recorded as dead ends in `docs/endpoints.md`).
- Tool schemas are strict: a parameter the tool does not have is rejected by name. Recorded research sessions passed
an `action` to `ancestry_search_records` 147 times without ever learning it was ignored; a misspelled real
parameter would have vanished the same way. `ancestry_search_records` accepts `action: "search"` for uniformity.
- A refused fact save is an HTTP 200 whose body says `statusCode: 400` (custom events do this); the tool reports it
as an error rather than a saved fact with id "0".
- Cloudflare's firewall rejects some long person notes with a 403 on an otherwise healthy session; the error says
so and how to shorten, instead of telling the agent to log in again.
- The research project that uses these servers should run them from a **separate stable clone**, not from a
checkout that is being edited: a running research session lost every record search for two hours after a code
session edited `src/client.ts` in the checkout its `.mcp.json` pointed at.
- The endpoints are internal and can change without notice. `pnpm mcp-test` is a quick regression check.
- `.session.json`, `.profile/` and `captures/` hold live session cookies and are gitignored. Never commit them.
- The server logs only to stderr, so it is safe on stdio.
TDQS
Scored across 13 tools
Each tool targets a distinct domain: session management, hints, trees, people, facts, sources, notes/tags, record search, record details, media, DNA, messages, and a raw API escape hatch. There is minimal overlap; even where hint and record interact, their roles are clearly separated and complementary.
Most tool names are concise lowercase nouns (person, tree, fact, source, record, media, dna) which is a consistent pattern. Minor deviations like search_records, notes_tags, and raw_request break the pure-noun convention but remain readable and predictable.
With 13 tools, the server is well-scoped for a comprehensive genealogy platform. Each tool represents a major functional area (trees, people, records, DNA, messaging, etc.) and earns its place without bloat or excessive granularity.
The tool surface covers the core ancestry workflows: tree CRUD, person management with relationships, facts, sourcing/citations, record search and attachment, media handling, DNA insights, hints review, notes/tags, and messaging. There are no obvious dead ends for typical genealogy research tasks.