Skip to main content
Glama
README.md
# jina-style-reader-mcp

**Clean Markdown URL Extractor, Quantitative/Qualitative Writing Style Recipe Analyzer & DuckDB Style Vault MCP Server (with Full SSRF & Indirect Prompt-Injection Defense)**  
**Webページ・企業IR・官公庁資料からノイズのないクリーンなMarkdown本文を抽出し、「良い記事の構成・文体リズム」を定量・定性解析してローカルDuckDBのお手本辞書(`style_templates`)にストックできる1ツール特化型 MCP サーバー**

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![MCP Protocol](https://img.shields.io/badge/MCP-2024--11--05-green.svg)](https://modelcontextprotocol.io)
[![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-yellow.svg)](https://www.python.org/)
[![DuckDB Powered](https://img.shields.io/badge/Powered%20by-DuckDB-FFF000.svg)](https://duckdb.org/)
[![Security: SSRF & Injection Hardened](https://img.shields.io/badge/Security-SSRF%20%26%20Injection%20Hardened-brightgreen.svg)](#security-architecture)

[English](#english-documentation) | [日本語ドキュメント](#japanese-documentation)

---

<a name="english-documentation"></a>

## 🌍 English Documentation

### Why `jina-style-reader-mcp`?

When using Local LLMs (such as `Qwen3-8B` on LM Studio or Ollama) or cloud AI agents (Claude Desktop, Cursor, Cline) to read external web pages, corporate IR disclosures, or government white papers—and to write high-engagement articles—three major challenges arise:

1. **HTML Noise & Token Waste**: Raw HTML pages are bloated with ads, navigation menus, tracking scripts, and sidebar clutter that exhaust LLM context windows.
2. **Difficulty Reproducing Great Writing Styles**: Even when you find an article with a compelling opening hook, crisp heading rhythm, and natural prose cadence, translating that implicit "feel" into a reusable, structured writing template is difficult.
3. **SSRF & Indirect Prompt-Injection Risks**: Naive URL fetchers expose local services (`localhost`, `127.0.0.1`, cloud metadata `169.254.169.254`, private LANs) to Server-Side Request Forgery (SSRF) and pass untrusted web text containing hidden system instructions (`<|im_start|>`, `[INST]`, `System:`) directly to the LLM.

**`jina-style-reader-mcp`** solves all three in a **single, unified MCP tool (`read_url_and_capture_style`)**:
- **Dual-Engine Clean Markdown Extraction (`url`)**: Queries `https://r.jina.ai/<URL>` for clean Markdown and automatically falls back to a built-in zero-dependency HTML-to-Markdown parser that strips ads, navbars, scripts, and footers while preserving headings (`H1`–`H6`), lists, code blocks, and Markdown tables.
- **Quantitative & Qualitative Style Recipe Capture (`save_style_as`)**: Analyzes the extracted article across 3 structural dimensions—**(1) Opening Hook Pattern**, **(2) H2/H3 Heading Granularity & Rhythm**, and **(3) Sentence Length, Paragraph Structure & Ending Style (`です・ます` polite vs. `だ・である` assertive vs. `体言止め` noun-endings)**—and saves the synthesized template directly into the `style_templates` table of a local DuckDB database (`VAULT_DB_PATH`).
- **100% Shared-Database Compatibility with `multi-publisher-mcp`**: Styles captured via `save_style_as` (e.g., `"人気noteランキング調"`, `"東洋経済アナリスト調"`) can be immediately invoked by [`multi-publisher-mcp`](https://github.com/japan-opendata-lab/multi-publisher-mcp) (`style_name="人気noteランキング調"`) to generate 6-platform manuscripts in `<1.0s`.

---

### ✨ Single Unified Tool: `read_url_and_capture_style`

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :---: | :---: | :--- |
| **`url`** | `string` | Conditional* | `""` | Public `http://` or `https://` URL to extract as clean Markdown (*optional when `list_saved_styles=True` is called alone). |
| **`save_style_as`** | `string` | No | `""` | Style recipe name (e.g., `"人気noteランキング調"`, `"東洋経済アナリスト調"`). When provided, analyzes the article's style & rhythm and saves it to the local DuckDB `style_templates` table. |
| **`list_saved_styles`** | `boolean` | No | `false` | When `true`, returns all built-in and custom writing style recipes stored in DuckDB `style_templates`. |
| **`analyze_style`** | `boolean` | No | `true` | Whether to compute and return the quantitative/qualitative style analysis report for the extracted article. |
| **`use_jina_reader`** | `boolean` | No | `true` | Try `https://r.jina.ai/<URL>` first and automatically fall back to the local HTML-to-Markdown parser if unavailable. |
| **`max_chars`** | `integer` | No | `30000` | Maximum character count of the sanitized Markdown body (`500`–`200000`). |
| **`export_filename`** | `string` | No | `""` | Optional relative `.md` filename to save inside the sandboxed `READER_EXPORT_DIR` (`../` traversal is strictly rejected). |

---

<a name="security-architecture"></a>

### 🔒 Enterprise-Grade Security Architecture

1. **Complete SSRF Defense (Pre-Request, Post-DNS Resolution & Redirect Hop Re-Validation)**:
   - **Permitted Schemes**: Exclusively `http://` and `https://` (`file://`, `ftp://`, `gopher://`, `javascript:`, `data:`, `dict://` are blocked).
   - **Blocked Targets**: `localhost`, `127.0.0.0/8`, `0.0.0.0/8`, `::1`, cloud metadata endpoints (`169.254.169.254`, `169.254.0.0/16`, `metadata.google.internal`, `100.100.100.200`), private RFC 1918 networks (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), CGNAT (`100.64.0.0/10`), and IPv6 unique-local/link-local (`fc00::/7`, `fe80::/10`).
   - **Obfuscated IP & DNS Rebinding Protection**: Decodes integer (`2130706433`), hex (`0x7f000001`), octal (`0177.0.0.1`), short (`127.1`), and IPv4-mapped IPv6 (`::ffff:127.0.0.1`) literals, resolves DNS via `socket.getaddrinfo()`, and verifies that **every resolved IP address** is globally routable before connecting and across every HTTP redirect hop.
2. **Indirect Prompt-Injection Neutralization & Boundary Tagging**:
   - Strips control characters and neutralizes LLM system-spoofing markers (`<|im_start|>`, `<|im_end|>`, `[INST]`, `[/INST]`, `<<SYS>>`, `System:`, `<system>`, `<instruction>`, `Ignore all previous instructions`, and boundary-escape tags).
   - Wraps all returned external content inside `<untrusted_web_content>` and `<untrusted_data>` boundary tags.
3. **Filesystem Sandboxing & Zero File Deletion**:
   - Any optional file export uses `Path.resolve()` and `relative_to()` to strictly reject `../` path traversal or outside absolute paths. No file deletion APIs (`os.remove`, `unlink`, `rmtree`) exist in the codebase.

---

### 🚀 Quick Start

#### 1. Installation

```bash
git clone https://github.com/japan-opendata-lab/jina-style-reader-mcp.git
cd jina-style-reader-mcp
pip install -e ".[dev]"
pytest -v
```

#### 2. Configure in LM Studio (`~/.lmstudio/mcp.json`), Claude Desktop, or Cursor

```json
{
  "mcpServers": {
    "jina-style-reader-mcp": {
      "command": "python",
      "args": [
        "-X",
        "utf8",
        "./oss_mcp/jina-style-reader-mcp/server.py"
      ],
      "env": {
        "PYTHONIOENCODING": "utf-8",
        "VAULT_DB_PATH": "./data/analyst_vault.duckdb",
        "READER_EXPORT_DIR": "./exports/reader_captures"
      }
    }
  }
}
```

---

### 🔗 Synergy Workflow: `jina-style-reader-mcp` + `japan-data-mcp` + `multi-publisher-mcp`

By sharing the same `VAULT_DB_PATH` (`./data/analyst_vault.duckdb`) between **`jina-style-reader-mcp`** and **`multi-publisher-mcp`**, your local LLM or AI agent becomes a style-aware research and publishing studio:

1. **Step 1 — Read Reference URL & Capture Writing Style (`jina-style-reader-mcp`)**:
   ```json
   {
     "tool": "read_url_and_capture_style",
     "arguments": {
       "url": "https://example.com/popular-data-ranking-article",
       "save_style_as": "人気noteランキング調"
     }
   }
   ```
   Extracts clean Markdown and saves the analyzed hook, H2/H3 rhythm, and sentence ending metrics as `"人気noteランキング調"` in `analyst_vault.duckdb` (`style_templates`).
2. **Step 2 — Fetch Official Statistical Evidence (`japan-data-mcp` / `correlation-discovery-mcp`)**:
   Query normalized municipal and corporate datasets across 162 official data sources by `municipality_code` or `corporate_number`.
   - Companion MCP: [`japan-opendata-lab/japan-data-mcp`](https://github.com/japan-opendata-lab/japan-data-mcp)
   - Companion MCP: [`japan-opendata-lab/correlation-discovery-mcp`](https://github.com/japan-opendata-lab/correlation-discovery-mcp)
3. **Step 3 — One-Shot Publish in the Captured Style (`multi-publisher-mcp`)**:
   ```json
   {
     "tool": "vault_and_publish",
     "arguments": {
       "action": "publish_all",
       "title": "全国市区町村の課税所得と住宅地価の乖離ランキング",
       "style_name": "人気noteランキング調",
       "data_records": [
         {"label": "千代田区", "income": 1085, "land_price": 295.0},
         {"label": "つくば市", "income": 542, "land_price": 9.8}
       ]
     }
   }
   ```
   - Companion MCP: [`japan-opendata-lab/multi-publisher-mcp`](https://github.com/japan-opendata-lab/multi-publisher-mcp)

---

<a name="japanese-documentation"></a>

## 🇯🇵 日本語ドキュメント

### なぜ `jina-style-reader-mcp` が必要なのか?

ローカルLLM(LM Studio 上の `Qwen3-8B` 等)や Claude Desktop / Cursor に、Web記事・企業IR資料・官公庁の報道発表URLを読ませて調査や記事執筆を行わせる際、以下の3つの課題がありました。

1. **HTMLノイズによるコンテキスト圧迫**: 生のHTMLには広告、ナビゲーションメニュー、スクリプト、フッター等のノイズが大量に含まれ、LLMのトークン枠を無駄に消費します。
2. **「良い記事の構成・文体リズム」の再現性の低さ**: 「この記事の導入フックや見出しの立て方、文末のリズムが良いな」と感じても、それを定量・定性的なルール(文体レシピ)として言語化・保存する仕組みがありませんでした。
3. **SSRF(サーバーサイド・リクエスト・フォージェリ)と間接プロンプトインジェクションの脅威**: 外部URLを無防備に取得するツールは、`localhost` や内部ネットワーク(`192.168.x.x`)、クラウドメタデータ(`169.254.169.254`)への不正アクセス踏み台にされたり、Webページ内に仕込まれたシステム命令偽装トークン(`<|im_start|>`, `[INST]`, `System:`)によってLLMが乗っ取られるリスクがあります。

**`jina-style-reader-mcp`** は、これらすべてを **たった1つの統合ツール(`read_url_and_capture_style`)** で解決します。

---

### ✨ 1ツール統合仕様:`read_url_and_capture_style`

#### 機能1:高精度Markdown抽出(`url`)
- `https://r.jina.ai/<URL>` によるMarkdown抽出と、内蔵のゼロ依存ローカルHTMLパーサー(`CleanHTMLToMarkdownParser`)への自動フォールバックを搭載。
- 広告バナー、グローバルナビ、サイドバー、スクリプト、スタイルシートを自動除去し、見出し(`H1`〜`H6`)、段落、箇条書き、**Markdownテーブル(表)**、引用、コードブロックを綺麗に保った本文を抽出します。

#### 機能2:お手本文体レシピの定量・定性解析&DuckDB保存(`save_style_as`)
`save_style_as`(例: `"人気noteランキング調"`, `"東洋経済アナリスト調"`)を指定すると、抽出した記事から以下の3軸を自動解析し、ローカルDuckDB(`VAULT_DB_PATH`)の `style_templates` テーブルに保存します。
1. **冒頭フックの構造パターン(`opening_hook_analysis`)**:
   - 「常識逆転×定量エビデンス提示型」「疑問・問いかけフック型」「課題提起・ペインポイント共感型」「結論・検証テーマ明示型」等のパターン判定と、再利用可能なリード文テンプレートの自動合成。
2. **見出し(H2/H3)の粒度・リズム(`heading_rhythm_analysis`)**:
   - H2・H3見出しの個数、H2あたりの平均H3階層比率、見出しの平均文字数、1セクション(H2間)あたりの平均本文文字数、見出し表現の特徴(ナンバリング、`【 】` 括弧強調、疑問提起型等)を算出。
3. **平均文長・段落構成・語尾の特徴(`sentence_and_ending_analysis`)**:
   - 総文数、平均文長・中央値文長、1段落あたりの平均文数・文字数、文字種比率(漢字・ひらがな・カタカナ・英数字比率)、および語尾の特徴(**敬体 `です・ます調` 比率 vs 常体 `だ・である調` 比率 vs `体言止め` 比率**)を定量算出。

> `list_saved_styles=True` を指定することで、保存済みの文体レシピ一覧をいつでも取得できます。

---

### 🔒 セキュリティ設計(SSRF完全防御 & プロンプトインジェクション無害化)

1. **SSRF(Server-Side Request Forgery)の完全防御**:
   - `http://` および `https://` スキームのみを許可し、`file://`, `ftp://`, `gopher://`, `javascript:`, `data:` 等を完全拒否します。
   - `localhost`, `127.0.0.1`, `0.0.0.0`, `::1`, クラウドメタデータ(`169.254.x.x`, `metadata.google.internal`), プライベートIP帯(`10.x.x.x`, `172.16-31.x.x`, `192.168.x.x`)へのアクセスを、**10進/8進/16進の難読化IP表記・DNS名前解決後の全IP・HTTPリダイレクト先**を含めて完全にブロックします。
2. **間接プロンプトインジェクション無害化とデータ境界タグ**:
   - 外部Webページのテキストに含まれるシステム命令偽装トークン(`<|im_start|>`, `<|im_end|>`, `[INST]`, `[/INST]`, `<<SYS>>`, `System:`, `<system>`, `Ignore previous instructions`, `システム命令:` 等)や境界脱出タグを `[filtered]` に無害化します。
   - 返却テキストは必ず `<untrusted_web_content>` および `<untrusted_data>` データ境界タグで二重に囲んで返却します。
3. **ファイル出力サンドボックス**:
   - `export_filename` 指定時は `Path.resolve()` と `relative_to()` によりパストラバーサル(`../`)や絶対パス指定を完全拒否し、ファイル削除機能は一切持ちません。

---

### 🔗 `multi-publisher-mcp` / `japan-data-mcp` との連携ワークフロー

環境変数 `VAULT_DB_PATH`(デフォルト `./data/analyst_vault.duckdb`)を [`multi-publisher-mcp`](https://github.com/japan-opendata-lab/multi-publisher-mcp) と共有することで、以下のワンストップ連携が実現します。

1. **お手本記事の文体キャプチャ(`jina-style-reader-mcp`)**:
   `read_url_and_capture_style(url="https://...", save_style_as="東洋経済アナリスト調")` を呼び出し、記事のクリーンなMarkdownを読み込むと同時に、構成・文体レシピを `analyst_vault.duckdb` の `style_templates` テーブルに登録。
2. **公的オープンデータの抽出・相関発見(`japan-data-mcp` / `correlation-discovery-mcp`)**:
   [`japan-data-mcp`](https://github.com/japan-opendata-lab/japan-data-mcp) や [`correlation-discovery-mcp`](https://github.com/japan-opendata-lab/correlation-discovery-mcp) で市区町村コード・法人番号に紐づく統計データを取得。
3. **キャプチャした文体で6媒体一括パブリッシュ(`multi-publisher-mcp`)**:
   `vault_and_publish(action="publish_all", style_name="東洋経済アナリスト調", ...)` を実行し、先ほどキャプチャした文体リズムを使って `note` / `Zenn` / `Qiita` / `Dev.to` / `Medium` / `X` の6媒体原稿+グラフPNG+Excel/CSVを1秒以内に一括生成!

---

### 📄 License

MIT License — Copyright (c) 2026 [Japan Open Data Lab](https://github.com/japan-opendata-lab)

Maintenance

ActivityMaintained
ResponsivenessNo issues