Your car already has an API
What would my car say if something actually listened?
Turns out, a lot. I can ask Claude “is the check engine light on, and why?” or “has someone cleared the codes recently?” and it answers by reading the car itself. Every car sold in the US since 1996 has a diagnostic port under the dashboard, and it speaks a standard called OBD-II. A cheap adapter turns that port into something your laptop, and your AI, can talk to.
What's possible
Once the car can answer questions, a lot opens up. A few I like:
- Ask why the check engine light is on before you call a mechanic, and walk in knowing the code.
- Check a used car before you buy it. No codes plus unfinished self-tests usually means someone cleared them recently.
- Watch live data on a drive: engine speed, coolant temperature, load.
- Have an agent check the car every morning and only speak up if something changed.
- Keep a history of codes over time, so a problem that comes and goes has a record.
I've built the first three. The last two are where it gets fun.
How to get started
- Buy a Wi-Fi or Bluetooth ELM327 adapter and plug it into the port near your knees.
- Connect with any free OBD app first, just to see live data and codes come through.
- Write a small script that sends the standard requests and decodes the replies. Mine is a Node script with no dependencies that serves a JSON API.
- Put a dashboard on that API if you want one.
- Wrap the API in an MCP server so Claude can use it. Mine gets seven tools.
- car_status
- Summary: connection, adapter, warning light, key gauges.
- car_pids
- Every decoded reading, filterable by name.
- car_codes
- Stored, pending and permanent codes, with descriptions.
- car_modules
- The modules found on the bus.
- car_module
- One module's identification and data.
- car_log
- The last lines of the raw conversation with the adapter.
- car_send
- One raw request, only if it is a read.
Lessons, so you can start further along
Put the allowlist in before the AI. The adapter will carry anything you send it, including requests that clear codes or change settings. So every request goes through a list of nine read services, checked in the MCP server and again in the API, and everything else is refused before it leaves the laptop.
const READS = [
"01", "02", "03", "07", "09",
"0A", "21", "22", "1A",
]
function allowed(cmd) {
return READS.includes(cmd.slice(0, 2))
}
// anything else: refused, never sentGive the AI narrow tools, not a raw pipe. “Read the codes” is a better tool than “send anything”. The one raw tool I kept only takes reads.
One question at a time. The adapter handles one command at a time, and a slow answer can land after your timeout and get read as the answer to the next question. Drop anything that shows up late.
A failed read is not a clean car. If a query didn't come back, say so. Never report no codes when you just didn't get an answer.
Record once, build anywhere. I saved a sanitized capture and wrote a fake adapter that replays it, so most of this got built at a desk, not in the driveway.
Keep a local code table. P0301 means nothing on its own. A local table of about 9,000 standard code descriptions turns it into “misfire in cylinder 1” without a network call.
Leave locked things locked. A few modules in my car wouldn't say what they were. I leave them alone. What the port offers is plenty.
Why read-only first
Most companies have a version of this car: older systems full of useful data that nobody wants an AI changing. Read-only is how you start, and write access can come later, once reading has earned it.
If you've hooked an AI up to your car, or to anything old and stubborn, I'd love to hear what you'd do differently.