mcp-cite-fragment
v1.0.0
Published
MCP server for verifying claims against source documents and generating citation links with W3C Text Fragments
Maintainers
Readme
Cite-Fragment MCP Server
AIが生成する調査レポートの主張をソース文書で裏付け、該当箇所に直接ジャンプできる引用リンクを生成するMCPサーバー。
W3C Text Fragments (#:~:text=) とPDFページリンク (#page=N) を活用し、引用の透明性を確保する。
解決する課題
AIがWeb検索結果を引用する際、ソースURLは提示されるが ページ内のどの箇所を参照しているかは不明瞭 である。ユーザーが事実確認するには長いページをスクロールして該当箇所を探す必要があり、ファクトチェックのコストが高い。
Before: [Source](https://example.com/article)
After: [Source](https://example.com/article#:~:text=引用テキスト)
クリックすると該当箇所が黄色ハイライトされた状態で表示される提供ツール
| ツール | 用途 | 主な利用シーン |
|---|---|---|
| verify_claims | 複数の主張を一括でソース文書と照合 | 調査レポートの全主張を一度に検証 |
| generate_text_fragment | 単一の引用テキストからFragment URLを生成 | 個別の引用リンク作成 |
| check_fragment_url | 生成済みFragment URLの有効性検証 | ページ更新後のリンク切れ確認 |
| highlight_pdf | PDF内の該当テキストに黄色ハイライト注釈を追加して保存 | レビュー用のPDF配布 |
verify_claims (推奨: 調査タスクのメインツール)
複数の主張を一括でソース文書と照合する。同一URLへの複数の主張はページを1回だけ取得する。
// 入力
{
"claims": [
{"url": "https://example.com/article", "claim": "actual text from the source", "lang": "en"},
{"url": "https://example.com/article", "claim": "another claim from same page"},
{"url": "https://example.jp/doc.pdf", "claim": "PDF内のテキスト", "lang": "ja"}
]
}
// 出力
{
"results": [
{
"claim": "actual text from the source",
"url": "https://example.com/article",
"status": "verified",
"fragment_url": "https://example.com/article#:~:text=actual%20text%20from%20the%20source",
"matched_text": "actual text from the source",
"surrounding_context": "...preceding text actual text from the source following text..."
},
{
"claim": "another claim from same page",
"status": "not_found",
"message": "引用テキストがページ内に見つかりません"
},
{
"claim": "PDF内のテキスト",
"status": "verified",
"fragment_url": "https://example.jp/doc.pdf#page=5",
"page_number": 5,
"matched_text": "PDF内のテキスト",
"surrounding_context": "...前後100文字の文脈..."
}
],
"summary": { "total": 3, "verified": 2, "not_found": 1, "fetch_failed": 0 }
}出力フィールドの意味:
| フィールド | 意味 |
|---|---|
| status | verified = ソースに存在, not_found = 見つからない, fetch_failed = ページ取得失敗 |
| matched_text | ソース上で実際にマッチしたテキスト(正規化前の原文) |
| surrounding_context | マッチ箇所の前後100文字。主張が正確な引用か意訳かを判断するために使用 |
| fragment_url | 該当箇所に直接ジャンプするURL(HTML: #:~:text=..., PDF: #page=N) |
generate_text_fragment
単一の引用テキストからFragment URLを生成する。
// 入力
{ "url": "https://example.com/article", "cited_text": "引用テキスト", "lang": "ja" }
// 出力 (成功)
{
"fragment_url": "https://example.com/article#:~:text=引用テキスト",
"detected_lang": "ja",
"is_unique": true,
"context_used": false,
"match_count": 1,
"matched_text": "引用テキスト",
"surrounding_context": "...前後の文脈..."
}
// 出力 (エラー)
{
"error": "NOT_FOUND",
"message": "引用テキストがページ内に見つかりません",
"suggestion": "引用テキストを短縮して再試行してください"
}highlight_pdf
// 入力
{ "url": "https://example.jp/doc.pdf", "cited_text": "ハイライトするテキスト", "lang": "ja" }
// 出力
{
"file_path": "/tmp/mcp-text-fragment/doc_highlighted.pdf",
"page_number": 5,
"match_count": 3,
"detected_lang": "ja"
}セットアップ
前提条件
- Node.js 18+ (
Intl.Segmenterの日本語対応に必要) qpdf(暗号化PDFのハイライトに必要。オプション)
# macOS
brew install qpdf
# Ubuntu/Debian
apt-get install qpdfClaude Code
claude mcp add --transport stdio cite-fragment -- npx -y mcp-cite-fragmentこれだけで完了。次回の Claude Code セッションから verify_claims 等のツールが利用可能になる。
スコープの選択:
# ローカル (デフォルト) — このプロジェクトの自分だけ
claude mcp add --transport stdio cite-fragment -- npx -y mcp-cite-fragment
# プロジェクト共有 — .mcp.json に保存、チーム全員が利用可能
claude mcp add --transport stdio --scope project cite-fragment -- npx -y mcp-cite-fragment
# ユーザー全体 — すべてのプロジェクトで利用可能
claude mcp add --transport stdio --scope user cite-fragment -- npx -y mcp-cite-fragmentClaude Desktop
- Settings → Developer → Edit Config を開く
claude_desktop_config.jsonに以下を追加:
{
"mcpServers": {
"cite-fragment": {
"command": "npx",
"args": ["-y", "mcp-cite-fragment"]
}
}
}設定ファイルの場所:
| OS | パス |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
- Claude Desktop を再起動
確認
# Claude Code の場合
claude mcp list
# cite-fragment: npx -y mcp-cite-fragment - ✓ Connected と表示されれば成功ソースからビルド (開発者向け)
git clone https://github.com/ryo63-bot/mcp-cite-fragment
cd mcp-cite-fragment
npm install
npm run build
# ローカルビルドを直接指定して接続
claude mcp add --transport stdio cite-fragment -- node dist/index.jsDocker
docker compose up --build # ビルド&起動
docker compose up -d # バックグラウンド起動Claudeへの指示例 (System Prompt)
Web検索で情報を引用する際、以下を実行すること:
1. 調査レポートを書き終えたら、全主張を verify_claims で一括検証する
2. status: "not_found" の主張はソースの原文を確認し、正確な表現に修正する
3. status: "verified" の主張は fragment_url をMarkdownリンクとして使用する
4. surrounding_context を確認し、主張が文脈的にも正確か検証する
5. PDFからの引用が重要な場合、highlight_pdf でハイライト付きPDFを保存する
書式例:
[Source](https://example.com/article#:~:text=引用テキスト)対応コンテンツ
| コンテンツ種別 | Fragment形式 | 対応状況 |
|---|---|---|
| HTML (静的/SSR) | #:~:text=... | 対応 |
| PDF | #page=N | 対応 |
| PDF (暗号化) | #page=N + ハイライト | 対応 (qpdf必要) |
| SPA / CSR (React等) | - | 非対応 (Phase 2) |
| ログイン必須サイト | - | 非対応 |
言語対応
日本語と英語に最適化。言語は自動検出される(lang パラメータで明示指定も可能)。
| 処理 | 英語 | 日本語 |
|---|---|---|
| 正規化 | 小文字化 + 空白正規化 | 全角/半角統一 + カタカナ→ひらがな |
| Context抽出 | スペース区切り3語 | Intl.Segmenter 6形態素 |
| 長文閾値 | 60文字 → start,end形式 | 30文字 → start,end形式 |
| 最小引用長 | 5文字 | 4文字 |
エラーコード
| コード | 意味 | 推奨対応 |
|---|---|---|
| NOT_FOUND | 引用テキストがソースに存在しない | ソースの原文を確認して再試行 |
| FETCH_FAILED | ページの取得に失敗 (403, timeout等) | ソースURLのみ提示 |
| INVALID_URL | https以外のURL | httpsのURLを使用 |
| TEXT_TOO_SHORT | 引用テキストが最小長未満 | より長いテキストを使用 |
| ENCODE_TOO_LONG | Fragment URL が2000文字超 | 引用テキストを短縮 |
アーキテクチャ
┌──────────────────────────────────────────────────────────┐
│ Claude (Claude Code / Claude Desktop) │
│ │
│ verify_claims([{url, claim}, ...]) │
└───────────────────┬──────────────────────────────────────┘
│ stdio (JSON-RPC)
┌───────────────────▼──────────────────────────────────────┐
│ Cite-Fragment MCP Server (Node.js 18+) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tools │ │
│ │ verify_claims | generate | check | highlight_pdf │ │
│ └─────────────────────┬───────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Core Pipeline │ │
│ │ │ │
│ │ fetcher ─→ extractor (HTML) ─→ detector │ │
│ │ └→ pdf-extractor (PDF) ↓ │ │
│ │ normalizer (ja|en) │ │
│ │ ↓ │ │
│ │ matcher → context │ │
│ │ ↓ │ │
│ │ fragment → encoder │ │
│ │ │ │
│ │ pdf-highlighter (pdfjs-dist + pdf-lib + qpdf) │ │
│ └─────────────────────────────────────────────────────┘ │
└───────────────────┬──────────────────────────────────────┘
│ HTTPS
┌─────▼─────┐
│ Web / PDF │
└───────────┘開発
npm run build # TypeScriptコンパイル
npm run dev # 開発モード (tsx)
npm test # 全テスト (vitest)
npm run test:unit # ユニットテストのみ
npm run test:integ # 統合テストのみ
npm run lint # ESLint
npm run format # Prettierディレクトリ構成
src/
├── index.ts # エントリポイント (stdio接続)
├── server.ts # MCPサーバー定義 (4ツール登録)
├── types.ts # 型定義 (Lang, LangConfig, 入出力型)
├── tools/
│ ├── generate.ts # generate_text_fragment 実装
│ ├── verify.ts # verify_claims 実装 (ページキャッシュ付き)
│ ├── check.ts # check_fragment_url 実装
│ └── highlight.ts # highlight_pdf 実装
└── lib/
├── fetcher.ts # HTML/PDF取得 (タイムアウト・サイズ制限)
├── extractor.ts # HTML本文抽出 (Readability + linkedom)
├── pdf-extractor.ts # PDFテキスト抽出 (pdf-parse, ページ単位)
├── pdf-highlighter.ts # PDFハイライト注釈 (pdfjs-dist + pdf-lib)
├── detector.ts # 言語自動検出 (ja/en)
├── matcher.ts # テキスト検索・位置マッピング
├── fragment.ts # Fragment URL組み立て・エンコード
├── boundary.ts # ブロック境界検出
├── normalizer/
│ ├── index.ts # 言語ディスパッチ
│ ├── english.ts # 英語正規化 (小文字・空白)
│ └── japanese.ts # 日本語正規化 (全角半角・カナ変換)
└── context/
├── index.ts # 言語ディスパッチ
├── english.ts # 英語context抽出 (スペース区切り)
└── japanese.ts # 日本語context抽出 (Intl.Segmenter)ブラウザ対応
Text Fragments (#:~:text=) のブラウザ対応状況:
| ブラウザ | 対応状況 | |---|---| | Chrome / Edge | 80+ で対応 | | Firefox | 131+ で対応 | | Safari | 16.1+ で対応 |
PDF #page=N は主要ブラウザの内蔵PDFビューアすべてで対応。
ライセンス
Apache License 2.0
