pitwall-mcp

io.github.sergioprats由 GitHub 账号 sergioprats 发布v0.1.1更新于 Oct 24, 2026

Solo lectura sobre BMW CarData: mantenimiento, neumaticos y bateria de 12V. No afiliado a BMW AG.

已验证STDIO仅桌面位置与生活数据与分析

概览

AI 生成的概览

只读的 MCP 服务器,让助手获取单辆 BMW 的 CarData 遥测数据:里程、保养项目、胎压、12V 电池和故障记忆。

可以做什么
在不消耗请求配额的情况下搜索 BMW 描述符目录并查询剩余 API 配额,并通过本地缓存列出车辆和基本车辆数据。读取缓存的遥测数据、车辆状态和 Check Control 警告,生成包含保养项目、里程和各项到期预测的保养摘要。读取轮胎诊断、油量、续航、油耗和故障记忆,并比较两次读取之间的故障码变化。依据本地历史判断软件更新条件,并说明 CarData 无法观测的部分。保留本地 SQLite 历史,使回答反映趋势而非单次快照,并标明每个数据的来源和时间。
适用场景
适合已启用 CarData 的 BMW 车主,希望助手跟踪随时间变化的情况:各项保养真正何时到期、某条轮胎是否漏气、自上次读取以来出现了或消失了哪些故障码。不适合翻译或清除故障码,也不适合更改车辆设置,这些需要 OBD 诊断工具。
运行要求
以 Python 3.12+ 包形式通过 stdio 在本地运行。需要具有读取权限的 BMW CarData 客户端 ID(PITWALL_CLIENT_ID)、登记为主车辆的 VIN(PITWALL_VIN),以及事先用仓库脚本创建的遥测容器 ID(PITWALL_CONTAINER_ID)。OAuth 设备码登录会把令牌存入文件,刷新令牌有效期为 14 天。BMW 遥测目录需下载一次。需要访问 BMW 端点的网络连接。
安装前请注意
CarData REST API 每个账户每 24 小时限 50 次请求,与同一账户上的其他应用共享;超限会返回 403 直到次日,因此设有本地每日上限(默认 20)。服务器为只读,不会向车辆写入任何内容,但会在磁盘上保存 OAuth 令牌和客户端 ID。创建容器的脚本会写入你的 BMW 账户,需在服务器之外手动运行。与 BMW AG 无关联。

安装

在 SourceWeft 中

  1. 打开 控制台中的 pitwall-mcp,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

pitwall-mcp

[CI] [PyPI] [Licencia MIT]

Servidor MCP local, de solo lectura, sobre la API BMW CarData de un BMW X1 sDrive18i (U11, gasolina, España).

Proyecto no afiliado a BMW AG. "BMW" y "CarData" son marcas de sus respectivos propietarios. Este proyecto no está respaldado, patrocinado ni revisado por BMW.

Le da a un asistente acceso a los datos de mantenimiento del coche — kilometraje, avisos CBS, presiones de neumáticos, batería de 12 V — sin que pueda tocar nada del vehículo, y sin agotar la cuota diaria de la API.

Qué se siente al usarlo

Le preguntas al asistente "¿qué le toca al coche?" y contesta con esto. Es una respuesta real, de la lectura del 16 de septiembre de 2026, recortada:

RESUMEN DE MANTENIMIENTO
Kilometraje: 48.731 kmProximo 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.

Fíjate en lo que hace esa respuesta además de dar números: avisa de que la cifra global esconde lo urgente, traduce "1.600 km" a una fecha, admite que dos cifras de BMW no cuadran en vez de elegir una, y dice de cuándo es el dato más viejo que ha usado.

¿Te sirve a ti?

  • Sí si tienes un BMW con CarData activado y quieres saber qué cambia con el tiempo: cuándo vence cada cosa de verdad, si una rueda pierde aire, qué códigos de avería han aparecido desde la última vez.
  • No si buscas traducir códigos de avería, borrarlos o tocar cualquier ajuste del coche. Eso es terreno de un diagnóstico por OBD, no de esta API.
  • Ten en cuenta que es de uso ocasional. El dato crudo ya lo enseña la pantalla del coche; lo que aquí se añade es la serie, la previsión y los cambios.

Qué hace y qué no hace

Sí:

  • Lee datos telemáticos, datos básicos del vehículo y el diagnóstico de neumáticos, siempre a través de caché.
  • Guarda un histórico local en SQLite, para poder razonar sobre series y no sobre fotos puntuales.
  • Dice de dónde viene cada dato: timestamp de BMW, timestamp de la lectura, y si es API, caché o histórico.
  • Cuando un dato no existe, explica por qué no existe.

No, y no lo hará:

  • Ninguna herramienta escribe, manda, activa ni insinúa que puede actuar sobre el coche. CarData es una API de lectura.
  • Ninguna herramienta crea ni borra contenedores. Eso vive en un script que se lanza a mano.
  • No hay get_software_version: ese descriptor no existe en el catálogo de BMW. Lo único disponible es puStep, el paso de actualización de producto.

La cuota manda

La API REST de CarData está limitada a 50 peticiones cada 24 h y por cuenta. Pasarse devuelve 403 CU-429 hasta el día siguiente, y el contador es de BMW: incluye cualquier otra aplicación que use la misma cuenta.

Por eso:

  • Toda petición pasa obligatoriamente por caché y por un contador local.
  • El tope local por defecto es de 20 peticiones/día, para dejar margen.
  • Con un TTL de 12 h en el contenedor de mantenimiento, el uso normal son 2 peticiones al día.
  • search_descriptors y get_api_quota no gastan cuota nunca.

Consulta get_api_quota antes de encadenar llamadas.

DatoTTL
/telematicData (contenedor de mantenimiento)12 h
/smartMaintenanceTyreDiagnosis7 días
/basicData30 días
/mappings30 días

Por qué esto y no la app oficial

La app te enseña el estado de hoy. Esto guarda cada lectura, así que puede comparar. Cuatro cosas concretas que salen de ahí, todas vistas en este coche:

  • Cuándo, no solo cuánto. La app dice "frenos delanteros, 1.600 km". El resumen dice unas 2,8 semanas, hacia el 5 de octubre, al ritmo real de uso, y avisa si una partida vence antes por kilómetros que por fecha.
  • La cifra global no basta, y aquí se ve. Con los frenos en PENDING a 1.900 km, serviceDistance.next marcaba 13.560. Quedarse con ese número habría escondido lo único urgente, así que toda partida que BMW no marque OK sale con un aviso propio.
  • Neumáticos sin confundir el calor con una fuga. El objetivo de presión de BMW no es fijo: se ha visto en 250 kPa en frío y en 290 con el neumático caliente. Comparar la presión de hoy con un objetivo de otro momento da un diferencial falso, así que cada rueda se compara con su pareja de eje en la misma lectura, que comparte temperatura y carga.
  • Qué cambia en la memoria de averías. El 18 de septiembre, entre dos lecturas, entraron 11 códigos y salieron otros 11: apareció una centralita entera y desaparecieron tres. Eso no se ve en ningún sitio si no guardas la lectura anterior.

Y lo que no aporta, para que no haya malentendidos:

  • No traduce los códigos de avería. Su significado no está en el catálogo de BMW; inventarlo sería peor que no darlo. Para eso está una herramienta de diagnóstico por OBD, del estilo de BimmerLink, que además los lee con su descripción y puede borrarlos.
  • No escribe nada. Ni codificación, ni resets, ni una sola orden al coche. Eso es terreno de BimmerCode y de un adaptador OBD, con el coche delante.
  • No inventa lo que no llega. Un dato ausente, uno presente pero vacío y un -NA- son tres cosas distintas, y las herramientas las distinguen en vez de enseñar un cero. De los 42 descriptores del contenedor, en la última lectura llegaron 28 con valor y 14 vacíos.

Herramientas

HerramientaGasta cuotaEstado
search_descriptors(query, limit, include_electric)nofunciona
get_api_quota()nofunciona
list_vehicles()sí (1/mes con caché)funciona
get_vehicle_basic_data()sí (1/mes con caché)funciona
report_product_update_step()sí (comparte caché con basicData)funciona, pero este vehículo no devuelve puStep
get_telematic_data()sí (2/día con caché)funciona
get_vehicle_status()sí (comparte caché)funciona
get_tyre_diagnosis()sí (1/semana con caché)funciona, pero este vehículo devuelve la estructura vacía
get_maintenance_summary()sí (compuesta)funciona
diagnose_software_update()sí (compuesta, comparte caché)funciona, con veredicto acotado
get_fuel_status()sí (comparte caché)funciona
get_fault_memory()sí (comparte caché)funciona; no traduce los códigos

Las dos herramientas marcadas con una reserva funcionan y declaran la ausencia: no rellenan el hueco con ceros ni con un valor plausible. puStep no llega en /basicData para este coche, y el diagnóstico de neumáticos vuelve con etiquetas y ceros de relleno que no son medidas.

get_vehicle_status() y get_maintenance_summary() muestran también los avisos Check Control, y añaden una línea OJO cuando alguna partida CBS deja de estar en OK. La cifra global de próximo servicio no basta: con los frenos delanteros en PENDING a 1.900 km, marcaba 13.560.

get_maintenance_summary() añade además, sin gastar peticiones, una previsión en semanas y fechas y la tendencia de presiones por eje. Las dos salen del histórico local, y el porqué de cada una está en Por qué esto y no la app oficial. La previsión usa la media semanal de BMW o la del histórico, la mayor de las dos, para no quedarse corta, y avisa cuando una partida vence antes por kilómetros que por fecha.

Dos herramientas más con datos que la app oficial no enseña:

  • get_fuel_status(): depósito y autonomía, los repostajes que detecta en el histórico y el consumo real desde el último. Solo da el consumo a partir de 300 km, y siempre con su margen, porque el aforador puede desviarse hasta 6 L. Muestra también el consumo homologado (OBFCM), pero como lo que es: una cifra de por vida que en este coche no se mueve desde octubre de 2024, no el consumo de hoy.
  • get_fault_memory(): la memoria de averías que el coche guarda para el taller, agrupada por centralita, y qué códigos aparecen o desaparecen entre lecturas. No traduce ningún código: su significado no está en el catálogo de BMW, y darlo sería inventarlo.

diagnose_software_update() razona sobre la serie del histórico, no sobre una foto. De las tres condiciones que BMW documenta para no ofrecer una actualización, CarData sólo permite observar una, y la herramienta declara las otras dos como NO OBSERVABLE POR CARDATA en lugar de razonar como si las hubiera descartado. Deja fuera cualquier voltaje de 13,5 V o más, porque eso es el alternador cargando y no dice nada de la batería en reposo. Si no le quedan al menos dos observaciones en reposo, responde SIN VEREDICTO y dice qué le falta. Y como isIgnitionOn llega vacío, avisa de que parte de cualquier pendiente puede ser sólo motor en marcha frente a motor parado, no una batería descargándose.


El informe local

El histórico que va guardando el servidor no se ve en ningún sitio: las herramientas contestan con la foto del momento, no con la serie. Para mirar la serie hay una página HTML que se genera a mano desde la SQLite:

bash
.venv/Scripts/python.exe scripts/report.py              # captures/report-*.html.venv/Scripts/python.exe scripts/report.py --full-vin   # con el VIN entero

No gasta ninguna petición. No usa el token, no llama a ningún endpoint y no toca el contador de cuota: sólo lee la base de datos local. La página es un único fichero sin JavaScript ni dependencias externas, imprime bien, y sale con el VIN enmascarado a captures/, que está en .gitignore.

Lo primero que enseña no es un número, es cuántos de los 42 descriptores del contenedor traen valor de verdad, y en cuál de los tres estados vacíos está cada uno de los demás.


Instalación

Requiere Python 3.12+.

bash
git clone https://github.com/sergioprats/pitwall-mcpcd pitwall-mcppython -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

Ese último paso no es opcional: el catálogo telemático es un documento de BMW y este repositorio no lo redistribuye, así que hay que descargarlo una vez. Viene de zweckj/bmw-cardata (MIT), no de la API de CarData, de modo que no gasta ninguna petición de tu cuota. Los detalles están en spec/README.md.

Si instalas el paquete en vez de clonar el repositorio no tienes scripts/, así que el ejecutable trae lo imprescindible: --fetch-catalogue para el catálogo y --login para autenticarte. Lo que sigue necesitando el repositorio es crear el contenedor telemático, y es a propósito: ese paso escribe en tu cuenta de BMW y por norma del proyecto no vive dentro del servidor.

bash
pitwall-mcp --fetch-catalogue          # junto a la base de datospitwall-mcp --fetch-catalogue --out /otra/ruta.json

Se niega a escribir si lo que baja no es un catálogo —un portal cautivo responde 200 con HTML—, así que un catálogo que ya te funcionaba no se pierde por una descarga mala.

Los comandos de abajo usan el intérprete del entorno virtual de forma explícita (.venv/Scripts/python.exe en Windows, .venv/bin/python en Linux y macOS). Si activas el entorno (.venv/Scripts/activate, o source .venv/bin/activate), te basta con python. Sin activar y con el python del sistema, fallará con ModuleNotFoundError: No module named 'bmw_cardata'.

1. Configuración

bash
cp .env.example .env

Rellena PITWALL_CLIENT_ID con el client id de tu cliente CarData. Se crea en la web My BMW de tu país, en la ficha de tu vehículo → BMW CarData → "Acceso técnico a BMW CarData" (en España: bmw.es/es-es/mybmw/vehicle-overview). No es un portal aparte. Necesitas los scopes cardata:api:read y, si algún día usas streaming, cardata:streaming:read.

2. Login

bash
pitwall-mcp --login                              # instalado con pip.venv/Scripts/python.exe scripts/login.py        # o desde el repositorio: hace lo mismo

Device flow: imprime una URL y un código, tú lo autorizas en el navegador. Los tokens se guardan en ~/.config/pitwall-mcp/tokens.json con permisos 600, fuera del repositorio. Habla con el OAuth de BMW, no con la API de CarData, así que no gasta cuota.

El script del repositorio es solo un envoltorio del mismo código: una única definición del flujo, y así quien instala con pip también puede autenticarse.

El refresh token dura 14 días. El servidor avisa de forma visible cuando quedan menos de 3; si caduca, hay que repetir este paso a mano.

3. Contenedor

bash
.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

Copia el containerId resultante a PITWALL_CONTAINER_ID en el .env. El servidor MCP solo consume ese id: nunca crea ni borra nada.

El contenedor pide 42 descriptores. Además del mantenimiento, incluye el depósito, la autonomía, el consumo homologado, la memoria de averías y la temperatura del motor. En el coche de referencia llegan todos menos tres. Si tu contenedor es de antes de esta ampliación, créalo de nuevo con el mismo script.

4. Arrancar

bash
.venv/Scripts/python.exe -m pitwall_mcp

Transporte stdio. El .env se busca en el directorio desde el que se lanza el servidor y, si no aparece, en la raíz del repositorio — un cliente MCP arranca el proceso con el directorio de trabajo que le apetece. Si tu .env vive en otro sitio, indícalo con PITWALL_ENV_FILE.

Para Claude Desktop o cualquier cliente MCP:

json
{  "mcpServers": {    "pitwall": {      "command": "/ruta/al/.venv/bin/python",      "args": ["-m", "pitwall_mcp"],      "env": { "PITWALL_CLIENT_ID": "...", "PITWALL_VIN": "...", "PITWALL_CONTAINER_ID": "..." }    }  }}

5. Streaming (opcional)

El daemon MQTT escribe en la misma base de datos que lee el servidor MCP, sin gastar cuota. Es opcional: sin él todo funciona igual, con dos fotos al día.

bash
.venv/Scripts/python.exe -m pitwall_mcp.streamd     # o: pitwall-streamd

Antes de arrancarlo hay un paso que no se puede automatizar: entrar en Data Selection, en el portal de BMW, y marcar a mano los descriptores que quieres recibir. No hay API para eso. Si el stream no trae nada, la primera hipótesis no es un bug del daemon: es esa página.

Tres cosas que conviene saber:

  • BMW permite una sola conexión de streaming por cuenta. El daemon toma un cerrojo en la base de datos antes de tocar la red, así que dos instancias no pueden pisarse: la segunda sale diciendo qué PID tiene la conexión y desde cuándo. Lo que el cerrojo no puede ver es otro cliente distinto (Home Assistant, evcc); eso se detecta por el síntoma, desconexiones en cadena, y el daemon lo dice con ese nombre en vez de dejarte adivinando.
  • El silencio es normal. Un coche parado en una cochera, en sueño profundo, puede pasar semanas sin emitir. El daemon no reconecta por falta de datos, y get_api_quota te dice "lleva N días callado, que es NORMAL" en vez de sugerirte reiniciar algo que funciona.
  • No libera al diagnóstico de la batería de la REST. Las dos señales de salud (serviceDemand.replace y .recharge) no son streamable y no lo serán. El streaming aporta la serie continua de voltaje y carga, que es lo que falta; el veredicto de salud sigue llegando dos veces al día.

Sin auto-arranque por defecto, y a propósito: toma la única conexión disponible, y eso tiene que ser una decisión tuya.


Desarrollo

bash
.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?

Ningún test hace llamadas reales. Todos van contra fixtures grabados a mano a partir de los esquemas del swagger. Una suite que gaste cuota es un bug, y la fixture no_network lo hace cumplir: hace fallar cualquier intento de abrir una conexión de verdad.

Eso mismo corre en GitHub Actions en cada push y en cada pull request, sobre Linux y Windows, con Python 3.12 y 3.13. El flujo empieza descargando el catálogo, porque el repositorio no lo redistribuye, y no necesita ningún secreto: si un test intentara llamar a la API de BMW, fallaría.

Además se ejecuta una vez por semana, sin que nadie toque nada. No es por costumbre: como el catálogo se descarga en cada ejecución, esa pasada semanal avisa si el catálogo de origen deja de traer un descriptor en el que el proyecto se apoya. Entonces falla un test, y te enteras por correo en vez de por una herramienta comportándose raro meses después. Ojo con lo que eso no hace: no vigila cualquier cambio del catálogo, solo los que romperían algo.

Documentación relevante:


Estado

Todas las herramientas están implementadas contra respuestas reales grabadas como fixtures.

La Fase 2 (daemon MQTT de streaming) está implementada y conecta en campo, pero todavía no ha recibido un solo mensaje, ni con el coche parado ni rodando. De las dos preguntas que solo se contestan dejándolo correr contra BMW, una ya tiene respuesta: BMW corta la sesión cuando caduca el token con el que se conectó, y el daemon reconecta solo con uno nuevo. La otra —si este coche emite algo por MQTT— sigue abierta, y lo primero que hay que revisar es la selección de datos (Data Selection) del portal. El esquema SQLite ya tenía la columna source y la tabla stream_state desde el primer commit, así que no hizo falta ninguna migración.

Lo que ya se sabe de este coche, verificado con lecturas reales entre el 7 de septiembre y el 2 de octubre de 2026:

  • 12 de los 42 descriptores del contenedor llegan siempre vacíos, incluso con el contacto dado y el coche rodando. Entre ellos están el estado de carga de la batería, el contacto, si el coche se mueve y las temperaturas de los neumáticos. No es falta de lectura: este coche no los emite. Otros van y vienen: el desglose CBS unas lecturas trae las partidas y otras llega vacío, y el sueño profundo, vacío durante casi un mes, llegó con valor por primera vez el 2 de octubre.

  • El voltaje de la batería de 12 V viaja en un grupo que por REST se refresca muy de tarde en tarde. En quince días han salido seis voltajes: 14,39 / 14,35 / 14,77 / 13,92 / 14,80 V —todos tensión de alternador, por encima del umbral de 13,5 V— y, el 22 de septiembre, 12,73 V con el motor apagado. Pero ese día el coche estaba en el taller, donde pudo tener conectado un mantenedor de carga o un equipo de diagnóstico, así que no cuenta como una medida limpia en reposo.

    En su uso normal, la REST todavía no ha dado ni un voltaje en reposo, y menos una serie. diagnose_software_update sigue en SIN VEREDICTO, que es lo correcto: hacen falta dos puntos en reposo para hablar de tendencia. El streaming sigue siendo la vía razonable para conseguirlos, pero en su primera prueba de campo no recibió nada, ni siquiera con el coche rodando. Hay que descartar antes la configuración de Data Selection en el portal.

  • Quedan abiertos otros tres puntos:

    • qué es la cabecera de la memoria de averías, que ha anunciado 72, 67, 68 y 67 códigos trayendo siempre 44 entradas. Puede que 44 sea el tope del envío, como el contador de avisos CBS resultó ser un máximo, pero eso es una sospecha;
    • en qué huso horario reinicia BMW la cuota diaria;
    • qué es el campo de kilómetros que acompaña a los avisos Check Control. Van dos interpretaciones probadas y las dos han caído, así que la herramienta lo enseña pelado y dice que BMW no lo documenta.

Licencia

MIT, © 2026 Panesoft. Ver LICENSE.

Los documentos de BMW no se redistribuyen desde aquí. Ni el catálogo telemático ni la especificación OpenAPI están en el control de versiones: se descargan en tu máquina, y spec/ está en .gitignore. Ver spec/README.md.

Proyecto no afiliado a BMW AG. "BMW" y "CarData" son marcas de sus respectivos propietarios.

来源:README.md,提交 f210bbc

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

2
  1. v0.1.1最新Sep 22, 2026
  2. v0.1.0Sep 19, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分