内容
# gramps-remote-mcp
一个用于 [Gramps](https://gramps-project.org) 的远程 [Model Context Protocol](https://modelcontextprotocol.io) (MCP) 服务器,这是一个开源的家谱应用程序。它允许MCP客户端(Claude或任何支持MCP的助手)通过其REST API读取和编辑托管在运行中的 [Gramps Web](https://www.grampsweb.org) 实例上的家谱。
与读取本地Gramps数据库文件的MCP服务器不同,这个服务器通过HTTP与Gramps Web通信,因此它适用于实时、共享的实例——并且专注于**有指导、有保护的写入操作**:新记录标记为未确认,以后需要审查,并且字段突变和记录创建受到前后快照和记录数量保护。
## 工具
服务器通过MCP **stdio** 传输暴露了25种工具,以及3种可选的破坏性工具(`gramps_delete_person`、`gramps_delete_family`、`gramps_delete_blog_post`),只有在明确启用时才会注册 - 请参阅 [破坏性工具](#破坏性工具)。
**读取**
| 工具 | 用途 |
| --- | --- |
| `gramps_get_person(gramps_id)` | 通过Gramps ID获取当前实时人员记录(例如`I0024`)。 |
| `gramps_search_person(query, limit=None)` | 在名字、姓氏、组合的`First Surname`、昵称和替代/娘家姓氏中搜索人员(不区分大小写子字符串);可选结果`limit`。 |
| `gramps_list_people(keys=None, page=None, pagesize=None)` | 列出人员,可选选择字段(`keys`)和分页(`page`基于1,`pagesize`限制行数)。如果两者都省略,则返回所有人员。 |
| `gramps_get_object_counts()` | 返回树的对象数量(人员、家庭、事件、注释、媒体等)- 作为前后保护的便捷方式。 |
| `gramps_get_descendants(gramps_id, grade=1)` | 返回人员及其后代作为嵌套的JSON树,`grade`代深。 |
| `gramps_get_ancestors(gramps_id, grade=1)` | 返回人员及其祖先作为嵌套的JSON树,`grade`代向上。 |
| `gramps_get_relations(gramps_id)` | 返回人员的家庭上下文:父家庭(父亲/母亲槽)和自己的家庭(伴侣+子女)。每个人都有自己的`gender`;父亲/母亲是血统槽,不是性别。 |
**写入**
| 工具 | 用途 |
| --- | --- |
| `gramps_set_gender(gramps_id, gender)` | 设置人员的性别(`0`=女性,`1`=男性,`2`=未知,`3`=其他)。 |
| `gramps_set_surname(gramps_id, surname, name_type=None)` | 设置主要姓氏,可选名称类型(例如`Married Name`)。 |
| `gramps_set_first_name(gramps_id, first_name)` | 设置人员的主要给定(第一个)名称。非破坏性;返回前后。 |
| `gramps_set_gender_bulk(items)` | 一次调用设置多个人员的性别(`items` = `[{"gramps_id", "gender"}, …]`)在单个计数保护下;尽力而为,每项结果和错误。 |
| `gramps_set_surname_bulk(items)` | 一次调用设置多个人员的姓氏(`items` = `[{"gramps_id", "surname", "name_type"?}, …]`)在单个计数保护下;尽力而为。 |
| `gramps_add_birth_name(gramps_id, surname, first_name=None)` | 添加`Birth Name`替代名称条目。 |
| `gramps_add_alternate_name(gramps_id, surname, first_name=None, name_type="Birth Name")` | 添加指定类型的替代名称(例如`Birth Name`,`Married Name`)。姓氏内容从参数中获取;其他子字段默认为。 |
| `gramps_swap_primary_name(gramps_id, alt_index=0)` | 交换人员的首要名称与其中一个替代名称(`alt_index`选择哪个,默认为第一个)。提升例如`Birth Name`为首要并降级`Married Name`为替代;如果索引处没有替代,则出错。 |
| `gramps_add_person(first_name, surname, gender, birth_year=None, birth_quality=None, birth_year_to=None, note=None)` | 创建一个新人员,标记为**未确认**,以供后续审查。返回新的Gramps ID。 |
| `gramps_add_family(spouse_a_id, spouse_b_id=None)` | 创建一个链接一个或两个配偶的家庭。返回新的家庭Gramps ID。 |
| `gramps_add_child_to_family(family_id, child_id)` | 将现有人员链接为现有家庭的子代。 |
| `gramps_set_family_parent(family_id, gramps_id, role)` | 设置**现有**家庭的父亲或母亲(`role` = `father`/`mother`,明确的血统槽 - 永远不会按性别重新排序)。添加缺失的父母或替换错误的父母(拒绝将一个人设置为两个父母);返回被取代的父母为`previous`。 |
| `gramps_remove_child_from_family(family_id, child_id)` | 从家庭的子代中删除人员(`gramps_add_child_to_family`的逆操作)- 例如,分离错误记录为子代的配偶。兄弟姐妹保持完整。 |
| `gramps_confirm_person(gramps_id)` | 删除**未确认**标签,将人员标记为已确认。 |
### 博客帖子
博客帖子是一个标记为`Blog`的`Source`对象,其正文存储在其第一个注释中 - 而不是一个专用的博客记录(请参阅 [`docs/blog-crud.md`](docs/blog-crud.md) 以获取完整的数据模型)。 正文的存储格式由 `GRAMPS_BLOG_BODY_FORMAT` 控制:默认为纯文本,或设置为 `html` 时为 HTML(在服务器端渲染和清理)。帖子的正文注释类型在创建帖子时固定,因此每个部署只需设置 `GRAMPS_BLOG_BODY_FORMAT`一次:将此设置应用于已经存在帖子的树会使这些帖子以其原始格式渲染(后续HTML更新到文本模式帖子会显示为转义 - 可见但无害)。
删除博客帖子(`gramps_delete_blog_post`)是破坏性的,仅在破坏性工具门控打开时可用 - 请参阅 [破坏性工具](#破坏性工具)。
| 工具 | 用途 |
| --- | --- |
| `gramps_create_blog_post(title, body, author=None)` | 创建博客帖子(一个标记为`Blog`的`Source` + 正文注释)。 正文存储在 `GRAMPS_BLOG_BODY_FORMAT` 中(默认为纯文本,或HTML)。 返回新的源Gramps ID(例如`S0002`)。 |
| `gramps_list_blog_posts(page=None, pagesize=None)` | 列出博客帖子(标记为`Blog`的`Source`),最新的在前。 返回`[{gramps_id, title, author, change}]`。 `page`基于1,`pagesize`限制行数;两者均省略以获取所有。 |
| `gramps_get_blog_post(gramps_id)` | 通过其源Gramps ID获取一个博客帖子。 返回标题、作者、更改(unix时间戳)、正文为渲染的HTML(`body_html`)和原始字符串(`body_text`),以及`note_gramps_id`。 |
| `gramps_update_blog_post(gramps_id, title=None, body=None, author=None)` | 更新博客帖子的标题、正文和/或作者(仅限传递的内容)。 `body`存储在 `GRAMPS_BLOG_BODY_FORMAT` 中,并替换现有的注释文本。 返回 `{gramps_id, updated}`。 |
### 破坏性工具
这些破坏性工具存在但**默认关闭且未注册**,因此MCP客户端在未经选择的情况下永远不会看到它们:
| 工具 | 用途 |
| --- | --- |
| `gramps_delete_person(gramps_id, confirm)` | 永久删除人员(用于重复/错误条目)。 需要 `confirm=True` 并保护人员数量恰好减少一个。 删除链接的人员会取消链接到其家庭(该槽被清除)而不是级联删除。 附加到此人员的注释也会被清理(共享注释将被保留);已删除的注释句柄作为 `deleted_notes` 返回。 |
| `gramps_delete_family(family_id, confirm)` | 永久删除家庭 - 用于清理孤立/无孩子的家庭,留下的家庭在重新安置其子女后。 需要 `confirm=True`,如果家庭仍有子女则拒绝(首先删除它们),并保护家庭数量恰好减少一个。 |
| `gramps_delete_blog_post(gramps_id, confirm)` | 永久删除博客帖子。 需要 `confirm=True`。 删除`Source`并清理其正文注释(如果现在孤立,则共享注释将被保留);保护源数量恰好减少一个。 |
要启用它,请在服务器的环境中设置 `GRAMPS_ENABLE_DESTRUCTIVE=1`;帐户还需要对树的删除权限。
### 备份/还原
两个工具通过挂载的备份目录移动整个树的 文件:
- `gramps_export_tree(filename=None, extension="gramps")` — 将`.gramps`(gzip XML)备份写入备份目录;返回 `{path, bytes, counts}`。 只读,始终可用。
- `gramps_import_file(filename, extension="gramps")` — 将文件从备份目录导入树(**添加** - Gramps导入从不合并,它堆叠)。 返回 `{before, after, added}`。 **需要帐户具有OWNER角色**(在运行 `ops/setup-automation-user.sh` 时使用 `GRAMPS_ROLE=4`)。
设置 `GRAMPS_BACKUP_DIR` 为容器内的目录,并在那里挂载主机目录,以便文件存活并可从主机访问:
```bash
docker run --rm -i \
--env-file .env \
-v /home/you/gramps/export:/data \
-e GRAMPS_BACKUP_DIR=/data \
gramps-remote-mcp
```
导出写入该目录;要导入,请先将文件放入主机目录,然后调用 `gramps_import_file("your-file.gramps")`。 导入的完成通过轮询对象计数(而非任务端点)进行确认,因此它适用于同步和Celery支持的Gramps Web部署。
## 先决条件
- 一个通过HTTP(S)可访问且启用REST API的 **Gramps Web** 实例。
- 具有 **EDITOR角色** 的Gramps Web **用户帐户**(角色`3`)。 编辑权限对于写入工具(创建人员/家庭、设置字段、添加子代、确认)是必需的。
- Python 3.12+(或Docker)来运行服务器。
服务器使用`POST /api/token/`对用户名/密码进行身份验证,然后在每个请求上将返回的JWT作为Bearer令牌发送,并在`401`时透明地重新进行身份验证。
> **提示:** 创建一个专用的、权限最小的自动化用户,而不是重用个人登录。 辅助脚本 [`ops/setup-automation-user.sh`](ops/setup-automation-user.sh) 创建一个持久的EDITOR用户,并打印生成的密码。
## 配置
服务器从环境变量中读取其连接设置(请参阅 [`.env.example`](.env.example)):
| 变量 | 描述 |
| --- | --- |
| `GRAMPS_BASE_URL` | Gramps Web实例的基本URL,例如 `https://gramps.example.com`。 |
| `GRAMPS_USERNAME` | EDITOR角色帐户的用户名。 |
| `GRAMPS_PASSWORD` | 该帐户的密码。 |
复制`.env.example`到`.env`并填写实际值。 `.env`被git忽略 - 永远不要提交凭证。
## 使用Docker运行
```bash
docker build -t gramps-remote-mcp .
docker run --rm -i --env-file .env gramps-remote-mcp
```
镜像运行`python server.py`作为其入口点,并通过stdio公开MCP,因此需要 `-i`(交互式stdin)。
## 本地运行
```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
GRAMPS_BASE_URL=https://gramps.example.com \
GRAMPS_USERNAME=mcp-automation \
GRAMPS_PASSWORD=... \
.venv/bin/python server.py
```
## 配置MCP客户端
将服务器添加到MCP客户端配置中。 示例(Claude Desktop / `.mcp.json` 样式),使用Docker镜像:
```json
{
"mcpServers": {
"gramps": {
"command": "docker",
"args": ["run", "--rm", "-i", "--env-file", "/absolute/path/to/.env", "gramps-remote-mcp"]
}
}
}
```
或直接运行Python脚本:
```json
{
"mcpServers": {
"gramps": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/server.py"],
"env": {
"GRAMPS_BASE_URL": "https://gramps.example.com",
"GRAMPS_USERNAME": "mcp-automation",
"GRAMPS_PASSWORD": "..."
}
}
}
}
```
## 设计重点
- **未确认生命周期。** `gramps_add_person` 将每个新记录标记为未确认(一个专用的审查标签); `gramps_confirm_person` 删除它。 这为您提供一个记录创建的审查队列,而这些记录是由助手创建的,然后被接受为最终记录。
- **保护写入。** 字段突变捕获 `before`/`after` 快照,并验证总人数(或家庭)数量保持不变 - 或在创建时恰好增加一个,否则会引发错误,因此意外的副作用会大声失败而不是静默地破坏树。 结构化家庭编辑(`add_child_to_family`、`remove_child_from_family`、`set_family_parent`)改为PUT整个家庭,并依赖Gramps Web API的引用完整性保护,而不是本地计数保护;破坏性的`delete_person`具有自己的确切-1人员计数保护。
- **幂等保护。** `gramps_add_child_to_family` 拒绝已经链接的子代;未确认标签被查找并重用(使用Unicode NFC标准化)而不是重复。
- **基于性别的父本槽。** 创建家庭时使用 `gramps_add_family`,具有女性性别的配偶被分配为母亲,另一个分配为父亲;如果性别不能区分,则调用顺序决定确定性地。 要将父本放入特定槽,无论性别如何,请使用 `gramps_set_family_parent` 并指定显式 `role`。
- **结构化相对树。** `gramps_get_descendants` 和 `gramps_get_ancestors` 返回嵌套的JSON树,限定为 `grade` 代, `gramps_get_relations` 提供一个人的完整家庭上下文,一次调用 - 而不是扁平列表。
- **血统 ≠ 性别。** 在GEDCOM导入的数据中,家庭的 `father`/`mother` 槽遵循血统,而不是性别。 因此,关系/祖先工具永远不会根据槽推断性别:每个人都有自己的 `gender`,而伴侣是根据“另一个槽”解析的,而不考虑性别。 `gramps_set_family_parent` 遵循相同的规则 - 您传递显式 `role`(`father`/`mother`),它永远不会按性别重新排序。
- **批量写入具有一个保护。** `gramps_set_gender_bulk` / `gramps_set_surname_bulk` 在单个记录计数保护下应用多个更新。 它们是尽力而为的(非原子):失败的项目在 `errors` 中报告,不会中止其余部分,并且计数保护被报告(`count_guard_ok`)而不是引发,因此部分结果永远不会丢失。
## 开发
安装开发依赖并运行测试套件:
```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest -q
```
测试模拟Gramps Web HTTP层,因此不需要实时实例。 测试fixtures使用通用占位符名称。
### linting
该项目使用 [ruff](https://docs.astral.sh/ruff/) 进行linting和格式化(配置在 `pyproject.toml` 中):
```bash
.venv/bin/ruff check . # lint
.venv/bin/ruff format . # auto-format
.venv/bin/ruff format --check . # verify formatting (what CI checks)
```
启用git钩子,以便在每次提交时运行linting(自动修复)并阻止推送时阻止:
```bash
.venv/bin/pre-commit install --hook-type pre-commit --hook-type pre-push
```
CI(`.github/workflows/lint.yml`)在每个拉取请求上运行相同的检查;绿色`ruff`检查是合并到 `main` 的要求。## 许可证
在 [MIT License](LICENSE) 下发布。