Skip to main content
Glama
pricewise-mcp

pricewise-mcp

by cardohso

pricewise-mcp

ci

An MCP server that lets Claude Desktop, Claude Code or any MCP client compute Portuguese vehicle taxes (ISV and IUC) and price ranges, using the tested code in pricewise-pt.

In 30 seconds

  • Three tools: calculate_isv, calculate_iuc, estimate_price_range.

  • Deterministic: same input, same output. No network calls and no file writes; the tests block all non-loopback network access and fail if a tool path opens a file for writing. The tax arithmetic lives in pricewise-pt, written from the legal articles and checked against hand-worked examples; this server adds typed inputs, validation, sources and error messages a language model can act on.

  • Every tax answer lists its assumptions (flags), its legal sources and a not-tax-advice disclaimer.

  • The price tool is a demo on synthetic data, labelled as such in every answer.

  • Resources for the versioned tax tables and a limitations document, plus one prompt.

  • stdio for local clients, Streamable HTTP for remote use.

Real transcript of every tool: docs/demo-transcript.md.

Related MCP server: Clara Carros MCP server

Install and connect

Needs uv. Nothing is published to PyPI; the commands install straight from GitHub.

1. Install once

uv tool install git+https://github.com/cardohso/pricewise-mcp
pricewise-mcp --version    # run it once now: the first launch is slow, later ones take seconds

Install once instead of launching with uvx --from git+...: uvx contacts GitHub on start and rebuilds the package after the repo changes, and a cold start can exceed the 30-second connect timeout Claude Code applies (this happened while testing). The installed command starts offline. To update later: uv tool upgrade pricewise-mcp.

2. Connect a client

Claude Code

claude mcp add pricewise -- pricewise-mcp

Claude Desktop: add to claude_desktop_config.json (Settings, Developer, Edit Config):

{
  "mcpServers": {
    "pricewise": {
      "command": "pricewise-mcp"
    }
  }
}

Claude Desktop may not find the command on its PATH. If the server does not start, put the full path from which pricewise-mcp in "command".

Streamable HTTP (remote or shared use):

pricewise-mcp --transport http --port 8000
claude mcp add --transport http pricewise http://127.0.0.1:8000/mcp

HTTP is stateless, answers in JSON and binds to 127.0.0.1 by default. The server has no authentication; if you bind another address it warns on stderr. Put it behind a reverse proxy that authenticates callers before exposing it.

No install (slower and more fragile): replace pricewise-mcp in the commands above with uvx --from git+https://github.com/cardohso/pricewise-mcp pricewise-mcp.

From a clone

git clone https://github.com/cardohso/pricewise-mcp && cd pricewise-mcp
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pricewise-mcp --version

The first valuation call after the server starts can take up to about 20 seconds: the model is fitted in a background thread at launch.

Tools

All three are read-only, idempotent and closed-world (tool annotations). Responses are structured JSON matching the tool's output schema. Examples below are real server output; sources and disclaimer are shortened here and complete in the transcript.

calculate_isv

The one-off registration tax for a used passenger car brought in from another EU/EEA state.

Input: a vehicle (first registration date, fuel, engine cc, CO2 g/km, CO2 test standard, and optional Euro class, diesel particles, electric range, hybrid base fuel), a required declaration_date, and an optional tax_year.

{"vehicle": {"first_registration": "2019-06-15", "fuel": "petrol", "engine_cc": 1199,
             "co2_g_km": 125, "co2_standard": "wltp"},
 "declaration_date": "2026-10-01"}
{
  "total_eur": 211.3,
  "engine_component_eur": 197.45,
  "co2_component_eur": 13.85,
  "age_reduction": 0.65,
  "regime": "normal",
  "minimum_applied": false,
  "co2_standard_used": "wltp",
  "co2_standard_assumed": false,
  "flags": [],
  "tax_year": 2026,
  "sources": ["Código do Imposto sobre Veículos (Lei 22-A/2007), art. 7, tabela A ...", "..."],
  "disclaimer": "Illustration only, not tax advice. ..."
}

With the CO2 standard and a diesel's particle value left out, the same call still answers, with the costlier assumption made explicit:

{
  "total_eur": 1717.64,
  "particle_surcharge_eur": 500.0,
  "co2_standard_assumed": true,
  "flags": [
    "CO2 standard (NEDC/WLTP) assumed from the registration date: confirm it on the certificate of conformity",
    "diesel particle value unknown: check field V.5 of the registration document"
  ]
}

(Other fields omitted. Input: a 2020-03-01 diesel, 1598 cc, 115 g/km.)

calculate_iuc

One year of the yearly circulation tax for a category B car (first registered on or after 1 July 2007). Same vehicle object; optional tax_year.

{"vehicle": {"first_registration": "2019-06-15", "fuel": "petrol", "engine_cc": 1199,
             "co2_g_km": 125, "co2_standard": "wltp"}}
{"total_eur": 111.46, "engine_component_eur": 31.77, "co2_component_eur": 65.15,
 "co2_extra_eur": 0.0, "year_coefficient": 1.15, "diesel_extra_eur": 0.0,
 "co2_standard_assumed": false, "flags": [], "tax_year": 2026}

(sources and disclaimer omitted.) Cars registered before July 2007 get an error.

estimate_price_range

A used-car asking price with a 50% and an 80% range. Trained on synthetic, made-up listings: it demonstrates calibrated uncertainty and says nothing about a real car or market. Input: category (compact, hatchback, crossover, estate, electric), age_years, usage (km), optional condition.

{"category": "compact", "age_years": 6, "usage": 90000, "condition": "good"}
{
  "point_eur": 10504.17,
  "range_50": {"low_eur": 9362.4, "high_eur": 11535.41},
  "range_80": {"low_eur": 8548.65, "high_eur": 12704.31},
  "comparables_median_eur": 10190.0,
  "data_source": "synthetic",
  "flags": [],
  "notice": "Trained on synthetic, made-up listings. ..."
}

Inputs outside the synthetic training range add an extrapolation flag.

What a wrong call looks like

Invalid arguments for calculate_isv. Fix these and call again:
- vehicle.first_registration: input should be a valid date or datetime, invalid character in year (got '15/06/2019'). Hint: dates are YYYY-MM-DD; if '15/06/2019' is day/month/year, send '2019-06-15'
- vehicle.fuel: input should be 'petrol', 'diesel', 'hybrid', 'plugin_hybrid' or 'electric' (got 'gasoline'). Hint: use 'petrol' for 'gasoline'

Resources and prompt

URI

What

pricewise://tables

Index of the available table years, with legal sources

pricewise://tables/isv/2026

The ISV rate tables exactly as the calculator uses them

pricewise://tables/iuc/2026

The IUC rate tables

pricewise://limitations

What the server does not do

Prompt explain_tax_breakdown(vehicle_description, declaration_date): tells the model to call both tax tools, ask for missing fields instead of guessing, explain each component, list the flags and finish with the disclaimer.

Design decisions

  • Deterministic tools only. A model is good at conversation and bad at arithmetic on legal tables. These tools do the arithmetic; the model only gathers inputs and explains outputs. All tax logic stays in pricewise-pt, the server is a thin typed layer.

  • declaration_date is required. ISV depends on the car's age on that date. A hidden default of "today" would make results change silently, so the model must pass it.

  • Assumptions are data. Instead of refusing when the CO2 standard or a diesel's particle value is unknown, ISV computes the costlier case and returns the assumption in flags, with the other standard's total in alternative_total_eur where both apply. IUC assumes the standard from the registration year and flags it. Tool descriptions tell the model to relay flags; engine size and CO2 are never guessed (the model is told to ask).

  • Structured output plus provenance. Every tax result carries sources (the articles the tables come from) and the disclaimer, so they survive however the model paraphrases.

  • Errors written for a model, not a developer. pydantic's raw text (internal names, the echoed input, a docs URL) is replaced by one line per problem with the field, the value sent and a hint for common mistakes (gasoline, day/month/year dates, litres for cm3). Domain errors say whether retrying makes sense. Unknown top-level arguments are rejected: the SDK would otherwise drop a misspelt optional argument silently.

  • One nested vehicle object shared by the tax tools, so the model fills the same shape every time and a future multi-vehicle tool can reuse it.

  • No compare_costs tool. A model can call the two tools and add the results. A multi-year total would assume today's IUC rates and coefficients hold for N years, which is false, and would look more precise than it is.

  • Synthetic valuation, labelled everywhere. The model is fitted at startup from pricewise-pt's generator (about 15 s), so no data file ships and the output is reproducible.

  • Stateless HTTP. No sessions to store; any instance can answer any request.

  • Pinned library. pricewise-pt is not on PyPI, so it is pinned to a commit in pyproject.toml and its tax tests remain the source of truth.

Limitations

Short version; the full text is the pricewise://limitations resource.

  • Not tax advice. 2026 tables only; confirm with the Autoridade Tributária e Aduaneira.

  • Used passenger cars from another EU/EEA state. Not covered: imports from outside the EU/EEA, commercial vehicles, motorcycles, exemptions beyond the hybrid regimes in the tables.

  • IUC is one year at the table year's rates; category A (before July 2007) is rejected.

  • The price model is trained on synthetic data and its ranges are calibrated only on that data.

  • HTTP has no authentication.

  • Tested with the MCP Python SDK's client over stdio and HTTP (in CI), and by hand on 2 November 2026 in two clients, asking for the ISV and IUC of the 2019 petrol car above and for a compact's price range:

    • Claude Code (terminal): called the three tools and reported €211.30, €111.46 and the synthetic range, with the disclaimer and the synthetic-data warning. That session also hit the cold-start timeout described under Install.

    • Claude Desktop chat: called the three tools and reported the same figures and the synthetic-data warning, but did not repeat the "not tax advice" disclaimer even though every tax result carries it and the tool descriptions ask the model to relay it. The server returns the disclaimer; whether a client shows it is up to the model.

Development

pip install -e ".[dev]"
ruff check . && ruff format --check .
pytest -q          # about 30 s: the valuation model is fitted a few times
python examples/demo_client.py > docs/demo-transcript.md   # regenerate the transcript

The tests run offline (only loopback is reachable), assert no file is opened for writing in the ISV, IUC and valuation paths, list the tools through an in-process client, start the server as a real subprocess over stdio and HTTP, and reuse the hand-worked ISV and IUC cases from pricewise-pt as golden values.

Licence

MIT, see LICENSE.

Related MCP Connectors

Related MCP Servers