Skip to main content
Glama
Lumenco Catalog MCP Server

Lumenco Catalog MCP Server

Lumenco Catalog(阶段 1 爬虫 + 阶段 2 MCP)

本仓库包含两层:

  1. 阶段 1 将 https://en.staging.lumenco.ca/ 抓取到 PostgreSQL。

  2. 阶段 2 通过一个只读的 Model Context Protocol 服务器暴露该目录,使 Claude 无需浏览 Lumenco 即可检索产品、规格、列表和推荐候选。

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

阶段 1 抓取。阶段 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(每页 24 个产品)。总页数位于 #am-page-count 中。

产品

规范 URL 如 /aaled-aa-900018-1x4-bl.html。服务端渲染的 HTML 包含 JSON-LD、SKU、价格、库存、规格表以及规格表链接。

规格表

通常是同源 PDF,位于 /dev/*.pdf 下。

站点地图

/sitemap.xml 当前返回 HTTP 500 错误。爬虫仍会尝试已知的站点地图路径,然后回退到品牌 + 类别发现。

GraphQL

/graphql 存在,但暂存环境的 schema 已损坏(Config element "String" is not declared)。HTML 抓取是可靠来源。

抓取

产品页面是服务端渲染的。Scrapling 的 HTTP FetcherSession 是默认选项。AsyncDynamicSession 注册为懒加载回退,当产品页面缺少必需字段时使用。

爬虫停留在 en.staging.lumenco.ca 上。外部规格表 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 个,下载其规格表,将行存储到 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. 恢复抓取

通过 CRAWL_DIR(默认 ./data/crawl)启用 Scrapling 检查点。按一次 Ctrl+C 可优雅暂停。再次运行:

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. 规格表的处理方式

对于每个产品页面,解析器查找:

  • a.document-item-link(Lumenco 的“规格表”控件)

  • 等效标签:规格表、规格书、规格、技术数据、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 中。

缺少规格表

某些 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。HTTP 抓取足以处理当前暂存 HTML。

数据库连接错误

检查 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,否则拒绝完整目录抓取。

阶段 2.5 — 100 产品数据质量

本项目当前针对受控的约 100 个产品的 Lumenco 数据集。实时目录有 30,000+ SKU;完整目录抓取有意不在范围内。

流程

Scrapling → 产品提取 → PDF 下载 → PDF 文本或 OCR → 规格标准化 → PostgreSQL → 只读 MCP

首先尝试 PDF 文本提取。仅当 PDF 没有有意义的文本时,才运行 OCR(通过 pytesseract 的 Tesseract)。设置 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

阶段 3A — 向量搜索 + 产品嵌入

阶段 3A 通过 PostgreSQL + pgvector 添加语义产品表示。它不实现相关/升级/交叉销售排名(那是阶段 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。现有的推荐工具不变。

阶段 3B — 混合推荐引擎

推荐结合 pgvector 相似度 和 结构化产品规则。仅靠向量相似度不够:18W T8 灯管、30W T8 灯管和 T8 灯具可能在语义上接近,但它们分别映射到相关、升级和交叉销售。

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

类型

含义

相关

相似的使用场景 / 类别 / 规格

升级

同一系列 且 可衡量的改进(不仅仅是价格)

交叉销售

互补(驱动器、装饰、外壳、灯具↔灯管)

排名中不使用 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)。

阶段 3C — Claude + MCP 工作流

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

职责

层

作用

Scrapling

爬取 / 存储

PostgreSQL + pgvector

数据源 + 向量

RecommendationService

确定性推荐 / 关联/交叉销售排序

MCP

只读(无写入工具)

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+)有意不在范围内。

第二阶段 — Lumenco 产品数据库 MCP

名为 Lumenco 产品数据库 的只读 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. 完成第一阶段设置(PostgreSQL + .env + python -m alembic upgrade head)。

  2. 运行一次爬取,以便填充目录。

  3. 如果 requirements.txt 中还没有 MCP 附加依赖,请安装:

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

对于生产环境,创建一个只读角色:

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

连接到 http://localhost:8000/mcp,传输方式为 Streamable HTTP。如果设置了 MCP_AUTH_TOKEN,请添加:

Authorization: Bearer <token>

确认所有八个工具都已列出并且可以执行。

可用工具

所有工具只读取 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

结构化规格以及存储的规格表文本。不会下载 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 应请求一个候选池,然后自行选择最终的 3 个相关 / 4 个升级销售 / 7 个交叉销售。

示例工作流

用户:分析 https://en.staging.lumenco.ca/brand/aaled 中的前 10 个产品,并给出 3 个相关、4 个升级销售、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(bearer 中间件是隔离的,因此以后可以用 OAuth 替换)

  • 只读的 DATABASE_URL

  • 在 /health 上进行健康检查

不要发布端口 5432。

Claude 自定义连接器

服务器在公共 HTTPS URL 上可达后:

  1. 在 Claude 中,添加一个自定义连接器。

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

  3. 服务器名称应显示为 Lumenco 产品数据库。

  4. 使用 MCP_AUTH_TOKEN 配置 bearer 认证,或者如果您替换了中间件,则使用 OAuth。

  5. 询问:“Lumenco 数据库中当前有多少产品?”Claude 应调用 get_catalog_status。

用于本地测试的临时公共 HTTPS:Cloudflare Tunnel、ngrok 或类似工具,放在 localhost:8000 前面。

安全

  • 没有 execute_sql、fetch_url、run_command 或爬取工具

  • 仅使用 SQLAlchemy 参数化查询

  • 强制查询限制

  • 会话在 PostgreSQL 上开启 SET TRANSACTION READ ONLY

  • 工具错误中不会返回密钥

Related MCP Connectors

Related MCP Servers