内容
# easynews-mcp
一个容器化的MCP服务器,封装了[Easynews](https://www.easynews.com/) Usenet服务,并将其作为工具暴露在Claude Desktop中。在Synology NAS(DSM 7.x)上作为持久容器运行;Windows上的Claude Desktop通过LAN上的SSE连接到它,通过[`mcp-remote`](https://www.npmjs.com/package/mcp-remote)桥接。
```
Windows 11 Claude Desktop ──(stdio)── npx mcp-remote ──(SSE, LAN)──► NAS 容器 :8765
│ 卷挂载
▼
NZBGet 监视文件夹 ──► NZBGet
```
## 工具
| 工具 | 功能 | 示例提示 |
|---|---|---|
| `search_usenet` | 搜索Easynews;返回一个表格和包含结果ID的JSON | *"搜索Usenet中的Ubuntu 24.04 ISO,显示10个结果"* |
| `get_nzb_url` | 返回由该容器提供的可GET的NZB链接 | *"获取第一个结果的NZB URL"* |
| `download_nzb` | 直接将NZB保存到NZBGet监视文件夹 | *"下载结果3的NZB"* |
`search_usenet`接受可选的`file_type`(视频、音频、图像、文本、存档)和`max_results`。结果ID是自包含的令牌(`hash|b64(name):b64(ext)&sig=…`);调用之间不保留状态。
同一端口上的纯HTTP路由:`GET /health`(Docker健康检查,无身份验证)和`GET /nzb/{id}`(代理Easynews的仅POST NZB端点 — 这是`get_nzb_url`链接指向的内容)。**两者都不需要身份验证 — 保持端口8765仅限LAN访问。**
## 先决条件
- Easynews账户
- 带有容器管理器的Synology NAS(提供`docker`和`docker-compose` —
注意DSM将compose作为**带连字符**的`docker-compose`,而不是`docker compose`)
- Windows 11上的Claude Desktop,以及Node.js(用于`mcp-remote`桥接)
- NZBGet(或类似)监视NAS上的文件夹
## 设置
### 1. 文件到NAS
从Windows上的项目目录(DSM默认禁用SFTP,因此使用`-O`强制使用旧scp协议;`-P`是您的SSH端口):
```powershell
scp -O -P <ssh-port> Dockerfile docker-compose.yml requirements.txt easynews_client.py server.py .dockerignore <user>@<nas-ip>:/volume1/tools/easynews-mcp/
```
### 2. 配置
在NAS上创建`.env`(永远不要复制本地的;永远不要提交):
```
EASYNEWS_USERNAME=...
EASYNEWS_PASSWORD=...
EASYNEWS_DOWNLOAD_PATH=/downloads/nzb
MCP_HOST=0.0.0.0
MCP_PORT=8765
MCP_PUBLIC_URL=http://<nas-ip>:8765
```
```bash
chmod 600 .env
```
`MCP_PUBLIC_URL`是客户端用于访问服务器的地址 — `get_nzb_url`从中构建其链接。
### 3. docker-compose.yml — 两个需要检查的值
- **卷:** 卷映射的左侧必须是您的实际NZBGet监视文件夹(NZBGet设置 → 路径 → NzbDir):
```yaml
volumes:
- /volume1/Incoming/nzbget/downloads/nzb:/downloads/nzb
```
- **用户:** 容器必须以uid运行,而Synology的ACL允许写入该文件夹。模式位告诉您(`777`但写入失败)意味着ACL — 使用`synoacltool -get <watch-folder>`和`id <user>`检查,然后设置:
```yaml
user: "1028:100" # uid:gid of a DSM user the ACL allows
```
### 4. 构建和运行
```bash
cd /volume1/tools/easynews-mcp
sudo docker-compose up -d --build
sudo docker-compose ps # expect: Up (healthy)
curl http://localhost:8765/health
```
### 5. Claude Desktop
`%APPDATA%\Claude\claude_desktop_config.json` — 配置文件仅支持stdio服务器(纯`"url"`条目被忽略),因此使用`mcp-remote`桥接。
`--allow-http`对于纯HTTP LAN URL是必需的:
```json
{
"mcpServers": {
"easynews": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://<nas-ip>:8765/sse", "--transport", "sse-only", "--allow-http"]
}
}
}
```
完全退出Claude Desktop(系统托盘 → 退出)并重新启动。
### 替代:从GHCR拉取预构建的镜像
每次推送到`main`都会通过GitHub Actions发布`ghcr.io/sdolgin/easymcp:latest`(amd64 + arm64)。要通过拉取而不是构建来部署,请在`docker-compose.yml`中替换`build: .`行,并将`image:`指向GHCR:
```yaml
image: ghcr.io/sdolgin/easymcp:latest
```
然后使用`sudo docker-compose pull && sudo docker-compose up -d`更新。(如果GHCR软件包是私有的,请先使用GitHub PAT进行`sudo docker login ghcr.io`。)
## 维护手册
| 任务 | 命令(在NAS上) |
|---|---|
| 查看日志 | `sudo docker logs easynews-mcp -f` |
| 代码更改后更新 | scp更改的文件,然后`sudo docker-compose up -d --build` |
| 旋转凭证 | 编辑`.env`,然后`sudo docker-compose up -d --force-recreate` |
| 重启 | `sudo docker-compose restart` |
| Windows健康检查 | `curl.exe http://<nas-ip>:8765/health` |
艰难的教训:
- **`.env`或compose编辑后需要`--force-recreate`** — DSM的compose v1不会在env文件更改后自动重新创建容器。
- **容器重启会断开实时SSE会话。** 之后的第一个工具调用可能会失败(`Invalid request parameters`);桥接器在下一次调用时重新连接。不需要重启Claude Desktop。
- **NZB `sig`是必需的。** Easynews的`dl-nzb`端点返回HTTP 200和一个*空* `<nzb/>`骨架,如果缺少sig — 客户端在每个结果ID中嵌入sig并拒绝空NZB,因此NZBGet中的“文件无内容”错误意味着旧ID来自旧搜索:只需重新搜索。
## 开发
```powershell
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env # fill in credentials
python test_client.py # 4 smoke tests against the live API
python server.py # local server on :8765
npx -y @modelcontextprotocol/inspector --cli http://localhost:8765/sse --transport sse --method tools/list
```
## 已知限制
- 搜索使用Easynews的1.0 RSS输出(`sS=5`);非常旧的帖子可能缺少可解析的时间戳(`age_days: null`)或新闻组。
- `/nzb/{id}`和`/health`按设计未经身份验证 — 不要将8765端口转发到互联网。
- Easynews在服务器端限制页面大小;非常大的`max_results`值会被限制。
- 没有重试/速率限制处理 — Easynews在实践中尚未显示限制,但批量自动化可能需要回退。