Als je veel WordPress-sites beheert, ben je altijd op zoek naar een nieuwe, makkelijke manier om minder tijd te besteden aan het openen van dashboards en het doorklikken van allerlei knoppen.
MCP’s (Model Context Protocols) hebben de laatste tijd veel aandacht gekregen, en we hebben besloten om te onderzoeken hoe MCP’s, in combinatie met de Kinsta API, een bureau kunnen helpen dat zoveel websites beheert.
In dit artikel laten we je een praktisch voorbeeld zien van het bouwen van een MCP server die AI assistenten zoals Claude verbindt met de Kinsta API om WordPress hostingtaken te beheren die bureaus dagelijks uitvoeren.
Wat we bouwen
We bouwen een MCP-server die een reeks tools beschikbaar stelt, zodat AI-assistenten acties kunnen uitvoeren zoals:
- Alle WordPress-sites onder je account weergeven
- Omgevingen voor een specifieke site weergeven
- De cache in een bepaalde omgeving wissen
- Een bestaande site klonen om een nieuwe op te zetten
- Bekijken welke plugins en thema’s verouderd of kwetsbaar zijn
- Plugin-updates activeren in specifieke omgevingen
Zodra de server is ingesteld, wordt deze verbonden met een MCP-host/client (in dit geval Claude for Desktop):

Let op hoe het verschillende tools aanroept en vervolgens het volgende antwoord teruggeeft:

Aan de slag
Voordat je aan de slag gaat met de code, is het handig om een paar basisprincipes te begrijpen over hoe MCP in deze opzet past.
Een MCP-server zit tussen een AI-assistent en een bestaande API in. Hij vervangt de API niet en verandert ook niets aan hoe deze werkt. In plaats daarvan stelt hij een set tools beschikbaar die de AI kan aanroepen wanneer dat nodig is. Elke tool is gekoppeld aan een specifieke actie, zoals het weergeven van sites of het leegmaken van de cache van je site.
Wanneer je een vraag stelt aan een AI-assistent, beslist deze of een van die tools relevant is. Als dat het geval is, roept de assistent de tool op via de MCP-server, de server communiceert met de API en het resultaat wordt teruggestuurd als een eenvoudig antwoord. Niets wordt automatisch uitgevoerd zonder jouw goedkeuring, en alleen de tools die je beschikbaar stelt, zijn beschikbaar.
In dit voorbeeld communiceert de MCP-server met de Kinsta API en stelt een beperkte set WordPress-hostingacties beschikbaar. Er is geen aangepaste gebruikersinterface, automatisering op de achtergrond of speciale AI-configuratie nodig.
Vereisten
Om mee te kunnen doen, moet je een paar dingen hebben:
- Een stabiele Node.js-installatie
- Basisvaardigheid in TypeScript
- Een Kinsta-account met ingeschakelde API-toegang
- Je Kinsta API-sleutel en bedrijfs-ID
Je hebt geen eerdere ervaring met MCP nodig en je hoeft geen AI-model te bouwen of te trainen. We richten ons alleen op het aan elkaar koppelen van bestaande tools.
Het project opzetten
Begin met het aanmaken van een nieuwe map voor het project en het initialiseren van een Node.js-app:
mkdir kinsta-mcp
cd kinsta-mcp
npm init -y
Installeer vervolgens de MCP SDK en de kleine set dependencies die we gebruiken:
npm install @modelcontextprotocol/sdk zod@3
npm install -D typescript @types/node
Maak een basisstructuur voor het project:
mkdir src
touch src/index.ts
Werk vervolgens je package.json bij, zodat Node de gebouwde server kan draaien:
{
"name": "kinsta-mcp-server",
"version": "1.0.0",
"description": "MCP-server voor het beheren van WordPress-sites via de Kinsta API",
"type": "module",
"scripts": {
"build": "tsc"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.24.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.0.0"
}
}
Voeg ten slotte een tsconfig.json toe aan de hoofdmap van het project:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Nu dat geregeld is, ben je klaar om de MCP-server zelf te bouwen.
De MCP-server bouwen
Nu het project is opgezet, is het tijd om de MCP-server zelf te bouwen.
We beginnen met het importeren van de benodigde pakketten en het aanmaken van de serverinstantie. Vervolgens voegen we een kleine helper toe om met de API te communiceren. Daarna registreren we tools die direct koppelen aan WordPress-hostingacties.
Pakketten importeren en de server aanmaken
Open src/index.ts en voeg bovenaan het bestand de volgende imports toe:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
Deze doen drie dingen:
McpServeris de kernserver die tools registreert en verzoeken van de AI-client afhandeltStdioServerTransportzorgt ervoor dat de server kan communiceren via standaard input/output, wat de manier is waarop de meeste desktop-AI-clients verbinding makenzodwordt gebruikt om de invoer te definiëren en te valideren die elke tool accepteert
Definieer vervolgens een paar constanten voor de API en inloggegevens:
const KINSTA_API_BASE = "https://api.kinsta.com/v2";
const KINSTA_API_KEY = process.env.KINSTA_API_KEY;
const KINSTA_COMPANY_ID = process.env.KINSTA_COMPANY_ID;
Maak nu de MCP-serverinstantie aan:
const server = new McpServer({
name: "kinsta",
version: "1.0.0",
});
De naam is hoe de server wordt weergegeven in een MCP-client. De versie is optioneel, maar handig zodra je gaat itereren.
Een helper toevoegen voor API-verzoeken
De meeste tools die we bouwen, moeten HTTP-verzoeken naar de API sturen. In plaats van die logica overal te herhalen, maak je één helperfunctie. Voeg dit toe onder de serverconfiguratie:
async function kinstaRequest(
endpoint: string,
options: RequestInit = {}
): Promise {
const url = `${KINSTA_API_BASE}${endpoint}`;
const headers = {
Authorization: `Bearer ${KINSTA_API_KEY}`,
"Content-Type": "application/json",
...options.headers,
};
const response = await fetch(url, { ...options, headers });
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Kinsta API-fout (${response.status}): ${errorText}`);
}
return response.json() as Promise;
}
De uitvoering van tools implementeren
Tools zijn het belangrijkste wat een MCP-server aanbiedt. Elke tool is een functie die een AI-assistent, met jouw goedkeuring, kan aanroepen om een specifieke taak uit te voeren.
In deze server volgt elke tool dezelfde structuur:
- Een toolnaam (zoals
list_sites) - Een korte beschrijving (dit helpt de assistent te weten wanneer hij deze moet gebruiken)
- Een invoerschema (zodat de tool alleen met geldige invoer werkt)
- Een handlerfunctie (waarbij we de API aanroepen en de uitvoer opmaken)
We formatteren reacties bewust als platte tekst. AI-assistenten werken het beste wanneer tools duidelijke, leesbare uitvoer retourneren in plaats van ruwe JSON te dumpen.
Tool 1: Sites weergeven
Deze tool haalt alle WordPress-sites op onder je bedrijfsaccount. Dit is meestal het eerste wat je nodig hebt als je met meerdere sites werkt, aangezien de meeste andere acties beginnen met een site-ID.
Het API-antwoord bevat basisinformatie over elke site, dus we definiëren een eenvoudige structuur om mee te werken:
interface Site {
id: string;
name: string;
display_name: string;
status: string;
site_labels: Array;
}
interface ListSitesResponse {
company: {
sites: Site[];
};
}
Nu dat geregeld is, kunnen we de tool registreren:
server.registerTool(
"list_sites",
{
description:
"Haal alle WordPress-sites voor je bedrijf op. Geeft site-ID's, namen en status terug.",
inputSchema: {},
},
async () => {
const data = await kinstaRequest(
`/sites?company=${KINSTA_COMPANY_ID}`
);
const sites = data.company.sites;
if (!sites || sites.length === 0) {
return {
content: [
{ type: "text", text: "Geen sites gevonden voor dit bedrijf." }
],
};
}
const siteList = sites
.map((site) => {
const labels =
site.site_labels?.map((l) => l.name).join(", ") || "none";
return `• ${site.display_name} (${site.name})
ID: ${site.id}
Status: ${site.status}
Labels: ${labels}`;
})
.join("\n\n");
return {
content: [
{
type: "text",
text: `Found ${sites.length} site(s):\n\n${siteList}`,
},
],
};
}
);
Deze tool heeft geen invoer nodig, dus het invoerschema is leeg. Binnen de handler roepen we de API aan, controleren we of het resultaat leeg is en formatteren we het antwoord vervolgens als leesbare tekst.
In plaats van ruwe JSON terug te sturen, geven we een korte samenvatting die goed werkt in een chatinterface. Dit maakt het voor een AI-assistent makkelijk om vragen te beantwoorden als “Welke sites heb ik?” of “Laat me al mijn WordPress-sites zien” zonder extra parseren.
Tool 2: Omgevingen ophalen
Zodra je een site-ID hebt, is de volgende gebruikelijke stap het controleren van de omgevingen. Deze tool geeft alle omgevingen voor een bepaalde site weer, inclusief live-, test- en premium testomgevingen.
interface Environment {
id: string;
name: string;
display_name: string;
is_premium: boolean;
primaryDomain?: {
id: string;
name: string;
};
container_info?: {
php_engine_version: string;
};
}
interface GetEnvironmentsResponse {
site: {
environments: Environment[];
};
}
Sommige velden zijn optioneel, zoals het primaire domein of de PHP-versie, dus die zijn ook zo gemarkeerd. De tool zelf heeft alleen de site-ID nodig:
server.registerTool(
"get_environments",
{
description:
"Haal omgevingen (live, staging) op voor een specifieke site. Hiervoor is de site-ID nodig.",
inputSchema: {
site_id: z.string().describe("De site-ID waarvoor je omgevingen wilt ophalen"),
},
},
async ({ site_id }) => {
const data = await kinstaRequest(
`/sites/${site_id}/environments`
);
const envs = data.site.environments;
if (!envs || envs.length === 0) {
return {
content: [
{ type: "text", text: "Geen omgevingen gevonden voor deze site." }
],
};
}
const envList = envs
.map((env) => {
const domain = env.primaryDomain?.name || "Geen domein";
const php = env.container_info?.php_engine_version || "Onbekend";
const type = env.is_premium
? "Premium Staging"
: env.name === "live"
? "Live"
: "Staging";
return `• ${env.display_name} (${type})
ID: ${env.id}
Domein: ${domain}
PHP: ${php}`;
})
.join("\n\n");
return {
content: [
{
type: "text",
text: `${envs.length} omgeving(en) gevonden:\n\n${envList}`,
},
],
};
}
);
Dit is meestal de volgende stap voordat je acties uitvoert zoals het leegmaken van de cache, het klonen van een site of het updaten van plugins.
Tool 3: Site-cache leegmaken
Het leegmaken van de cache is een routinetaken, maar het is ook een asynchrone bewerking. Wanneer je dit activeert, reageert de API onmiddellijk met een bewerkings-ID, terwijl het leegmaken van de cache op de achtergrond doorgaat.
Hier is de typedefinitie en de toolfunctie:
interface OperationResponse {
operation_id: string;
message: string;
status: number;
}
server.registerTool(
"clear_site_cache",
{
description:
"Wis de cache voor een site-omgeving. Vereist de omgeving-ID.",
inputSchema: {
environment_id: z
.string()
.describe("De omgeving-ID waarvoor de cache moet worden gewist"),
},
},
async ({ environment_id }) => {
const data = await kinstaRequest(
"/sites/tools/clear-cache",
{
method: "POST",
body: JSON.stringify({ environment_id }),
}
);
return {
content: [
{
type: "text",
text: `Cache leegmaken gestart!
Operation ID: ${data.operation_id}
Message: ${data.message}
Gebruik get_operation_status om de voortgang te controleren.`,
},
],
};
}
);
In plaats van te wachten tot de bewerking klaar is, geeft de tool meteen de bewerkings-ID terug. Zo blijft de interactie snel en kan de AI-assistent later indien nodig verdergaan.
Tool 4: Site klonen
Het klonen van een site is een van die acties die bureaus voortdurend uitvoeren, vooral wanneer ze met templates werken of nieuwe sites voor klanten opzetten. In plaats van helemaal opnieuw te beginnen, neem je een bestaande omgeving en maak je daar een nieuwe site op basis van.
Het antwoord gebruikt dezelfde bewerkingsvorm die we eerder zagen, dus je hoeft deze niet opnieuw te definiëren. De tool heeft een weergavenaam nodig voor de nieuwe site en de omgevings-ID waarvan gekloond moet worden:
server.registerTool(
"clone_site",
{
description:
"Kloon een bestaande site-omgeving om een nieuwe site te maken. Ideaal om nieuwe klantensites op te zetten vanuit een sjabloon.",
inputSchema: {
display_name: z
.string()
.describe("Naam voor de nieuwe gekloonde site"),
source_env_id: z
.string()
.describe("De omgevings-ID waarvan gekloond moet worden"),
},
},
async ({ display_name, source_env_id }) => {
const data = await kinstaRequest(
"/sites/clone",
{
method: "POST",
body: JSON.stringify({
company: KINSTA_COMPANY_ID,
display_name,
source_env_id,
}),
}
);
return {
content: [
{
type: "text",
text: `Kloon van de site gestart!
Nieuwe site: ${display_name}
Operatie-ID: ${data.operation_id}
Bericht: ${data.message}
Gebruik get_operation_status om de voortgang te controleren.`,
},
],
};
}
);
Deze tool is vooral handig in combinatie met andere tools. Een AI-assistent kan bijvoorbeeld een site klonen en vervolgens meteen de omgevingen weergeven of de status van plugins controleren zodra de bewerking is voltooid.
Tool 5: Bewerkingsstatus ophalen
Omdat sommige acties asynchroon worden uitgevoerd, hebben we een manier nodig om de voortgang te controleren. Daar is deze tool voor bedoeld.
interface OperationStatusResponse {
status?: number;
message?: string;
}
server.registerTool(
"get_operation_status",
{
description:
"De status van een asynchrone bewerking controleren (cache leegmaken, site klonen, enz.)",
inputSchema: {
operation_id: z
.string()
.describe("De bewerkings-ID die je wilt controleren"),
},
},
async ({ operation_id }) => {
const response = await fetch(
`${KINSTA_API_BASE}/operations/${encodeURIComponent(operation_id)}`,
{
headers: {
Authorization: `Bearer ${KINSTA_API_KEY}`,
},
}
);
const data: OperationStatusResponse = await response.json();
if (response.status === 200) {
return {
content: [
{
type: "text",
text: `Operatie succesvol voltooid!
Message: ${data.message || "Bewerking voltooid"}`,
},
],
};
}
if (response.status === 202) {
return {
content: [
{
type: "text",
text: `Bewerking nog bezig...
Bericht: ${data.message || "Bezig"}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Status van de bewerking: ${response.status}
Bericht: ${data.message || "Onbekende status"}`,
},
],
};
}
);
Tool 6: Plugins op alle sites bekijken
Als je veel WordPress-sites beheert, zijn plugins vaak de plek waar het misgaat. Deze tool lost dat op door naar plugins in het hele bedrijfsaccount te kijken, in plaats van per site.
De API geeft veel informatie terug, zoals in welke omgevingen elke plugin is geïnstalleerd, of er updates beschikbaar zijn en of een versie als kwetsbaar is gemarkeerd. Om met die gegevens te werken, definiëren we de volgende shapes:
interface PluginEnvironment {
id: string;
site_display_name: string;
display_name: string;
plugin_status: string;
plugin_update: string | null;
plugin_version: string;
is_plugin_version_vulnerable: boolean;
plugin_update_version: string | null;
}
interface Plugin {
name: string;
title: string;
latest_version: string | null;
is_latest_version_vulnerable: boolean;
environment_count: number;
update_count: number;
environments: PluginEnvironment[];
}
interface GetPluginsResponse {
company: {
plugins: {
total: number;
items: Plugin[];
};
};
}
De tool zelf heeft geen invoer nodig:
server.registerTool(
"get_plugins",
{
description:
"Haal alle WordPress-plugins op voor alle sites. Laat zien welke plugins updates beschikbaar hebben of beveiligingslekken vertonen.",
inputSchema: {},
},
async () => {
const data = await kinstaRequest(
`/company/${KINSTA_COMPANY_ID}/wp-plugins`
);
const plugins = data.company.plugins.items;
if (!plugins || plugins.length === 0) {
return {
content: [
{ type: "text", text: "Geen plug-ins gevonden." }
],
};
}
const sorted = [...plugins].sort(
(a, b) => b.update_count - a.update_count
);
const pluginList = sorted.slice(0, 20).map((plugin) => {
const status =
plugin.update_count > 0
? `⚠️ ${plugin.update_count} site(s) moeten worden bijgewerkt`
: "✅ Up-to-date";
const kwetsbaar =
plugin.is_latest_version_vulnerable ? " 🔴 KWETSBAAR" : "";
return `• ${plugin.title} (${plugin.name})${kwetsbaar}
Laatste versie: ${plugin.latest_version || "onbekend"}
Geïnstalleerd op: ${plugin.environment_count} omgeving(en)
${status}`;
}).join("\n\n");
const outdatedCount = plugins.filter(
(p) => p.update_count > 0
).length;
return {
content: [
{
type: "text",
text: `${data.company.plugins.total} plug-ins gevonden (${outdatedCount} hebben updates beschikbaar):\n\n${pluginList}`,
},
],
};
}
);
Tool 7: Zoek thema’s op alle sites
Thema’s hebben soortgelijke problemen als plugins, maar ze worden vaak nog minder vaak gecontroleerd. Deze tool werkt op dezelfde manier als de plugin-tool, maar richt zich in plaats daarvan op WordPress-thema’s.
De responsstructuur is hetzelfde als bij het plugin-endpoint, maar dan met themaspecifieke velden:
interface ThemeEnvironment {
id: string;
site_display_name: string;
display_name: string;
theme_status: string;
theme_update: string | null;
theme_version: string;
is_theme_version_vulnerable: boolean;
theme_update_version: string | null;
}
interface Theme {
name: string;
title: string;
latest_version: string | null;
is_latest_version_vulnerable: boolean;
environment_count: getal;
update_count: getal;
environments: ThemeEnvironment[];
}
interface GetThemesResponse {
company: {
themes: {
total: getal;
items: Theme[];
};
};
}
Tool 8: Plugin updaten
Problemen opsommen is handig, maar uiteindelijk moet je ze oplossen. Met deze tool kun je een specifieke plug-in in een specifieke omgeving updaten.
Het update-endpoint retourneert dezelfde asynchrone bewerking als eerder gebruikt, dus dat kunnen we overslaan. Hier is de definitie van de tool:
server.registerTool(
"update_plugin",
{
description:
"Een specifieke plug-in updaten naar een nieuwe versie in een site-omgeving.",
inputSchema: {
environment_id: z
.string()
.describe("De omgeving-ID waar de plug-in is geïnstalleerd"),
plugin_name: z
.string()
.describe("De naam/slug van de plug-in (bijv. 'akismet', 'elementor')"),
update_version: z
.string()
.describe("De versie waarnaar je wilt updaten (bijv. '5.3')"),
},
},
async ({ environment_id, plugin_name, update_version }) => {
const data = await kinstaRequest(
`/sites/environments/${environment_id}/plugins`,
{
method: "PUT",
body: JSON.stringify({
name: plugin_name,
update_version,
}),
}
);
return {
content: [
{
type: "text",
text: `Plugin-update gestart!
Plugin: ${plugin_name}
Doelversie: ${update_version}
Operatie-ID: ${data.operation_id}
Bericht: ${data.message}
Gebruik get_operation_status om de voortgang te controleren.`,
},
],
};
}
);
Net als het leegmaken van de cache en het klonen van sites, worden updates asynchroon uitgevoerd. Door de bewerkings-ID terug te geven, kan de AI-assistent de voortgang volgen in plaats van aan te nemen dat de update direct is voltooid.
De server starten
Nu alle tools zijn geregistreerd, is de laatste stap het starten van de MCP-server en deze beschikbaar maken voor een AI-client.
Voeg onderaan je bestand de main-functie toe die de server verbindt via het STDIO-transport:
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Kinsta MCP-server draait op stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
Dit vertelt de MCP-server om te luisteren naar verzoeken via standaard invoer en uitvoer. Hierdoor wordt de server vindbaar voor MCP-compatibele desktopclients.
Een belangrijk detail hierbij is loggen. Omdat deze server via STDIO communiceert, moeten alle logs naar stderr gaan. Schrijven naar stdout kan MCP-berichten verstoren en de verbinding verbreken.
Bouw vervolgens het project:
npm run build
Dit compileert de TypeScript-bestanden naar de build-map en maakt het startpunt uitvoerbaar.
Zodra de build is voltooid, is de server klaar om te worden gestart door een MCP-client. Je kunt de volledige code op GitHub vinden.
Je server testen met Claude for Desktop
Om je MCP-server te gebruiken, moet Claude for Desktop weten hoe deze te starten. Open het configuratiebestand van Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json
Maak het bestand aan als het nog niet bestaat. Als je VS Code gebruikt, kun je het rechtstreeks vanuit de terminal openen:
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Voeg in het bestand je MCP-server toe onder de sleutel mcpServers. Bijvoorbeeld:
{
"mcpServers": {
"kinsta": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/mcp-server-demo-kinsta-api/build/index.js"],
"env": {
"KINSTA_API_KEY": "je-api-sleutel-hier",
"KINSTA_COMPANY_ID": "je-bedrijfs-id-hier"
}
}
}
}
Deze configuratie vertelt Claude for Desktop dat er een MCP-server is met de naam kinsta, dat deze moet worden gestart met Node.js en dat het startpunt het gebouwde index.js-bestand is.
Zorg ervoor dat het pad verwijst naar het gecompileerde bestand in de build-map, niet naar de TypeScript-bron. Sla het bestand op en start Claude for Desktop opnieuw op.
De verbinding controleren
Zodra Claude opnieuw is opgestart, open je een nieuwe chat. Klik op het pictogram ‘ ‘ naast het invoerveld en beweeg vervolgens de muis over ‘Connectors‘. Je zou je MCP-server in de lijst moeten zien staan.

Zodra de server is verbonden, kun je hem meteen gaan gebruiken. Claude bepaalt welke tool hij gebruikt, geeft de benodigde invoer door en geeft het resultaat terug als platte tekst.

Een andere manier om met de tools te werken die je al hebt
Wat er op dit moment verandert, zijn niet de onderliggende tools. API’s blijven API’s. Hostingplatforms werken nog steeds op dezelfde manier. Wat verandert, is hoe we ermee omgaan.
AI-tools beginnen steeds minder op chatboxen te lijken en steeds meer op interfaces. Deze MCP-server is een klein voorbeeld van die verschuiving. Hij introduceert geen nieuwe mogelijkheden. Hij maakt bestaande mogelijkheden beschikbaar op een manier die aansluit bij hoe mensen daadwerkelijk werken.
Waar dit naartoe gaat, is aan jou. Je kunt het simpel en alleen-lezen houden. Je kunt meer automatisering toevoegen met goedkeuringen en veiligheidsmaatregelen. Of je kunt dezelfde server koppelen aan andere tools in je workflow.
Als je nieuwe tools en workflows zoals deze verkent, is een solide hostingbasis van groot belang. Het laatste wat je wilt, is tijd verliezen met het oplossen van downtime of prestatieproblemen in plaats van je sites te bouwen en te verbeteren.
Kinsta biedt managed hosting voor WordPress waarmee je sites betrouwbaar blijven draaien, zelfs als je offline bent. Bekijk onze hostingpakketten of neem contact op met ons verkoopteam om het juiste pakket voor jou te vinden.