# MCP Server for 企微 AI Robot — ⚠️ DECOMMISSIONED

> **2026-06-13: This service is permanently decommissioned.** Hermes now directly serves as the 企微 bot (no MCP plugin architecture). The cloudflared tunnel no longer routes `mcp.icoach.chat`. Code at `~/project/scripts/mcp_server.py` may still exist but is not running.

Code: `~/project/scripts/mcp_server.py`, port 8100, ~~served at `https://mcp.icoach.chat`~~ (decommissioned).

## Tools

| Tool | Params | Returns |
|------|--------|---------|
| `get_today_articles` | none | `{count, articles: [{id, title, source, url, time}]}` |
| `get_article_detail` | `article_id` | `{id, title, source, url, content, time}` |

## Architecture

Uses Starlette + Uvicorn. Supports both SSE (`/sse` + `/messages/`) and Streamable HTTP (`/rpc`) transports. The 企微 robot **must** use the `/rpc` endpoint via Cloudflare Tunnel — SSE is unreliable on 企微 (session loss causes plugin removal).

**⚠️ Mcp-Session-Id 头必须返回** — 企微使用多台服务器轮询，`initialize` 响应必须包含 `Mcp-Session-Id` 头，否则后续 `tools/call` 被企微内部拒绝（报「工具名称无法正确匹配」）。参见 `devops/wecom-bot-integration` 技能的 `references/mcp-server-implementation.md`。

## Critical Fix: DB Write Lock Blocking

**Problem**: `get_article_detail` timed out (30s+) when reading from the live we-mp-rss SQLite DB. The we-mp-rss service holds a write lock during article fetching, blocking all reads.

**Solution**: `_get_db()` reads from a pre-copied DB snapshot at `/tmp/mcp_db_copy.db`. A cron job refreshes the copy every 5 minutes:

```
*/5 * * * * cp /home/agentuser/project/we-mp-rss-data/db.db /tmp/mcp_db_copy.db 2>/dev/null
```

The `_get_db()` function refreshes the copy on-demand every 5 minutes, but only if the DB copy doesn't exist yet or is stale:

```python
DB_COPY = "/tmp/mcp_db_copy.db"
_db_copy_time = time.time() if os.path.exists(DB_COPY) else 0

def _get_db():
    import shutil
    now = time.time()
    if not os.path.exists(DB_COPY) or (now - _db_copy_time > 300):
        try:
            shutil.copy2(DB_PATH, DB_COPY)
            _db_copy_time = now
        except Exception:
            pass
    db = sqlite3.connect(DB_COPY if os.path.exists(DB_COPY) else DB_PATH)
    db.row_factory = sqlite3.Row
    return db
```

**Key insight**: `shutil.copy2` on the live DB also blocks when we-mp-rss holds a write lock. Rely on the cron refresh, not on-demand copying, for timely requests.

## Critical Fix: Article Content Extraction

**Problem**: `_strip_html()` was returning JavaScript (Vue.js framework code) instead of article text. The raw `content` column stores the full WeChat MP article HTML (~3MB), mostly scripts and metadata.

**Solution**: Target the `js_content` div (WeChat MP's article body container) before stripping HTML:

```python
def _strip_html(raw: str) -> str:
    if not raw:
        return ""
    # Extract js_content div first (the article body)
    m = re.search(r'id="js_content"[^>]*>(.*?)</div>\s*<script', raw, re.DOTALL)
    if m:
        text = m.group(1)
    else:
        # Fallback: truncate to 200KB then strip all HTML
        text = raw[:200 * 1024]
    # Remove remaining tags
    text = re.sub(r'<(script|style)[^>]*>.*?</\1>', '', text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r'<!--.*?-->', '', text, flags=re.DOTALL)
    text = re.sub(r'<[^>]+>', '', text)
    text = html_unescape(text)
    text = re.sub(r'\s+', ' ', text).strip()
    return text
```

Result: `content[:4000]` returns readable Chinese article text instead of JS source.

## Service Management

Manual start (venv python required for starlette dependency):
```bash
~/project/we-mp-rss-main源文件/we-mp-rss-main/venv/bin/python3 \
  ~/project/scripts/mcp_server.py &
```

**Auto-start**: `@reboot` crontab (not systemd — daemon-reload hangs on this server):
```
@reboot sleep 10 && ~/project/we-mp-rss-main源文件/we-mp-rss-main/venv/bin/python3 ~/project/scripts/mcp_server.py >> /tmp/mcp_server.log 2>&1
```

## Health Check

```bash
# Local
curl http://localhost:8100/health

# Public (decommissioned)
# curl https://mcp.icoach.chat/health
```

## Testing Tools via API

```bash
# List tools
curl -s -X POST http://localhost:8100/rpc -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Get today's articles
curl -s -X POST http://localhost:8100/rpc -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_today_articles","arguments":{}}}'

# Get article detail
curl -s -X POST http://localhost:8100/rpc -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_article_detail","arguments":{"article_id":"3596295911-2247811149_1"}}}'
```
