Jimmy Crutchfield
8 June 2026
Part 2 in our MCP server series.
Earlier this year we launched an MCP server for Cito, our web hosting platform. We built it with a pretty simple goal: to let developers manage their Cito sites with theAI assistants they’re already using.
The Model Context Protocol (MCP) is a relatively new open standard that gives AI assistants a structured way to talk to external systems. Because the Protocol itself is so new, there’s not yet a “right” way to build MCP servers. As such, we’ve noticed that MCP implementations vary pretty widely across the board. Some run locally on your laptop and shell out to a CLI. Some sit remotely as a proxy to a SaaS API. And, some auto-generate themselves from an OpenAPI spec.
Here’s a deep dive into how we built our Cito MCP server, why we made the decisions we did, and what this means for you.
Our approach: trust before features
We’ve all heard the horror stories about AI assistants going off-piste – deleting files, dropping production databases, sending strange emails on someone’s behalf. These aren’t fiction; they’re a real consequence of LLMs being non-deterministic. Anyone shipping AI tooling against real systems has to take that seriously.
That shaped the entire build. We were conservative about scope, careful about prompting, and aggressive about guardrails. We’d rather ship something narrower that we can vouch for than something flashy that worries us. For us, trust always comes first.
A remote, tool-based MCP server
You can vary a few axes when building an MCP server:
- Local vs remote: does the server run on the user’s machine or as a hosted service?
- CLI wrapper vs API client: does it shell out to a command-line tool or call a backend API directly?
- Hand-built vs schema-generated: did the team curate the tools or did they auto-generate them from an API spec?
We chose a remote server so we could centralise authentication and access management. What we didn’t want was for our customers to hold long-lived API tokens on their laptops.
The Cito MCP server authenticates via OAuth against the ProStack portal; this is the same authentication path used by Cito itself, so we know it’s robust.
We also chose to declare each Cito API capability as an explicit MCP tool, rather than handing the AI a raw OpenAPI spec and letting it work things out. The AI sees a curated set of tools – cito_list_servers, cito_get_logs, cito_create_site, and so on – each with a description, a strict input schema, and tool annotations that hint at whether the call is read-only or destructive.
The interesting part is what happens next. We don’t try to match user intent to tools ourselves; the AI does that. If a customer says “my site is broken”, the model decides on its own to call cito_list_sites, then cito_list_logs, then cito_get_logs, and then reason about the output. We don’t need a rules engine for “user-said-broken-therefore-get-logs”; the model already knows that’s a reasonable thing to do, because it knows what server logs are and what they’re for.
There’s a useful side effect to this design: we get smarter as the models do. Every Claude release that improves debugging or infrastructure reasoning makes our MCP server more useful, with no code changes on our side.
That’s a much better leverage profile than trying to encode that intelligence ourselves.
Deployment: the one place we’re opinionated
Most of the tools we expose are wrappers around the Cito API. Deployment is the exception.
“Deploy this project to Cito” is a multi-step workflow where the AI needs to do several things in the right order, and we didn’t want to leave that to chance. That’s why we designed a specific pattern for deploying projects from your machines.
When you use the MCP to deploy projects to Cito, it:
- Detects the project type by inspecting the repository. Is it Laravel, WordPress, a Node app, a static site, or something else?
- Creates a Cito site with the matching deployment profile or uses an existing site if you’ve specified one.
- Generates an ephemeral SSH keypair via cito_deploy_init. The private key lives only in memory and is never written to disk.
- Rsyncs the project files to the site, with a sensible default exclude list –.git, node_modules, .env*, test directories, IDE config, build files like Dockerfile, READMEs, and so on.
- Runs framework-appropriate post-deploy commands. Commands like composer install, npm install – the obvious ones. The command set is whitelisted; arbitrary shell is not allowed.
- Returns a preview URL to you.
The AI is taught this flow through a combination of tool descriptions, a short deployment guide, and the structure of the cito_deploy_init / cito_deploy_finalize tool pair. This essentially makes the workflow self-documenting at every step.
Guardrails and security
When building our MCP server, we spent the most time figuring out which guardrails to put in place.
The shape of the problem looked look this: read-only tooling isn’t useful enough to ship – the whole point is to do things – but read-write tooling against production infrastructure is exactly where AI mistakes can do the most harm.
We had to balance utility with safety, something we’re well aware is a difficult thing to achieve.
Here’s just a handful of the guardrails we put in place:
- OAuth, not API tokens. The MCP server uses the ProStack portal as its OAuth authorisation server and so the AI client never sees a Cito API key. It gets a scoped, revocable token, and every tool call is mediated through the MCP server itself. This gives us a single chokepoint to enforce policy.
- Destructive operations require confirmation. Anything that deletes, overwrites, or otherwise mutates state in a way that’s hard to reverse will prompt you before executing. This is enforced in two places: in the prompting layer and via MCP tool annotations – specifically the destructiveHint and readOnlyHint fields. These let well-behaved AI assistants show a confirmation dialog automatically. Two layers means a single failure (like a prompt-injection or a misbehaving assistant) doesn’t bypass the safeguard.
- No arbitrary command execution. The deployment tools can only run a fixed allowlist of commands relevant to the framework being deployed. composer install is fine, but anything outside the list is rejected before it reaches a shell. This is a hard boundary.
- Ephemeral SSH credentials. Deployment keys are generated per-session, used for the rsync, and destroyed. They aren’t stored, they aren’t written to disk, and we don’t touch your local SSH keys.
- Filesystem scoping. The MCP server can’t read or write outside the /home tree on a Cito server. Even if a tool call asked it to, the underlying API would refuse.
None of these safeguards is sufficient alone. If there’s a single failure (a model hallucination, for example, or a bad prompt), the idea is that it hits at least one layer before it causes any damage.
Where do we go from here?
It’s been a really exciting few months for us and we don’t expect to slow down.
The Protocol itself is moving fast, with the MCP server specification being refined and new features being added all the time. We’re also still getting feedback from our customers using Cito’s MCP server in beta, which is shaping our future roadmap nicely.
If you’d like to read our thoughts a few months on from shipping this tool, check out part three of this blog series: Shipping the Cito MCP server: a retro
And don’t forget – if you’re a Cito customer and you’ve not yet tested this out for yourself, it’s really quick and easy to get started. Just head to the Cito MCP server and you’ll find instructions on connecting it to Claude Code, Claude Desktop, Cursor, or ChatGPT.



