> ## Agent Instructions
>
> Base URL: https://api.anysite.io
> Authentication: send the `access-token` header. Do NOT use `Authorization: Bearer`.
> Full endpoint catalog: https://app.anysite.io/docs
# CRM 工具

让你的 AI 客户端通过 MCP 服务器读取并安全地补全 HubSpot 或 Pipedrive 记录，支持预览和撤销。

目标：连接你的 CRM，让 AI 客户端补全联系人和公司信息，同时不覆盖你关心的数据。

## 设置

1. 打开你的个人资料，开启**CRM 集成**。在开启之前，`crm_*` 工具对你的 MCP 客户端是隐藏的，CRM 调用
   会失败并返回 `CRM integration is not enabled. Activate it in account settings first.`
2. 点击**连接 HubSpot**或**连接 Pipedrive**。会打开一个新的 CRM 标签页：在那里批准访问，并在
   10 分钟内完成。连接会在你的个人资料中显示为 `active`。
3. 可选：在**受保护字段**下，列出智能体绝不能写入的其他属性名（用逗号分隔）。
4. 刷新 MCP 客户端中的工具列表（或开始一个新对话）。更改会在一分钟内同步到 MCP 服务器。

> ACTION FOR THE HUMAN: open https://app.anysite.io/profile (打开个人资料).

你也可以从对话中连接：`crm_connect` 工具会返回一个在浏览器中打开的链接，`crm_connect_status` 会报告
连接何时激活。同一时间只能连接一个 CRM —— 连接第二个会失败，返回
`Another CRM is already connected. Disconnect it before connecting a new one.` 要切换，请先在个人资料
中断开当前连接。

## 写入是如何工作的

`crm_upsert_contacts` 和 `crm_upsert_companies` 对每条记录的每个字段都应用相同的规则：

- **只填空白字段。** 已经有值的字段不会被改动，除非它的名字在 `overwrite_properties` 中。
- **受保护字段永远不会被写入**，即使在 `overwrite_properties` 中也不行。内置的受保护字段：HubSpot 的
  `hubspot_owner_id`、`lifecyclestage`、`hs_lead_status`；Pipedrive 的 `owner_id`、`label`、
  `label_ids`、`visible_to`。你在个人资料中设置的受保护字段会追加在这些之上。
- **只读的 CRM 属性**、空值，以及不属于下拉属性可选项的值，都会被跳过。
- **默认不创建新记录。** 只有设置 `allow_create: true` 时，不存在的记录才会被创建。
- **先预览。** `dry_run: true` 会针对每条记录返回将要写入的字段及其新旧值，并且不会真正写入。客户端
  应该在批量写入前把这份预览展示给你。

每个被跳过的字段或记录都会带着原因作为警告返回，不会有任何改动被悄悄丢弃。

### 记录匹配方式

| 对象 | 匹配依据 | 创建新记录需要 |
|---|---|---|
| 联系人 | `email`、`record_id` 或 `linkedin_url` | `email` |
| 公司 | `domain` 或 `record_id` | `domain` |

- `linkedin_url` 匹配在 HubSpot 中始终有效。在 Pipedrive 中，只有当账号为 LinkedIn 设置了 Pipedrive
  搜索能访问到的自定义人员字段时才有效；否则这类记录永远不会匹配。
- 当一个键匹配到多条记录时，会更新最旧的一条，并通过 `duplicate_contacts` / `duplicate_companies`
  警告报告候选记录有多少条。
- 可以用 `associate_company_domain` 或 `associate_company_id` 把联系人关联到公司。已有的公司关联会被
  保留，除非设置 `overwrite_associations: true`。

### 警告原因

| 原因 | 含义 |
|---|---|
| `fill_blank_skip` | 该字段已经有值，且不在 `overwrite_properties` 中。 |
| `protected_field` | 该字段受保护。 |
| `read_only_property` | CRM 不允许写入该属性。 |
| `empty_value` | 要写入的值为空。 |
| `enum_option_missing` | 该值不是该属性的可选项之一。 |
| `not_found` + `create_not_allowed` | 没有匹配到记录，且 `allow_create` 未开启。 |
| `record_not_found` | 给定的 `record_id` 不存在。 |
| `create_requires_email` | 没有邮箱无法创建新联系人。 |
| `duplicate_contacts` / `duplicate_companies` | 匹配到多条记录；更新了最旧的一条。 |
| `scan_incomplete` | CRM 规模过大，无法完整搜索，可能遗漏了某个已有公司。 |

## 读取记录

`crm_get_schema` 返回联系人和公司的属性（包括自定义属性和下拉选项）、列表以及受保护字段；指定具体
属性可以让返回结果更简短。`crm_query_records` 按列表、记录 ID、联系人邮箱或全文搜索读取记录，可以
指定要返回的属性，并通过游标获取下一页。

## 撤销

每次真正的写入都会返回一个 `run_id`。`crm_undo(run_id)` 会恢复该次运行所写入的每个字段的原值，并
移除它新增的公司关联：

- 如果字段在该次运行之后又被改动过，会在 `conflicts` 中报告，并保持不变。
- 现在变成受保护或只读的字段会被跳过并给出警告；之后被删除的记录会报告为 `record_missing`。
- 该次运行创建的记录不会被删除；运行写入的字段会被清空。
- 撤销本身也是一次运行：它的 `undo_run_id` 同样可以用相同的方式被撤销。

## 验证

让客户端以 dry run 方式执行一次小规模补全，例如
`Enrich my 5 newest HubSpot contacts with their current job title — dry run first`。你应该能看到带
新旧值的预览，且 CRM 中没有任何变化。真正执行一次后，再用返回的 `run_id` 调用 `crm_undo`，确认数值
被恢复。

## 故障排查

| 消息 | 解决方法 |
|---|---|
| 客户端中没有 `crm_*` 工具 | 在个人资料中开启**CRM 集成**，然后刷新客户端的工具列表。 |
| `No active CRM connection. Run crm_connect first.` | 在个人资料中连接一个 CRM，或使用 `crm_connect`。 |
| `Multiple active CRM connections, pass connection_id explicitly.` | 让客户端使用 `crm_list_connections`，并显式传入 `connection_id`。 |
| `CRM connection was revoked. Reconnect your CRM.` / `CRM connection is not active` | 访问权限已在 CRM 一侧被移除。请在个人资料中断开连接并重新连接。 |
| `Run not found` | `run_id` 不正确，或属于另一个连接。 |
