Somewhere between shipping Lueira's WhatsApp agent and wiring up an internal tool for our own ops team, I noticed we'd stopped asking "should this be a REST API or a dashboard?" and started asking "what tools does the AI client need?" It's a small shift in framing, but it's changed how we build almost everything that isn't a pure human-facing screen. This post is about noticing that pattern out loud, across three very different pieces of internal tooling.
1. The pattern we didn't notice we'd started
In an earlier post I wrote about how Lueira's WhatsApp AI agent works: not a model improvising against a booking calendar, but a small, strictly-typed set of tools — check_availability, create_booking, send_payment_link — that the model can call, each one validated against the real booking engine before anything commits. The point of that design wasn't the AI part; it was the discipline. Define a narrow surface of actions, type every input and output, and never let the model touch the database directly.
Once that discipline exists for one product, it turns out to be the right answer for problems that have nothing to do with WhatsApp or bookings. Model Context Protocol (MCP) — the open protocol from Anthropic — is essentially that same idea standardized: instead of building one bespoke integration between "the model" and "our system," you expose a set of typed tools over a common transport, and any MCP-compatible client can discover and call them.
2. Ringover: turning a telephony API into reusable tools
The clearest example is internal, not customer-facing. We use Ringover for calls, SMS, WhatsApp templates and contacts, and like most SaaS telephony providers it has a REST API that nobody at Maladeta wants to integrate against by hand every time someone needs to know "can you check who called us last week." So we built an MCP server for it — using the xmcp framework — exposing 26 curated tools across calls, AI call analytics, contacts, messaging, live-call control, and directory lookups.
Authentication is per-request: the server doesn't store anyone's Ringover credentials, it forwards the caller's own Ringover API key on each call. What that buys us in practice is that anyone with an MCP-compatible client can ask "how many missed calls did we get last week" or "text this customer that we're running late" and get an answer or an action, without anyone writing bespoke integration code. Here's a simplified version of what one tool looks like:
export const schema = {
to: z.string().describe("E.164 phone number of the recipient"),
message: z.string().max(1600).describe("SMS body to send"),
from_number_id: z.string().describe("Ringover number to send the SMS from"),
};
export const metadata = {
name: "ringover_send_sms",
description: "Send an SMS to a contact through Ringover",
};
export default async function ringoverSendSms(
{ to, message, from_number_id },
{ ringoverApiKey }
) {
const result = await ringoverClient(ringoverApiKey).sms.send({
to,
body: message,
number_id: from_number_id,
});
if (!result.ok) {
throw new Error(`Ringover rejected the SMS: ${result.error}`);
}
return { status: "sent", message_id: result.id };
}
3. Interactive widgets, not just data
The Generic Marketplace platform — the shared core behind RocRoi, KayakGrandTour and NaturaAdventour — pushed the pattern somewhere I didn't expect: past data and into UI. Its catalog MCP server doesn't only expose tools that return JSON; it also exposes "MCP App" widgets, like a product card or an availability calendar, that render directly inside an MCP-compatible client's own interface — Claude or ChatGPT, without either product having built a bespoke UI for it.
4. When MCP is the right answer, and when it isn't
A purely human-facing dashboard doesn't get better because you bolt an MCP server onto it. MCP earns its place specifically when an AI client needs to reliably take a real action or pull real data, and you'd rather define that once than build a bespoke integration every time.
Building an MCP server well takes exactly the discipline that building a good API always has. It's tempting to wrap every table in your database in a tool and call it "MCP-enabled," and that's a worse idea than not building one at all. What's changed for us isn't the discipline — it's the default. A year ago, "we need the AI to talk to this system" meant a one-off integration. Now it means: define a small set of typed tools, validate every call against the real system of record, and expose them over MCP.