{"slug": "building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com", "title": "Building an MCP Server for eSIM Comparison: Giving AI Assistants Live Data on esimkenya.com", "summary": "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.", "body_md": "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.\n\nSo 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.\n\nThe problem: comparison content is exactly what LLMs get wrong\n\n[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.\n\nNone 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.\n\nThat's the whole pitch for MCP here: don't teach the model eSIM trivia, give it a tool.\n\nWhat the server exposes\n\nThe server ships three tools, each mapping to something a traveler actually asks:\n\nlist_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.\n\nget_esim_provider — full detail and plan tiers for a single provider, looked up by slug.\n\ncompare_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.\n\nEvery 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.\n\nArchitecture: stateless by design\n\nThe 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:\n\ntypescript\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\nimport { z } from \"zod\";\n\nexport default {\n\n  async fetch(request: Request, env: Env): Promise {\n\n    const server = new McpServer({ name: \"esimkenya-mcp\", version: \"1.0.0\" });\n\n```\nserver.registerTool(\n  \"list_esim_providers\",\n  {\n    description: \"Search and filter eSIM providers for Kenya destinations\",\n    inputSchema: {\n      destination: z.string().optional(),\n      safari_coverage: z.boolean().optional(),\n      min_rating: z.number().optional(),\n      max_price: z.number().optional(),\n    },\n  },\n  async (input) => {\n    const providers = await queryProviders(env, input);\n    return { content: [{ type: \"text\", text: JSON.stringify(providers) }] };\n  }\n);\n\n// get_esim_provider and compare_esim_providers registered the same way\n\nconst transport = new WebStandardStreamableHTTPServerTransport();\nawait server.connect(transport);\nreturn transport.handleRequest(request);\n```\n\n},\n\n};\n\nThe 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.\n\nData 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.\n\nLocal dev and testing\n\nDevelopment is about as unremarkable as Workers development gets:\n\nbash\n\nnpm install\n\nnpm run dev        # wrangler dev, serves at [http://localhost:8787](http://localhost:8787)\n\nnpm run typecheck\n\nFor 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:\n\nbash\n\nnpx @modelcontextprotocol/inspector@latest\n\nIt'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.\n\nShipping it\n\nDeployment 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.\n\nOnce 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.\n\nWhat this actually unlocks\n\nThe 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.\n\nIf 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.\n\nRepo: github.com/esimkenya/esimkenya-mcp (MIT licensed)\n\nLive endpoint: [https://mcp.esimkenya.com/mcp](https://mcp.esimkenya.com/mcp)\n\nSite: [esimkenya.com](https://esimkenya.com)", "url": "https://wpnews.pro/news/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com", "canonical_source": "https://dev.to/esimkenya/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-esimkenyacom-3j4n", "published_at": "2026-09-25 13:50:02+00:00", "updated_at": "2026-09-25 14:00:43.270996+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["esimkenya-mcp", "esimkenya.com", "Model Context Protocol", "Cloudflare Workers", "@modelcontextprotocol/sdk", "MCP Registry", "Safaricom", "Airtel"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com", "markdown": "https://wpnews.pro/news/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com.md", "text": "https://wpnews.pro/news/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com.txt", "jsonld": "https://wpnews.pro/news/building-an-mcp-server-for-esim-comparison-giving-ai-assistants-live-data-on-com.jsonld"}}