内容
# 呼叫代理 Callwright
一种语音代理,可以代表您拨打电话——预订、预约、确认、一般查询——由大型语言模型的指导和基础知识塑造。它会透露自己是人工智能,在您设定的范围内进行协商,永远不会捏造,并报告结果。
基于 [Retell](https://retellai.com) 构建。作为 **MCP 服务器** 暴露,以便任何具有 MCP 能力的助手(Claude、ChatGPT、Cursor、Copilot)都可以为您拨打电话 - 将呼叫代理(执行器)与您的其他 MCP(WorkIQ、Gmail、日历 - 传感器)结合使用以获取上下文。
## 直接询问
您不需要填写表格 - 您可以用自然语言询问您的助手,它会从您的日历、电子邮件或聊天中收集上下文,组织通话,朗读给您,并在此基础上拨打电话:
> *"在我的日历中查找维修预约,并致电承包商确认星期四是否仍然有效。"*
> *"致电 Sakura Sushi,预约本周五晚上 7 点 4 人用餐 - 6:30 至 8 点之间皆可。"*
> *"打电话给我的牙医,将我的洁牙预约改为下周;我的会员 ID 在我的个人资料中。"*
> *"電話して、東京の美容院に明日の午後カットの予約を取って."* - *(用日语拨打电话)*
> *"致电京都的这家酒店,询问他们的租车费率 - 只需收集信息,不要预订。"*
助手只提取每次通话所需的最小详细信息,在通话前先与您确认,然后进行通话并在您要求时获取结果。
## 工作原理
```
您(聊天)--> 主 LLM -- 从其他 MCP 收集上下文 --> 组织一次通话
|
v
呼叫代理 MCP -- 验证 + 配置文件 + 组织 + 保护 --> Retell --> 电话
| |
+-- 获取通话结果 / 从通话中学习 <-- 结果 --+
```
- **通用代理,非特定场景。** 一个代理提示 = 固定的防护措施(披露 AI,永不支付,永不捏造,最小化 PII)+ 注入的每次通话指令。处理任何场景和语言。
- **多语言,可在运行时扩展。** 默认支持英语和日语;`add_language` 可以启动一种全新的通话语言(翻译的提示 + 原生语音 + 自己的代理)- 无需重新部署。所有语言的措辞都是数据(`lang-phrases.json`),而非代码。
- **目的优先的披露。** 以目标开头("我是一个 AI 助手,我打电话是为了确认..."),而不是名字。只有在业务需要时才会透露您的名字。
- **数据最小化,按需填写。** 个人身份信息存储在 `principal.facts`(仅在需要时共享);每次通话只携带最小的相关子集,朗读回声中会精确列出要输出的数据类型。当通话使用您尚未保存的持久事实时,它会提供记住该事实的选项 - 因此配置文件会从使用中填写,而不是预先采访。
- **从每次通话中学习。** 当通话延迟某个详细信息时,配置文件会得到丰富,以便下次可以提前收集 - 手动(`learn_from_call`)或 **自动** 通过签名验证的 Retell webhook。 全新的场景会被 **分阶段** 和 **提议**(而不会被无声创建),并有防止过度特定分裂的保护措施。
- **有界重试。** 未接听/忙碌的通话会重试 - **默认情况下恰好一次**,无需明确请求(反垃圾邮件)。
## 工具
**设置和配置文件**
| 工具 | 用途 |
|---|---|
| `get_setup_status` | 已配置/缺失的内容 |
| `configure` | 保存您的姓名、回调、固定事实(+可选配置) |
| `list_scenarios` | 已知场景配置文件(+待处理候选) |
**呼叫**
| 工具 | 用途 |
|---|---|
| `place_call` | **默认干预运行**(返回朗读回声);`confirm:true` 拨打。支持 `lang` 和 `overrides.retry` |
| `get_call_outcome` | 结果 + 分析 + `call_id` 的转录记录 |
| `list_recent_calls` | 最近的通话 |
| `list_retries` | 最近的自动无应答重拨 |
**语言**
| 工具 | 用途 |
|---|---|
| `list_voices` | 浏览 Retell 语音(用于添加语言) |
| `list_languages` | 服务器可以拨打的语言 |
| `add_language` | 在运行时添加新的通话语言(翻译 → 代理) |
| `verify_language` | 新语言的质量检查(审查卡 + 可选的实时测试通话) |
| `update_language` / `remove_language` | 更改/删除已添加的语言 |
**学习**
| 工具 | 用途 |
|---|---|
| `learn_from_call` | 丰富配置文件或分阶段新的场景候选 |
| `list_candidates` | 分阶段的场景候选(待处理的新配置文件) |
| `create_profile` | 创建新的场景配置文件(提议并确认,受保护) |
| `add_scenario_alias` | 将呼叫类型合并到现有的配置文件中 |
| `reject_candidate` | 丢弃噪声/一次性的候选 |
`place_call` 是 **无火不焦虑** 且 **有门控** 的:第一次通话返回朗读回声(包括 PII 足迹);您只有在确认 `confirm:true` 后才会拨打电话。
---
## Retell 设置(前提条件)
Callwright 通过 [Retell](https://retellai.com) 拨打电话,因此您需要一个具有 **启用外呼** 的 Retell 帐户。 在 [Retell 仪表盘](https://dashboard.retellai.com) 中:
1. **创建帐户** 并 **添加付款方式**(Stripe)。通话和电话号码按需付费;Callwright 不会额外收取费用。
2. **完成 KYC 验证。** 这是 **必填** 的,用于解锁 **外呼** 和 **购买电话号码** - 没有它,通话将被阻止。(这是一次性身份/商业检查;通常很快会获得批准。)
3. **购买电话号码。** 这成为您的 caller ID(`from` 号码)。美国本地号码最便宜;Callwright 在 `configure run_provisioning` 期间自动选择它。
4. **创建 API 密钥**(设置 → API 密钥)。这是您的 `RETELL_API_KEY` - 它保留在服务器端,绝不能进入浏览器或聊天。
**可选,推荐:**
- **验证电话号码/品牌呼叫**(仪表盘 → 电话)- 注册您的号码,以便运营商不太可能将其标记为 *"可能为垃圾电话"*,这可以显著提高外呼的接听率。
**不需要(目前处于休眠状态):**
- **A2P 10DLC 注册** - 仅在通过多步骤业务配置文件 → 品牌 →活动审批(有循环成本)发送 SMS 时需要。Callwright 的 SMS 通知已内置但搁置,因此您可以完全跳过此步骤以进行呼叫。
> 费用按分钟和目的地驱动(大约 ~$0.015/min 美国,国际更高)加上每月号码租赁。
---
## 本地运行
```bash
npm install
node init.js # 一次性设置向导(号码、代理、您的身份)
export RETELL_API_KEY=key_xxx # [环境]::SetEnvironmentVariable 在 Windows 上
node server.js # MCP 服务器在 :8787
```
随时检查设置:`node init.js status`。
从 CLI 发起通话(绕过 MCP):`node dispatch.js <job.json> --go`。
---
## 部署托管(单用户)
目标:一个远程 MCP URL **仅您可以使用**。适用于网页客户端(claude.ai,chatgpt.com)和本地客户端。
### 1. 选择具有 **持久磁盘** 的主机
`config.json` 和 `scenario-profiles.json` 在运行时写入(由 `configure` 和 `learn_from_call`),因此避免使用短暂/无服务器文件系统。 好的选择: **Fly.io** (卷), **Railway** (卷), **Render** (磁盘)。 一个小型始终在线的实例就可以了 - 通话量很小。
### 2. 设置环境变量(主机仪表盘)
```
RETELL_API_KEY = key_xxx # 您的 Retell 机密 - 保留在服务器端
MCP_AUTH_TOKEN = <long random> # 只有您持有的 bearer token
PORT = 8787 # 或主机注入的任意值
CALLWRIGHT_DATA_DIR = /data # 运行时状态的持久卷
CALLWRIGHT_PUBLIC_URL = https://your-host.example.com # 可选:启用自动学习 webhook(Retell 将 call_analyzed 发送回此处)
```
参见 `.env.example`。 **永远不要** 将 Retell 密钥放入浏览器或聊天中。
### 3. 部署
提供了 Docker:
```bash
docker build -t callwright .
docker run -p 8787:8787 --env-file .env callwright
```
或者,将主机指向此仓库(它会自动检测 Dockerfile)。在应用程序目录挂载一个卷,以便 `config.json` / `scenario-profiles.json` 在重启时持续存在。
### 4. 通过 MCP 进行首次设置(无需网页)
在聊天中进行设置:
1. 连接服务器(如下)。
2. 请求助手使用 `run_provisioning: true` 和您的姓名 + 回叫号码调用 `configure`。这将验证密钥,选择您的 Retell 号码,创建通用代理,并保存您的个人资料。
3. `get_setup_status` 现在应该报告 `ready: true`。
(或者,如果您愿意,可以在主机 shell 上运行 `node init.js` 一次。)
---
## 连接客户端
将 URL 替换为您的部署主机,将令牌替换为您的 `MCP_AUTH_TOKEN`。服务器通过两种方式接受令牌:`Authorization: Bearer` 头(桌面/代码/光标)**或** `?key=` URL 查询参数(网页客户端,其连接器表单没有头字段)。
### Claude.ai(网页)
网页连接器表单仅接受 URL,因此请在 URL 中添加令牌:
1. **设置 → 连接器 → 添加自定义连接器**(或 **浏览连接器 → 添加自定义**)。
2. **名称:** `Callwright`
3. **网址:** 粘贴您的主机 + `/mcp` + 您的令牌作为 `?key=`:
```
https://your-host.example.com/mcp?key=<MCP_AUTH_TOKEN>
```
4. 保存,然后在聊天中启用它( 🔌/tools 菜单)。询问 *"您有哪些 callwright 工具?"* 以确认它已连接。
> 由于令牌在 URL 中,因此请像密码一样保护该 URL - 任何拥有它的人都可以拨打计费到您的 Retell 帐户的电话。 通过更改 `MCP_AUTH_TOKEN` 并更新连接器来轮换。
### Claude 桌面版 / Cursor
```json
{
"mcpServers": {
"callwright": {
"url": "https://your-host.example.com/mcp",
"headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
```
### Claude 代码
```bash
claude mcp add --transport http callwright https://your-host.example.com/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"
```
### ChatGPT(开发者模式/连接器)
添加一个自定义 MCP 连接器,使用相同的 URL - 头部或 `?key=` 均可。
> 保持 **"运行工具前确认"** 开启。 `place_call` 默认情况下是干预运行,但工具确认是额外的安全措施,以防止花费和打扰真实的人。
---
## 安全说明
- **单个 bearer token = 您的锁。** 任何拥有该令牌的人都可以拨打计费到您的 Retell 帐户的电话。像密码一样对待它;通过更改 `MCP_AUTH_TOKEN` 来轮换。
- **最小权限 Retell 密钥。** 如果您不需要完全的管理权限,请使用范围限定的密钥。
- **个人身份信息。** 固定事实存储在 `config.json` 中。在您的主机上。通话仅发送最小的子集,仅在业务询问时共享;回读始终显示数据 footprint。
- 如果 `MCP_AUTH_TOKEN` 未设置,服务器仅接受 **本地开发**(本地开发)。
## 关键文件
| 文件 | 用途 |
|---|---|
| `server.js` | MCP 服务器(Streamable HTTP,bearer auth)+ 自动学习/重试 webhook |
| `dispatch-core.js` | 验证 → 配置文件 → 组成变量 → 调度(共享核心) |
| `dispatch.js` | CLI 通过 dispatch-core |
| `setup-core.js` | 配置 + 配置文件 + 状态 + 延迟保存助手(init 和 MCP 共享) |
| `lang-core.js` | 运行时语言管理(添加/验证/更新/删除,语音) |
| `lang-phrases.json` | 每种语言的组成短语(数据,而非代码) |
| `learn-core.js` | 配置文件丰富 + 新场景候选分阶段(防分裂) |
| `retry-core.js` | 无人应答重试策略 + 有限重拨决策 |
| `init.js` | 终端设置向导(`node init.js` / `node init.js status`) |
| `update-prompt.js` | 将提示 + 工具 + 礼仪 + webhook 推送到现有代理 |
| `place_call.schema.json` | LLM 填写的作业合约 |
| `generic-prompt.md` / `.ja.md` | 通用代理提示(防护措施 + `{{variables}}`) |
| `scenario-profiles.json` | 学习配置文件(每个场景的推荐详细信息) |
| `learn.js` / `get-call.js` | CLI:丰富配置文件 / 获取通话结果 |
| `paths.js` | 解析可变路径(遵守 `CALLWRIGHT_DATA_DIR`) |
| `docs/` | 设计规格(add-language, profile-learning) |
| `webhook/` | 可选的 serverless Retell→Resend 电子邮件通知器(独立于 MCP) |
> 用户运行时文件 - `config.json`, `agents.json`, `.retell-ids.json`, `job.*.json`, `grounding.*.json` - 被 git 忽略。它们在运行时创建(由 `init`/`configure`)或 是个人通话数据;永远不要提交它们。从 `config.example.json` 和 `examples/` 开始。
## 披露和合法性说明
代理在每次通话开始时 **始终披露它是一个 AI** - 这是一个坚定、不可更改的行为。 AI 通话披露在某些司法管辖区(例如加利福尼亚州的 B.O.T. 法案)是合法要求的,并且在任何地方都是良好的做法。代理也不会捏造信息,并且仅在业务询问时共享个人数据。对于商业或大规模使用,请与您所在司法管辖区的律师确认要求。
## 可选:每次通话的电子邮件通知
参见 [`webhook/`](./webhook) - 一个 serverless Retell→Resend 通知器,它会向您发送每次通话的结果。它 **独立于 MCP**(按需通过 `get_call_outcome` 检索结果),并且 **默认情况下不部署或连接**。