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 分