内容
MCP 代理
本地 **stdio MCP 服务器**,用于桥接 [IFTTT 的远程 MCP 端点](https://ifttt.com/mcp) 与任何仅支持 stdio 传输的 MCP 客户端(例如 [Amazon Quick](https://amazon.com/quick),Claude Desktop,Cursor 等)。
## 问题
IFTTT 在 `https://ifttt.com/mcp` 处公开了一个远程 MCP 服务器,使用:
- **OAuth 1 with PKCE** 进行身份验证
- **Streamable HTTP** 传输配合 **SSE(服务器发送事件)** 用于流式响应
许多 MCP 客户端(包括 Amazon Quick 和 Claude Desktop)仅支持 **stdio** 传输 — 它们启动一个本地进程并通过 stdin/stdout 进行通信。它们无法直接连接到需要 OAuth 重定向流的远程 HTTP MCP 服务器。
## 解决方案
该代理作为一个本地 Node.js 进程运行:
1. **处理 OAuth 2.1 + PKCE** — 基于浏览器的一次性身份验证流程,自动刷新令牌
2. **桥接 stdio ↔ HTTP** — 从 stdin 读取 JSON-RPC 消息,转发到 IFTTT 的远程端点,并将响应写入 stdout
3. **处理 SSE 流** — IFTTT 以 HTTP 202 + SSE 流响应工具调用;代理保持在写入 stdout 之前收集完整响应
4. **转换响应** — IFTTT 在 `structuredContent` 中返回数据,而标准 MCP 客户端期望在 `content[]` 中;代理自动规范化此数据
5. **自动刷新令牌** — 检测过期令牌并使用刷新令牌透明地获取新访问令牌
## 架构
```
┌─────────────┐ stdio ┌──────────────────┐ HTTPS/SSE ┌─────────────┐
│ MCP 客户端 │ ──stdin/stdout──│ ifttt-mcp-proxy │ ───────────────│ ifttt.com/ │
│ (例如 │ │ │ │ mcp │
│ Amazon │ │ • OAuth 令牌 │ │ │
│ Quick) │ │ • SSE 处理 │ │ (远程 │
│ │ │ • 响应转换 │ │ MCP │
│ │ │ │ │ 服务器) │
└─────────────┘ └──────────────────┘ └─────────────┘
```
## 安装
```bash
git clone https://github.com/maishsk/ifttt-mcp-proxy.git
cd ifttt-mcp-proxy
```
无需额外依赖 — 仅使用 Node.js 内置模块(`http`、`https`、`crypto`、`fs`、`path`、`url`、`child_process`)。
**要求:** Node.js 18+
## 使用
### 1. 身份验证(一次性)
```bash
node index.js --auth
```
这将为您打开 IFTTT OAuth 流的浏览器。授权后,令牌将保存到 `~/.quickwork/ifttt-token.json`(权限为 `0600`)。
### 2. 检查令牌状态
```bash
node index.js --status
```
显示您的令牌是否有效、获取时间以及过期时间。
### 3. 作为 MCP 代理运行
```bash
node index.js
```
在 stdio 模式下启动 — 从 stdin 读取 JSON-RPC,代理到 IFTTT,响应写入 stdout。诊断日志输出到 stderr。
### 4. 在您的 MCP 客户端中配置
在您的 MCP 客户端配置中添加:
```json
{
"mcpServers": {
"ifttt": {
"command": "node",
"args": ["/path/to/ifttt-mcp-proxy/index.js"]
}
}
}
```
或使用附带的包装脚本,自动处理首次运行的身份验证:
```json
{
"mcpServers": {
"ifttt": {
"command": "bash",
"args": ["/path/to/ifttt-mcp-proxy/start.sh"]
}
}
}
```
## 工作原理
### OAuth 2.1 + PKCE 流
1. 生成 PKCE 代码验证器/挑战对
2. 使用挑战打开浏览器到 `https://ifttt.com/oauth/authorize`
3. 启动本地临时 HTTP 服务器到 `localhost:3118` 以接收回调
4. 将授权代码交换为访问令牌 + 刷新令牌
5. 将令牌保存到 `~/.quickwork/ifttt-token.json`
### Streamable HTTP + SSE 处理
IFTTT 的 MCP 服务器使用 **Streamable HTTP** MCP 传输:
- 简单请求(例如 `initialize`、`tools/list`)返回直接 JSON 响应
- 工具调用(`tools/call`)可能返回 HTTP 200/202 且 `Content-Type: text/event-stream`
- SSE 流包含具有实际 JSON-RPC 响应的 `data:` 行
代理处理三种响应类型:
- **直接 JSON**(200 + `application/json`)→ 解析并转发
- **SSE 流**(200/202 + `text/event-stream`)→ 收集直到流结束,事件解析并转发
- **错误响应**(4xx/5xx)→ 转换为 JSON-RPC 错误对象
### 响应转换
IFTTT 将工具结果放入非标准 `structuredContent` 字段,同时将标准 `content` 数组留空。代理检测到这一点并将数据移动到 `content[{type: "text", text: ...}]` 以实现兼容性。
### 令牌自动刷新
在每个请求中,代理检查令牌过期(有 60 秒缓冲)。如果过期,它使用刷新令牌获取新访问令牌,然后继续。如果请求返回 HTTP 401,它会尝试刷新并重试一次。
## 文件
| 文件 | 描述 |
|------|-------------|
| `index.js` | 主要代理服务器(OAuth + stdio 代理) |
| `package.json` | 包元数据 |
| `start.sh` | 带有首次运行自动身份验证的包装脚本 |
## 令牌存储
令牌存储在 `~/.quickwork/ifttt-token.json`,文件权限为 `0600`(仅所有者可读/写)。文件包含:
- `access_token` — IFTTT API 的 Bearer 令牌
- `refresh_token` — 用于获取新访问令牌
- `expires_in` — 令牌寿命(秒)
- `obtained_at` — 获取令牌的时间戳
## 故障排除
### "未找到令牌"
运行 `node index.js --auth` 以完成 OAuth 流。
### "令牌刷新失败"
您的刷新令牌可能已被吊销。重新运行 `node index.js --auth`。
### 工具调用的空响应
这通常意味着 SSE 流未被正确消费。检查 stderr 日志以获取详细信息。代理处理常见的 IFTTT 模式 202 + SSE。
### 端口 3118 已被占用
OAuth 回调使用端口 311