I spent the last six weeks building an unofficial MCP server for Infomaniak — the Swiss hosting provider — that lets Claude (and any MCP client) drive web hosting, mail, kDrive, DNS, SSL certificates and AI tools from natural language. It's MIT, on npm as infomaniak-mcp-agent, runs locally over stdio. This post walks through what I learned, what's surprisingly hard, and what I'd do differently.
Repo: .
The first surprise: the public API is missing half of what the manager does
I started with the public Infomaniak API. Documented at . The cookie extraction is done with chrome-cookies-secure in memory only — nothing is written to disk.
The second surprise: Infomaniak's rate limit is shared per token
60 req/min sounds generous until you write a workflow that iterates over 50 domains and makes 3 calls each. You hit the limit in 30 seconds and Infomaniak starts returning 429 with a 60-second cool-off.
I implemented a token-bucket in src/throttle/:
class TokenBucket {
private tokens: number;
private readonly capacity: number;
private readonly refillPerMs: number;
private lastRefill = Date.now();
constructor(capacityPerMinute: number) {
this.capacity = capacityPerMinute;
this.tokens = capacityPerMinute;
this.refillPerMs = capacityPerMinute / 60_000;
}
async acquire(): Promise<void> {
while (this.tokens < 1) {
this.refill();
if (this.tokens < 1) await sleep(50);
}
this.tokens -= 1;
}
private refill(): void {
const now = Date.now();
this.tokens = Math.min(this.capacity, this.tokens + (now - this.lastRefill) * this.refillPerMs);
this.lastRefill = now;
}
}
Wrapped around every HTTP call. Workflows like audit_dns_zones now run reliably across 50+ domains, just slower (1 second per call instead of 100 ms — but they finish).
The third surprise: destructive operations need a confirmation dance
Claude is enthusiastic. Give it a tool called delete_site and a thread of context saying "let's clean up old test sites", and it will happily delete production.
The MCP spec has tool annotations (destructiveHint, idempotentHint) but they're hints — they don't enforce anything. I added a requireConfirmation wrapper:
// First call: returns a confirmation token, no destructive action yet.
delete_site({ host_id: 12345 })
// → { confirmation_token: "abc...", expires_in_seconds: 60, "what_will_happen": "Site 'legacy-corp.be' (123 files, 2 databases) will be deleted." }
// Second call (within 60s): actually deletes.
delete_site({ host_id: 12345, confirmation: "abc..." })
// → { deleted: true, host_id: 12345 }
The first call describes what's going to happen and returns. The LLM has to ask the human (or itself) "are you sure?" before the second call. The token expires after 60s. Multiple in-flight tokens per resource are allowed.
This pattern saved me from production accidents twice already during dogfooding.
The fourth surprise: MCP JSON Schema strictness varies across clients
zod-to-json-schema produces JSON Schema Draft 7. Anthropic API and Claude Desktop are happy with that. The MCP Inspector tool? Stricter. Some clients use Draft 2020-12 and reject exclusiveMinimum: true (Draft 4 syntax) — they want exclusiveMinimum: <number> (Draft 6+).
A community contributor (@ruffzy) sent a PR fixing this by targeting jsonSchema7 explicitly in zodToJsonSchema config. I merged it and shipped 0.8.2 within a day. Open source working as intended.
What's hard about a hosting-provider MCP that isn't obvious
Idempotency is the LLM's responsibility, but the tool author has to surface enough information. The
list_hostingstool returnsis_locked: bool— if I hid that, the LLM would happily try operations on locked hostings and fail. Verbose output is fine; surprise failures aren't.Pagination has to be invisible. Some Infomaniak endpoints page at 25 items, others at 50. The MCP tool always pages through everything and returns the merged list. Letting the LLM do pagination = it forgets, gets the first page only, and reasons over incomplete data.
Error shapes must be normalized. Infomaniak's public API returns
{error: {code, description}}. The manager-private API returns either that or{"errors": [{"code", "description"}]}or raw HTML on auth failure. I wroteInfomaniakErrorto flatten everything into a consistent{kind, code, message, raw}so tools can handle errors uniformly.Logs go to stderr, not stdout. stdio transport mixes JSON-RPC and arbitrary writes on stdout, so any
console.logcorrupts the protocol. I usepinowith stderr destination. If you build an MCP server, do this from day one.npx -yrequiresbinfield + shebang in your built JS. tsup config:
banner: { js: "#!/usr/bin/env node" }
And in package.json:
"bin": { "infomaniak-mcp-agent": "dist/server.js" }
Missing either and npx -y either fails silently or runs the wrong entry point.
What I'd do differently next time
Cookie-based manager auth is a maintenance debt. The session cookies expire every few hours. Users have to re-open the manager in Chrome to refresh them. A long-lived service account would be cleaner if Infomaniak ever ships one.
Reverse-engineering needs a version pinning strategy. The manager-private endpoints change without notice. I'd add a smoke-test workflow that hits a known set of endpoints daily and opens an issue when something 404s.
Start with tests, not tools. I built the tools first and added tests later. Inverted, I'd have caught the rate-limit issue 3 weeks earlier.
Make the README the install path. Anyone who lands on the npm page should be able to copy 3 lines and have it running in Claude Desktop. That's the win condition.
Try it
npx -y infomaniak-mcp-agent
You'll need an Infomaniak API token (.
If you're on Infomaniak and you hit a bug, open an issue with the exact tool call + response (sanitize tokens). I'll usually patch within a day.
If you're building an MCP server for your niche provider, the patterns above (token bucket, confirmation dance, error normalization, stderr-only logging) are reusable. The repo is MIT, fork it as a starting point.
⭐ if it saved you time. PRs welcome.
SOCIAL SHARE CARD GENERATOR