Skip to main content
Glama
ancestry-com-mcp

ancestry-com-mcp

by ball2jh

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 and docs/writes.md.

Companion server: 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) 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.

Related MCP server: Gramps MCP

Setup

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

{
  "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.

Available Tools

13 tools
ancestry_sessionA
Read-only

Ancestry login session (cookies in .session.json; no browser needed for normal use). "status" reports whether the session is valid and who the user is. "login" opens the sign-in page in a dedicated Chrome window, waits for the user to sign in, saves the cookies, and closes Chrome — run it only when status says not logged in.

Inputs: action (required), timeoutSeconds.

dnaB

AncestryDNA. Actions: tests (testGuid per kit) · matches (the website match list with all its filters: minCm/maxCm, parentalSide maternal|paternal|both|unassigned, trees public|private|unlinked, groupIds (2 = starred; custom ids from groups), nameSearch (match name or username), unviewedOnly, notesOnly, newOnly, sharedWithMatchGuid = shared matches with that match; sortBy RELATIONSHIP (default) | DATE; pageSize ≤ 50) · groups (starred/custom group ids and match counts) · add_to_group / remove_from_group (matchGuids + groupId) · match_detail (shared cM/segments, predicted relationship, trees) · set_note (private note on a match; empty clears) · ethnicity (estimate regions).

Inputs: action (required), note, page, maxCm, +15 more.

factA
Destructive

Facts/events on a person (birth, death, residence, occupation, census, burial, marriage, custom…). Actions: list_types (valid eventType keys) · add · edit (all fields replaced; needs assertionId from person.get) · delete · attach_citation / detach_citation (existing citationId; pass databaseId+recordId for record citations) · attach_media / detach_media (mediaId). Relationship events (marriage, divorce, engagement…) require spouseId. Dates are free text that Ancestry parses: "12 Mar 1850", "Mar 1850", "1850", and the GEDCOM qualifiers "abt 1850", "bef 1850", "aft 1850", "bet 1850 and 1860"; anything else ("circa", "1850s", "early 1800s") is stored unparsed and never matches records or hints. Place is free text, most specific first, like "Cocke County, Tennessee, USA".

Inputs: action (required), date, place, title, +11 more.

hintA

Ancestry hints (suggested records, photos, member-tree people). Actions: summary (counts) · list (pending hints across a tree, newest first; filter by firstName/lastName; page/pageSize) · compare (record vs tree person side by side — what the Review page shows; do this before accepting) · set_state (rejected = No/ignore, deferred = Maybe, pending = undo ignore). To ACCEPT a record hint use record.save_to_person; to accept a photo hint use media.copy_from_tree.

Inputs: action (required), page, state, hintId, +7 more.

mediaA
Destructive

Photos, documents and stories in a tree. Actions: list (for a person, or the whole tree when personId is omitted) · get · download (full-size file to outPath, or base64; maxSide scales) · upload (local file → person gallery) · update (title/date/description/location; omitted fields kept) · set_profile_photo · copy_from_tree (copy a photo from another member's public tree or a photo hint into destPersonIds) · delete. Attaching media to a fact is fact.attach_media.

Inputs: action (required), date, page, cropX, +15 more.

messagesA

Ancestry member messaging and notifications. Actions: list_conversations · get_messages (conversationId) · member_profile (public profile by userId) · send (reply in conversationId, or start a new one with toUserId — sends real mail to a real person; confirm with the user first) · notifications.

Inputs: action (required), limit, userId, content, +2 more.

notes_tagsB

Research notes and MyTreeTags on a person. Actions: get_notes · save_notes (replaces the whole note; read first to append) · list_tags (tag ids by category, includes custom tags) · add_tags / remove_tags (tagIds; set custom=true for custom tag ids) · create_custom_tag.

Inputs: action (required), name, note, custom, +4 more.

personA
Destructive

People in a tree (also works read-only on other members' public trees). Actions: list (100/page, optional name filter; brief rows — use get for details) · find (name autocomplete → PID, years) · get (full profile: facts with assertionIds, citations, sources, relationships with modifiers, notes, web links; set includeSourceDetails for full source bibliographies) · family_view (ancestors/descendants graph) · relationship_to_home · add (NEW person related to anchorPersonId as Father/Mother/Spouse/Child/Sibling) · link_existing (link two existing people) · update (quick edit name/gender/living/birth/death; omitted fields kept) · change_relationship (modifier codes: parent-child pcb biological, pca adopted, pcs step, pcf foster; spouse spu spouse, spx ex, spp partner) · remove_relationship (unlink, keeps both people) · delete (permanent; displayName must equal "First Last"). Living people: add/update default to deceased; a person marked living is hidden from everyone the tree is shared with and from public view, and Ancestry auto-marks people born within ~100 years with no death as living in some views — set living explicitly. type codes for relationship actions are the related person's role seen from personId: F father, M mother, C child, H husband, W wife.

Inputs: action (required), name, page, type, +23 more.

raw_requestA
Destructive

Escape hatch: perform any ancestry.com request through the logged-in browser (same-origin fetch with cookies; CSRF added for hintsui/DNA APIs). Only when no action above fits. path like "/api/treesui-list/trees?rights=own". See docs/endpoints.md for known endpoints.

Inputs: path (required), method, jsonBody, dnaContext.

recordA

Historical records and places. Actions: get (indexed fields, image id, which of my trees have it, suggested records, other people on the same image) · image_index (every record indexed on one image, e.g. a census page) · download_image (full-res JPEG; maxSide to scale; outPath to write a file) · save_to_person (attach record to a tree person exactly like the site Review page: creates the citation and merges events — events "new" (default) | "all" | list like ["Residence"]; updateExisting=true replaces tree facts that differ (site default), saveAsAlternate=true keeps both, updateName=true takes the record name; includeRelatives adds household/parents/spouse from the record as new people or links matched ones — run once without it and read availableRelatives, then pass their ids or "all") · suggest_places (place autocomplete → gpid) · shoebox_list / shoebox_save / shoebox_remove.

Inputs: action (required), term, title, events, +11 more.

search_recordsA
Read-only

Search Ancestry historical records (census, vital, military, immigration…), optionally member trees, stories and photos — the full website search form. Names are "First Last"; years are 4-digit; places are free text ("Cocke County, Tennessee, USA") and should carry a gpid from record.suggest_places when a placeScope is used. Default matching is fuzzy everywhere (results are ranked, not filtered). Tighten per field: firstNameMatch/lastNameMatch (cumulative levels like the site slider: exact ⊂ similar ⊂ phonetic ⊂ initials|soundex); each event object takes year, yearEnd (range), yearTolerance (0 = that year only, 1/2/5/10 = ± years, omit = fuzzy), place, gpid (from record.suggest_places) and placeScope (exact, county, county_adjacent, state, state_adjacent, country; omit = fuzzy); exactRelatives, exactKeyword, exactRace. Name wildcards: "?" = one character, "" = 0–5 characters; a wildcarded name needs at least 3 literal letters and must start or end with a letter (Sm?th, Johson) and only applies with exact name matching. Start broad (name + birth year), then narrow. collectionFocus re-ranks toward a country/ethnicity; collectionId restricts to one collection; extraParams passes any other site query parameter (collection-specific fields, e.g. f-SelfMilitaryRank). Returns up to 50 hits per page with indexed fields and collection/record ids for record.get and record.save_to_person.

Inputs: page, race, birth, child, +28 more.

sourceA
Destructive

Sources, repositories, citations and web links. A source is the work (book, deed book, website); a citation is a person-level reference into it, which fact.attach_citation then links to facts. Actions: list_sources · create_source (title only; then update_source for author/publisher/etc.) · update_source · create_repository · link_repository / unlink_repository · create_citation (sourceId + detail, optional url) · update_citation · get_citation (with the facts it supports and, for record citations, the indexed record) · remove_citation · add_web_link · remove_web_link (webLinkId from person.get).

Inputs: action (required), url, date, name, +22 more.

treeA
Destructive

Family trees the user owns. Actions: list (own or shared trees) · get (home person, counts, quality stats) · create (new tree with a root person) · update (name/description) · set_privacy · export_gedcom (writes ZIP with .ged to outPath, or returns base64) · import_gedcom (local .ged file → NEW tree, waits for completion) · delete (PERMANENT; requires confirmName = exact tree name; confirm with the user first). Required: get/update/set_privacy/export_gedcom/delete need treeId; create needs name + root* fields; import_gedcom needs name + filePath.

Inputs: action (required), name, rights, treeId, +12 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 13 tools

    Disambiguation5/5

    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.

    Naming Consistency4/5

    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.

    Tool Count5/5

    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.

    Completeness5/5

    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.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI agents to create, edit, and query genealogical data from GEDCOM files. Supports complex genealogy searches, automatic data enrichment from web sources, relationship analysis, and biography generation for individuals and families.
      17
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to interact with Gramps genealogy databases for intelligent family tree research and management. Provides comprehensive tools for searching family data, creating records, analyzing relationships, and tracking genealogy research through natural language.
      51
      AGPL 3.0
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI assistants to load and query local GEDCOM genealogy files, supporting search, browsing, and family tree traversal.
      2
      2
      AGPL 3.0