Skip to main content
Glama
ball2jh

familysearch-org-mcp

by ball2jh
README.md
# familysearch-org-mcp

An MCP server for FamilySearch.org: the shared Family Tree (read, search, add people, facts, relationships,
sources, notes, memories), historical record search and record reading, and standardized places.
Companion to [ancestry-com-mcp](https://github.com/ball2jh/ancestry-com-mcp); both can be attached at once.
Tools are prefixed `familysearch_` and `ancestry_` so they never collide.

> **Unofficial.** Not affiliated with or endorsed by FamilySearch. It authenticates with your own website
> session rather than a developer key, which FamilySearch may not sanction; the Family Tree is shared and
> public, so every write is visible to everyone. Use it with care, at your own risk.

## How it works

FamilySearch has a documented platform API (GEDCOM X). Production developer keys are only issued to registered
businesses, but the website's own session cookie (`fssessionid`) is accepted as an OAuth bearer token by
`api.familysearch.org`, and plain Node requests are not bot-blocked there. So this server runs **without a
browser**: all API calls go straight from Node with `Authorization: Bearer <cookie>`. Historical record search
lives on `www.familysearch.org/service/…` behind Imperva bot protection and goes through a Chrome-impersonating
client ([impit](https://github.com/apify/impit)) instead. Cookies are kept in `.session.json` (mode 600,
git-ignored). The token expires after 24 h or 60 min idle, but the server mints a new one silently by replaying
the website's OAuth login redirect with the saved identity cookies, so a real Chrome is needed only for the
initial sign-in (`pnpm login` or the `familysearch_session` tool's `login` action: opens a dedicated Chrome
profile, waits for you to log in, saves the cookies, closes the window) and again if the identity session
itself expires.

Requirements: Node 26+, pnpm, Google Chrome.

## Setup

```bash
git clone https://github.com/ball2jh/familysearch-org-mcp
cd familysearch-org-mcp
pnpm install
pnpm login            # one-time: opens Chrome to sign in, saves .session.json, closes Chrome
pnpm mcp-test         # read-only check
```

Claude Code / Claude Desktop config (stdio):

```json
{ "mcpServers": { "familysearch": { "command": "node", "args": ["/absolute/path/to/familysearch-org-mcp/src/server.ts"] } } }
```

## 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). Every tool takes 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 10K tokens.

| Tool | Actions | Writes? |
|---|---|---|
| `familysearch_session` | status, login | opens Chrome, saves cookies |
| `familysearch_person` | get, search, ancestry, descendancy, changes, matches, notes | no |
| `familysearch_person_edit` | create, update, delete_conclusion, delete | yes, public |
| `familysearch_relationship` | get_couple, get_child_parents | no |
| `familysearch_relationship_edit` | create_couple, create_child_parents, delete_couple, delete_child_parents | yes, public |
| `familysearch_source` | list_person, source_box, get | no |
| `familysearch_source_edit` | create, attach, detach, delete_description | yes, public |
| `familysearch_note_edit` | add, delete | yes, public |
| `familysearch_memory` | list_person, list_mine, get | no |
| `familysearch_memory_edit` | upload, attach, delete | yes |
| `familysearch_record` | search, fulltext (OCR text of unindexed images), get, collections, collection, catalog / catalog_item (the FamilySearch Catalog: books, microfilm, digital folders and their film numbers), browse (a collection's county/volume/image hierarchy), film (every image of a digital film) | no |
| `familysearch_download` | record_image (by persona, record or image ark, or a film image's storage id; optional region to zoom), memory | writes a local file, or returns the image inline |
| `familysearch_place` | search, get | no |
| `familysearch_raw_request` | any platform request (or a `/service/` website request with `viaSite`) | can |

Files: `familysearch_download` writes record images and memory files to a local `outPath`; without `outPath`,
images come back inline (record images scaled to `maxWidth`, default 1600 px, and always fitted to what the
model can see: at most 2000 px on the long edge and 4784 visual tokens) so the assistant can look at them. To
read small handwriting, `region` cuts part of the page from the full-resolution scan (with the `sharp` image
library), so a quarter page reaches the model near full resolution. `familysearch_memory_edit upload` reads from a local `filePath`.

The Family Tree is a single shared public tree. Every write is visible to everyone and attributed to the
user, so the edit tools take a `changeMessage`/`reason` and the server instructions tell the model to search
before creating, be conservative, cite sources, and confirm deletions.

Results: JSON, trimmed to what a caller acts on. 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
FamilySearch is retried once after its `Retry-After`; if it persists the error says how long to wait.

## Tests

- `pnpm test` — unit tests, no network: result helpers, error and `Warning` parsing, fact-type normalization, restricted-image access rules (`test/restricted.test.ts`), the search, relationship, ancestry, descendancy, collection, catalog (holdings, film paging and filter), full-text (the boolean query compiler, paging through split searches, facets) and waypoint projections on recorded response shapes (`test/fixtures/`), and `test/writes.test.ts`, which runs the real client against a fake person in `test/fake-transport.ts` that applies each write the way FamilySearch does, to check the body every edit sends and the result it reads back (a date-only fact edit keeps its place, a second Birth is refused, a new name is not preferred, a warning is an error). `test/conventions.test.ts` and `test/tools.test.ts` are static checks: no write swallows a failed read, every write's result comes from a returned id or a read-back and never an optional id or a bare `ok: true`, every person POST goes through the `Warning` check, failed requests keep their HTTP status, every tool action has a handler and 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, downloads one image inline and
  one to disk, and checks that error paths come back as `isError` results. Prints definition size and
  per-call latency.
- `pnpm mcp-write-test` — creates three *living* (therefore private) "Mcptest" people and touches nothing else:
  adds and removes a note, facts, names, a source and a memory on one of them, checks each refusal (a second Birth,
  a new preferred name, an unknown place id or source description, a delete without a reason), links them as a
  couple and a child, and reads every write back to check what was stored and that the unmentioned fields survived.
  Cleanup runs in a `finally`, so a failing step does not leave test people behind.

## Repository layout

- `src/transport.ts` — bearer-token HTTP, silent token renewal, Imperva client, pacing, rate-limit handling, error unwrapping
- `src/session.ts` — cookie jar persisted in `.session.json`
- `src/browser.ts` — Chrome bootstrap used only by `login`
- `src/client.ts` — typed client over the GEDCOM X platform API, with response compaction
- `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, and the two end-to-end tests
- `test/` — unit tests
- `docs/endpoints.md` — verified endpoint table, including which documented paths do not work

## Caveats

- The session token expires (24 h / 60 min idle); the server renews it silently from the saved identity cookies.
  When that renewal fails too, tools report not logged in and `familysearch_session login` gets you back in.
- FamilySearch throttles (HTTP 429); requests are spaced 120 ms apart and a 429 is retried once after `Retry-After`.
- Unindexed material: `fulltext` searches the OCR'd text of wills, deeds, probate, court and land books and
  newspapers, which the indexed `search` never sees; every hit carries the entire page text upstream, so the tool
  returns a snippet around the match unless `includeText` is asked for. FamilySearch's own query language is not
  boolean (`+word` required, `-word` excluded, every other word optional; AND/OR are ignored words and parentheses group
  nothing), so the tool compiles its boolean `text` into that: words are required, and an OR inside an AND runs as up to
  8 searches that do not overlap, whose totals add up (see "Full-text query syntax" in `docs/endpoints.md`). Its place, collection, year and record-type filters are the site's facets:
  the upstream `q.place` parameter is silently ignored, so `place` is matched against the index's place tree
  (region > country or state > county > town) and a part it cannot match is refused with the choices rather than
  searched wider. `browse` walks a collection's waypoints
  (county → volume → images) and `film` lists a digital film's images in order (the storage service ignores
  paging offsets, so the tool pages client-side). Both hand back image arks or storage ids that
  `familysearch_download record_image` accepts.
- Restricted images (viewable only at a FamilySearch center or affiliate library, only by Church members, or not at
  all) cannot be downloaded: `record_image` fails naming the rule, `get` on the image ark returns `restricted` with
  its collection, waypoint and citation, and `film` says `restricted` (or `mixed: …`) up front. The index of such an
  image is often still readable with `search` and `get`. See "Restricted images" in `docs/endpoints.md`.
- Replace-style writes refill what the caller left out. A conclusion sent with an id replaces the stored one
  wholesale, so `familysearch_person_edit update` reads the person first and keeps the unmentioned date, place,
  value, name type, preferred flag and name parts; `scripts/mcp-write-test.ts` edits a fact with only a date and
  checks the place survived. Any new write that replaces a record must follow the same rule.
- Some writes that look like additions are replacements upstream and are refused instead: a second Birth,
  Christening, Death, Burial or other single-instance fact without an id (FamilySearch overwrites the stored one),
  and a new name sent as preferred (it replaces the preferred name). New names are added as alternates.
- FamilySearch answers HTTP 200 to writes it only partly applied or ignored (an unknown place id, an unknown source
  description, a repeated source attachment). A person POST with a `Warning` header is reported as an error, place
  ids and source descriptions are checked before writing, and every write's result is read back from the person.
- Tool schemas are strict: a parameter the tool does not have is rejected by name rather than dropped. Per-page
  counts are clamped to what fits the result budget (record search 40, full text 20, film and browse 250,
  collections 80, catalog 50, catalog_item films 200); recorded sessions asked for 50 to 1000 and got truncated results they then worked from.
- Tree search accepts a year range ("1745-1780") and sends it as from/to; the platform rejects the single value.
- The research project that uses these servers should run them from a separate stable clone (see `CLAUDE.md`),
  not from a checkout that is being edited.
- Not covered: LDS ordinance data, user (private) trees, discussions, and the Source Box folder management.
- `.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

A4/5.0

Scored across 9 tools

Disambiguation5/5

Each tool maps to a clear, distinct resource type—person, relationship, source, record, memory, note, place, session, and raw request. Descriptions clearly separate overlapping concepts like person.search vs record.search or memory.attach vs source.attach. No two tools appear to do the same thing.

Naming Consistency5/5

All tool names are singular nouns representing domain entities, with compound names using underscores (fs_session, raw_request). This is a consistent, predictable pattern. No mixing of verb styles, casing, or conventions.

Tool Count5/5

9 tools is well within the ideal 3-15 range and appropriately scoped for FamilySearch. Each tool covers a distinct functional area without redundancy or bloat. The set feels balanced for the platform's core genealogy workflows.

Completeness4/5

Core lifecycles are well covered: person CRUD, relationship create/get/delete, source attach/detach, memory upload/attach, note add/delete, record search/get, and place lookup. Minor gaps like updating a note, source description, or relationship are workaroundable via delete/recreate, so slightly short of full CRUD completeness.

Maintenance

ActivityMaintained
ResponsivenessNo issues