
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.
概览
只读的 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 端点的网络连接。
安装
在 SourceWeft 中
- 打开 控制台中的 pitwall-mcp,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
pitwall-mcp
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:
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 espuStep, 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_descriptorsyget_api_quotano gastan cuota nunca.
Consulta get_api_quota antes de encadenar llamadas.
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
PENDINGa 1.900 km,serviceDistance.nextmarcaba 13.560. Quedarse con ese número habría escondido lo único urgente, así que toda partida que BMW no marqueOKsale 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
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:
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+.
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.
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
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
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
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
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:
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.
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_quotate 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.replacey.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
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:
CLAUDE.md— documento de gobierno del repositorio.docs/resumen-del-proyecto.md— qué hace, cómo lo hace y para qué sirve, para quien llega de nuevas.docs/paso-0-descriptores.md— los descriptores confirmados, lo que no existe, y las rarezas del catálogo y del swagger.docs/publicacion.md— cómo se publica esto: el orden de los pasos, y qué no debe salir del repositorio.docs/streaming-design.md— la Fase 2, el daemon MQTT: por qué está hecho así, con los criterios de aceptación y lo que sigue sin saberse.
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_updatesigue enSIN 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版本历史
2- v0.1.1最新Sep 22, 2026
- v0.1.0Sep 19, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分