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 live provider, plan, pricing, and coverage data directly to AI assistants. It's live at 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 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}, 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
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
Site: esimkenya.com