内容
# 实际MCP服务器
[](https://github.com/Type-zero-labs/actual-mcp/actions/workflows/ci.yml)
[](./LICENSE)
**适用于 Actual Budget 的 MCP 服务器 — 带有每种工具的安全层。**
Actual 本身没有 HTTP 或 MCP 接口;它提供了一个 Node.js SDK。本项目将该 SDK 封装为 MCP 服务器,以便任何代理 — Claude Desktop、Cursor 或您自己的代理 — 都可以通过自然语言读取和编辑您的预算。
与普通的封装不同:**每种工具都带有自己的风险。** 读取是一种读取。变更会立即执行。而 **删除操作永远不会在代理的指令下执行** — 它会返回一个确认令牌,并在您再次调用它并设置 `confirm: true` 之前拒绝。模型可以自由规划;它不能在提示漂移时悄悄删除一个类别。
> 给予代理对您的财务的写入权限是一种责任,除非工具本身带有安全带。最安全的执行规则的地方是工具,而不是提示。
---
## 要求
- **Node.js 20+**
- 一个正在运行的、自我托管的 **Actual 同步服务器**(您同步预算到的 `actual-server` — 而不是桌面应用程序)。您需要它的 URL、密码(如果设置)和您要公开的预算的 Sync ID。
## 快速入门
### 1. 查找您的 Sync ID
在 Actual 中:**设置 → 显示高级设置 → Sync ID**。它是一个 UUID,如 `a1b2c3d4-....`
### 2. 将服务器添加到您的 MCP 客户端
<details open>
<summary><b>Claude Desktop</b></summary>
编辑 `claude_desktop_config.json`(Claude → 设置 → 开发者 → 编辑配置):
```json
{
"mcpServers": {
"actual": {
"command": "npx",
"args": ["-y", "actual-mcp"],
"env": {
"ACTUAL_SERVER_URL": "https://actual.example.com",
"ACTUAL_PASSWORD": "your-server-password",
"ACTUAL_SYNC_ID": "your-sync-id"
}
}
}
}
```
重启 Claude Desktop。您将在 🔌 图标下看到工具。
</details>
<details>
<summary><b>Cursor</b> / 其他 MCP 客户端</summary>
任何 stdio MCP 客户端都可以使用 — 将其指向 `npx -y actual-mcp`,并使用相同的四个环境变量。在 Cursor 中:**设置 → MCP → 添加新**,并使用相同的 `command`/`args`/`env` 形状。
</details>
### 3. 向它提问
```
我上个月在杂货上花了多少钱?
列出我的账户和余额。
将 200 雷亚尔从 7 月份的餐饮预算转移到储蓄预算。
在 Nubank 添加一个 54.90 雷亚尔的交易,支付人 "Uber",类别为交通,日期为今天。
```
## 配置
| 环境变量 | 必需 | 描述 |
|---|---|---|
| `ACTUAL_SERVER_URL` | ✅ | 您的 Actual 同步服务器 URL。 |
| `ACTUAL_SYNC_ID` | ✅ | 要打开的预算的 Sync ID。 |
| `ACTUAL_PASSWORD` | — | 服务器密码。如果服务器没有密码,请省略。 |
| `ACTUAL_DATA_DIR` | — | SDK 缓存预算的目录。默认为 `/tmp/actual-mcp-data`。 |
> **金额为整数小单位**,与 Actual SDK 匹配 — 200.00 雷亚尔为 `20000`,支出为负数(`-5490` = 54.90 雷亚尔支出)。
## 安全模型
| 级别 | 工具 | 行为 |
|---|---|---|
| 🟢 **只读** | 所有 `list_*`、`get_*` | 永远不会触及数据。 |
| 🟡 **变更** | 所有 `create_*`、`update_*`、`set_budget_*` | 变更数据;立即运行。 |
| 🔴 **受保护** | 所有 `delete_*` | 变更数据;**在 `confirm: true` 之前拒绝**。 |
每种工具的级别通过三种方式公开,以便在调用前可见安全带:在工具描述中,在 MCP `readOnlyHint` / `destructiveHint` 注解中,以及对于受保护的工具,在调用时作为硬拒绝。
**受保护调用的样子。** 代理尝试删除,并收到带有确切参数和确认令牌的拒绝:
```jsonc
// → delete_category { "id": "cat-abc-123" }
// ← (isError)
⚠️ "delete_category" 是一个破坏性操作,需要确认。
它将运行:
{ "id": "cat-abc-123" }
要继续,请再次调用 "delete_category",并设置 "confirm": true。
确认令牌:75aa4f772b62
```
只有显式重新调用才能清除它:
```jsonc
// → delete_category { "id": "cat-abc-123", "confirm": true }
// ← { "ok": true }
```
人类阅读转录记录,精确地看到 "yes" 被授权的内容。
## 工具(28)
| 工具 | 级别 | 参数 |
|---|---|---|
| `list_category_groups` | 🟢 | — |
| `create_category_group` | 🟡 | `name` |
| `update_category_group` | 🟡 | `id`, `name` |
| `delete_category_group` | 🔴 | `id`, `confirm` |
| `list_categories` | 🟢 | — |
| `create_category` | 🟡 | `name`, `group_id` |
| `update_category` | 🟡 | `id`, `name?`, `group_id?`, `hidden?` |
| `delete_category` | 🔴 | `id`, `confirm` |
| `list_transactions` | 🟢 | `account_id`, `start_date?`, `end_date?` |
| `create_transaction` | 🟡 | `account_id`, `date`, `amount`, `payee_name?`, `category?`, `notes?` |
| `update_transaction` | 🟡 | `id`, `date?`, `amount?`, `payee_name?`, `category?`, `notes?`, `cleared?` |
| `delete_transaction` | 🔴 | `id`, `confirm` |
| `list_accounts` | 🟢 | — |
| `get_account_balance` | 🟢 | `account_id` |
| `create_account` | 🟡 | `name`, `offbudget?` |
| `update_account` | 🟡 | `id`, `name?`, `offbudget?` |
| `list_budget_months` | 🟢 | — |
| `get_budget_month` | 🟢 | `month` (`YYYY-MM`) |
| `set_budget_amount` | 🟡 | `month`, `category_id`, `amount` |
| `set_budget_carryover` | 🟡 | `month`, `category_id`, `enabled` |
| `list_schedules` | 🟢 | — |
| `create_schedule` | 🟡 | `account_id`, `amount`, `start_date`, `name?`, `frequency?`, `category_id?`, `payee_name?` |
| `update_schedule` | 🟡 | `id`, + 任意 schedule 字段 |
| `delete_schedule` | 🔴 | `id`, `confirm` |
| `list_payees` | 🟢 | — |
| `create_payee` | 🟡 | `name` |
| `update_payee` | 🟡 | `id`, `name?`, `category?` |
| `delete_payee` | 🔴 | `id`, `confirm` |
## 故障排除
- **"actual-mcp 未配置"** — 客户端的 `env` 块中缺少 `ACTUAL_SERVER_URL` 或 `ACTUAL_SYNC_ID`。
- **无法连接 / 身份验证失败** — 检查 URL 是否为 *同步服务器*(而非桌面应用程序),并且密码匹配。通过在浏览器中登录该 URL 进行测试。
- **数据错误或为空** — Sync ID 指向不同的预算文件。从设置 → 显示高级设置 → Sync ID 重新复制它。
- **Claude Desktop 中未显示工具** — 完全退出并重新打开;检查配置文件是否为有效的 JSON。
## 验证您的设置
在将其连接到代理之前,请确认服务器可以访问您的 Actual 实例。这仅打印 **计数** — 永远不会打印名称或金额:
```bash
ACTUAL_SERVER_URL=https://actual.example.com \
ACTUAL_SYNC_ID=your-sync-id \
ACTUAL_PASSWORD=your-password \
npm run smoke
```
```
✓ 连接到 Actual
账户: 1
类别: 63
类别组: 17
预算月份: 21
✓ 干净的关闭
```
## 开发
```bash
npm install
npm run build
npm run inspect # 对构建的服务器使用 @modelcontextprotocol/inspector
npm run smoke # 连接检查(仅计数)
```
## 许可证
MIT © [Mauricio Juba](https://mauriciojuba.com) · [TYPE:ZERO](https://typezerolabs.com)
不隶属于 Actual Budget。基于官方的 [`@actual-app/api`](https://www.npmjs.com/package/@actual-app/api) 构建。