Building an Energy Comparator Designed to Be Read by AI Agents Before Humans
I run a small Italian energy tariff comparison site called SwitchAI. For a while the plan was completely ordinary: user uploads a PDF bill, server parses it, site shows cheaper offers. Then I tested that plan against ten real bills from five different Italian providers, and it fell apart in a way that ended up reshaping the whole product. Here's what happened, and what I think it says about building for a world where the "user" filling in your form is increasingly an LLM holding a PDF, not a person typing numbers.
The parsing problem that wasn't really a parsing problem
Italian energy bills are not a standard format. Enel, Octopus, A2A, NeN, and Eni Plenitude each lay out consumption, POD/PDR codes, and cost breakdowns differently enough that a regex-based parser trained on one provider silently breaks on another. My hosting made this worse: shared OVH hosting, no pdftotext, no Python runtime, no OCR. I could get a PHP-native parser working reasonably well on Enel bills. Octopus bills, structured completely differently, just didn't cooperate.
I ran the same ten PDFs through Claude, GPT, and Gemini as a sanity check before writing more parsing code. All three extracted consumption, POD, spend, and zone correctly, 10/10, across every provider format, with zero custom logic on my end. That was the moment the project's architecture changed. I stopped trying to out-parse the LLM at something it was already better at than my code, and started asking a different question: what does a product look like if the LLM is the input layer, and my job is just to be an excellent thing for it to call afterward?
Three front doors, one calculation engine
SwitchAI now has three ways in, all hitting the same tariff-comparison core:
User โ uploads bill to Claude/ChatGPT/Gemini
โ
LLM โ extracts consumption, cost, zone (and, separately, PII if activation is wanted)
โ
LLM โ calls SwitchAI:
POST /api/analyze (plain REST)
POST /mcp (MCP server, JSON-RPC 2.0)
WebMCP (in-browser agent tool)
โ
SwitchAI โ compares against 5,600+ ARERA-regulated offers โ top 3 + risk assessment
โ
LLM โ presents the result to the user in natural language
The REST endpoint, the MCP server, and the WebMCP integration all call the same PHP calculation engine underneath - I didn't want three implementations of ARERA's tariff math to drift out of sync with each other.
POST /api/analyze is the one I'd point any agent at: it collapses what used to be 2โ3 round trips into a single call and returns a compact, agent-shaped payload - top offers, a plain-language agent_summary, a savings breakdown, and a subscription_url ready to be handed back to the user.
Keeping personal data out of my own API
This is the part I think is actually generalizable to anyone building tools for agents, not just energy comparison sites. The tool never receives a name, address, or fiscal code. It only ever receives numbers - consumption, spend, zone. The LLM extracts the PII from the bill and holds onto it in its own context, and only uses it much later, client-side, to build a prefilled URL:
/sottoscrizione?tariff=ID&nome=Mario&cognome=Rossi&pod=IT001E...&consumi=2700
The tool descriptions carry the guardrails directly, since that's the only "documentation" an agent actually reads before acting:
- Reassure the user their data is used only for activation and isn't stored beyond the session
- Get explicit consent before including any personal field in a URL
- Never claim the activation is done - a double opt-in confirmation email is still required before anything reaches the provider
None of this is enforced by a schema or a validator. It's enforced by writing the tool description as if it were a very literal-minded new hire, because that's approximately what's calling it.
The endpoint that didn't exist
Here's the one that actually made me stop and double-check my own code: during a security pass over the API surface, I came across a confident, detailed description of a submit_subscription endpoint - the kind of description that reads like documentation, not speculation. It didn't exist anywhere in my routes, my codebase, or anything I'd actually shipped. It had been inferred, plausibly and with total conviction, because the rest of the flow made it look like it should exist.
Nothing exploitable came of it, but it's a preview of a genuinely new category of risk: not a model getting a fact wrong in an obviously-wrong way, but inventing a plausible piece of your own API surface with total confidence. If you're building anything agent-facing, verifying "does this actually exist in my code" against every confident claim about your own system - including claims that sound like careful analysis - is now part of the job.
Discovery, but for agents instead of search engines
Classic technical SEO still matters - canonical URLs, a real sitemap, noindex on thin auto-generated pages (SwitchAI has 373 indexed provider pages and deliberately zero indexed offer-detail pages, to avoid doorway-page penalties). But I've been treating a second, parallel discovery layer as equally important:
llms.txt- a plain-language description of the site for models that support itwebmcp.json+ registered WebMCP tools, so Chrome's in-browser agent tooling can find and call the site directlyopenapi.json, so ChatGPT's Actions (or anything else that consumes OpenAPI) can import the API in one steprobots.txtexplicitly allowingClaudeBot,GPTBot,Google-Extended,PerplexityBot, andanthropic-ai- rather than the default-deny a lot of boilerplate configs still ship with
None of this is complicated to add. Almost none of it is being done by comparable sites in this category yet, which is a strange kind of first-mover advantage that costs nothing but attention.
What I'd tell someone starting this today
- If an LLM is already excellent at part of your pipeline (unstructured extraction, in my case), stop building around that assumption being temporary. Build the seams instead.
- Keep personal data out of your tool's input surface wherever you can. Let the agent carry it, and only reunite it with your system at the last, human-verified step.
- Write your tool descriptions like a spec for a very capable but very literal new employee - because functionally, that's your audience for that specific piece of text.
- Assume an agent will occasionally invent an endpoint that sounds like it should exist. Have a clear, boring source of truth for what actually does.
- Treat
llms.txt/webmcp.json/openapi.jsonthe way you'd treatsitemap.xmla decade ago: not required, cheap to add, and increasingly where a real slice of your traffic will originate.
If you want to see the whole thing end to end: the site is at switchai.it, the MCP server is on npm and GitHub, and you can add it as a connector directly in Claude (Settings โ Connectors โ https://www.switchai.it/mcp). Happy to go deeper on any piece of this - the ARERA cost-calculation logic in particular has its own rabbit hole of regulatory edge cases I could write a whole separate post about.
Comments
No comments yet. Start the discussion.