关于 pitwall-mcp
pitwall-mcp 是面向启用 CarData 的 BMW 车主的本地只读 MCP 服务,读取车辆维护、轮胎、电池和燃油信息。它用 SQLite 保存历史读数,提供趋势与预测,并通过缓存和本地计数控制 API 用量。
主要功能
- 01读取车辆维护与 CBS 提醒
- 02查询轮胎、电池及燃油数据
- 03SQLite 保存读数历史
- 04比较故障码的新增与消失
- 05按历史数据预测维护时间
- 06缓存请求并追踪 API 配额
适用场景
- 01检查哪些维护项目即将到期
- 02追踪轮胎压力的历史变化
- 03查看故障记忆在两次读取间的变化
- 04分析加油记录与实际油耗
MCP 相关
它作为本地 MCP 服务向助手提供只读车辆数据工具。所有 API 请求都经过缓存和本地配额计数,不提供对车辆的写入或控制。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx pitwall-mcpPITWALL_CLIENT_ID密钥Client id de tu aplicacion BMW CarData, creada en https://bmw-cardata.bmwgroup.com. Se obtiene en el portal; no lo comparte nadie contigo.PITWALL_VIN密钥VIN del vehiculo, 17 caracteres. Debe estar mapeado como PRIMARY en la cuenta.PITWALL_CONTAINER_IDId del contenedor telematico creado por scripts/bootstrap_containers.py. El servidor solo lo consume: nunca crea ni borra contenedores.PITWALL_TOKEN_FILE密钥Ruta del fichero de tokens OAuth. Por defecto ~/.config/pitwall-mcp/tokens.json, con permisos 600 y fuera del repositorio.PITWALL_DB_PATHRuta de la base SQLite con la cache, el registro de cuota y el historico. Por defecto ~/.local/share/pitwall-mcp/pitwall.db.PITWALL_DAILY_QUOTATope duro de peticiones REST al dia. BMW permite 50 cada 24 h por cuenta; el valor por defecto es 20 para dejar margen. Nunca puede superar 50.PITWALL_LOG_LEVELDEBUG, INFO, WARNING o ERROR. Los logs van a stderr: stdout es el canal MCP.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
pitwall-mcp
基于 BMW CarData API、面向一辆 BMW X1 sDrive18i(U11,汽油版,西班牙)的本地只读 MCP 服务。
本项目与 BMW AG 无关联。“BMW”和“CarData”是各自所有者的商标。本项目未获 BMW 背书、赞助或审核。
它让助手能够访问车辆维护数据——里程、CBS 提醒、轮胎气压、12 V 电池——但无法对车辆进行任何操作,也不会耗尽 API 的每日配额。
使用体验如何
你问助手*“车子该做什么保养了?”*,它就会给出这样的回答。这是 2026 年 9 月 16 日读取的真实回答,内容有所删节:
RESUMEN DE MANTENIMIENTO
Kilometraje: 48.731 km
Proximo servicio en: 13460 km
OJO: hay partidas CBS que BMW ya no marca como OK; no te fies solo de la
cifra global: Frenos delanteros [PENDING]: 1.600 km.
Partidas CBS: 5
- Frenos delanteros [PENDING]: 1.600 km
- Aceite de motor [OK]: 14.000 km, hasta 2027-07
- Inspeccion tecnica (ITV) [OK]: hasta 2027-01
conditionBasedServicesCount dice 9: es el maximo de avisos que este vehiculo
puede transmitir, no los que tiene. El desglose trae 5, los transmitidos.
Prevision orientativa (calculo propio sobre los km de BMW, no un dato de BMW):
Ritmo usado: 570 km/semana (media semanal que da BMW); tu historico local,
8 dias: 420. Se usa el mayor para no quedarse corto.
- Frenos delanteros: 1.600 km, unas 2,8 semanas, hacia el 05-10-2026.
Bateria de 12V:
Voltaje: 13.92 V (2026-09-14 13:49 UTC (hace 40 horas))
Dato mas antiguo utilizado: 2026-09-14 13:49 UTC (hace 40 horas).
Procedencia: cache local. Lectura realizada: 2026-09-16 06:09 UTC.
请留意这段回答除了提供数字之外还做了什么:提醒你总体数字掩盖了紧急事项,将“1,600 km”换算成日期,承认 BMW 的两个数字对不上,而不是自作主张选一个,并说明所用的最旧数据是什么时候的。
适合你吗?
- 适合:如果你有一辆已启用 CarData 的 BMW,并想知道哪些情况随时间发生变化:每项保养实际何时到期、某个轮胎是否在漏气、上次检查后出现了哪些故障代码。
- 不适合:如果你想翻译故障代码、清除故障代码或调整车辆设置。这些属于 OBD 诊断的范畴,不是此 API 的用途。
- 请注意:它适合偶尔使用。车辆屏幕已经显示原始数据;这里额外提供的是时间序列、预测和变化情况。
功能与限制
可以:
- 读取远程信息处理数据、车辆基本数据和轮胎诊断数据,且始终通过缓存读取。
- 在本地使用 SQLite 保存历史记录,从而可以基于时间序列而非单次快照进行推理。
- 说明每项数据的来源:BMW 时间戳、读取时间戳,以及数据来自 API、缓存还是历史记录。
- 当数据不存在时,解释原因。
不可以,也不会:
- 任何工具都不会写入、发送、激活任何内容,也不会暗示它能对车辆进行操作。CarData 是一个只读 API。
- 任何工具都不会创建或删除容器。这项操作由手动运行的脚本负责。
- 没有
get_software_version:BMW 的目录中不存在该描述符。唯一可用的是产品更新步骤puStep。
配额优先
CarData REST API 的限制为每个账户每 24 小时 50 次请求。超过限制会返回 403 CU-429,直到次日才会恢复;计数由 BMW 维护,包含同一账户使用的任何其他应用。
因此:
- 所有请求都必须经过缓存和本地计数器。
- 本地默认上限为每天 20 次请求,以留出余量。
- 维护容器的 TTL 为 12 小时,正常使用时每天请求 2 次。
search_descriptors和get_api_quota永远不会消耗配额。
连续发起多次调用前,请先查询 get_api_quota。
| 数据 | TTL |
|---|---|
/telematicData(维护容器) | 12 h |
/smartMaintenanceTyreDiagnosis | 7 天 |
/basicData | 30 天 |
/mappings | 30 天 |
为什么选择它,而不是官方应用
官方应用显示的是今天的状态。这个工具会保存每次读取的数据,因此可以进行比较。由此得出的四个具体发现,都来自这辆车:
- 看的是何时,而不只是还有多少。 官方应用显示“前刹车,1.600 km”。摘要则显示,按实际使用速度,大约 2.8 周后,也就是 10 月 5 日前后到期;如果某项服务按里程而不是按日期更早到期,也会发出提醒。
- 总体数值不足以说明问题,这里就能看出来。 刹车在 1.900 km 时处于
PENDING状态,而serviceDistance.next显示 13.560。只看这个数值就会掩盖唯一紧急的事项,因此凡是 BMW 未标记为OK的项目,都会单独显示提醒。 - 识别轮胎问题,不把高温误当成漏气。 BMW 的目标胎压并非固定值:冷胎时曾显示 250 kPa,热胎时则显示 290。用今天的胎压和其他时刻的目标值比较,会产生错误的压差,因此每个车轮都会与同一次读取中同轴另一侧的车轮比较,二者的温度和负载相同。
- 故障记忆发生了什么变化。 9 月 18 日,两次读取之间新增了 11 个代码,又移除了另外 11 个:新增了一个完整控制单元,并移除了三个。如果不保存上一次读取的数据,这些变化就无从得知。
以下是它不会提供的功能,以免产生误解:
- 不会翻译故障代码。 BMW 目录中没有这些代码的含义;与其编造,不如不提供。要查阅这些信息,可以使用 BimmerLink 这类 OBD 诊断工具;它还能读取带说明的故障代码并将其清除。
- 不会写入任何内容。 不进行编码,不重置,也不会向汽车发送任何命令。这些操作应交由 BimmerCode 和 OBD 适配器完成,并且需要在汽车旁进行。
- 不会编造缺失的数据。 数据缺失、数据存在但为空,以及
-NA-是三种不同情况;工具会区分它们,而不是显示为零。在 42 个容器描述符中,最近一次读取有 28 个包含值,14 个为空。
工具
| 工具 | 消耗配额 | 状态 |
|---|---|---|
search_descriptors(query, limit, include_electric) | 否 | 正常 |
get_api_quota() | 否 | 正常 |
list_vehicles() | 是(每月 1 次,带缓存) | 正常 |
get_vehicle_basic_data() | 是(每月 1 次,带缓存) | 正常 |
report_product_update_step() | 是(与 basicData 共用缓存) | 正常,但这辆车不会返回 puStep |
get_telematic_data() | 是(每天 2 次,带缓存) | 正常 |
get_vehicle_status() | 是(共用缓存) | 正常 |
get_tyre_diagnosis() | 是(每周 1 次,带缓存) | 正常,但这辆车返回空结构 |
get_maintenance_summary() | 是(组合调用) | 正常 |
diagnose_software_update() | 是(组合调用,共用缓存) | 正常,但结论有局限 |
get_fuel_status() | 是(共用缓存) | 正常 |
get_fault_memory() | 是(共用缓存) | 正常;不会翻译代码 |
两种标注了保留说明的工具运行正常,并会明确说明数据缺失:不会用零或看似合理的值来填补空缺。这辆车的 /basicData 中没有 puStep,而轮胎诊断返回的标签和填充值为零并非实际测量结果。
get_vehicle_status() 和 get_maintenance_summary() 也会显示 Check Control 警告,并在任何 CBS 项目的状态不再是 OK 时添加一行 OJO。整体的下次保养里程并不够准确:前刹车处于 PENDING 状态,剩余 1,900 km 时,显示的却是 13,560。
get_maintenance_summary() 还会在不消耗请求配额的情况下,添加按周和日期计算的预测,以及各车轴的胎压趋势。两者都来自本地历史数据,计算依据见为什么选择这种方式,而不是官方应用。预测采用 BMW 的周均值或历史数据中的周均值,以较大者为准,避免低估;如果某个项目按里程计算会早于按日期到期,也会发出提醒。
另外两种工具也能提供官方应用不显示的数据:
get_fuel_status():油箱油量和续航里程、历史数据中检测到的加油记录,以及自上次加油以来的实际油耗。只有行驶至少 300 km 后才会显示油耗,并始终附带误差范围,因为油位传感器的读数最多可能偏差 6 L。 也会显示认证油耗(OBFCM),但会明确说明其实际含义:这是一个终身累计数值,这辆车自 2024 年 10 月以来一直没有变化,并非当前油耗。get_fault_memory():车辆为维修厂保存的故障记忆,按控制单元分组,并显示哪些代码在不同读取结果之间出现或消失。不会翻译任何代码:BMW 目录中没有其含义,给出解释就属于凭空捏造。
diagnose_software_update() 会根据历史数据中的连续记录进行判断,而不是依据单次快照。BMW 记录了不提供更新的三项条件,CarData 只能观察其中一项;对于另外两项,工具会标记为 NO OBSERVABLE POR CARDATA,而不会假装已经排除了它们。电压达到或超过 13,5 V 时的数据不会纳入判断,因为这表示发电机正在充电,无法说明电池静置时的状态。如果剩余的静置状态观测少于两次,就会返回SIN VEREDICTO并说明还缺少什么。此外,isIgnitionOn 返回为空,因此工具会提醒:任何待处理状态都可能只是由发动机运行与否导致的差异,并不一定意味着电池正在放电。
本地报告
服务器保存的历史记录无处可见:工具返回的是当前时刻的快照,而非时间序列。要查看时间序列,可以手动从 SQLite 生成一个 HTML 页面:
.venv/Scripts/python.exe scripts/report.py # captures/report-*.html
.venv/Scripts/python.exe scripts/report.py --full-vin # con el VIN entero
不会消耗任何请求。 它不使用令牌、不调用任何端点,也不触碰配额计数器:只读取本地数据库。页面是单个文件,不含 JavaScript,也没有外部依赖,打印效果良好;生成的文件会带有经过掩码处理的 VIN,并保存到 captures/,该目录已列入 .gitignore。
它首先展示的不是一个数字,而是容器的 42 个描述符中有多少个带有真实值,以及其余描述符各自处于三种空状态中的哪一种。
安装
需要 Python 3.12+。
git clone https://github.com/sergioprats/pitwall-mcp
cd pitwall-mcp
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[dev]" # Linux/macOS: .venv/bin/python
.venv/Scripts/python.exe scripts/refresh_catalogue.py
最后一步不可省略:遥测目录是 BMW 的文档,本仓库不会重新分发,因此需要下载一次。它来自 zweckj/bmw-cardata(MIT),而非 CarData API,因此不会消耗你的配额请求。详情见 spec/README.md。
如果你安装的是软件包而非克隆仓库,就没有 scripts/,因此可执行文件会提供必需功能:--fetch-catalogue 用于获取目录,--login 用于身份验证。仍然需要仓库才能执行的操作是创建遥测容器,这是有意为之:该步骤会写入你的 BMW 账户,且按照项目规定,不会放在服务器中。
pitwall-mcp --fetch-catalogue # junto a la base de datos
pitwall-mcp --fetch-catalogue --out /otra/ruta.json
如果下载的内容不是目录——例如,强制门户会返回状态码为 200 的 HTML 页面——程序会拒绝写入,因此即使下载失败,也不会丢失原本可用的目录。
以下命令会明确使用虚拟环境中的解释器(Windows 上为 .venv/Scripts/python.exe,Linux 和 macOS 上为 .venv/bin/python)。如果你激活了虚拟环境(.venv/Scripts/activate,或 source .venv/bin/activate),直接使用 python 即可。若不激活环境而使用系统的 python,则会因 ModuleNotFoundError: No module named 'bmw_cardata' 而失败。
1. 配置
cp .env.example .env
将你的 CarData 客户端 ID 填入 PITWALL_CLIENT_ID。你可以在所在国家/地区的 My BMW 网站上创建该客户端:进入车辆页面 → BMW CarData →“BMW CarData 技术访问”(西班牙:
bmw.es/es-es/mybmw/vehicle-overview)。
这不是一个单独的门户。你需要 cardata:api:read 权限范围;如果以后使用流式传输,还需要 cardata:streaming:read。
2. 登录
pitwall-mcp --login # instalado con pip
.venv/Scripts/python.exe scripts/login.py # o desde el repositorio: hace lo mismo
设备授权流程:程序会打印一个 URL 和一个代码,你需要在浏览器中完成授权。令牌会以 600 权限保存在 ~/.config/pitwall-mcp/tokens.json 中,位于仓库之外。该流程与 BMW 的 OAuth 通信,而非 CarData API,因此不会消耗配额。
仓库中的脚本只是相同代码的封装:授权流程只定义一次,这样通过 pip 安装的用户也能完成身份验证。
刷新令牌的有效期为 14 天。剩余时间不足 3 天时,服务器会发出醒目提示;如果令牌过期,必须手动重复此步骤。
3. 容器
.venv/Scripts/python.exe scripts/bootstrap_containers.py --dry-run # muestra qué enviaría
.venv/Scripts/python.exe scripts/bootstrap_containers.py --create # gasta 1 petición
将生成的 containerId 复制到 .env 中的 PITWALL_CONTAINER_ID。MCP 服务只会使用这个 ID:绝不会创建或删除任何内容。
该容器会请求 42 个描述符。除维护信息外,还包括油量、续航里程、认证油耗、故障记忆和发动机温度。在参考车辆上,除三个以外的描述符都会返回数据。如果你的容器是在此次扩展之前创建的,请使用同一个脚本重新创建。
4. 启动
.venv/Scripts/python.exe -m pitwall_mcp
使用 stdio 传输方式。.env 会先在启动服务器的目录中查找;如果找不到,则会在仓库根目录中查找——MCP 客户端启动进程时使用的工作目录并不固定。如果你的 .env 位于其他位置,请通过 PITWALL_ENV_FILE 指定。
适用于 Claude Desktop 或任何 MCP 客户端:
{
"mcpServers": {
"pitwall": {
"command": "/ruta/al/.venv/bin/python",
"args": ["-m", "pitwall_mcp"],
"env": { "PITWALL_CLIENT_ID": "...", "PITWALL_VIN": "...", "PITWALL_CONTAINER_ID": "..." }
}
}
}
5. 流式传输(可选)
MQTT 守护进程写入 MCP 服务读取的同一个数据库,不会消耗配额。这是可选的:没有它,一切照常运行,只是每天获取两次照片。
.venv/Scripts/python.exe -m pitwall_mcp.streamd # o: pitwall-streamd
启动前有一步无法自动化:进入 BMW 门户中的 Data Selection,手动勾选你想接收的描述符。没有相关 API。如果流中没有数据,首先应该检查的不是守护进程是否有 bug,而是这个页面。
有三件事值得了解:
- BMW 每个账号只允许一个流式连接。 守护进程会在访问网络前先在数据库中加锁,因此两个实例不会互相冲突:第二个实例会退出,并说明哪个 PID 占用了连接,以及从什么时候开始占用。这个锁无法检测其他客户端(Home Assistant、evcc);但可以根据连续断开连接的症状判断出来,守护进程会直接指出名称,而不是让你猜。
- 没有消息很正常。 停在车库里的车可能处于深度睡眠状态,连续几周都不发出任何消息。守护进程不会因缺少数据而重新连接,
get_api_quota会告诉你“已经安静了 N 天,这是正常的”,而不是建议你重启一个运行正常的服务。 - 电池诊断仍然依赖 REST。 两项健康状态信号(
serviceDemand.replace和.recharge)无法通过流式传输获取,将来也不会支持。流式传输提供连续的电压和电量数据,补上了目前缺少的部分;健康状态结论仍然每天获取两次。
默认不会自动启动,而且这是有意为之:它会占用唯一可用的连接,因此必须由你来决定是否启用。
开发
.venv/Scripts/python.exe -m pytest # ningún test toca la red
.venv/Scripts/python.exe scripts/refresh_catalogue.py --check # ¿ha cambiado el catálogo?
所有测试都不会发起真实调用。 测试使用的都是根据 swagger 架构手动录制的 fixtures。消耗配额的测试就是 bug,而 no_network fixture 会强制执行这一点:任何尝试建立真实连接的行为都会导致测试失败。
同样的测试也会在每次推送和每个拉取请求时,通过 GitHub Actions 运行,覆盖 Linux 和 Windows,以及 Python 3.12 和 3.13。工作流会先下载目录,因为仓库不会重新分发该目录,而且不需要任何密钥:如果某个测试尝试调用 BMW API,就会失败。
此外,测试还会每周运行一次,无需任何人操作。这并非惯例使然:由于每次运行都会下载目录,每周运行一次就能发现源目录是否不再包含项目所依赖的描述符。届时测试会失败,你会通过邮件得知,而不是几个月后才发现某个工具表现异常。需要注意,这项检查不会监控目录的所有变更,只会发现会导致功能出错的变更。
相关文档:
CLAUDE.md— 仓库治理文档。docs/resumen-del-proyecto.md— 介绍项目的功能、实现方式和用途,适合初次接触项目的人阅读。docs/paso-0-descriptores.md— 已确认的描述符、不存在的内容,以及目录和 swagger 中的特殊情况。docs/publicacion.md— 项目发布方式:各步骤的顺序,以及哪些内容不得离开仓库。docs/streaming-design.md— 第 2 阶段 MQTT 守护进程的设计文档:设计缘由、验收标准,以及仍未明确的事项。
状态
所有工具均已通过录制的真实响应作为 fixture 实现。
阶段 2(流式 MQTT 守护进程)已实现并在实地连接成功,但尚未收到任何消息,无论车辆静止还是行驶。两个只能通过持续连接 BMW 才能回答的问题中,已有一个答案:连接会话所用的令牌过期后,BMW 会断开会话,守护进程会自动使用新令牌重新连接。另一个问题——这辆车是否会通过 MQTT 发送任何内容——仍未解决,首先要检查的是门户中的数据选择(Data Selection)。SQLite 架构从第一次提交起就已有 source 列和 stream_state 表,因此无需进行迁移。
截至 2026 年 9 月 7 日至 10 月 2 日,通过真实读取已确认这辆车的情况:
- 容器中的 42 个描述符里,有 12 个始终为空,即使已打开点火开关并且车辆正在行驶也是如此。其中包括电池荷电状态、点火状态、车辆是否在移动以及轮胎温度。这不是读取失败:这辆车不会发送这些数据。其他数据则时有时无:有些读取结果包含 CBS 明细,有些则为空;深度睡眠数据近一个月都为空,直到 10 月 2 日才首次返回数值。
- 12 V 电池电压属于一个通过 REST 很少刷新的数据组。 15 天内出现了六个电压值:14.39 / 14.35 / 14.77 / 13.92 / 14.80 V——全部是高于 13.5 V 阈值的发电机电压;此外,9 月 22 日还测得发动机关闭时为 12.73 V。但当天车辆在维修厂,可能连接了充电维护器或诊断设备,因此不能算作在静置状态下的可靠测量值。
车辆正常使用时,REST 尚未提供过任何静置电压,更不用说一组连续数据了。
diagnose_software_update 仍处于 SIN VEREDICTO(尚无结论)状态,这才是正确的:需要两个静置状态下的数据点才能判断趋势。流式传输仍是获取这些数据的合理途径,但首次实地测试没有收到任何内容,即使车辆正在行驶也是如此。必须先排除门户中的 Data Selection 配置问题。
- 还有三个问题尚未解决:
- 故障记忆的标头是什么:它显示的故障代码数量依次为 72、67、68 和 67,但每次都只带回 44 条记录。44 可能是发送上限,就像 CBS 警告计数器后来证实是最大值一样,但这只是推测;
- BMW 每天的配额在哪个时区重置;
- 随 Check Control 警告附带的里程数字段表示什么。已验证过两种解释,结果都不成立,因此工具会原样显示该字段,并注明 BMW 未提供相关文档。
许可证
MIT,© 2026 Panesoft。请参阅 LICENSE。
这里不再分发 BMW 文档。 遥测目录和 OpenAPI 规范均未纳入版本控制:需要下载到你的计算机上,且 spec/ 位于 .gitignore 中。请参阅
spec/README.md。
本项目与 BMW AG 无关联。“BMW”和“CarData”是各自所有者的商标。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。