Building an MCP Server for eSIM Comparison: Giving AI Assistants Live Data on esimkenya.com A developer built esimkenya-mcp, a read-only Model Context Protocol server that exposes live eSIM provider, plan, pricing, and safari-park coverage data from esimkenya.com to AI assistants. The server, live at mcp.esimkenya.com and listed in the official MCP Registry as com.esimkenya/mcp, ships three tools — list_esim_providers, get_esim_provider, and compare_esim_providers — and runs statelessly as a Cloudflare Worker using the @modelcontextprotocol/sdk. Each provider response includes a trackable affiliate link so assistants return real URLs instead of hallucinated ones. A few months ago I noticed a pattern: ask any AI assistant "what's the best eSIM for a Kenya safari" and you'd get a plausible-sounding but stale answer — provider names that no longer offer that plan, prices that were right in 2024, zero mention of which network actually holds up inside Maasai Mara. The assistant wasn't wrong to try. It just had no way to look. So I built esimkenya-mcp: a read-only MCP server that exposes esimkenya.com's https://esimkenya.com live provider, plan, pricing, and coverage data directly to AI assistants. It's live at https://mcp.esimkenya.com/mcp https://mcp.esimkenya.com/mcp and listed in the official MCP Registry as com.esimkenya/mcp. Here's how it's put together and why I made the choices I did. The problem: comparison content is exactly what LLMs get wrong esimkenya.com https://esimkenya.com already does the hard part — it tracks 8–12 eSIM providers for Kenya, tests them against real safari-park coverage Maasai Mara, Amboseli, Samburu, Nairobi, the coast , and scores them on network quality, price per GB, and activation speed. Safaricom versus Airtel isn't a trivia detail here — it's the difference between having signal at your lodge gate and having none for four days. None of that is the kind of thing an LLM should be reciting from memory. Prices change, providers get delisted, coverage notes get updated after a testing pass. What an assistant actually needs is a way to ask the site directly instead of guessing — and to hand the user a real, working affiliate link instead of a made-up one. That's the whole pitch for MCP here: don't teach the model eSIM trivia, give it a tool. What the server exposes The server ships three tools, each mapping to something a traveler actually asks: list esim providers — search and filter providers by Kenya destination, badge, safari coverage, rating, or price. It can optionally enrich results with park-level coverage notes, so "which eSIM works in Amboseli" gets a real answer instead of a generic one. get esim provider — full detail and plan tiers for a single provider, looked up by slug. compare esim providers — this is the one I like most. If esimkenya.com already has a curated head-to-head verdict for a given pair of providers, it returns that. If not, it falls back to both providers' full details side by side and lets the model do the comparing. Every provider response carries an affiliate link in the shape https://esimkenya.com/go/{slug} https://esimkenya.com/go/%7Bslug%7D , so when an assistant recommends "Safari eSIM for the Mara," the link it hands back is a real, trackable one — not a hallucinated URL. Architecture: stateless by design The whole thing runs as a Cloudflare Worker fetch handler, built on McpServer and WebStandardStreamableHTTPServerTransport from @modelcontextprotocol/sdk. Roughly, the shape of it looks like this: typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { z } from "zod"; export default { async fetch request: Request, env: Env : Promise { const server = new McpServer { name: "esimkenya-mcp", version: "1.0.0" } ; server.registerTool "list esim providers", { description: "Search and filter eSIM providers for Kenya destinations", inputSchema: { destination: z.string .optional , safari coverage: z.boolean .optional , min rating: z.number .optional , max price: z.number .optional , }, }, async input = { const providers = await queryProviders env, input ; return { content: { type: "text", text: JSON.stringify providers } }; } ; // get esim provider and compare esim providers registered the same way const transport = new WebStandardStreamableHTTPServerTransport ; await server.connect transport ; return transport.handleRequest request ; }, }; The deliberate decision here was to keep it stateless. Every request creates its own server instance and handles itself independently — no Durable Object, no session storage, no coordination between requests. MCP servers can get session-heavy fast if you let them, but this one doesn't need to: there's no conversation state to track, just read-only lookups against a database. Stateless means it scales the same way any other Worker does, with none of the operational overhead of managing durable state. Data comes from the same Supabase project the main site uses, read through the public anon key. That key is RLS-gated and has no write access — it's the same key that's already shipped in the main site's browser bundle, so exposing it to the MCP server doesn't widen the attack surface at all. No service-role key anywhere near this thing. Local dev and testing Development is about as unremarkable as Workers development gets: bash npm install npm run dev wrangler dev, serves at http://localhost:8787 http://localhost:8787 npm run typecheck For actually poking at the tools before wiring up a real client, the MCP Inspector is what I used to sanity-check tool schemas and responses: bash npx @modelcontextprotocol/inspector@latest It's a small thing, but having a GUI to fire test calls at list esim providers and immediately see the JSON come back saved a lot of round-tripping through an actual LLM client during development. Shipping it Deployment is npm run deploy a thin wrapper over wrangler deploy , with SUPABASE URL and SUPABASE ANON KEY set as vars in wrangler.toml. Since the anon key is already public in the site's frontend bundle and RLS does the actual gatekeeping, there's no secret-management ceremony needed — it's safe to commit. Once it was live at mcp.esimkenya.com/mcp, I listed it in the official MCP Registry under com.esimkenya/mcp, which is what lets MCP-aware clients discover it without someone having to paste in a raw URL. What this actually unlocks The point isn't a novelty integration — it's that an assistant with this server connected can now answer "what's the best eSIM for a Maasai Mara safari that also covers Nairobi" with a real comparison, current pricing, and a working affiliate link, instead of politely making something up. For a niche, fast-moving comparison space like eSIM plans, that's the difference between a useful answer and a confidently wrong one. If you're sitting on a comparison or review site with structured data behind it, this pattern generalizes cleanly: keep the tool surface small three tools was enough here , keep the server stateless if you can, and reuse your existing read-only credentials instead of minting new ones. Repo: github.com/esimkenya/esimkenya-mcp MIT licensed Live endpoint: https://mcp.esimkenya.com/mcp https://mcp.esimkenya.com/mcp Site: esimkenya.com https://esimkenya.com