Skip to main content
Glama
car-price-mcp
by macte02
README.md
# car-price-mcp

[![tests](https://github.com/macte02/car-price-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/macte02/car-price-mcp/actions/workflows/tests.yml)
![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white)
![MCP](https://img.shields.io/badge/MCP-Python%20SDK%202.x-555)

A small [Model Context Protocol](https://modelcontextprotocol.io) server that lets an AI agent look up
used-car auction prices. It gives Claude Code, or any other MCP client, four read-only tools over a SQLite
database of finished auctions. With them the agent can find comparable sales, estimate what a car would
sell for and summarise how a model is trading.

I co-run a small used-car business in Finland and we price cars from auction results every week. This is
the lookup part of that work pulled out into its own server, so the agent can run the comparables search
itself instead of me pasting tables into a chat.

**The data in this repo is synthetic.** The schema follows my private auction database (without plates,
VINs or listing text), but every row comes from [`sample_data.py`](src/car_price_mcp/sample_data.py) with
a fixed seed. Don't use the numbers to price a real car.

## 30-second demo

You need Python 3.10 or newer and [Claude Code](https://docs.claude.com/en/docs/claude-code).

```bash
git clone https://github.com/macte02/car-price-mcp
cd car-price-mcp
python -m venv .venv
.venv/bin/pip install -e .
claude mcp add car-prices -- "$PWD/.venv/bin/car-price-mcp"
```

On Windows the last two lines are `.venv\Scripts\pip install -e .` and
`claude mcp add car-prices -- "$PWD\.venv\Scripts\car-price-mcp.exe"` in PowerShell.

Start `claude`, check with `/mcp` that car-prices is connected, and ask something like

> What would a 2016 Skoda Octavia diesel with 190,000 km go for at auction?

Claude calls `estimate_price` and gets this back (cut down to two of the five closest sales)

```json
{
  "make": "Skoda",
  "model": "Octavia",
  "year": 2016,
  "mileage_km": 190000,
  "estimate_eur": 5900,
  "range_eur": [4200, 7650],
  "comparables": 14,
  "match": "close match",
  "criteria": "model year 2015-2017, 165,000-215,000 km, same fuel",
  "confidence": "medium",
  "closest_sales": [
    {"ad_id": "demo-01774", "end_time": "2026-07-08", "year": 2016, "mileage_km": 195000,
     "engine": "2.0 TDI", "transmission": "manual", "sold_price": 6850, "bid_count": 5, "...": "..."},
    {"ad_id": "demo-01089", "end_time": "2026-09-18", "year": 2016, "mileage_km": 178000,
     "engine": "1.6 TDI", "transmission": "manual", "sold_price": 3600, "bid_count": 24, "...": "..."}
  ],
  "method": "Median of accepted auction prices for similar cars. The range is the middle half (25th to 75th percentile)."
}
```

If you'd rather not clone anything, uv can run it straight from GitHub.

```bash
claude mcp add car-prices -- uvx --from git+https://github.com/macte02/car-price-mcp car-price-mcp
```

Other MCP clients such as Claude Desktop and Cursor take the same command in their JSON config.

```json
{
  "mcpServers": {
    "car-prices": {
      "command": "/path/to/car-price-mcp/.venv/bin/car-price-mcp"
    }
  }
}
```

## What the agent gets

| Tool | What it does |
| --- | --- |
| `list_models(make?)` | Makes and models in the data with auction counts and the model years covered. Useful when the agent isn't sure how a name is spelled. |
| `search_comparables(make, model, year_min?, year_max?, km_min?, km_max?, fuel?, transmission?, accepted_only=true, limit=20)` | Finished auctions that match the filters, newest first, plus how many matched in total. |
| `estimate_price(make, model, year, mileage_km, fuel?, transmission?)` | Median of similar accepted sales, the middle-half price range, how many sales it used, how far it had to widen the search and the five closest sales. |
| `summarise_market(make, model)` | Number of auctions, how often sellers accept the top bid, price quartiles, fuel and gearbox mix, and median price by model year and by month. |

There is also a resource, `cars://schema`, that describes the table and what the dataset covers, and a
prompt, `value_a_car`, that asks for an estimate backed by the closest sales.

## How the estimate works

It starts with accepted sales of the same model within 1 model year and 25,000 km of the car, and with
the same fuel and gearbox if you give them. If that finds fewer than 5 sales it widens to 2 years and
50,000 km, and then to 3 years and 80,000 km without the fuel and gearbox filters. If even the widest
search has fewer than 3 sales it returns an error saying so instead of guessing.

The answer says which step it stopped at, and gives a confidence level from the number of sales and how
spread out their prices are. A median of comparables is deliberately simple. It's how we price cars by
hand, and the agent can show the sales behind the number so a person can check it.

## A few design choices

- **Read-only.** A database file is opened with SQLite's `mode=ro` and every tool carries the MCP
  `readOnlyHint` annotation, so a client knows none of them change anything.
- **Errors the model can act on.** A typo like "Octavai" comes back as a tool error that says
  "Did you mean Skoda Octavia?" rather than an empty list. "VW" and "Merc" work as makes.
- **Structured output.** Tools return JSON objects, so clients get `structuredContent` and the model sees
  the same data as text.
- **No MCP code in the logic.** [`market.py`](src/car_price_mcp/market.py) is plain Python and SQL.
  [`server.py`](src/car_price_mcp/server.py) is a thin layer that registers it with the SDK, which keeps
  the tests quick and the queries easy to reuse elsewhere.

## Using your own data

```bash
car-price-mcp --db path/to/cars.db
```

or set `CAR_PRICE_DB`. The file needs a `sales` table with the columns listed in the `cars://schema`
resource. Without either, the server builds the synthetic sample in memory on startup, which takes well
under a second. To get the sample as a file you can open in a SQLite browser, run
`python -m car_price_mcp.sample_data --out cars.db`.

## Tests

```bash
pip install -e ".[dev]"
pytest
```

The query tests run on 15 hand-written auctions where every median and count can be worked out on paper.
Other tests check that the generator is deterministic and its cars make sense (newer and lower-mileage
ones sell for more), and talk to the server through the SDK's MCP client, both in-process and over stdio
as a subprocess. GitHub Actions runs everything on Python 3.10 to 3.13.

## Layout

```
src/car_price_mcp/
  server.py        MCP tools, resource and prompt
  market.py        queries and the estimate logic, no MCP imports
  sample_data.py   table schema and the synthetic data generator
tests/
.github/workflows/tests.yml
```

Built on the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) 2.x, where
FastMCP is now called `MCPServer`. MIT licensed.