技术文章聚合与检索服务

siftfeed 是一个从 RSS 等来源收集技术文章,通过多阶段管道进行实体抽取并存储到 SQLite FTS5 数据库中的 MCP 服务器,支持从 Claude Code 进行全文检索的知识库服务。

  • by junpei-kwj
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 fugithora812/siftfeed

使用说明

siftfeed

技術記事を RSS 等から収集し、多段パイプラインでエンティティ抽出した上で SQLite FTS5 に蓄積する MCP Server。

Claude Code からナレッジベースを全文検索できる。

着想

Siftly の 「多段 AI パイプライン + SQLite FTS5」アーキテクチャを参考にしている。 "siftfeed" = "sift through feeds"(フィードをふるいにかける)。

セットアップ

git clone https://github.com/fugithora812/siftfeed.git
cd siftfeed
npm install

MCP 登録

~/.mcp.json(または プロジェクトの .mcp.json)に追加:

{
  "mcpServers": {
    "siftfeed": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "/path/to/siftfeed/src/index.ts"],
      "env": {
        "SIFTFEED_DB_PATH": "/path/to/siftfeed/data/siftfeed.db"
      }
    }
  }
}

SIFTFEED_DB_PATH を省略すると data/siftfeed.db がデフォルトで使われる。

使い方

記事の取り込み

# RSS フィードから取り込み
npm run ingest -- --source rss

# 統計だけ確認
npm run ingest -- --stats

Claude Code からの検索

MCP ツールとして登録すると、どのプロジェクトでも利用できる。

「最近の WebRTC 関連の記事ある?」
「Kubernetes のセキュリティについて何かある?」

Claude Code が search_siftfeed を自動で呼び出し、 蓄積済みの記事から検索結果を返す。

アーキテクチャ

[RSS Feeds (config/feeds.json)]
        |
        v
  +-----------+     +------------------------+
  |  Ingester |---->| Stage 1: Entity Extrac |  (ローカル、API 不要)
  | (rss.ts)  |     | (stage1-extract.ts)    |
  +-----------+     +------------------------+
        |                    |
        v                    v
  +----------+     +------------------+
  |  Scanner |     |  entities table  |
  | 重複排除 |     +------------------+
  +----------+
        |
        v
  +----------------------------+
  |  SQLite (better-sqlite3)   |
  |  - articles     (本体)     |
  |  - articles_fts (FTS5)     |
  |  - entities     (抽出済)   |
  |  - tags         (Phase 2)  |
  |  - categories   (Phase 2)  |
  |  - cross_references (〃)   |
  +----------------------------+
        |
        v
  +----------------------------+
  |  MCP Server (stdio)        |
  |  - search_siftfeed         |
  |  - siftfeed_stats          |
  +----------------------------+

ディレクトリ構成

siftfeed/
├── bin/
│   └── ingest.ts           # CLI エントリ
├── config/
│   ├── feeds.json          # RSS フィード定義
│   └── categories.json     # カテゴリ定義 (Phase 2 用)
├── src/
│   ├── index.ts            # MCP サーバエントリ (stdio)
│   ├── server.ts           # ツール登録
│   ├── db/
│   │   ├── schema.ts       # SQLite スキーマ + FTS5 定義
│   │   └── connection.ts   # DB 接続管理 (WAL モード)
│   ├── ingest/
│   │   ├── adapter.ts      # Article 型定義 + ハッシュ計算
│   │   └── rss.ts          # rss-parser による取得
│   ├── pipeline/
│   │   ├── scanner.ts      # URL ベースの重複排除 + 挿入
│   │   ├── stage1-extract.ts  # エンティティ抽出
│   │   └── runner.ts       # パイプライン統合
│   └── tools/
│       ├── search.ts       # search_siftfeed ツール
│       └── stats.ts        # siftfeed_stats ツール
├── tests/                  # vitest テスト (21 ケース)
├── data/
│   └── siftfeed.db         # SQLite DB (gitignored)
├── package.json
└── tsconfig.json

技術詳細

SQLite FTS5 + trigram tokenizer

全文検索には SQLite の FTS5 拡張を採用し、 トークナイザに trigram を指定している。

CREATE VIRTUAL TABLE articles_fts USING fts5(
  title, content, tags, summary_ja,
  article_id UNINDEXED,
  tokenize='trigram'
);

trigram は入力テキストを 3 文字ずつスライドしてインデックスを構築する。 ICU や MeCab のような外部辞書が不要で、日本語・英語を区別なく検索できる。

トレードオフとして、インデックスサイズが本文の約 3 倍になるが、 10,000 記事で 65 MB 程度なので実用上問題ない。

検索クエリは内部でダブルクォートに囲んで発行している。 これにより、ハイフンを含む full-text のような語句が FTS5 のカラムフィルタ構文と誤認されるのを防いでいる。

エンティティ抽出(Stage 1)

API を呼ばずにローカルで完結する。以下の 4 種類を正規表現で抽出する。

種別 方法 例
library 既知リスト照合 (80+) react, kubernetes, claude
language 既知リスト照合 (15) typescript, rust, go
version semver パターン v2.1.0, 3.0.0-beta.1
github_repo URL パターン viperrcrypto/Siftly

既知リストはコード内に直接定義しており (stage1-extract.ts)、 追加が必要な場合はここに項目を足すだけで済む。

重複排除

articles.url に UNIQUE 制約を設けており、 同じ URL の記事は scanner.ts で INSERT 前にスキップされる。 日次実行でも既存記事を再処理しない。

DB 接続

better-sqlite3 を WAL モードで使用している。 WAL モードにより、読み取り(MCP ツール)と書き込み(インジェスト CLI)が 同時に走っても互いをブロックしない。

DB パスは環境変数 SIFTFEED_DB_PATH で上書き可能。 未設定の場合は data/siftfeed.db がデフォルト。

MCP ツール

search_siftfeed

FTS5 の MATCH クエリで全文検索を行い、 articles テーブルと JOIN して結果を返す。

パラメータ 型 説明
query string (必須) 検索クエリ
source enum all / x_bookmark / rss / github
category string カテゴリフィルタ
dateFrom string 開始日 (YYYY-MM-DD)
dateTo string 終了日 (YYYY-MM-DD)
limit int (1-50) 取得件数 (デフォルト: 10)

siftfeed_stats

パラメータなし。記事数・ソース別内訳・エンティティ数・ トップタグ・最終インデックス日時を返す。

RSS フィード

config/feeds.json で定義。デフォルトで 8 フィードを同梱。

フィード category_hint
Zenn -
Qiita (人気記事) -
dev.to -
GitHub Blog developer_tools
GCP Release Notes infra_devops
AWS Blog infra_devops
Cloudflare Blog infra_devops
Publickey -

フィードを追加するには feeds.json に項目を足すだけでよい。

パフォーマンス

macOS arm64 / Node.js v22 でのベンチマーク結果:

指標 1,000 記事 5,000 記事 10,000 記事
インジェスト 1.0 s 4.2 s 8.9 s
スループット 986/秒 1,193/秒 1,123/秒
DB サイズ 6.7 MB 32.8 MB 65.5 MB
ヒープメモリ 12.8 MB 34.5 MB 58.8 MB
検索 P50 1.7 ms 3.6 ms 6.7 ms
検索 P95 3.5 ms 6.3 ms 12.5 ms

日次運用(20-30 記事/日)では、インジェストは 1 秒未満、 検索は P95 でも数ミリ秒で応答する。

テスト

npm test           # vitest (21 ケース)
npx tsc --noEmit   # 型チェック

今後のフェーズ

Phase 2: AI Enrichment

Claude Haiku で記事ごとにセマンティックタグ (10-15 個) + カテゴリ分類 (信頼度スコア付き) + 日本語要約を生成し、 tags / categories テーブルに保存する。

Phase 3: 自動化 + レポート

  • launchd / cron で日次 RSS 取り込みを自動実行
  • 週次トレンドレポートを Markdown 生成
  • X ブックマーク / GitHub trending のインジェスト追加

ライセンス

MIT