Skip to main content
Glama
mwl313

Namu Watcher

by mwl313
README.md
# Namu Watch πŸ”

**AI둜 λ‚˜λ¬΄μœ„ν‚€λ₯Ό 읽닀**

Namu WatchλŠ” AI 챗봇이 μ‚¬μš©μžμ˜ μžμ—°μ–΄ μš”μ²­μ— 따라 λ‚˜λ¬΄μœ„ν‚€ λ¬Έμ„œλ₯Ό κ²€μƒ‰ν•˜κ³  읽을 수 있게 ν•΄μ£ΌλŠ” MCP μ„œλ²„μž…λ‹ˆλ‹€.

"**λ‚˜λ¬΄μœ„ν‚€μ—μ„œ μ΄μˆœμ‹  μ•Œλ €μ€˜**" β€” 이 ν•œλ§ˆλ””λ‘œ AIκ°€ λ‚˜λ¬΄μœ„ν‚€ λ¬Έμ„œλ₯Ό μ°Ύμ•„ μš”μ•½ν•΄μ€λ‹ˆλ‹€. μ‹€μ‹œκ°„ 검색어 νŠΈλ Œλ“œμ™€ 각 ν‚€μ›Œλ“œκ°€ μ™œ λœ¨λŠ”μ§€λ„ ν•¨κ»˜ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

---

## μ£Όμš” κΈ°λŠ₯

| κΈ°λŠ₯ | 도ꡬλͺ… | μ„€λͺ… |
|:-----|:-------|:------|
| πŸ” **λ‚˜λ¬΄μœ„ν‚€ 검색** | `search` | λ‚˜λ¬΄μœ„ν‚€ λ¬Έμ„œ 검색 및 λ‚΄μš© 쑰회 (8000자 λ‹¨μœ„ 청크 λΆ„ν• , 멀티블둝 응닡). **핡심 κΈ°λŠ₯** |
| πŸ”₯ **μ‹€μ‹œκ°„ 검색어** | `trending` | λ‚˜λ¬΄μœ„ν‚€ 곡식 API 기반 μ‹€κ²€ TOP N 쑰회 |
| ❓ **μ™œ λ–΄λŠ”μ§€** | `why` | νŠΉμ • ν‚€μ›Œλ“œκ°€ 싀검에 였λ₯Έ 이유 μ„€λͺ… (μ•„μΉ΄λΌμ΄λΈŒ β†’ λ‚˜λ¬΄μœ„ν‚€ fallback) |
| πŸ“… **κ³Όκ±° 이λ ₯** | `history` | νŠΉμ • λ‚ μ§œμ˜ μ‹€κ²€ μˆœμœ„ λ˜λŠ” ν‚€μ›Œλ“œ μ‹œκ°„λ³„ λ“±μž₯ 기둝 |
| πŸ“ˆ **κΈ‰μƒμŠΉ 감지** | `velocity` | 졜근 Nμ‹œκ°„ λ™μ•ˆ 쑰회수 급증 ν‚€μ›Œλ“œ 탐지 |
| πŸ”— **μ—°κ΄€ ν‚€μ›Œλ“œ** | `related` | νŠΉμ • ν‚€μ›Œλ“œμ™€ ν•¨κ»˜ 자주 λ“±μž₯ν•˜λŠ” μ—°κ΄€ ν‚€μ›Œλ“œ |
| πŸ‘« **ν•¨κ»˜ νŠΈλ Œλ”©** | `trending_together` | λ™μ‹œκ°„λŒ€ ν•¨κ»˜ λ“±μž₯ν•˜λŠ” ν‚€μ›Œλ“œ 쌍 뢄석 |
| πŸ“Š **였늘의 리포트** | `trend_of_day` | 였늘의 μ‹€κ²€ μ’…ν•© 리포트 |

## μ‚¬μš© μ˜ˆμ‹œ

μ‚¬μš©μžλŠ” μΉ΄μΉ΄μ˜€ν†‘ μ±„νŒ…μ—μ„œ μžμ—°μ–΄λ‘œ μ§ˆλ¬Έν•˜λ©΄ λ©λ‹ˆλ‹€. λͺ¨λ“  λͺ…λ Ήμ–΄λŠ” AIκ°€ μžλ™μœΌλ‘œ μΈμ‹ν•©λ‹ˆλ‹€.

```
πŸ‘€ "λ‚˜λ¬΄μœ„ν‚€μ—μ„œ μ΄μˆœμ‹ μ— λŒ€ν•΄ μ•Œλ €μ€˜"
β†’ AIκ°€ search("μ΄μˆœμ‹ ") 호좜 β†’ λ¬Έμ„œ 전체λ₯Ό 청크 λ‹¨μœ„λ‘œ μˆ˜μ‹ 
πŸ€– "μ΄μˆœμ‹ μ€ μ‘°μ„  μ€‘κΈ°μ˜ λ¬΄μ‹ μœΌλ‘œ..."

πŸ‘€ "ㄱㄱㅅ이 λˆ„κ΅¬μ•Ό?"
β†’ AIκ°€ search("γ„±γ„±γ……") 호좜 β†’ fallback chain: "γ„±γ„±γ……" β†’ "γ„±γ„±" β†’ μ μ ˆν•œ λ¬Έμ„œ 탐색
πŸ€– "κ³ κ΅¬λ§ˆμˆœμ΄λΌκ³ λ„ λΆˆλ¦¬λŠ”..."

πŸ‘€ "μ§€κΈˆ μ‹€κ²€ 뭐 뜨고 μžˆμ–΄?"
β†’ AIκ°€ trending() 호좜 β†’ λ‚˜λ¬΄μœ„ν‚€ 곡식 μ‹€κ²€ API
πŸ€– "πŸ”₯ μ‹€μ‹œκ°„ 검색어 TOP 10..."

πŸ‘€ "ν˜Έλ‚ λ‘ μ™œ 싀검에 λ–΄μ–΄?"
β†’ AIκ°€ why("ν˜Έλ‚ λ‘") 호좜 β†’ μ•„μΉ΄λΌμ΄λΈŒ κ²Œμ‹œκΈ€ 검색 β†’ λ‚˜λ¬΄μœ„ν‚€ fallback
πŸ€– "였늘 슀페인과의 16강전에 μ„ λ°œ μΆœμ „ν–ˆμœΌλ‚˜..."
```

## 데이터 μ†ŒμŠ€

- **μ£Ό μ†ŒμŠ€**: [λ‚˜λ¬΄μœ„ν‚€](https://namu.wiki) β€” AIκ°€ μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ— λ‹΅ν•˜κΈ° μœ„ν•΄ λ¬Έμ„œλ₯Ό κ²€μƒ‰ν•˜κ³  μš”μ•½ν•©λ‹ˆλ‹€.
  - 3단계 접속 우회: 직접 접속 β†’ μΏ ν‚€ νšλ“ β†’ Google Cache
- **μ‹€κ²€ 데이터**: [λ‚˜λ¬΄μœ„ν‚€ 곡식 μ‹€κ²€ API](https://search.namu.wiki/api/ranking) β€” μ‹€μ‹œκ°„ 검색어 μˆœμœ„ 제곡
- **μ‹€κ²€ 이유**: [μ•„μΉ΄λΌμ΄λΈŒ "λ‚˜λ¬΄μœ„ν‚€ μ‹€κ²€ μ•Œλ €μ£ΌλŠ” 채널"](https://arca.live/b/namuhotnow)
  - 각 κ²Œμ‹œκΈ€ = ν•˜λ‚˜μ˜ μ‹€κ²€ ν‚€μ›Œλ“œ + μ‚¬λžŒμ΄ μž‘μ„±ν•œ 이유 μ„€λͺ…

## 기술 μŠ€νƒ

| ν•­λͺ© | 기술 |
|:-----|:------|
| **μ–Έμ–΄** | TypeScript (Node.js 20+) |
| **λŸ°νƒ€μž„** | Node.js 20+ (ESM, `"type": "module"`) |
| **MCP ν”„λ‘œν† μ½œ** | Streamable HTTP β€” `POST /mcp` (JSON-RPC 2.0) |
| **HTTP μ„œλ²„** | Node.js λ‚΄μž₯ `http` λͺ¨λ“ˆ |
| **HTML νŒŒμ‹±** | cheerio |
| **λ°μ΄ν„°λ² μ΄μŠ€** | sql.js (SQLite β†’ WASM, λ„€μ΄ν‹°λΈŒ λΉŒλ“œ λΆˆν•„μš”) |
| **μž…λ ₯ 검증** | zod |
| **배포** | Docker (linux/amd64) |

## λΉ λ₯Έ μ‹œμž‘

### 사전 μš”κ΅¬μ‚¬ν•­

- Node.js 20+
- npm

### μ„€μΉ˜ 및 μ‹€ν–‰

```bash
git clone https://github.com/mwl313/NamuWatcher.git
cd NamuWatcher

npm install
npm run build
node dist/index.js
```

μ„œλ²„κ°€ `http://localhost:3000`μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€.

### Docker

```bash
docker build --platform linux/amd64 -t namu-watch .
docker run --platform linux/amd64 -p 3000:3000 -v namu-data:/app/data namu-watch
```

## API μ—”λ“œν¬μΈνŠΈ

| λ©”μ„œλ“œ | 경둜 | μ„€λͺ… |
|:-------|:-----|:------|
| `POST` | `/mcp` | MCP Streamable HTTP (JSON-RPC 2.0) |
| `GET` | `/healthz` | ν—¬μŠ€μ²΄ν¬ (μƒνƒœ JSON λ°˜ν™˜) |
| `OPTIONS` | `*` | CORS preflight |

### MCP ν”„λ‘œν† μ½œ

ν΄λΌμ΄μ–ΈνŠΈλŠ” ν‘œμ€€ MCP JSON-RPC λ©”μ‹œμ§€λ₯Ό `POST /mcp`둜 μ „μ†‘ν•©λ‹ˆλ‹€.

**μ΄ˆκΈ°ν™”:**
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {},
    "clientInfo": { "name": "my-client", "version": "1.0.0" }
  }
}
```

**도ꡬ 쑰회:**
```json
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
```

**도ꡬ 호좜 (예: search):**
```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "keyword": "μ΄μˆœμ‹ " }
  }
}
```

### 응닡 ν˜•μ‹ (search 도ꡬ)

search λ„κ΅¬λŠ” 멀티블둝 응닡을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"type\":\"search\",\"title\":\"μ΄μˆœμ‹ \",\"url\":\"...\",\"total_chunks\":5}"
      },
      {
        "type": "text",
        "text": "[1/5]\nμ΄μˆœμ‹ μ€ μ‘°μ„  μ€‘κΈ°μ˜ λ¬΄μ‹ μœΌλ‘œ..."
      },
      {
        "type": "text",
        "text": "[2/5]\n1592λ…„ μž„μ§„μ™œλž€μ΄ λ°œλ°œν•˜μž..."
      }
    ]
  }
}
```

- 첫 번째 블둝: 메타정보 JSON (`title`, `url`, `total_chunks`)
- 이후 블둝: `[i/total]` 라벨 + 8000자 λ‹¨μœ„ λ³Έλ¬Έ
- AIλŠ” 메타정보λ₯Ό 읽고, ν•„μš”ν•œ 청크λ₯Ό μ„ νƒν•˜μ—¬ 닡변을 μƒμ„±ν•©λ‹ˆλ‹€.

## ν™˜κ²½ λ³€μˆ˜

| λ³€μˆ˜ | κΈ°λ³Έκ°’ | μ„€λͺ… |
|:-----|:-------|:------|
| `PORT` | `3000` | HTTP μ„œλ²„ 포트 |
| `DB_PATH` | `/app/data/namu_watch.db` | SQLite 파일 경둜 |
| `NODE_ENV` | `production` | μ‹€ν–‰ λͺ¨λ“œ |

## ν”„λ‘œμ νŠΈ ꡬ쑰

```
namu-watch/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts               # HTTP μ„œλ²„ + JSON-RPC ν•Έλ“€λŸ¬
β”‚   β”œβ”€β”€ types.ts               # 곡톡 νƒ€μž… μ •μ˜
β”‚   β”œβ”€β”€ utils.ts               # μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ (fetch, delay, μΉ΄ν…Œκ³ λ¦¬ λΆ„λ₯˜)
β”‚   β”œβ”€β”€ tools/
β”‚   β”‚   β”œβ”€β”€ search.ts          # πŸ” λ‚˜λ¬΄μœ„ν‚€ λ¬Έμ„œ 검색 (청크 λΆ„ν•  + 멀티블둝 응닡)
β”‚   β”‚   β”œβ”€β”€ trending.ts        # πŸ”₯ λ‚˜λ¬΄μœ„ν‚€ 곡식 API μ‹€κ²€ TOP N
β”‚   β”‚   β”œβ”€β”€ why.ts             # ❓ μ‹€κ²€ 이유 μ„€λͺ… (μ•„μΉ΄λΌμ΄λΈŒ β†’ λ‚˜λ¬΄μœ„ν‚€ fallback)
β”‚   β”‚   β”œβ”€β”€ history.ts         # πŸ“… κ³Όκ±° μ‹€κ²€ 이λ ₯ 쑰회
β”‚   β”‚   β”œβ”€β”€ velocity.ts        # πŸ“ˆ κΈ‰μƒμŠΉ ν‚€μ›Œλ“œ 감지
β”‚   β”‚   β”œβ”€β”€ related.ts         # πŸ”— μ—°κ΄€ ν‚€μ›Œλ“œ
β”‚   β”‚   β”œβ”€β”€ trending-together.ts  # πŸ‘« ν•¨κ»˜ λœ¨λŠ” ν‚€μ›Œλ“œ
β”‚   β”‚   └── trend-of-day.ts    # πŸ“Š μ’…ν•© 리포트
β”‚   β”œβ”€β”€ scrapers/
β”‚   β”‚   β”œβ”€β”€ namuwiki.ts        # πŸ“– λ‚˜λ¬΄μœ„ν‚€ λ¬Έμ„œ νŒŒμ‹± (3단계 우회: 직행 β†’ μΏ ν‚€ β†’ Google Cache)
β”‚   β”‚   β”œβ”€β”€ ranking.ts         # πŸ“Š search.namu.wiki 곡식 μ‹€κ²€ API
β”‚   β”‚   └── arca.ts            # πŸ›οΈ μ•„μΉ΄λΌμ΄λΈŒ 싀검채널 μŠ€ν¬λž˜ν•‘ (vrow ꡬ쑰)
β”‚   └── db/
β”‚       β”œβ”€β”€ schema.ts          # SQLite μ΄ˆκΈ°ν™” (snapshots + articles)
β”‚       └── queries.ts         # DB CRUD ν•¨μˆ˜
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ package.json
└── tsconfig.json
```

## λΌμ΄μ„ μŠ€

MIT