mcpField
W
willmehr

willmehr

本地服务
github.com商业与营销28 周前更新

关于 willmehr

willmehr 是面向奥地利 willhaben.at 分类信息网站的 MCP 服务,可搜索刊登并按低于结果中位价的程度排序。公开搜索无需账户;需要会话 Cookie 的账户工具仅在本地 full 模式提供。

主要功能

  1. 01使用结构化筛选搜索并自动翻页
  2. 02获取刊登描述、图片和卖家信号
  3. 03按低于结果中位价排序刊登
  4. 04发现类别和品牌筛选项
  5. 05查看个人资料、收藏和对话

适用场景

  1. 01按地点和类别寻找二手商品
  2. 02筛选低于市场中位价的刊登
  3. 03检查候选刊登的详情和卖家信息
  4. 04查看账户中的收藏或近期对话

MCP 相关

向 MCP 客户端提供 willhaben.at 搜索、刊登详情、价格分析及账户工具。支持本地 stdio 和自托管 Streamable HTTP;账户工具需在本地 full 模式使用会话 Cookie。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"taneron-willmehr": {
"command": "npx",
"args": [
"-y",
"willmehr"
],
"env": {
"WILLMEHR_MODE": "<WILLMEHR_MODE>",
"WILLHABEN_COOKIE": "<WILLHABEN_COOKIE>",
"WILLHABEN_MIN_INTERVAL_MS": "<WILLHABEN_MIN_INTERVAL_MS>"
}
}
}
}
启动命令npx -y willmehr
需要配置的环境变量
WILLMEHR_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+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmwillmehr0.1.0

工具

5

收录的工具列表 · 更新于 2 周前

参数类型必填说明
pagesinteger否要获取的结果页数,每页约 25 条商品。默认值为 1,最大值为 5。
querystring否搜索词,例如 'ThinkPad T14' 或 'Fahrrad 28 Zoll'
locationstring否城市名称或德国邮政编码,例如 'Berlin' 或 '10115'
maxPriceinteger否最高价格,单位 EUR
minPriceinteger否以 EUR 计的最低价格
radiusKminteger否以公里为单位的 `location` 周边搜索半径
publishedAfterstring否仅包含在此本地 ISO 8601 日期时间或之后发布的商品,例如 '2026-08-01T00:00:00'。遇到更早发布的商品时,也会停止翻页。

详细介绍

willmehr

用于在德语区分类网站上寻找优惠商品的 MCP 服务。使用 TypeScript,可通过本地 stdio 或可自托管的 Streamable HTTP 运行;兼容 Claude Code、Claude Desktop 和其他 MCP 客户端。

此仓库提供两个服务,需分别注册:

服务网站入口点
willmehrwillhaben.at(奥地利)dist/index.js
kleinanzeigen-mcpkleinanzeigen.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 ms900 ms
Kleinanzeigen 间隔 / 页面数 / 批量数量800 ms / 5 / 201500 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 进行身份验证,因此无法自动执行用户名/密码授权。你可以从已登录的浏览器中提取会话:

  1. 登录 willhaben.at,然后访问个人资料或消息页面,让页面加载一个经过身份验证的端点。
  2. 开发者工具 → Network → 导出下拉菜单 → “Export HAR (with sensitive data)”。普通下载按钮会移除 Cookie,生成的文件无法使用。
  3. npm run session -- ~/Downloads/www.willhaben.at.har
  4. npm 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_MS400(公开模式为 900)请求之间的最小间隔
WILLMEHR_MODEfullfull 或 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_MS800(公共模式为 1500)请求之间的最小间隔
KZ_MAX_PAGE_COUNT5(公共模式为 3)搜索的页数上限(每页约 25 条信息)
KZ_MAX_BATCH_SIZE20(公共模式为 10)批量调用的 ID 数量上限
WILLMEHR_MODEfullfull 或 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 / HOST8080 / 0.0.0.0监听地址
MCP_RATE_BURST10单个 IP 可突发发送的请求数
MCP_RATE_PER_MINUTE20每个 IP 的补充速率
MCP_MAX_INFLIGHT8返回 503 前可并发处理的工具调用数
MCP_MAX_BODY_BYTES1000000请求正文大小上限
MCP_REQUEST_TIMEOUT_MS120000单个请求的超时上限
TRUST_PROXY关闭从 X-Forwarded-For 获取客户端 IP(在反向代理后设置)
MCP_ALLOWED_ORIGINS未设置(允许)以逗号分隔的 Origin 允许列表
MCP_ALLOWED_HOSTS未设置(允许)以逗号分隔的 Host 允许列表

部署到全新 VPS

每月 €4 的主机就足够了——这里受限于等待两个网站的 I/O,而非 CPU。将它部署在一台不运行其他任务的机器上:如果分类信息网站屏蔽了该 IP,你只会损失这台机器。

  1. 创建 VPS。 使用 Debian 12 或 Ubuntu 24.04,选择最小规格(Hetzner CX22、DO basic 等均可)。创建时添加你的 SSH 密钥。

  2. 将 DNS 指向它。 为所需子域名添加一条 A 记录,指向该主机的 IPv4 地址。先完成此步骤——Caddy 需要域名解析成功才能签发证书。使用 dig +short mcp.example.com 检查。

  3. 准备主机(安装 Docker、开放 80/443 端口、启用无人值守升级):

    ssh root@<ip> 'bash -s' < deploy/bootstrap-server.sh
    
  4. 部署:

    HOST=root@<ip> MCP_HOSTNAME=mcp.example.com ./deploy/deploy.sh
    

    它会同步目录树、写入服务器的 .env、构建并启动,然后等待 https://mcp.example.com/healthz。发布变更时重新运行即可;Caddy 的证书存放在具名卷中,不会重新签发。

  5. 注册:

    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;如果你要为他人托管,请先阅读 托管会带来哪些成本。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。