Skip to main content
Glama
Lumenco Catalog MCP Server

Lumenco Catalog MCP Server

Lumenco カタログ(Phase 1 スクレイパー + Phase 2 MCP)

このリポジトリには2つのレイヤーがあります:

  1. Phase 1 は https://en.staging.lumenco.ca/ をスクレイピングして PostgreSQL に取り込みます。

  2. Phase 2 はそのカタログを読み取り専用の Model Context Protocol サーバーとして公開し、Claude が Lumenco を閲覧することなく製品、仕様、リスト、レコメンデーション候補を取得できるようにします。

CLAUDE
  │ MCP / HTTPS
  ▼
Lumenco Product Database (Streamable HTTP)
  │ tools → services → repositories
  ▼
PostgreSQL  (Phase 1 catalog)

Phase 1 がスクレイプし、Phase 2 が公開する。Claude が推論する。

MCP サーバーは Lumenco をスクレイピングせず、仕様 PDF をダウンロードせず、LLM を呼び出さず、データベースに書き込むこともありません。

サイトの外観

Lumenco ステージングは Magento 2 のストアフロントです。

エリア

動作

ブランド

https://en.staging.lumenco.ca/brand に全ブランドが一覧表示されます(Amasty Brands)。そのページのブランドカードは staging.lumenco.ca を指すことがよくあります。スクレイパーはそれらを英語ホストに書き換えます。

ブランドリスト

https://en.staging.lumenco.ca/brand/{slug} に Magento のページネーション ?p=2 付き(1ページ24製品)。総ページ数は #am-page-count にあります。

製品

/aaled-aa-900018-1x4-bl.html のような正規 URL。サーバーレンダリングされた HTML には JSON-LD、SKU、価格、在庫、仕様テーブル、Specification Sheet リンクが含まれます。

仕様シート

通常は /dev/*.pdf 配下の同一オリジン PDF です。

サイトマップ

/sitemap.xml は現在 HTTP 500 でエラーになります。クローラーは既知のサイトマップパスを試した後、ブランド + カテゴリの発見にフォールバックします。

GraphQL

/graphql は存在しますが、ステージングのスキーマは壊れています(Config element "String" is not declared)。HTML クローリングが信頼できる情報源です。

フェッチ

製品ページはサーバーレンダリングです。Scrapling の HTTP FetcherSession がデフォルトです。AsyncDynamicSession は、製品ページに必須フィールドが欠けている場合の遅延フォールバックとして登録されています。

クローラーは en.staging.lumenco.ca に留まります。外部の Specification Sheet PDF は製品ドキュメントとしてダウンロードされる場合があります。広告、アナリティクス、カート、チェックアウト、ソーシャル URL は無視されます。

robots.txt は一般の検索エンジン向けに書かれています(User-agent: * は /brand と一部の CMS ページを除くほとんどのパスを拒否)。このスクレイパーはステージングに対する承認済みカタログ取り込みであるため、ROBOTS_TXT_OBEY はデフォルトで false です。Scrapling にそのファイルを尊重させたい場合は true に設定してください。

Related MCP server: Catalog Services MCP Server

プロジェクト構成

scraper/                    Phase 1 Scrapling crawler
  config.py
  spider.py
  discovery.py
  fetcher.py
  cli.py
  selectors/
  parsers/
  pipelines/
  database/                 shared SQLAlchemy models + repositories
  utils/
app/                        Phase 2 read-only MCP server
  server.py                 Streamable HTTP + /health
  config.py
  auth/middleware.py        bearer token (replaceable with OAuth)
  tools/                    MCP tool layer
  services/                 catalog / product / search / recommendations
  repositories/             read-only queries over Phase 1 tables
  schemas/
  database/session.py       pooled, read-only sessions
alembic/                    PostgreSQL migrations
tests/
scripts/create_readonly_user.sql

1. 依存関係のインストール

Python 3.10+ が必要です。

python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -r requirements.txt

Scrapling の HTTP/ブラウザ拡張は scrapling[fetchers] に含まれています。ブラウザフォールバック(DynamicFetcher)が必要な場合は、ブラウザバイナリをインストールしてください:

scrapling install

スキャンされた画像のみの仕様 PDF 用のオプションの OCR:

pip install pytesseract Pillow
# plus a Tesseract OCR engine on the host

OCR はデフォルトでオフです(ENABLE_OCR=false)。画像ベースの PDF は保存され、空のテキストとして保存されるのではなく ocr_required とマークされます。

2. PostgreSQL の設定

最速のローカルセットアップ:

docker compose up -d postgres

これで PostgreSQL 16 が起動します:

  • ユーザー: lumenco

  • パスワード: lumenco

  • データベース: lumenco

  • ホストポート: 5433(コンテナポートは 5432 のまま。5433 は既に 5432 を使用している Windows の PostgreSQL インストールを避けるため)

環境設定をコピーします:

copy .env.example .env   # Windows
cp .env.example .env     # macOS / Linux

デフォルトの接続文字列:

DATABASE_URL=postgresql+psycopg2://lumenco:lumenco@127.0.0.1:5433/lumenco
LUMENCO_BASE_URL=https://en.staging.lumenco.ca/

テーブルを作成します(どちらの方法でも機能します):

python -m scraper init-db
python -m alembic upgrade head

3. 5製品のテストクロールを実行

python -m scraper crawl --limit 5

これにより、ライブサイトから製品を発見し、最初の5つのみを処理し、それらの Specification Sheet をダウンロードし、PostgreSQL に行を保存し、クロールレポートを出力します。

ブランドを固定することもできます:

python -m scraper crawl --limit 5 --url https://en.staging.lumenco.ca/brand/aaled

または単一の製品:

python -m scraper crawl --url https://en.staging.lumenco.ca/aaled-aa-900018-1x4-bl.html

4. 完全なクロールを実行

python -m scraper crawl

これはすべてのブランド(およびカテゴリリスト)を巡回し、すべてのページネーションページをたどり、発見可能なすべての製品をスクレイプします。--limit を本番カタログの上限と混同しないでください。--limit は開発専用です。

レート制限が組み込まれています:並行性、ドメインごとの上限、ダウンロード遅延、指数バックオフ付きリトライ、オプションの AutoThrottle。.env で調整できます:

MAX_CONCURRENCY=5
CONCURRENT_REQUESTS_PER_DOMAIN=3
DOWNLOAD_DELAY=0.5
RETRY_COUNT=3
AUTOTHROTTLE_ENABLED=true

5. クロールの再開

Scrapling のチェックポイントは CRAWL_DIR(デフォルト ./data/crawl)で有効になります。Ctrl+C を1回押すと、正常に一時停止します。再度実行します:

python -m scraper crawl --resume

再開時の動作:

  • Scrapling は CRAWL_DIR から保留中のリクエストを復元します。

  • scrape_status=success で既に保存されている製品は、--force を渡さない限りスキップされます。

  • 失敗した製品は再試行されます。

  • ドキュメントハッシュが変更されていない場合、仕様 PDF は再抽出されません。

6. データベースの確認

python -m scraper stats
python -m scraper validate
python -m scraper product --sku aa-900018-1x4-bl

または psql で:

psql postgresql://lumenco:lumenco@127.0.0.1:5433/lumenco

便利なクエリ:

SELECT count(*) FROM products;
SELECT sku, product_name, price, brand FROM products ORDER BY last_scraped_at DESC LIMIT 20;

SELECT p.sku, d.filename, d.extraction_status, left(d.extracted_text, 200)
FROM specification_documents d
JOIN products p ON p.id = d.product_id
WHERE d.extraction_status = 'extracted'
LIMIT 10;

7. Specification Sheet の処理方法

製品ページごとに、パーサーは以下を探します:

  • a.document-item-link(Lumenco の「Specification Sheet」コントロール)

  • 同等のラベル:Specification Sheet、Spec Sheet、Specifications、Technical Data、PDF、Fiche technique など。

次にパイプライン:

  1. ドキュメント URL を保存します。

  2. httpx でファイルをダウンロードします(ブラウザではありません)。

  3. PDF のマジックバイト(%PDF)を検証します。

  4. 決定的なコピーを保存します:data/specifications/{sku}_{hash16}.pdf。

  5. PyMuPDF でテキストを抽出します。

  6. ページ/セクションの区切りを維持しながら空白をクリーンアップします。

  7. 抽出されたテキスト、SHA-256 ハッシュ、メソッド、ステータスを保存します。

  8. PDF から Label: Value 行をフィールドを発明せずに解析します。

  9. PDF の仕様と製品ページの仕様をマージし、ソースを保持します:

{
  "Voltage": {
    "value": "120-277V",
    "source": "product_page",
    "raw": "120-277V",
    "normalized": {"min": 120, "max": 277, "unit": "V"}
  }
}

PDF にテキストがほとんどない場合、ステータスは ocr_required になります(または ENABLE_OCR=true の場合は OCR が試行されます)。空の成功した抽出は静かに保存されません。

変更されていない PDF は、後続のクロールではコンテンツハッシュによってスキップされます。

8. 失敗した製品のトラブルシューティング

症状

対処方法

python -m scraper validate が問題を報告

JSON の issues リストを読んでください(missing_name、invalid_url、empty_extracted_text など)。

製品が HTTP 5xx / タイムアウトで失敗

python -m scraper crawl --resume を再実行してください。失敗は crawl_errors にあります。

Specification Sheet がない

一部の SKU では想定内です。ステータスは not_found で、製品行は引き続き保存されます。

PDF が ocr_required とマーク

OCR 拡張を有効にするか、data/specifications/ の保存ファイルを確認してください。

PDF が invalid_pdf とマーク

リンクされたファイルが PDF ではありませんでした(HTML エラーページなど)。specification_documents.error_message を確認してください。

重複製品

発生しないはずです:一意の product_url / canonical_url / sku に加えて upsert。validate を実行してください。

ブランドページが空に見える

en.staging.lumenco.ca にいることを確認してください。フランス語ホストではありません。スパイダーはこれを自動的に書き換えます。

DynamicFetcher エラー

scrapling install を実行してください。現在のステージング HTML には HTTP フェッチで十分です。

データベース接続エラー

DATABASE_URL、docker compose ps、python -m scraper init-db を確認してください。

構造化ログは次のようになります:

[INFO] PRODUCT_FETCH url=https://en.staging.lumenco.ca/aaled-aa-900018-1x4-bl.html sku=aa-900018-1x4-bl status=success
[INFO] SPEC_SHEET sku=aa-900018-1x4-bl status=extracted duration=0.84s
[ERROR] SPEC_SHEET sku=... status=failed error=...

テスト

pytest

カバレッジには、URL 正規化、製品/SKU/価格解析、仕様シート検出、PDF 抽出、データベース upsert / 重複防止、リストメンバーシップ順序、レコメンドスコアリング、MCP ツール統合が含まれます。

CLI リファレンス

python -m scraper crawl --limit 100
python -m scraper crawl --mode development --limit 100 --url https://en.staging.lumenco.ca/brand/aaled
python -m scraper crawl --resume
python -m scraper reprocess-specs
python -m scraper embeddings --limit 100
python -m scraper embedding-stats
python -m scraper recommend --sku ABC123 --type related --limit 5
python -m scraper recommendation-eval
python -m scraper validate
python -m scraper stats
python -m scraper sample
python -m scraper product --sku ABC123
python -m scraper init-db
python -m app.server

デフォルトのクロールモードは開発です:最大100の正常に処理された製品、ブランドのみ(カテゴリウォークなし)。--mode full --limit N または --mode full --confirm-full を渡さない限り、フルカタログクロールは拒否されます。

Phase 2.5 — 100製品のデータ品質

このプロジェクトは現在、管理された約100製品の Lumenco データセットを対象としています。ライブカタログには 30,000 以上の SKU があります。フルカタログクロールは意図的に範囲外です。

パイプライン

Scrapling → 製品抽出 → PDF ダウンロード → PDF テキストまたは OCR → 仕様の正規化 → PostgreSQL → 読み取り専用 MCP

PDF テキスト抽出が最初に試行されます。OCR(pytesseract 経由の Tesseract)は、PDF に意味のあるテキストがない場合にのみ実行されます。ENABLE_OCR=true を設定し、Tesseract と pip install pytesseract Pillow をインストールしてください。

正規化された仕様は、ソースと競合フラグを保持します。生の仕様シートテキストは specification_documents.extracted_text に保存されます。MCP get_product はコンパクトな構造化仕様を返します。get_product_specifications は include_raw_text=true の場合に生テキストを含めることができます。

フランス語の Magento カテゴリ URL(例:/eclairage-interieur や /electricite)は、英語ホストの共有ヘッダーにまだ表示されます。それらはそこで 404 になります。クローラーはそれらのパスをエンキューしません。新しいクロールは、分離された Scrapling チェックポイントディレクトリ(data/crawl/run-<id>)も使用するため、古い一時停止ファイルが数千のカテゴリ URL を再開することはありません。--resume は共有 data/crawl チェックポイントを続行する場合にのみ使用してください。

英語ホストに書き換えられたフランス語の Magento カテゴリ URL は expected_404 として分類され、製品の失敗としてカウントされません。

クロール後:

python -m scraper stats
python -m scraper validate
python -m scraper sample
python -m scraper product --sku L0110TUT8002020

Phase 3A — ベクトル検索 + 製品埋め込み

Phase 3A は、PostgreSQL + pgvector を使用したセマンティック製品表現を追加します。Related/Upsell/Cross-sell ランキングは実装しません(それは Phase 3B です)。

アーキテクチャ

~100 product dataset
        ↓
Canonical product text (cleaned, no HTML)
        ↓
EmbeddingService (OpenAI-compatible API)
        ↓
product_embeddings (pgvector)
        ↓
VectorSearchService
        ↓
MCP tool: search_similar_products

セットアップ

  1. pgvector を含む Postgres イメージを使用します(docker-compose.yml は pgvector/pgvector:pg16 を使用)。

  2. .env に埋め込み環境変数を設定します(.env.example を参照)。

  3. マイグレーション:

python -m alembic upgrade head
  1. 開発カタログの埋め込みを生成:

python -m scraper embeddings --limit 100
python -m scraper embedding-stats

変更されていない製品は content_hash でスキップされます。--force を使用してすべてを再生成します。

インデックス戦略

コサイン距離の HNSW(vector_cosine_ops、m=16、ef_construction=64)— 約 ~100 製品のデータセットに適しており、カタログが成長しても使用可能です。IVFFlat は、はるかに大きなカタログのために後で検討できます。

MCP

新しい読み取り専用ツール:search_similar_products。保存されたベクトルのみを読み取ります。埋め込み API を呼び出したり、Lumenco をスクレイプしたりしません。既存のレコメンドツールは変更されません。

Phase 3B — ハイブリッドレコメンドエンジン

レコメンドは pgvector 類似度 と 構造化製品ルール を組み合わせます。ベクトル類似度だけでは不十分です。18W T8 チューブ、30W T8 チューブ、T8 フィクスチャはすべて意味的に近いかもしれませんが、それぞれ Related、Upsell、Cross-sell にマッピングされます。

Product → vector candidates + structured neighbors
                ↓
        hard exclusions
                ↓
   Related / Upsell / Cross-sell scorers
                ↓
     scores + confidence + reasons → MCP

タイプ

意味

Related

類似のユースケース / カテゴリ / 仕様

Upsell

同じファミリー かつ 測定可能な改善(価格だけではない)

Cross-sell

補完的(ドライバー、トリム、ハウジング、フィクスチャ↔チューブ)

ランキング内で LLM は使用されません。MCP ツール find_related_products、find_upsell_products、find_cross_sell_products は RecommendationService(読み取り専用)を呼び出します。

CLI

python -m scraper recommend --sku L0110TUT8002020 --type related --limit 5
python -m scraper recommend --sku L0110TUT8002020 --type upsell --limit 5 --debug
python -m scraper recommend --sku L0110TUT8002020 --type cross-sell --limit 5
python -m scraper recommendation-eval --sample-size 10 --limit 3

重みは RELATED_VECTOR_WEIGHT、UPSELL_TECHNICAL_WEIGHT、CROSS_SELL_COMPATIBILITY_WEIGHT などの環境変数で設定できます(.env.example を参照)。

Phase 3C — Claude + MCP ワークフロー

User → Claude → MCP (/mcp) → PostgreSQL + pgvector + RecommendationService → Claude → User

責任

レイヤー

役割

Scrapling

クロール / 保存

PostgreSQL + pgvector

真実のソース + ベクトル

RecommendationService

決定的な関連商品 / アップセル / クロスセルのランキング

MCP

読み取り専用の取得(スクレイピングなし、書き込みなし、LLMなし)

Claude

会話、ツール選択、説明

Claude スキル

プロジェクトスキル: .cursor/skills/lumenco-product-mcp/SKILL.md

エンドツーエンドのプロンプト

docs/claude-e2e-tests.md を参照してください。

Claude / Inspector への接続

  1. docker compose up -d postgres

  2. python -m app.server

  3. クライアントを http://localhost:8000/mcp(Streamable HTTP)に向ける

  4. 任意: MCP_AUTH_TOKEN + Authorization: Bearer …

後でリモートデプロイする場合: MCP の HTTPS エンドポイントのみを公開し、PostgreSQL はプライベートに保つ。

開発用データセット

現在のカタログ: 約100製品。完全な Lumenco カタログ(30k+)は意図的にスコープ外。

フェーズ 2 — Lumenco 製品データベース MCP

Lumenco Product Database という名前の読み取り専用 Streamable HTTP MCP サーバー。

アーキテクチャ

Claude
  │ MCP / Streamable HTTP
  ▼
Lumenco MCP Server   (/mcp, /health)
  │
  ▼
MCP Tool Layer
  │
  ▼
Service Layer          catalog / product / search / similarity / recommendation
  │
  ▼
Repository Layer       SQLAlchemy, no raw SQL in tools
  │
  ▼
PostgreSQL + pgvector  products, specs, listings, product_embeddings

ローカルセットアップ

  1. フェーズ 1 のセットアップを完了する(PostgreSQL + .env + python -m alembic upgrade head)。

  2. カタログが入力されるようにクロールを実行する。

  3. MCP の追加機能がまだ requirements.txt にない場合はインストールする:

pip install -r requirements.txt
  1. .env に MCP 変数を設定する:

MCP_HOST=0.0.0.0
MCP_PORT=8000
MCP_AUTH_TOKEN=replace-with-a-long-random-token
DATABASE_URL=postgresql+psycopg2://lumenco:lumenco@127.0.0.1:5433/lumenco
DB_POOL_SIZE=10
DB_MAX_OVERFLOW=20
DB_POOL_TIMEOUT=30

本番環境では、SELECT 専用ロールを作成する:

psql postgresql://lumenco:lumenco@127.0.0.1:5433/lumenco -f scripts/create_readonly_user.sql

次に、DATABASE_URL を lumenco_mcp に向ける。

実行

python -m app.server

または:

uvicorn app.server:app --host 0.0.0.0 --port 8000

Docker:

docker compose up --build mcp

MCP エンドポイント

http://localhost:8000/mcp

ヘルスチェック

GET http://localhost:8000/health

{
  "status": "ok",
  "service": "lumenco-product-mcp",
  "database": "connected"
}

MCP Inspector

npx -y @modelcontextprotocol/inspector

トランスポート Streamable HTTP で http://localhost:8000/mcp に接続する。MCP_AUTH_TOKEN が設定されている場合は、次を追加する:

Authorization: Bearer <token>

8 つのツールがすべて一覧表示され、実行可能であることを確認する。

利用可能なツール

すべてのツールは PostgreSQL のみを読み取る。Lumenco の URL を取得するものはない。

get_catalog_status

カタログのサイズと最新のクロールの鮮度。入力なし。

get_listing_products

ブランド / カテゴリの一覧 URL 上の製品を、元の一覧の位置順で取得。

入力

必須

備考

listing_url

はい

正規化され、データベースキーとして使用される

limit

いいえ

デフォルト 20、最大 100

offset

いいえ

デフォルト 0

get_product

product_id および / または sku による完全な製品レコード。

get_product_specifications

構造化された仕様に加え、保存されている Specification Sheet のテキスト。PDF はダウンロードしない。

search_products

ローカルカタログ検索(SKU、名前、ブランド、カテゴリ、説明、仕様)。

任意のフィルター: brand、category、subcategory、sku、min_price、max_price。

search_similar_products

保存された pgvector 埋め込みからの意味的類似製品(コサイン類似度)。埋め込みの生成や LLM の呼び出しは行わない。

任意のフィルター: brand、category、subcategory、min_price、max_price。

ハイブリッドな 関連商品 候補(ベクトル + カテゴリ / 用途 / 仕様)。match_score、confidence、score_breakdown、match_reasons を含む。任意の debug=true。

find_upsell_products

ハイブリッドな アップセル 候補。測定可能な改善が必要(価格のみでは不可)。理由は upgrade_reasons に含まれる。

find_cross_sell_products

ハイブリッドな クロスセル 候補。互換性が優先され、同ファミリーの代替品は除外される。

レコメンデーションツールはソース製品を除外し、候補の重複を排除する。Claude は候補プールをリクエストし、最終的な Related 3 件 / Upsell 4 件 / Cross-sell 7 件を自身で選択する必要がある。

ワークフロー例

ユーザー: https://en.staging.lumenco.ca/brand/aaled の最初の 10 製品を分析し、Related 3 件、Upsell 4 件、Cross-sell 7 件を提示してください。

  1. get_listing_products(listing_url=..., limit=10)

  2. 各ソースに対して get_product(product_id=...)

  3. limit=10 で find_related_products / find_upsell_products / find_cross_sell_products

  4. Claude は候補プールから最終セットを選択する

本番デプロイ

MCP の HTTPS エンドポイントのみを公開する。PostgreSQL はプライベートに保つ。

Internet → HTTPS → MCP server → private PostgreSQL

適切なホスト: Railway、Render、Google Cloud Run、AWS、Cloudflare。

要件:

  • uvicorn / Docker イメージの前段に HTTPS ターミネータ

  • MCP_AUTH_TOKEN を設定(ベアラーミドルウェアは分離されているため、後で OAuth に置き換え可能)

  • 読み取り専用の DATABASE_URL

  • /health でのヘルスチェック

ポート 5432 は公開しない。

Claude カスタムコネクタ

サーバーが公開 HTTPS URL で到達可能になった後:

  1. Claude でカスタムコネクタを追加する。

  2. MCP URL: https://your-host/mcp

  3. サーバー名は Lumenco Product Database と表示されるはず。

  4. MCP_AUTH_TOKEN でベアラー認証を設定するか、ミドルウェアを置き換えた場合は OAuth を設定する。

  5. 質問する: 「Lumenco データベースには現在何製品ありますか?」 Claude は get_catalog_status を呼び出すはず。

ローカルテスト用の一時的な公開 HTTPS: localhost:8000 の前段に Cloudflare Tunnel、ngrok など。

セキュリティ

  • execute_sql、fetch_url、run_command、クロールツールはなし

  • SQLAlchemy のパラメータ化クエリのみ

  • クエリ制限を適用

  • セッションは PostgreSQL で SET TRANSACTION READ ONLY を開く

  • シークレットはツールエラーで返されない

Related MCP Connectors

Related MCP Servers