DEV Community

Viktor Kondas for Apify

Posted on

Giving AI agents live European fuel prices with Apify MCP

Giving AI agents live European fuel prices with Apify MCP

How I turned a CEE + DACH fuel-price Actor into a tool Cursor can call — and what I had to change once a model, not a spreadsheet, was reading the output.

I built European Fuel Station Prices API (CEE + DACH) (https://apify.com/kondasviktor/cee-dach-fuel-intelligence) as a normal Apify Actor first: normalized station prices across Central Europe and DACH, scheduled monitoring, and a route cheapest-stop mode. Then I wired the same Actor into Cursor through the Apify MCP server (https://docs.apify.com/platform/integrations/mcp). This piece is about that second step — Theme 2 in Apify's Content Program: your Actor as a tool for AI agents.

What you need

No local scrape setup. The Actor already runs on Apify; MCP is how the agent reaches it.

The guessing problem

Ask any LLM-based agent "where should I fill up between Budapest and Vienna?" and you get a fluent answer built from training data that is months or years stale. Pump prices move daily. Logistics teams want live, per-station, cross-border numbers — not a national average from memory.

CEE adds a second failure mode. Slovakia can apply dual pricing for foreign-plated vehicles (dualPricingForForeignPlates: true, verified 2026-07-24 in my Actor's regulatory config). Croatia can run price caps on regular stations while highway stations differ (priceCapActive: true, same date). Austria restricts when prices may increase to Mon/Wed/Fri (priceIncreaseRestrictedDays: ["MO","WE","FR"]). None of that belongs in model weights. It belongs in structured fields an agent reads at call time.

That gap — volatile, jurisdiction-specific data an LLM cannot reliably know — is what MCP tools are for. The interesting engineering is not scraping a price board. It is designing output so an agent knows how much to trust each number and receives few enough tokens to reason.

Why I built a regional Actor in Cursor

I live in CEE. The Apify Store had US/FR/AU fuel Actors; station-level CEE + DACH coverage did not. I wanted one schema across DE, AT, CH, HU, PL, CZ, SK, RO, HR, SI, and BG — official feeds where they exist, licensed aggregators elsewhere, EUR-normalized for cross-border comparison.

The Actor is live on Store (build 0.1.10 at time of writing), pay-per-event (https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event), with public example tasks. No buyer key for AT, SI, HR, RO. BYOK: Tankerkönig for DE, Fuelo for CH/HU/PL/CZ/SK/BG. You can smoke-test Austria in under a minute for the $0.10 minimum run charge.

I built the Actor in Cursor — the same client that now calls it via MCP. That closed loop mattered: every schema tweak got exercised by an agent that actually had to answer a logistics question, not by me staring at a dataset table.

Data sources are official public APIs or licensed aggregators with BYOK. The Actor does not scrape pump websites behind a login or ignore site terms; for Poland and similar Fuelo countries, missing a key fails loudly instead of inventing prices.

How the Actor is built

Each country is a small adapter behind one interface. Austria reads E-Control's public JSON API, maps DIE/SUP/GAS into canonical diesel/petrol95/cng, and tags every row dataConfidence: OFFICIAL_REALTIME with an honest sourceMessage that E-Control returns cheapest-N, not the full market:

Code (typescript)

// src/adapters/at.ts (excerpt)

url.searchParams.set('fuelType', mapFuel(query.fuel));

// ...

return {

stationId: s.id != null ? String(s.id) : null,

country: 'AT',

diesel: typeof die === 'number' ? die : null,

petrol95: typeof sup === 'number' ? sup : null,

dataConfidence: 'OFFICIAL_REALTIME',

source: 'E-Control Spritpreisrechner',

sourceMessage:

'E-Control returns cheapest-N stations for the search — not every station nationwide',

regulatoryFlags: regulatoryFlagsFor('AT'),

};

Prices pass through ECB FX into *Eur and priceEur so a Budapest→Vienna comparison is not HUF guesswork against EUR memory.

Route mode keeps cheapest-stop math deterministic — haversine detour, not LLM arithmetic:

Code (typescript)

// src/route/geometry.ts (excerpt)

const detour = toStation + stationToDest - direct;

if (detour > opts.maxDetourKm) continue;

if (!best || price < best.price || (price === best.price && detour < best.detour)) {

best = { station: s, price, detour, fromOrigin: toStation };

}

Partial runs never invent rows. markCountryStatus classifies each country (ok, empty, missing_fuelo_key, …) and the run still succeeds for the rest:

Code (typescript)

// src/main.ts (excerpt)

if (errMsg) {

countryStatus[country] = classifyAdapterError(errMsg).status;

return;

}

Pricing is explicit PPE: station-price $0.0025, route-recommendation $0.05, optional ai-digest $0.15 (BYOK, off by default), $0.10 minimum per run.

Architecture: agent decides, Actor evidences

Code (text)

User question

→ Cursor agent

→ Apify MCP (call-actor / fetch-actor-details)

→ Fuel Intelligence Actor

→ country adapters → live feeds

→ normalized dataset

→ agent recommendation

The agent is the decision layer. The Actor is the evidence layer. MCP is the bridge that removes the manual Console handoff.

Wiring Cursor to Apify MCP

Apify hosts MCP at https://mcp.apify.com with OAuth on first connect — no API token in your config file. In .cursor/mcp.json:

Code (json)

{

"mcpServers": {

"apify": {

"url": "https://mcp.apify.com/?tools=fetch-actor-details,kondasviktor/cee-dach-fuel-intelligence"

}

}

}

That pin matches the snippet on the Actor's Store page: fetch-actor-details plus the Actor id. For day-to-day exploration I also use a broader pin (actors,docs,...) so the agent can search Store and read docs; for a fleet assistant that always calls this Actor, the narrow pin skips a search round trip.

Reload Cursor, sign in to Apify when prompted, then enable the server under Settings → Tools & MCP.

Cursor MCP configuration in mcp.json showing the Apify server URL with the cee-dach-fuel-intelligence Actor pinned as a tool

Cursor MCP tool list for the Apify server — all tools enabled including kondasviktor/cee-dach-fuel-intelligence

Typical discovery path: search-actors → fetch-actor-details (read the input schema (https://docs.apify.com/platform/actors/development/actor-definition/input-schema)) → call-actor. The input schema tells the agent required fields (mode, lat/lng for point, origin/destination for route). Apify can also infer output shapes from recent successful runs; my Actor's dataset schema documents row types (station-price, route-recommendation, run-status). The Actor output schema in Console is mainly deep links (dataset, runOverview) — field typing for agents comes from input + dataset schemas, not from those links alone.

Three real agent runs (August 2026)

I did not tell the agent "run my fuel Actor." I asked logistics questions and let it resolve the tool through MCP. All runs below are cloud runs via Apify MCP; run IDs are real. Origin in Console shows MCP.

A — Vienna petrol, key-free official data

Prompt: I need current petrol prices near central Vienna for a delivery van, and tell me how reliable that data is.

Input the agent constructed:

Code (json)

{

"mode": "point",

"countries": ["AT"],

"lat": 48.2082,

"lng": 16.3738,

"radiusKm": 8,

"fuel": "petrol95",

"maxStations": 8,

"enableAIDigest": false

}

Run: chJk2dCvN45DnWGKC · SUCCEEDED in 4.5s · 10 dataset items · countryStatus.AT: ok

Cheapest row returned:

Code (json)

{

"recordType": "station-price",

"stationId": "1494440",

"brand": "TMC",

"name": "TMC Werkstatt & Tankstelle",

"country": "AT",

"city": "Wien",

"petrol95": 1.669,

"petrol95Eur": 1.669,

"priceEur": 1.669,

"currency": "EUR",

"dataConfidence": "OFFICIAL_REALTIME",

"source": "E-Control Spritpreisrechner",

"sourceStatus": "OK"

}

Agent conclusion: TMC at €1.669 is the cheapest nearby petrol95; data is official E-Control realtime, but cheapest-N — not every Austrian station.

Scenario A run detail in Apify Console — run chJk2dCvN45DnWGKC, SUCCEEDED, 10 results, origin MCP

Scenario A dataset — Vienna petrol95 station-price rows sorted by price, TMC first at €1.669, all with OFFICIAL_REALTIME confidence

B — Budapest→Vienna diesel route

Prompt: I'm driving a diesel van from Budapest to Vienna today. Where should I stop to fill up without a big detour, and how sure are you about that price?

Input (Fuelo key redacted — buyers supply their own):

Code (json)

{

"mode": "route",

"countries": ["HU", "AT"],

"originLat": 47.4979,

"originLng": 19.0402,

"destinationLat": 48.2082,

"destinationLng": 16.3738,

"fuel": "diesel",

"maxDetourKm": 20,

"maxStations": 25,

"radiusKm": 15,

"enableAIDigest": false,

"fueloApiKey": "YOUR_FUELO_KEY"

}

Run: mT6vN8TOsCG7P5d6s · SUCCEEDED in 12.9s · 39 items · one route-recommendation row

Code (json)

{

"recordType": "route-recommendation",

"stationId": "fuelo:HU:36084",

"brand": "Slovnaft",

"country": "HU",

"city": "Bratislava",

"fuel": "diesel",

"price": 1.706,

"priceEur": 1.706,

"currency": "EUR",

"distanceFromOriginKm": 158.38,

"estimatedDetourKm": 1.03,

"dataConfidence": "COMMUNITY_AGGREGATOR_UNVERIFIED"

}

Agent conclusion: Cheapest stop within ~1 km detour is Slovnaft diesel at €1.706 — but the agent must hedge: COMMUNITY_AGGREGATOR_UNVERIFIED, not official regulator data. The same run also returned OFFICIAL_REALTIME E-Control rows for AT segments; mixing tiers in one answer without naming confidence would be wrong.

This is token economics in practice: route mode returns one recommendation record plus bounded station rows (maxStations), not an unbounded national dump. MCP still returns full field lists per row — context control is the Actor's modes and caps, not automatic truncation in MCP.

Scenario B dataset — Budapest-Vienna route, row 42 is the route-recommendation: Slovnaft Bratislava, diesel €1.706, ~1.03 km detour

C — Poland without a Fuelo key (honest failure)

Prompt: Current diesel near central Warsaw — same kind of answer as Vienna.

Input: point query for PL — fueloApiKey deliberately omitted even though I have a key locally.

Code (json)

{

"mode": "point",

"countries": ["PL"],

"lat": 52.2297,

"lng": 21.0122,

"radiusKm": 10,

"fuel": "diesel",

"maxStations": 10,

"enableAIDigest": false

}

Run: RJCT720eHg3INuOzg · SUCCEEDED in 3.6s · 0 stations · 1 run-status row

Code (json)

{

"recordType": "run-status",

"countryStatus": { "PL": "missing_fuelo_key" },

"stationCount": 0,

"okStationCount": 0,

"notes": [

"PL: PL uses Fuelo.net (BYOK). Set input fueloApiKey from https://fuelo.net/about/api\_key\_request — we never use platform keys."

]

}

Agent conclusion: Polish station data is unavailable until the caller supplies a Fuelo key — do not invent Warsaw diesel prices. Honest failure beats silent emptiness: the run succeeded, the status says why it is empty.

Scenario C run detail — run RJCT720eHg3INuOzg, SUCCEEDED with 1 result, origin MCP, completed in 3 seconds

Scenario C Actor log — ERROR line shows PL uses Fuelo.net BYOK, followed by INFO: stations=0, status=missing_fuelo_key

What MCP removed

Without MCP, a human (or agent pretending to be one) does this:

  1. Open Apify Console
  2. Find the Actor
  3. Fill input JSON
  4. Start run
  5. Wait
  6. Open dataset
  7. Export or copy rows
  8. Paste into chat
  9. Ask the model to recommend

With MCP, steps 2–8 collapse into one agent turn: recognize live fuel data is needed → fetch-actor-details → call-actor → reason on structured rows. The manual handoff disappears.

Design choices that mattered for agents

A few patterns I would steal for any live-data Actor meant for MCP:

Put provenance on the row, not only in the README. dataConfidence, source, and sourceMessage ride on every station row so the agent can hedge inside one tool call. A spreadsheet buyer scrolls to methodology; an agent cannot.

Keep regulation in dated config, not model memory. regulatoryFlags come from JSON (lastVerified: 2026-07-24). The optional ai-digest PPE event stays off the hot path — narrative over aggregates is for scheduled batch runs, not every "where's cheapest" query.

Keep the core deterministic. Currency normalization, detour math, and cheapest-stop selection stay in TypeScript. Let the agent interpret; do not let it calculate pump prices.

Name fields so a model can infer meaning. estimatedDetourKm and missing_fuelo_key beat internal enums like tier2.

Design for agent call patterns. One route call beats eleven country calls. Cap maxStations. Batch countries in a single input when the schema allows it.

What broke while building this

  • AT always needs coordinates. Even country mode feeds a national centroid into E-Control's lat/lng search — there is no "whole country" dump.
  • asOfDate on E-Control throws explicitly when asOfDateStrict=true instead of silently serving live prices as history.
  • Tankerkönig free tier caps radius at 25 km — the input schema enforces the same cap.
  • Missing BYOK must not look like "no stations exist." Poland without fueloApiKey → missing_fuelo_key, not an empty success story.
  • Early MCP pins that only exposed call-actor without fetch-actor-details made the agent guess required fields. Pinning the Actor and details fixed that.

What I would do differently

I would ship the dataset schema and dataConfidence enums before the first public Store listing. I added provenance after watching an agent treat a Fuelo community price like E-Control realtime. I would also document MCP pin recipes on the Store page earlier — the narrow pin vs broad actors,docs pin is not obvious from Console alone.

Next on my list: richer route geometry than haversine (road network detours), and clearer city/country consistency checks when a community feed returns odd location labels.

Takeaways

One Actor now serves Console, API, schedules, automation tools — and agents via MCP. Fuel was the forcing function because bad answers are costly; the pattern generalizes to any live, messy, geographic dataset (EV charging, truck parking, tolls): normalize in the Actor, expose provenance on every row, keep math deterministic, let MCP handle the call, let the agent handle the question.

Try it: European Fuel Station Prices API (CEE + DACH) (https://apify.com/kondasviktor/cee-dach-fuel-intelligence) — AT/SI/HR/RO need no key. MCP setup: Apify MCP docs (https://docs.apify.com/platform/integrations/mcp).

Viktor Kondas · [Apify profile](https://apify.com/kondasviktor) · Built in Cursor with Apify MCP

Top comments (0)