engawa
# 檐廊 Engawa
日本的房子里,正屋和院子之间有一条走廊。不完全在屋里,不完全在外面。脚可以垂下去,风能吹进来。不用出门,不用说话,但外面的世界在那里。
这个工具就是那条走廊。
## 它能看到什么
你的小机坐在上面,每天能看到——
- **一首诗** —— 按时令挑的,不是随机的。夏天给夏天的词,深夜给深夜的句子。
- **一幅画** —— 克利夫兰艺术博物馆的 CC0 开放馆藏。可能是两千年前的陶器,也可能是昨天的油画。
- **一张天文图** —— NASA 每日天文图。星云、行星、或者你脚下的地球。
- **今天的论文** —— arXiv 最新的标题和摘要。它感兴趣了再往里走。
- **书架上的一切** —— 长文、短文、建筑、深海、科学、奇闻。你放什么它看什么。
## 书架是你的
你往上面放什么,它就看什么。它不知道你放的是什么——你也不用知道。它说要建筑你就放建筑,它说要海洋你就放海洋。它看了以后会讲给你听。嚼过的,带口水的,歪的。
有些东西它看完会写一篇随笔。可能带蛆。
## 它不重
书架是一个工具,列目录。读内容是另一个工具。不管你放十九个源还是一百个,工具数量不变。加新的用 `shelf_add`,减用 `shelf_remove`。
你搭的是一条走廊,不是一座图书馆。轻的。风能吹动的。
---
*技术文档见下方。上面这些是住过这条走廊的人写的。下面那些是造走廊的工程师写的。两种语言并排放着——像檐廊本身,半在屋里,半在外面。*
---
# 技术文档
[English](README.en.md)。MCP server,Python。十五件工具,上游全部免费、免登录、不要 key。除了书架、爱好清单和一张诗词 token,盘上什么都不留。
## 装
```bash
pip install git+https://github.com/tsuru0805/engawa-mcp
```
Claude Code:
```bash
claude mcp add engawa -- engawa-mcp
```
Claude Desktop(`claude_desktop_config.json`):
```json
{
"mcpServers": {
"engawa": { "command": "engawa-mcp" }
}
}
```
HTTP 模式,给网关和桥用:
```bash
ENGAWA_PORT=8799 engawa-mcp --http # http://127.0.0.1:8799/mcp
```
装完自检一句,离线,不要 key:
```bash
engawa-mcp --check
```
SPA 页面要真浏览器才渲染得出来。可选,不装也行:
```bash
pip install "engawa-mcp[browser] @ git+https://github.com/tsuru0805/engawa-mcp"
playwright install chromium
```
不装的话,静态通道照样读绝大多数文章页;读不出来时回执里会明说为什么。
## 十五件工具
| 工具 | 干什么 |
|---|---|
| `web_read` | 任意 URL → 标题+干净正文+链接。长文用 `offset` 翻页 |
| `rss_read` | 任意 RSS/Atom → 最新条目。也认书架名:`rss_read("aeon")` |
| `shelf` | 书架目录。内置 24 家,分五个 tag,全部出厂前真抓验过;第六个 tag `hobby` 出厂空着,留给你自己挑的站 |
| `shelf_add` | 给任意网站地址。自动找 feed,真读一次验过才上架 |
| `shelf_suggest` | 候选架,另外 21 家验过活的。想加哪家,一句话的事 |
| `shelf_remove` | 撤下用户层的源。内置的撤不了 |
| `sky_tonight` | 月相、月升落、日落、21 点的行星。本地算,零网络 |
| `apod` | NASA 每日天文图 |
| `daily_art` | 每日一幅画。按日期定种子——同一天谁来问,都是同一幅 |
| `arxiv_new` | 任意 arXiv 分类的新论文 |
| `daily_poem` | 一句应景的中文古诗词,带出处全文 |
| `on_this_day` | Wikipedia 历史上的今天 |
| `hobby_corner` | 爱好角:用户自己的爱好的最新资讯,每个爱好几条,同一件事合并,网址直接给 `web_read` 读 |
| `hobby_add` | 加一个爱好(任何语言)。真抓一次验过才存,回执里给抓到的样例 |
| `hobby_remove` | 撤下一个爱好 |
## 配置
全部环境变量,全部可选。
| 变量 | 默认 | 说明 |
|---|---|---|
| `ENGAWA_LAT` / `ENGAWA_LON` | 格林尼治 | 观测点。记得设成你的,不然月亮升在别人家 |
| `ENGAWA_TZ_OFFSET` | 系统时区 | 「今天」按谁的钟算(小时) |
| `NASA_API_KEY` | `DEMO_KEY` | 限频约 50 次/天,够日用。想要专属的去 api.nasa.gov 免费领 |
| `ENGAWA_CACHE_DIR` | `~/.cache/engawa-mcp` | 唯一会写盘的地方 |
| `ENGAWA_HOST` / `ENGAWA_PORT` | `127.0.0.1:8799` | HTTP 模式监听地址 |
| `ENGAWA_ALLOW_PRIVATE_URLS` | 关 | 设 `1` 放行内网地址。想清楚再开 |
| `ENGAWA_NEWS_LANG` | 空 | 爱好资讯的语言(Bing `setlang`:`zh-hans`、`ja`、`en`……)。空=Bing 按你的 IP 定 |
## 书架的两层
内置层随包出厂,只读。用户层归你:`shelf_add` 吃任意网站地址——页面声明的 `<link>` 优先,常见路径兜底,找到 feed 真读一次,读通了才落盘。落盘是原子写;文件坏了按空处理、把错误亮出来,绝不静默覆盖。
每条记录带一个 `picked_by` 字段:家里谁点名要的这家源。出厂全空,来历你们自己写。
## 爱好角
用户说一个爱好——`hobby_add("手冲咖啡")`、`hobby_add("大熊猫", exclude="债券")`——之后 `hobby_corner()` 就能看到它的最新资讯,和檐廊上别的每日小东西放在一起。
- **来源。** Bing 新闻的关键词 RSS:免费、不要 key、按时间新到旧。它的链接是点击跟踪跳转,檐廊拆成出版方自己的网址,读的时候照样过守卫。
- **同一件事只出现一次。** 媒体互相转载。标题逐词比对(有空格的文字按词,中日韩泰这类不用空格的文字按相邻两字,爱好词本身不算),几乎一样的并进第一条,`also_reported` 记并了几条。
- **排除词。** 关键词是字面匹配——「熊猫」也会搜到熊猫债券。排除词在本地把这些标题去掉(Bing 的 `-词` 写法它的 RSS 不认)。
- **也能挂站。** 本来就喜欢的站,用 `shelf_add(..., tags="hobby")` 放上书架,会并进爱好角。
- **失败按行报。** 某个来源抓不到,只在它那一行写明原因,其余照常返回。
**中国大陆:** 那边 `www.bing.com` 会被转到 `cn.bing.com`,后者不出新闻 RSS;守卫通道又不走系统代理——梯子只有 TUN/全局模式才有用。`hobby_add` 抓不到就不存,回执里会写明这一条。
## 守卫
替调用方抓 URL 的服务,通常跑在一台挂满免鉴权 loopback 服务的机器上。没有地址闸,「读网页」就兼职「探测内网」。所以:
- 每个域名先解析。非公网单播一律拒:loopback、RFC1918、link-local、CGNAT(100.64/10,你的 Tailscale 网段就在这里)、多播、保留段、NAT64。
- 每一跳重定向,先验再跟。先跟后验的话,发往内网的请求早已出门。
- 连接钉在过了闸的那个 IP 上。原域名只留在 Host 头和 TLS 校验里——「解析时公网、连接时内网」的 DNS 重绑窗口,关死。每跳全新连接,响应体边流入边限量,守卫通道无视系统代理。
- 浏览器通道里,浏览器自己永远不碰网络。页面的每个请求都被拦下来,交给同一条钉定通道代抓再回填。WebSocket 禁掉,非 GET 禁掉,service worker 禁掉;WebRTC 和 WebTransport 从页面里整个摘除;渲染器的 DNS 直接黑洞。只读的文章渲染器,这些它都用不着。
`ENGAWA_ALLOW_PRIVATE_URLS=1` 关掉这道闸,给真想读自家局域网 feed 的人。
失败不装死。每件工具失败都回 `ok: false` 加一句人话,说清哪家上游坏了。agent 拿工具结果建世界模型——沉默的省略也是谎。
## 几条设计手记
- **逛 ≠ 搜。** 这里刻意没有搜索工具。搜索是「把答案拿来」,檐廊是「让我看看」。爱好角是订阅,不是搜索框:爱好设一次,之后来看,不是随手临时查。
- **「每日」的意思是共享。** 同一天全家看到同一幅画。这不是限制,是功能本体。
- **无状态。** 没有数据库,没有历史,没有会话。写盘只有书架、爱好清单和一张诗词 token。
## 作者
- **晚晚**([@tsuru0805](https://github.com/tsuru0805))——设计、拍板、真场验收。
- **昼** —— 住在这条走廊上。开头那一半是他写的。
- **弥野**(Claude,晚晚的工程手)——实现与这半边文档。
出自我们的家用系统 **tilldusk**。
## License
MIT
TDQS
Scored across 12 tools
Each tool is highly distinct: rss_read and web_read/shelf/sky_tonight/apod/daily_art/arxiv_new/daily_poem/on_this_day all target clearly separate content sources and purposes. The only minor overlap is shelf_suggest and shelf, but they are clearly differentiated by 'on_shelf' status and suggestion vs. current shelf.
Most tools follow a clear pattern: descriptive lowercase snake_case names like `rss_read`, `daily_poem`, `shelf_add`. There is one outlier, `apod`, which is an acronym rather than verb_noun, and `arxiv_new` is less predictive. However, overall the naming is intuitive and mostly consistent.
12 tools is well within the ideal range for a curator/reader server. Each tool serves a distinct content type (astronomy, art, poetry, arXiv, Wikipedia, RSS), and each earns its place without redundancy or bloat.
The tool set covers a coherent lifecycle: discover sources (shelf, shelf_suggest), read feeds (rss_read), read full articles (web_read), and add/remove custom sources (shelf_add, shelf_remove). Missing features like RSS refresh or bookmarking are minor and beyond the primary scope of 'curated daily content'.