关于 Lens
Lens 是基于 TypeScript 的 Web 服务工具集,提供图像处理、网页截图、内容提取、字体等功能。其 HTTP API 也以 MCP 工具形式开放,供客户端调用。
主要功能
- 01调整尺寸、转换和优化图像
- 02截取网站页面
- 03将网页内容提取为 Markdown 或 HTML
- 04提供字体服务并生成 Open Graph 图像
- 05提取网站图标并代理 Gravatar 头像
- 06Redis 缓存与 URL 安全检查
适用场景
- 01按需生成指定格式的缩放图像
- 02截取桌面或移动设备网页画面
- 03提取文章内容供后续处理
- 04为网页生成 Open Graph 图像
MCP 相关
Lens 在 `/mcp` 提供 HTTP MCP 接口,将各项服务作为工具开放,包括内容提取、截图、网站图标、OG 图像、Gravatar 和图像转换。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.bysages.com/mcpclaude mcp add --transport http lens https://api.bysages.com/mcp{"mcpServers":{"lens":{"type":"http","url":"https://api.bysages.com/mcp"}}}在 Cursor 中打开{"servers":{"lens":{"type":"http","url":"https://api.bysages.com/mcp"}}}在 VS Code 中打开{"mcpServers":{"lens":{"type":"http","url":"https://api.bysages.com/mcp"}}}{"mcpServers":{"lens":{"serverUrl":"https://api.bysages.com/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
6| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | 要提取内容的网站 URL |
| format | enum | 否 | 输出格式(默认:markdown)html | markdown |
详细介绍
Lens
使用现代 TypeScript 构建的高性能图像代理和 Web 服务工具包。Lens 提供全面的 Web 实用工具套件,包括图像处理、截图、字体服务等。
🚀 功能
核心服务
- 🖼️ 图像代理:由 UnImage 驱动的图像处理,支持调整大小、格式转换和优化
- 📸 截图:快速的网站截图服务,并进行资源优化
- 📄 阅读器:使用 Readability 提取文章内容(Markdown/HTML)
- 🅰️ 字体服务:兼容 Google Fonts 的 API,支持多个提供商
- 🎨 Open Graph 图像:动态生成 OG 图像
- 🎯 网站图标提取:从网站智能提取网站图标
- 👤 Gravatar 代理:带缓存的 Gravatar 头像代理,支持输入邮箱或哈希值
- 🤖 MCP 服务:通过 JSON-RPC 2.0 将所有服务作为工具提供的 Model Context Protocol 服务
性能与可扩展性
- ⚡ Redis 缓存:由 Redis 驱动的缓存,支持 24 小时智能缓存
- 🧭 按需浏览器:通过 CDP 驱动 Moli 无头浏览器(Rust)——内存占用约为 Chromium 的 1/10
- 🛡️ 速率限制:限制高开销操作的速率
- 🔒 SSRF 防护:通过 DNS 解析检查验证 URL,并在浏览器层阻止访问专用网络
- 🔄 优雅降级:服务不可用时自动回退
📦 快速开始
前提条件
- Node.js 24+
- pnpm 10+(推荐)或 npm
- Moli 无头浏览器(截图/阅读器通过 CDP 连接到它)
安装
# Clone the repository
git clone https://github.com/bysages/lens.git
cd lens
# Install dependencies
pnpm install
# Copy environment configuration
cp .env.example .env
开发
# Start the Moli browser server (default CDP endpoint: http://127.0.0.1:9222)
moli serve --layout --resource
# In another terminal, start development server
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm preview
服务器默认在 http://localhost:3000 上启动。
🛠️ 配置
配置
所有配置项均为可选项;未配置时会进行优雅降级:
# Image proxy security
ALLOWED_DOMAINS=example.com,cdn.example.com
# Set only when requests arrive through a trusted reverse proxy
TRUST_PROXY=1
# Requests per IP within RATE_WINDOW seconds; Redis makes these cluster-wide
BROWSER_RATE_LIMIT=20
FAVICON_RATE_LIMIT=30
IMAGE_RATE_LIMIT=120
GRAVATAR_RATE_LIMIT=120
RATE_WINDOW=60
# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379
# Browser: Moli CDP endpoint (defaults to http://127.0.0.1:9222)
BROWSER_CDP_ENDPOINT=http://127.0.0.1:9222
# Browser: optional user-agent overrides (default: the engine's own UA)
#BROWSER_USER_AGENT=...
#BROWSER_USER_AGENT_MOBILE=...
# Browser: cluster-wide maximum with Redis (process-local fallback otherwise)
BROWSER_MAX_CONCURRENCY=4
# Maximum wait for a global browser slot (ms)
BROWSER_QUEUE_TIMEOUT=15000
# Reject full-page captures above this CSS pixel count
SCREENSHOT_MAX_PIXELS=24000000
# Cluster: worker count (Docker defaults to 2; raw Nitro defaults to CPU count)
NITRO_CLUSTER_WORKERS=2
所有可用选项请参阅 .env.example。
📡 API 参考
由缓存支持的路由会返回 X-Cache 和强 ETag;对于匹配的 If-None-Match 请求,会返回 304。
图像
图像代理
使用 UnImage 转换和优化图像,采用兼容 IPX 的 URL:
GET /img/{modifiers}/{image_url}
示例:
# Resize to 300px width, negotiate the best supported format
https://api.bysages.com/img/w_300,f_auto/https://example.com/image.jpg
# Explicit WebP output
https://api.bysages.com/img/w_300,f_webp/https://example.com/image.jpg
# Create a 200x200 cover thumbnail
https://api.bysages.com/img/s_200x200,q_80/https://example.com/image.png
# Rotate and desaturate
https://api.bysages.com/img/rotate_90,grayscale/https://example.com/photo.jpg
常用修饰符:
w_XXX/h_XXX- 宽度 / 高度s_XXXxYYY- 尺寸简写fit_contain|cover|fill|outside- 适配模式f_FORMAT- 输出格式(webp、jpg、png、avif、svg、gif等;f_auto会根据Accept协商格式)q_XXX- 质量rotate_90、grayscale、blur_2、sharpen_1、animated- 常见变换_/path- 不带修饰符的透传
服务器处理栈使用 SVGO 处理 SVG,使用 Canvas 执行支持的操作,并使用 Sharp 作为光栅图像的回退方案。远程图像受到域名允许列表、DNS/专用地址检查和重定向验证的保护。本地路径限定在 public 目录内。响应使用 Redis 缓存、强 ETag、304 状态码,以及针对协商格式的 Vary: Accept。
截图捕获
使用浏览器样式选项捕获网站截图:
GET /screenshot?url={website_url}&options
示例:
# Basic screenshot
https://api.bysages.com/screenshot?url=https://example.com
# JPEG with custom quality
https://api.bysages.com/screenshot?url=https://example.com&type=jpeg&quality=80
# Mobile screenshot with custom viewport
https://api.bysages.com/screenshot?url=https://example.com&width=375&height=667&mobile=true
# Full page capture
https://api.bysages.com/screenshot?url=https://example.com&fullPage=true
# Clip region (x,y,width,height)
https://api.bysages.com/screenshot?url=https://example.com&clip=0,0,400,300
# Disable animations for clean capture
https://api.bysages.com/screenshot?url=https://example.com&animations=disabled
# Custom CSS scale
https://api.bysages.com/screenshot?url=https://example.com&scale=css
# Inject custom stylesheet
https://api.bysages.com/screenshot?url=https://example.com&style=body{background:red}
参数:
| 参数 | 描述 | 默认值 |
|---|---|---|
url | 网站 URL(必填) | - |
width | 视口宽度(100-2560) | 1280 |
height | 视口高度(100-1440) | 720 |
type | 输出格式(png、jpeg) | png |
quality | 图像质量,1-100(仅适用于 jpeg) | - |
fullPage | 捕获完整可滚动页面(true/false) | false |
clip | 裁剪区域,格式为 x,y,width,height | - |
scale | 截图比例(css 表示 CSS 像素,device 表示设备像素) | device |
animations | 动画处理方式(disabled 或 allow) | disabled |
caret | 文本插入光标(hide 或 initial) | hide |
style | 要应用的自定义 CSS 样式表 | - |
timeout | 导航超时时间,单位为 ms(1000-30000) | 10000 |
mobile | 移动设备用户代理和触摸输入(true/false) | false |
darkMode | 深色模式(true/false) | false |
waitUntil | 导航等待条件(load、domcontentloaded、networkidle) | load |
delay | 页面加载后的额外延迟,单位为 ms(0-10000) | - |
性能说明:
- 截图会缓存 1 天,并进行速率限制
- 相同的请求会返回缓存结果,响应时间不到 1 秒
- 截图会短暂等待字体和图像加载,默认会冻结动画
- Moli 会将整页截图渲染为一张位图;
SCREENSHOT_MAX_PIXELS会拒绝尺寸过大的截图 BROWSER_MAX_CONCURRENCY是集群范围内的并发限制,使用 Redis,并包含有界队列
Open Graph 图像
生成动态 OG 图像:
GET /og?title={title}&description={description}
示例:
# Basic OG image
https://api.bysages.com/og?title=Welcome&description=Get%20started%20with%20Lens
# Custom styling
https://api.bysages.com/og?title=Hello%20World&theme=dark&fontSize=72&width=1200&height=630
缓存:
- 生成的 OG 图像会缓存 24 小时
- 参数相同的重复请求会立即返回缓存结果
Web 服务
Reader(内容提取)
从任意 URL 提取渲染后的 Markdown 或 HTML 内容:
GET /reader?url={website_url}&options
示例:
# Extract rendered Markdown (default)
https://api.bysages.com/reader?url=https://github.com/bysages/lens
# Get HTML instead
https://api.bysages.com/reader?url=https://github.com/bysages/lens&format=html
# Wait for full page load
https://api.bysages.com/reader?url=https://github.com/bysages/lens&waitUntil=load
参数:
| 参数 | 描述 | 默认值 |
|---|---|---|
url | 网站 URL(必填) | - |
format | 输出格式(html、markdown) | markdown |
waitUntil | 导航等待条件(load、domcontentloaded、networkidle) | domcontentloaded |
delay | 页面加载后的额外延迟,单位为 ms(0-10000) | - |
timeout | 导航超时时间,单位为 ms(1000-30000) | 10000 |
Favicon 提取
提取高质量的 favicon:
GET /favicon?url={website_url}&size={size}
示例:
# Extract favicon
https://api.bysages.com/favicon?url=github.com
# Custom size
https://api.bysages.com/favicon?url=github.com&size=64
功能:
- 从多个来源智能提取 favicon(PWA 清单、Apple 触控图标、HTML 标签)
- 支持 ETag/304 的服务端缓存,缓存时间为 30 天
- 如果未找到 favicon,则自动回退到生成的 favicon
Gravatar 代理
通过电子邮件地址、MD5 哈希或路径获取 Gravatar 头像。除 email/hash 外,所有查询参数都会直接转发给 Gravatar。只需替换 URL 前缀,即可切换 Gravatar。
GET /gravatar/{md5}?{gravatar_params}
GET /gravatar?email={email}
GET /gravatar?hash={md5}&{gravatar_params}
示例:
# By path (drop-in replacement for Gravatar URLs)
https://api.bysages.com/gravatar/{md5}?size=200
# By email
https://api.bysages.com/[email protected]
# By MD5 hash
https://api.bysages.com/gravatar?hash={md5}
# With Gravatar parameters
https://api.bysages.com/[email protected]&size=200&default=identicon&rating=pg
参数:
{md5}- URL 路径中的 MD5 哈希(直接替换模式)email- 电子邮件地址(会自动计算 MD5 哈希)hash- 预先计算的 MD5 哈希(可替代电子邮件地址)- 其他所有参数都会转发至 Gravatar API
MCP 服务
通过 HTTP 上的 JSON-RPC 2.0 将所有 Lens 服务作为工具公开的 Model Context Protocol 服务。
POST /mcp
在 Claude Desktop 中设置(claude_desktop_config.json):
{
"mcpServers": {
"lens": {
"url": "https://api.bysages.com/mcp"
}
}
}
可用工具:
| 工具 | 描述 | 返回值 |
|---|---|---|
reader | 从 URL 提取文章内容(Markdown/HTML) | 文本 |
screenshot | 截取网站屏幕截图 | 图像 |
favicon | 提取网站的 favicon | 图像 |
og | 生成动态 Open Graph 图像 | 图像 |
gravatar | 通过电子邮件地址或哈希获取 Gravatar 头像 | 图像 |
img | 转换并优化图像(调整大小、裁剪、转换格式) | 图像 |
字体
字体服务
兼容 Google Fonts 的 API,同时支持 v1 和 v2 端点:
GET /css?family={font_family}&display=swap
GET /css2?family={font_family}&display=swap
API 差异:
| 功能 | /css(v1) | /css2(v2) |
|---|---|---|
| 多种字体 | family=A|B(竖线分隔) | family=A&family=B(重复参数) |
| 样式语法 | FontName:400,700(旧) | FontName:wght@400;700(新,严格) |
| 可变字体 | ❌ 不支持 | ✅ 完全支持范围值([email protected]) |
| 基础 URL | fonts.googleapis.com/css | fonts.googleapis.com/css2 |
参数:
family- 字体系列名称(必填)display- 字体显示策略(默认:swap)subset- 字体子集(默认:latin)provider- 字体提供方(google、bunny、fontshare、fontsource,默认:google)proxy- 是否为字体文件使用代理(true/false,默认:false)
示例:
# Basic font (v1 API - pipe separator)
https://api.bysages.com/css?family=Roboto:wght@400;700|Open+Sans:wght@300;400;600&display=swap
# Multiple fonts (v2 API - repeated family parameter)
https://api.bysages.com/css2?family=Inter:wght@400;700&family=Roboto:wght@300;400&display=swap
# Variable fonts with weight range (v2 only)
https://api.bysages.com/css2?family=Inter:[email protected]&display=swap
# Font metadata
https://api.bysages.com/webfonts?sort=popularity&category=sans-serif
建议:
- 使用
/css,以便与旧版 Google Fonts 语法保持兼容 - 使用
/css2,适用于采用可变字体并追求更佳优化的现代应用 - 两种端点都支持所有字体提供方(Google、Bunny、Fontshare、Fontsource)
🏗️ 架构
Lens 采用现代 Web 标准构建,并遵循整洁架构原则:
核心技术
- 运行时:Node.js 24+ 和 Nitro
- 语言:TypeScript,具备严格的类型安全性
- 图像代理:UnImage,搭配 SVGO、Canvas 和 Sharp
- 浏览器自动化:Moli 无头浏览器,由 Playwright 通过 CDP 驱动
- 缓存:Redis 和 unstorage
设计原则
- KISS(保持简单):优先采用简单、专注的解决方案,而非复杂的抽象
- DRY(不要重复自己):共享工具和配置
- 优雅降级:为缺失的依赖项提供回退方案
- 类型安全:全面覆盖 TypeScript
- 性能优先:针对速度和效率进行优化
🚀 部署
先决条件
- Node.js 24+ 运行时
- pnpm 10+ 包管理器
- Redis(已包含在 Docker Compose 中;其他部署方式可选)
生产环境部署
# Build the application
pnpm run build
# Start production server
pnpm run preview
Docker 部署(推荐)
选项 1:使用 Docker Compose
# Copy environment variables
cp .env.example .env
# Edit .env file to configure your settings
# nano .env
# Start the application
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop the application
docker-compose down
应用将在 http://localhost:3000 上提供服务。
GET /healthz 会报告存活状态,包括浏览器(CDP)的可用性 ——
Compose 健康检查和编排器探针应指向此端点。
该堆栈包括:
- Lens 应用,运行在端口 3000,Moli 浏览器在容器内运行
- Redis,用于分布式缓存,并采用 LRU 淘汰策略限制容量(512 MB)
选项 2:使用 Docker Run
# Pull the official image
docker pull bysages/lens:latest
# Run container with host network (recommended for accurate IP logging);
# --init is needed because the image runs Moli alongside Node
docker run -d \
--name lens \
--network host \
--init \
--env-file .env \
--restart unless-stopped \
bysages/lens:latest
# View logs
docker logs -f lens
# Stop the container
docker stop lens
docker rm lens
选项 3:从源代码构建
# Build Docker image
docker build -t lens .
# Run container
docker run -d \
--name lens \
--network host \
--init \
--env-file .env \
--restart unless-stopped \
lens
环境变量
所有配置均为可选项:
# Image Proxy Security
ALLOWED_DOMAINS=example.com,cdn.example.com
# Set only when requests arrive through a trusted reverse proxy
TRUST_PROXY=1
# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379
# Browser: Moli CDP endpoint (defaults to http://127.0.0.1:9222)
BROWSER_CDP_ENDPOINT=http://127.0.0.1:9222
# Browser: cluster-wide maximum with Redis (process-local fallback otherwise)
BROWSER_MAX_CONCURRENCY=4
# Maximum wait for a global browser slot (ms)
BROWSER_QUEUE_TIMEOUT=15000
# Reject full-page captures above this CSS pixel count
SCREENSHOT_MAX_PIXELS=24000000
# Cluster: worker count (Docker defaults to 2; raw Nitro defaults to CPU count)
NITRO_CLUSTER_WORKERS=2
# Moli server flags used by the Docker entrypoint
# (--block-private-networks is always enforced by the entrypoint)
MOLI_FLAGS=--layout --resource --http-cache-dir=/tmp/lens-cache/moli-http --http-max-concurrent=32 --http-max-total-connections=64
请参阅 .env.example,了解所有可用选项。
📄 许可证
MIT 许可证 — 详情请参阅 LICENSE 文件。
由 By Sages 倾心打造 ❤️
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。