关于 willmehr
willmehr 是面向奥地利 willhaben.at 分类信息网站的 MCP 服务,可搜索刊登并按低于结果中位价的程度排序。公开搜索无需账户;需要会话 Cookie 的账户工具仅在本地 full 模式提供。
主要功能
- 01使用结构化筛选搜索并自动翻页
- 02获取刊登描述、图片和卖家信号
- 03按低于结果中位价排序刊登
- 04发现类别和品牌筛选项
- 05查看个人资料、收藏和对话
适用场景
- 01按地点和类别寻找二手商品
- 02筛选低于市场中位价的刊登
- 03检查候选刊登的详情和卖家信息
- 04查看账户中的收藏或近期对话
MCP 相关
向 MCP 客户端提供 willhaben.at 搜索、刊登详情、价格分析及账户工具。支持本地 stdio 和自托管 Streamable HTTP;账户工具需在本地 full 模式使用会话 Cookie。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y willmehrWILLMEHR_MODE'full' (default) registers the account tools; 'public' runs anonymously, reads no credential and paces requests for a shared IP.WILLHABEN_COOKIE密钥Session cookie from a logged-in willhaben browser session, needed only by the willhaben_my_* tools. Must contain BBX_JSESSIONID and x-bbx-csrf-token. Sessions expire within roughly half an hour.WILLHABEN_MIN_INTERVAL_MSMinimum spacing between requests to willhaben, in milliseconds.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
5收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pages | integer | 否 | 要获取的结果页数,每页约 25 条商品。默认值为 1,最大值为 5。 |
| query | string | 否 | 搜索词,例如 'ThinkPad T14' 或 'Fahrrad 28 Zoll' |
| location | string | 否 | 城市名称或德国邮政编码,例如 'Berlin' 或 '10115' |
| maxPrice | integer | 否 | 最高价格,单位 EUR |
| minPrice | integer | 否 | 以 EUR 计的最低价格 |
| radiusKm | integer | 否 | 以公里为单位的 `location` 周边搜索半径 |
| publishedAfter | string | 否 | 仅包含在此本地 ISO 8601 日期时间或之后发布的商品,例如 '2026-08-01T00:00:00'。遇到更早发布的商品时,也会停止翻页。 |
详细介绍
willmehr
用于在德语区分类网站上寻找优惠商品的 MCP 服务。使用 TypeScript,可通过本地 stdio 或可自托管的 Streamable HTTP 运行;兼容 Claude Code、Claude Desktop 和其他 MCP 客户端。
此仓库提供两个服务,需分别注册:
| 服务 | 网站 | 入口点 |
|---|---|---|
willmehr | willhaben.at(奥地利) | dist/index.js |
kleinanzeigen-mcp | kleinanzeigen.de(德国) | dist/kleinanzeigen/index.js |
它们作为两个 npm 包发布:willmehr
和 kleinanzeigen-mcp,由此仓库以相同版本同时发布。
willhaben 服务通过 HAR 捕获文件对 willhaben Web 应用自身的 JSON
API 进行逆向工程构建。Kleinanzeigen 服务是
jnslmk/kleinanzeigen-mcp 的移植版——请参阅下文的
Kleinanzeigen。
快速开始
claude mcp add willmehr -- npx -y willmehr
claude mcp add kleinanzeigen -- npx -y kleinanzeigen-mcp
或者从克隆的仓库运行,适合修改或自行托管:
git clone https://github.com/taneron/willmehr.git && cd willmehr
npm install && npm run build
claude mcp add willmehr -- node "$PWD/dist/index.js"
claude mcp add kleinanzeigen -- node "$PWD/dist/kleinanzeigen/index.js"
无论哪种方式,你都能在本地以 full 模式运行两个服务。它们都已列入官方 MCP 注册表,名称分别为 io.github.taneron/willmehr 和
io.github.taneron/kleinanzeigen,因此读取注册表的客户端无需查看此 README 就能找到它们。willhaben 账户工具需要
你自己的会话 Cookie——请参阅身份验证;其他所有功能都可匿名使用,无需设置。
若要在自己的设备上将其作为共享 HTTP 服务运行,请参阅托管。 此项目不会向任何其他人的实例发送数据。
模式
两个服务都以两种模式之一运行。代码和二进制文件相同——启动时通过
--public / --full 或 WILLMEHR_MODE 选择模式,默认使用 full。
full(默认) | public | |
|---|---|---|
| willhaben 账户工具 | 已注册 | 未注册 |
.env / WILLHABEN_COOKIE | 已加载 | 从不读取;忽略并在 stderr 输出警告 |
| willhaben 请求间隔 | 400 ms | 900 ms |
| Kleinanzeigen 间隔 / 页面数 / 批量数量 | 800 ms / 5 / 20 | 1500 ms / 3 / 10 |
public 模式适合托管给其他人使用:进程不持有任何凭据,也不会注册任何可使用凭据的工具,因此共享部署不会泄露从未加载过的会话。更严格的请求间隔是另一项保障——在 public 模式下,所有用户共用一个出站 IP,而两个网站都会按 IP 限流。
node dist/index.js --public # willhaben, anonymous
WILLMEHR_MODE=public node dist/kleinanzeigen/index.js
账户工具刻意不提供托管版本。willhaben 通过 Keycloak 和 PKCE 进行身份验证,因此无法授予第三方范围受限的令牌——托管服务唯一能索取的就是原始会话 Cookie,而它既可读取消息,也可操作账户。想使用这四个工具的用户,应在本地以自己的 Cookie 运行 full 模式。请参阅托管。
willhaben
功能
四个公开工具,无需账户:
| 工具 | 用途 |
|---|---|
willhaben_search | 使用结构化筛选条件搜索市场;自动翻页 |
willhaben_get_ad | 获取一则广告的完整详情:描述、图片、卖家信任指标 |
willhaben_find_deals | 查询商品的市场价格,并按低于中位数的程度排列商品 |
willhaben_discover_filters | 解析 willhaben 不透明的数字类别 / 品牌 ID |
四个账户工具,需要会话 Cookie:
| 工具 | 用途 |
|---|---|
willhaben_my_profile | 你的个人资料和数字用户 ID |
willhaben_my_watchlist | 已保存到关注列表的广告 |
willhaben_my_conversations | 最近的买家 / 卖家聊天 |
willhaben_my_alert_count | 活跃的已保存搜索提醒数量 |
安装
claude mcp add willmehr -- npx -y willmehr
也可以从源码安装:
npm install
npm run build
npm run pack:kz # stage build/kleinanzeigen-mcp for publishing
willmehr 从仓库根目录发布。kleinanzeigen-mcp 从
build/kleinanzeigen-mcp 发布,由 npm run pack:kz 暂存——它会复制 Kleinanzeigen 入口点所需的构建输出(其自身目录以及共享的模式模块),并围绕这些文件生成清单,因此两个包会以相同版本发布同一提交。server.json 和 server.kleinanzeigen.json 是对应的 MCP 注册表条目。
在 Claude Code 中注册:
claude mcp add willmehr -- node "$PWD/dist/index.js"
或在客户端的 MCP 配置中注册:
{
"mcpServers": {
"willmehr": {
"command": "node",
"args": ["/path/to/willmehr/dist/index.js"],
"env": { "WILLHABEN_COOKIE": "BBX_JSESSIONID=...; x-bbx-csrf-token=..." }
}
}
}
身份验证
willhaben 通过 Keycloak 使用 PKCE 进行身份验证,因此无法自动执行用户名/密码授权。你可以从已登录的浏览器中提取会话:
- 登录 willhaben.at,然后访问个人资料或消息页面,让页面加载一个经过身份验证的端点。
- 开发者工具 → Network → 导出下拉菜单 → “Export HAR (with sensitive data)”。普通下载按钮会移除 Cookie,生成的文件无法使用。
npm run session -- ~/Downloads/www.willhaben.at.harnpm run check:auth
npm run session 会写入 .env(权限模式为 0600,且已加入 gitignore),绝不会打印 Cookie 值。npm run check:auth 会测试全部四个账户工具;遇到第一个 401 时就会停止,而不是反复使用你的账户进行身份验证并失败。
Cookie 必须包含 BBX_JSESSIONID(会话)和 x-bbx-csrf-token。CSRF 令牌会自动从 Cookie 中读取——willhaben 的 Web 应用会将该 Cookie 镜像到每个 /webapi/ 调用中同名的请求头,服务器也会照此处理。WILLHABEN_CSRF_TOKEN 仅用于覆盖该令牌。
服务器会自行从包根目录加载 .env,因此 MCP 客户端无需通过配置传入 Cookie。
会话的有效期比 Cookie 声明的短
BBX_JSESSIONID 的 Max-Age 为五天,但服务器会在此之前使其失效——捕获会话并在 38 分钟后重放时,该会话已被拒绝。这并非 IP 或请求头绑定:即使在同一台机器、同一 IP 下,使用浏览器的完整请求头集合原样重放同一 Cookie 容器,仍会返回 401,而匿名请求依然可以正常使用。
请将会话视为短期有效。需要使用账户工具前,重新运行 npm run session,不要指望捕获的会话能持续有效数天。
环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
WILLHABEN_COOKIE | — | 账户工具的会话 Cookie |
WILLHABEN_CSRF_TOKEN | 来自 Cookie | 覆盖 CSRF 令牌 |
WILLHABEN_MIN_INTERVAL_MS | 400(公开模式为 900) | 请求之间的最小间隔 |
WILLMEHR_MODE | full | full 或 public——参见 模式 |
使用建议
筛选前先解析 ID。 类别和品牌都是不透明的整数。先调用
willhaben_discover_filters——它会返回每个筛选值及其 ID,以及当前查询的匹配数:
目前维也纳有哪些笔记本电脑品牌的商品价格低于 €300?
缩小范围后再评估优惠。 willhaben_find_deals 会将每条商品与结果集的中位数进行比较,因此查询必须描述同一类商品。搜索
thinkpad 会混合 €900 的笔记本电脑和 €5 的扩展坞,它报告的所有“折扣”都是噪声。在类别 5831(笔记本电脑)中搜索 thinkpad x1 carbon,并设置 priceFrom: 150,得出的中位数才有参考价值。
留意 suspiciouslyCheap。 任何低于中位数 20% 的商品都会被标记。有时这确实是捡漏;但更多时候是占位价格、仅售零件的商品或诱饵。该标记只是提醒你阅读广告内容,并非结论。
抢先购买。 将 sort: "newest" 与 maxAgeHours: 2 组合使用,只查看最近几个小时内发布的商品。
示例
帮我找出维也纳私人卖家发布的、定价偏低的 ThinkPad X1 Carbon,发布时间在过去一天内,并告诉我哪些值得联系卖家。
Agent 会按顺序调用 willhaben_discover_filters → willhaben_find_deals →
willhaben_get_ad,检查首选候选商品的卖家账号年龄和广告文本。
工作原理
HAR 分析的笔记,因为这些内容都没有公开文档说明:
- 必须提供
x-wh-client。 每个/webapi/路由在未提供该参数时,都会返回正文为空的400。这看起来完全像是查询参数格式错误,也是最容易卡住的地方。 - 搜索完全不需要 Cookie。 只有账号路由需要。
- 筛选条件可自我描述。 搜索响应包含
navigatorGroups[],其中每个筛选维度都会声明对应的查询参数,以及所有合法值和命中数。willhaben_discover_filters只是对这些信息进行简单投影,因此即使 willhaben 添加了类别,它也能保持准确。 - 排序 ID 是没有文档说明的整数。 经实测确认:
1表示最新,3表示价格升序,4表示价格降序,0表示相关性。 - 没有公开的单条广告 JSON 接口。
publicapi.willhaben.at和api.willhaben.at都会拒绝匿名调用者。广告页面使用 Next.js,因此会从__NEXT_DATA__中读取完整数据;已知 buildId 时使用较轻量的/_next/data/<buildId>/...json路由;当 willhaben 重新部署并轮换 ID 时,则回退到 HTML。 - 每个字段都以
{name, values[]}数据包的形式返回。src/normalize.ts会将其扁平化。
速率限制和条款
请求会通过单一队列串行发送,间隔至少为 400 ms;遇到 429/5xx 时,还会采用带抖动的指数退避。请保留这些设置。
请注意,willhaben 的 robots.txt 明确规定 “明确禁止使用爬虫、搜索机器人或其他自动方式访问 willhaben.at”,并禁止访问 /webapi/,还特别禁止使用此服务所用的 keyword、PRICE_FROM、PRICE_TO 和 periode 参数。其使用条款同样禁止未经同意自动复制内容。此工具执行的正是这类访问。
这是真实限制,不是走形式:被封禁的会是你的账号。此工具面向个人低频、偶尔使用,且仅限使用你自己的账号。不要用它进行批量采集,不要重新分发数据,也不要按紧密的时间间隔定期运行。
相关项目
aliildan/willhaben-mcp 覆盖更多垂直领域(汽车、房地产、工作),但仅支持匿名访问,也不具备价格分析功能。如果你需要的是在该市场上进行广泛的只读浏览,而不是寻找优惠,就改用那个项目。
Kleinanzeigen
这是将 jnslmk/kleinanzeigen-mcp(Python)移植到 TypeScript 的版本,支持 kleinanzeigen.de,这是德国最大的分类信息网站。无需账号。
工具
| 工具 | 用途 |
|---|---|
kleinanzeigen_search | 按关键词、地点、半径和价格搜索;自动翻页 |
kleinanzeigen_search_by_url | 使用粘贴的 Kleinanzeigen URL 进行搜索,保留结构化搜索无法表达的筛选条件 |
kleinanzeigen_get_listing | 获取一条广告的完整详情:描述、图片、卖家和属性 |
kleinanzeigen_get_listings_batch | 一次获取多个 ID 的完整详情——通常用于跟进搜索结果 |
kleinanzeigen_parse_search_url | 说明 URL 编码了哪些筛选条件。不会发起网络请求 |
推荐的流程是 kleinanzeigen_search → 选出感兴趣的 ID →
kleinanzeigen_get_listings_batch。搜索结果只包含网站自身的摘要文本,因此大范围搜索不会撑爆模型的上下文窗口。
安装
它有独立的包,因此可以单独安装,不必安装 willhaben 部分:
claude mcp add kleinanzeigen -- npx -y kleinanzeigen-mcp
或从源代码安装:
npm install && npm run build
claude mcp add kleinanzeigen -- node "$PWD/dist/kleinanzeigen/index.js"
或在客户端的 MCP 配置中使用:
{
"mcpServers": {
"kleinanzeigen": {
"command": "node",
"args": ["/path/to/willmehr/dist/kleinanzeigen/index.js"]
}
}
}
环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
KZ_MIN_INTERVAL_MS | 800(公共模式为 1500) | 请求之间的最小间隔 |
KZ_MAX_PAGE_COUNT | 5(公共模式为 3) | 搜索的页数上限(每页约 25 条信息) |
KZ_MAX_BATCH_SIZE | 20(公共模式为 10) | 批量调用的 ID 数量上限 |
WILLMEHR_MODE | full | full 或 public——参见模式 |
TZ | 主机默认值 | 信息日期不含时区;如需精确时间,请设置为 Europe/Berlin |
移植版的变更
不需要浏览器。 Python 原版通过
DanielWTE/ebay-kleinanzeigen-api
驱动无头 Chromium;该依赖固定在某个提交版本中,并打包进约 1.5 GB 的镜像。事实证明,这并无必要:
搜索结果、广告详情页和浏览量计数器都由服务器完整渲染,并且只需带有浏览器特征的 User-Agent 即可响应普通的 GET 请求。本移植版仅使用 fetch,
因此无需容器、约 1.5 GB 的内存底限和浏览器上下文池,也无需再处理原版不得不修补的死锁和上下文泄漏问题。
使用单个请求队列,而非信号量。 即使错开请求,Kleinanzeigen 也会阻止来自同一 IP 的并行请求,因此每个请求——搜索页、广告页、浏览量计数器——都会通过单个队列串行处理,最小间隔为 800 ms,并在遇到 429/5xx 时采用带随机抖动的指数退避。这取代了上游的
KZ_MAX_CONCURRENT/KZ_MAX_CONTEXTS 参数;它们原本用于设置浏览器池大小。
每种事物只用一个名称。 上游在搜索时返回 adid,但在详情页返回 id;同一个参数在一个工具中叫 page_count,在另一个工具中却叫 max_pages;
Python 服务端为这两者都保留了兼容层。这里不存在这些命名不一致,因此已移除兼容层:所有地方统一使用 id 和 pages。
使用带类型的值,而非字符串。 价格以
{amount, label, negotiable, previousAmount} 的形式返回,而不是预先拼接好的字符串——
amount: 0 表示 免费赠送,null 表示未显示价格。已下架的广告会抛出 ListingGoneError,而不是返回一个所有字段都是 null 的空对象;在批量调用中,它会记入 errors,其余 ID 仍会正常返回。
原版遗漏的字段,因为它们会改变对一条信息的理解:
wanted——求购信息表示有人想要买入。上游将这类信息与出售商品混在一起返回,模型可能会误以为这是价格低得可疑的商品。distanceKm——设置半径时,结果页会在地点后附加“(4 km)”;这是搜索条件,而非广告本身的信息,因此将其单独列出。shippingPossible/tags、previousAmount和seller.shopUrl。- 地区字段按实际含义命名。上游将“13088 Pankow - Weissensee”解析为
{zip, city: "Weissensee", state: "Pankow"};这里则解析为{postcode, city: "Pankow", district: "Weissensee"}。
浏览量默认不读取。 #viewad-cntr-num 是页面上唯一由 JS 从 /s-vac-inc-get.json 填充的内容。该端点响应普通的 GET 请求,因此仍可获取浏览量——但“inc”确实是字面意思:读取它会像打开页面一样增加卖家的计数器。需要浏览量时,请传入 includeViewCount: true;每条广告会因此多发起一个请求。
未移植的部分: HTTP 传输和 /healthz 路由。这里的两个服务端都使用 stdio,与它们注册到本地客户端的方式一致。
注意事项
Kleinanzeigen 没有公共 API,因此这里会读取网站的 HTML。网站更改标记时,解析就可能失效——这正是 npm run smoke:kz 中包含解析检查的原因。大量或并行使用会触发机器人检测;数据中心 IP 也会遭到 403 拒绝,而家庭网络通常不会。
抓取行为也与 Kleinanzeigen 的服务条款相悖。请求限速有其必要,请勿移除,并将用途控制在个人使用范围内。
托管
dist/serve-http.js 通过 MCP 的 Streamable HTTP 传输为两个服务提供服务,适合希望让客户端连接 URL 而不是安装任何内容的用户。
npm run build && npm run serve # http://0.0.0.0:8080
docker build -t willmehr-http . && docker run -p 8080:8080 willmehr-http
| 端点 | 内容 |
|---|---|
POST /willhaben/mcp | 四个公开的 willhaben 工具 |
POST /kleinanzeigen/mcp | 全部五个 Kleinanzeigen 工具 |
GET /healthz | 存活探针 |
claude mcp add --transport http willhaben https://<your-host>/willhaben/mcp
claude mcp add --transport http kleinanzeigen https://<your-host>/kleinanzeigen/mcp
模式固定为 public,无法覆盖:如果设置 WILLMEHR_MODE=full,主机将拒绝启动,而不是把运营者自己的账户暴露给所有调用者。
传输是无状态的——每个请求都会创建一个全新的服务,请求之间不保留任何内容。两个上游客户端特意设为进程级共享:它们的队列能让整个部署对每个站点保持礼貌的单一流量。
| 变量 | 默认值 | 用途 |
|---|---|---|
PORT / HOST | 8080 / 0.0.0.0 | 监听地址 |
MCP_RATE_BURST | 10 | 单个 IP 可突发发送的请求数 |
MCP_RATE_PER_MINUTE | 20 | 每个 IP 的补充速率 |
MCP_MAX_INFLIGHT | 8 | 返回 503 前可并发处理的工具调用数 |
MCP_MAX_BODY_BYTES | 1000000 | 请求正文大小上限 |
MCP_REQUEST_TIMEOUT_MS | 120000 | 单个请求的超时上限 |
TRUST_PROXY | 关闭 | 从 X-Forwarded-For 获取客户端 IP(在反向代理后设置) |
MCP_ALLOWED_ORIGINS | 未设置(允许) | 以逗号分隔的 Origin 允许列表 |
MCP_ALLOWED_HOSTS | 未设置(允许) | 以逗号分隔的 Host 允许列表 |
部署到全新 VPS
每月 €4 的主机就足够了——这里受限于等待两个网站的 I/O,而非 CPU。将它部署在一台不运行其他任务的机器上:如果分类信息网站屏蔽了该 IP,你只会损失这台机器。
-
创建 VPS。 使用 Debian 12 或 Ubuntu 24.04,选择最小规格(Hetzner CX22、DO basic 等均可)。创建时添加你的 SSH 密钥。
-
将 DNS 指向它。 为所需子域名添加一条
A记录,指向该主机的 IPv4 地址。先完成此步骤——Caddy 需要域名解析成功才能签发证书。使用dig +short mcp.example.com检查。 -
准备主机(安装 Docker、开放 80/443 端口、启用无人值守升级):
ssh root@<ip> 'bash -s' < deploy/bootstrap-server.sh -
部署:
HOST=root@<ip> MCP_HOSTNAME=mcp.example.com ./deploy/deploy.sh它会同步目录树、写入服务器的
.env、构建并启动,然后等待https://mcp.example.com/healthz。发布变更时重新运行即可;Caddy 的证书存放在具名卷中,不会重新签发。 -
注册:
claude mcp add --transport http willhaben https://mcp.example.com/willhaben/mcp claude mcp add --transport http kleinanzeigen https://mcp.example.com/kleinanzeigen/mcp
deploy.sh 会特意在同步时排除 .env 和 *.har——本地文件中包含 willhaben 会话 Cookie 和对应的捕获文件。服务器会写入自己的 .env,其中只包含主机名。
运维方式:
ssh <host> 'cd /opt/willmehr && docker compose logs -f --tail 100'
ssh <host> 'cd /opt/willmehr && docker compose restart mcp'
curl -s https://mcp.example.com/healthz # mode, in-flight count, endpoints
托管的成本
成本不是秘密——秘密永远不会传到服务器。你付出的成本是一个 IP。本地运行时,每个用户都通过自己的地址抓取;托管运行时,所有人的流量都会从你的地址发出,而两个网站都会按 IP 检测机器人。来自同一地址的 Kleinanzeigen 并发请求即使间隔发送也会触发检测,因此出站队列是全局的,公开默认设置也更慢。预计你需要缓存响应;用户达到几个以上后,还需要出口代理。属于大型无服务器平台的数据中心 IP 段受到的挑战最为严格。
为第三方托管抓取工具,与为自己抓取相比,也意味着你对待两个网站服务条款的方式有所不同。请保持部署规模小、不以营利为目的,并确保可以轻松关闭。
开发
npm run typecheck # tsc --noEmit
npm run smoke # end-to-end checks against the live willhaben API
npm run smoke:kz # end-to-end checks against live kleinanzeigen.de
npm run session -- <path-to.har> # refresh the account session from a HAR
npm run check:auth # verify the session + account tools
npm run build
scripts/smoke.ts 是一个实时集成测试:它会断言筛选条件确实缩小了结果范围、price_asc 确实按升序排序、广告详情可以从搜索结果往返获取,以及交易排名有序且低于中位数。
scripts/smoke-kleinanzeigen.ts 对 Kleinanzeigen 服务执行相同测试,并在不发起网络调用的情况下测试纯解析逻辑(德语日期、价格、URL 筛选条件、页面注入)——因此,这部分测试失败意味着逻辑错误,而后续测试失败则意味着网站的标记发生了变化。
许可证
MIT——详见 LICENSE。Kleinanzeigen 服务是
jnslmk/kleinanzeigen-mcp 的移植版本(MIT,
© Jonas Lemke),相关声明保留在 NOTICE 中。
这两个网站均未为此背书。它会以刻意放慢的速度读取公开页面和 willhaben Web 应用自身的 JSON API;如果你要为他人托管,请先阅读 托管会带来哪些成本。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。