When you arrange numerous WordPress websites, you might be at all times in search of the following simple solution to prohibit the period of time you spend gaining access to dashboards and clicking via a sequence of buttons.

MCPs (Style Context Protocols) were making numerous waves lately, and we made up our minds to discover how MCPs, along the Kinsta API may lend a hand an company managing such a lot of web sites.

On this article, we stroll you via a realistic instance of establishing an MCP server that connects AI assistants like Claude to the Kinsta API to control WordPress website hosting duties companies already carry out each day.

What we’re construction

We’re construction an MCP server that exposes a suite of equipment, so AI assistants can carry out movements like:

  • Record all WordPress websites underneath your account
  • Display environments for a selected web page
  • Transparent the cache in a given surroundings
  • Clone an present web page to spin up a brand new one
  • See which plugins and subject matters are out of date or susceptible
  • Cause plugin updates on particular environments

As soon as the server is ready up, it’s hooked up to an MCP host/shopper (on this case, Claude for Desktop):

Claude calling MCP tools to retrieve external data during a prompt.
Claude calling MCP equipment to retrieve exterior information throughout a urged.

Realize the way it calls quite a lot of equipment after which returns the next reaction:

Claude displaying a structured response generated from retrieved tool data.
Claude showing a structured reaction generated from retrieved software information.

Getting began

Prior to leaping into the code, it is helping to know a couple of fundamentals about how MCP suits into this setup.

An MCP server sits between an AI assistant and an present API. It doesn’t substitute the API or trade the way it works. As a substitute, it exposes a suite of equipment that the AI can name when wanted. Each and every software maps to a selected motion, like record websites or clearing your web page’s cache.

Whilst you ask a query in an AI assistant, it comes to a decision whether or not a kind of equipment is related. Whether it is, the assistant calls the software during the MCP server, the server talks to the API, and the result’s returned as a easy reaction. Not anything runs robotically with out your approval, and solely the equipment you reveal are to be had.

On this instance, the MCP server communicates with the Kinsta API and exposes a restricted set of WordPress website hosting movements. No customized UI, background automation, or particular AI setup is needed.

Necessities

To practice alongside, you want a couple of issues in position:

  • A strong Node.js setup
  • Fundamental familiarity with TypeScript
  • A Kinsta account with API get entry to enabled
  • Your Kinsta API key and corporate ID

You don’t want any prior revel in with MCP, and also you don’t wish to construct or educate an AI style. We center of attention solely on wiring present equipment in combination.

Putting in place the undertaking

Get started by way of growing a brand new listing for the undertaking and initializing a Node.js app:

mkdir kinsta-mcp
cd kinsta-mcp
npm init -y

Subsequent, set up the MCP SDK and the small set of dependencies we use:

npm set up @modelcontextprotocol/sdk zod@3
npm set up -D typescript @varieties/node

Create a fundamental undertaking construction:

mkdir src
contact src/index.ts

Then replace your package deal.json so Node can run the constructed server:

{
  "identify": "kinsta-mcp-server",
  "model": "1.0.0",
  "description": "MCP server for managing WordPress websites by way of the Kinsta API",
  "kind": "module",
  "scripts": {
    "construct": "tsc"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.24.0"
  },
  "devDependencies": {
    "@varieties/node": "^22.0.0",
    "typescript": "^5.0.0"
  }
}

After all, upload a tsconfig.json on the root of the undertaking:

{
  "compilerOptions": {
    "goal": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./construct",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "come with": ["src/**/*"],
  "exclude": ["node_modules"]
}

With that during position, you’re able to start out construction the MCP server itself.

Development the MCP server

Now that the undertaking is ready up, it’s time to construct the MCP server itself.

We commence by way of uploading the specified applications and growing the server example. Then upload a small helper for chatting with the API. After that, we sign up equipment that map immediately to WordPress website hosting movements.

Uploading applications and growing the server

Open src/index.ts and upload the next imports on the most sensible of the record:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

Those do 3 issues:

  • McpServer is the core server that registers equipment and handles requests from the AI shopper
  • StdioServerTransport we could the server keep in touch over same old enter/output, which is how maximum desktop AI purchasers attach
  • zod is used to outline and validate the enter each and every software accepts

Subsequent, outline a couple of constants for the API and credentials:

const KINSTA_API_BASE = "https://api.kinsta.com/v2";
const KINSTA_API_KEY = procedure.env.KINSTA_API_KEY;
const KINSTA_COMPANY_ID = procedure.env.KINSTA_COMPANY_ID;

Now create the MCP server example:

const server = new McpServer({
  identify: "kinsta",
  model: "1.0.0",
});

The identify is how the server seems within an MCP shopper. The model is not obligatory, however helpful when you get started iterating.

Including a helper for API requests

Lots of the equipment we construct wish to make HTTP requests to the API. Fairly than repeating that good judgment in every single place, create a unmarried helper serve as. Upload this under the server setup:

async serve as kinstaRequest(
  endpoint: string,
  choices: RequestInit = {}
): Promise {
  const url = `${KINSTA_API_BASE}${endpoint}`;
  const headers = {
    Authorization: `Bearer ${KINSTA_API_KEY}`,
    "Content material-Sort": "software/json",
    ...choices.headers,
  };

  const reaction = watch for fetch(url, { ...choices, headers });

  if (!reaction.good enough) {
    const errorText = watch for reaction.textual content();
    throw new Error(`Kinsta API error (${reaction.standing}): ${errorText}`);
  }

  go back reaction.json() as Promise;
}

Enforcing software execution

Gear are the primary factor an MCP server exposes. Each and every software is a serve as an AI assistant can name, along with your approval, to accomplish a selected process.

On this server, each and every software follows the similar construction:

  • A device identify (like list_sites)
  • A brief description (this is helping the assistant know when to make use of it)
  • An enter schema (so the software solely runs with legitimate enter)
  • A handler serve as (the place we name the API and layout the output)

We layout responses as simple textual content on objective. AI assistants paintings absolute best when equipment go back clean, readable output as an alternative of dumping uncooked JSON.

Instrument 1: Record websites

This software retrieves all WordPress websites underneath your corporation account. It’s typically the very first thing you wish to have when operating with more than one websites, since maximum different movements get started with a web page ID.

The API reaction comprises fundamental details about each and every web page, so we outline a easy form to paintings with:

interface Website {
  identification: string;
  identify: string;
  display_name: string;
  standing: string;
  site_labels: Array;
}

interface ListSitesResponse {
  corporation: {
    websites: Website[];
  };
}

With that during position, we will sign up the software:

server.registerTool(
  "list_sites",
  {
    description:
      "Get all WordPress websites in your corporation. Returns web page IDs, names, and standing.",
    inputSchema: {},
  },
  async () => {
    const information = watch for kinstaRequest(
      `/websites?corporation=${KINSTA_COMPANY_ID}`
    );

    const websites = information.corporation.websites;

    if (!websites || websites.duration === 0) {
      go back {
        content material: [
          { type: "text", text: "No sites found for this company." }
        ],
      };
    }

    const siteList = websites
      .map((web page) => {
        const labels =
          web page.site_labels?.map((l) => l.identify).sign up for(", ") || "none";

        go back `• ${web page.display_name} (${web page.identify})
  ID: ${web page.identification}
  Standing: ${web page.standing}
  Labels: ${labels}`;
      })
      .sign up for("nn");

    go back {
      content material: [
        {
          type: "text",
          text: `Found ${sites.length} site(s):nn${siteList}`,
        },
      ],
    };
  }
);

This software doesn’t require any enter, so the enter schema is empty. Throughout the handler, we name the API, take a look at for an empty consequence, after which layout the reaction as readable textual content.

As a substitute of returning uncooked JSON, we go back a brief abstract that works neatly in a talk interface. This makes it simple for an AI assistant to respond to questions like “What websites do I’ve?” or “Display me all my WordPress websites” with none further parsing.

Instrument 2: Get environments

Upon getting a web page ID, the following not unusual step is checking its environments. This software returns all environments for a given web page, together with reside, staging, and top class staging environments.

interface Surroundings {
  identification: string;
  identify: string;
  display_name: string;
  is_premium: boolean;
  primaryDomain?: {
    identification: string;
    identify: string;
  };
  container_info?: {
    php_engine_version: string;
  };
}

interface GetEnvironmentsResponse {
  web page: {
    environments: Surroundings[];
  };
}

Some fields are not obligatory, like the principle area or PHP model, in order that they’re marked accordingly. The software itself takes solely the web page ID:

server.registerTool(
  "get_environments",
  {
    description:
      "Get environments (reside, staging) for a selected web page. Calls for the web page ID.",
    inputSchema: {
      site_id: z.string().describe("The web page ID to get environments for"),
    },
  },
  async ({ site_id }) => {
    const information = watch for kinstaRequest(
      `/websites/${site_id}/environments`
    );

    const envs = information.web page.environments;

    if (!envs || envs.duration === 0) {
      go back {
        content material: [
          { type: "text", text: "No environments found for this site." }
        ],
      };
    }

    const envList = envs
      .map((env) => {
        const area = env.primaryDomain?.identify || "No area";
        const php = env.container_info?.php_engine_version || "Unknown";
        const kind = env.is_premium
          ? "Top rate Staging"
          : env.identify === "reside"
            ? "Are living"
            : "Staging";

        go back `• ${env.display_name} (${kind})
  ID: ${env.identification}
  Area: ${area}
  PHP: ${php}`;
      })
      .sign up for("nn");

    go back {
      content material: [
        {
          type: "text",
          text: `Found ${envs.length} environment(s):nn${envList}`,
        },
      ],
    };
  }
);

That is typically the next move earlier than movements comparable to clearing cache, cloning a web page, or updating plugins.

Instrument 3: Transparent web page cache

Clearing cache is a regimen process, however it’s additionally an asynchronous operation. Whilst you cause it, the API responds straight away with an operation ID, whilst the cache clean continues within the background.

Here’s the kind definition and power serve as:

interface OperationResponse {
  operation_id: string;
  message: string;
  standing: quantity;
}

server.registerTool(
  "clear_site_cache",
  {
    description:
      "Transparent the cache for a web page surroundings. Calls for the surroundings ID.",
    inputSchema: {
      environment_id: z
        .string()
        .describe("The surroundings ID to clean cache for"),
    },
  },
  async ({ environment_id }) => {
    const information = watch for kinstaRequest(
      "/websites/equipment/clear-cache",
      {
        means: "POST",
        frame: JSON.stringify({ environment_id }),
      }
    );

    go back {
      content material: [
        {
          type: "text",
          text: `Cache clear initiated!

Operation ID: ${data.operation_id}
Message: ${data.message}

Use get_operation_status to check progress.`,
        },
      ],
    };
  }
);

As a substitute of looking forward to the operation to complete, the software straight away returns the operation ID. This assists in keeping the interplay rapid and lets in the AI assistant to practice up later if wanted.

Instrument 4: Clone web page

Cloning a web page is a kind of movements companies use at all times, particularly when operating from templates or spinning up new shopper websites. As a substitute of ranging from scratch, you’re taking an present surroundings and create a brand new web page in keeping with it.

The reaction makes use of the similar operation form we noticed previous, so no wish to outline it once more. The software calls for a show identify for the brand new web page and the surroundings ID to clone from:

server.registerTool(
  "clone_site",
  {
    description:
      "Clone an present web page surroundings to create a brand new web page. Nice for spinning up new shopper websites from a template.",
    inputSchema: {
      display_name: z
        .string()
        .describe("Title for the brand new cloned web page"),
      source_env_id: z
        .string()
        .describe("The surroundings ID to clone from"),
    },
  },
  async ({ display_name, source_env_id }) => {
    const information = watch for kinstaRequest(
      "/websites/clone",
      {
        means: "POST",
        frame: JSON.stringify({
          corporation: KINSTA_COMPANY_ID,
          display_name,
          source_env_id,
        }),
      }
    );

    go back {
      content material: [
        {
          type: "text",
          text: `Site clone initiated!

New site: ${display_name}
Operation ID: ${data.operation_id}
Message: ${data.message}

Use get_operation_status to check progress.`,
        },
      ],
    };
  }
);

This software is particularly helpful when paired with different equipment. For instance, an AI assistant can clone a web page after which straight away checklist environments or take a look at plugin standing as soon as the operation completes.

Instrument 5: Get operation standing

As a result of some movements run asynchronously, we’d like a solution to take a look at their growth. That’s what this software is for.

interface OperationStatusResponse {
  standing?: quantity;
  message?: string;
}

server.registerTool(
  "get_operation_status",
  {
    description:
      "Take a look at the standing of an async operation (cache clean, web page clone, and so forth.)",
    inputSchema: {
      operation_id: z
        .string()
        .describe("The operation ID to test"),
    },
  },
  async ({ operation_id }) => {
    const reaction = watch for fetch(
      `${KINSTA_API_BASE}/operations/${encodeURIComponent(operation_id)}`,
      {
        headers: {
          Authorization: `Bearer ${KINSTA_API_KEY}`,
        },
      }
    );

    const information: OperationStatusResponse = watch for reaction.json();

    if (reaction.standing === 200) {
      go back {
        content material: [
          {
            type: "text",
            text: `Operation completed successfully!

Message: $ "Operation finished"`,
          },
        ],
      };
    }

    if (reaction.standing === 202) {
      go back {
        content material: [
          {
            type: "text",
            text: `Operation still in progress...

Message: $ "Processing"`,
          },
        ],
      };
    }

    go back {
      content material: [
        {
          type: "text",
          text: `Operation status: ${response.status}

Message: $ "Unknown status"`,
        },
      ],
    };
  }
);

Instrument 6: Get plugins throughout all websites

Whilst you arrange many WordPress websites, plugins are typically the place issues begin to float. This software solves that by way of having a look at plugins throughout all of the corporation account, no longer one web page at a time.

The API returns numerous data, together with which environments each and every plugin is put in in, whether or not updates are to be had, and whether or not a model is marked as susceptible. To paintings with that information, we outline the next shapes:

interface PluginEnvironment  null;
  plugin_version: string;
  is_plugin_version_vulnerable: boolean;
  plugin_update_version: string 

interface Plugin  null;
  is_latest_version_vulnerable: boolean;
  environment_count: quantity;
  update_count: quantity;
  environments: PluginEnvironment[];


interface GetPluginsResponse {
  corporation: {
    plugins: {
      general: quantity;
      pieces: Plugin[];
    };
  };
}

The software itself doesn’t take any enter:

server.registerTool(
  "get_plugins",
  {
    description:
      "Get all WordPress plugins throughout all websites. Displays which plugins have updates to be had or safety vulnerabilities.",
    inputSchema: {},
  },
  async () => {
    const information = watch for kinstaRequest(
      `/corporation/${KINSTA_COMPANY_ID}/wp-plugins`
    );

    const plugins = information.corporation.plugins.pieces;

    if (!plugins || plugins.duration === 0) {
      go back {
        content material: [
          { type: "text", text: "No plugins found." }
        ],
      };
    }

    const looked after = [...plugins].type(
      (a, b) => b.update_count - a.update_count
    );

    const pluginList = looked after.slice(0, 20).map((plugin) => {
      const standing =
        plugin.update_count > 0
          ? `⚠ ${plugin.update_count} web page(s) want replace`
          : "✅ Up to the moment";

      const susceptible =
        plugin.is_latest_version_vulnerable ? " 🔴 VULNERABLE" : "";

      go back `• ${plugin.name} (${plugin.identify})${susceptible}
  Newest: $ "unknown"
  Put in on: ${plugin.environment_count} surroundings(s)
  ${standing}`;
    }).sign up for("nn");

    const outdatedCount = plugins.filter out(
      (p) => p.update_count > 0
    ).duration;

    go back {
      content material: [
        {
          type: "text",
          text: `Found ${data.company.plugins.total} plugins (${outdatedCount} have updates available):nn${pluginList}`,
        },
      ],
    };
  }
);

Instrument 7: Get subject matters throughout all websites

Topics have equivalent issues to plugins, however they’re ceaselessly checked even much less regularly. This software works the similar manner because the plugin software, however specializes in WordPress subject matters as an alternative.

The reaction construction mirrors the plugin endpoint, simply with theme-specific fields:

interface ThemeEnvironment  null;
  theme_version: string;
  is_theme_version_vulnerable: boolean;
  theme_update_version: string 

interface Theme  null;
  is_latest_version_vulnerable: boolean;
  environment_count: quantity;
  update_count: quantity;
  environments: ThemeEnvironment[];


interface GetThemesResponse {
  corporation: {
    subject matters: {
      general: quantity;
      pieces: Theme[];
    };
  };
}

Instrument 8: Replace plugin

List issues turns out to be useful, however in the end you want to mend them. This software lets you replace a selected plugin on a selected surroundings.

The replace endpoint returns the similar async operation form used previous, so we will skip it. Right here’s the software definition:

server.registerTool(
  "update_plugin",
  {
    description:
      "Replace a selected plugin to a brand new model on a web page surroundings.",
    inputSchema: {
      environment_id: z
        .string()
        .describe("The surroundings ID the place the plugin is put in"),
      plugin_name: z
        .string()
        .describe("The plugin identify/slug (e.g., 'akismet', 'elementor')"),
      update_version: z
        .string()
        .describe("The model to replace to (e.g., '5.3')"),
    },
  },
  async ({ environment_id, plugin_name, update_version }) => {
    const information = watch for kinstaRequest(
      `/websites/environments/${environment_id}/plugins`,
      {
        means: "PUT",
        frame: JSON.stringify({
          identify: plugin_name,
          update_version,
        }),
      }
    );

    go back {
      content material: [
        {
          type: "text",
          text: `Plugin update initiated!

Plugin: ${plugin_name}
Target version: ${update_version}
Operation ID: ${data.operation_id}
Message: ${data.message}

Use get_operation_status to check progress.`,
        },
      ],
    };
  }
);

Like cache clears and web page clones, updates run asynchronously. Returning the operation ID we could the AI assistant observe growth as an alternative of assuming the replace completed immediately.

Operating the server

With all equipment registered, the final step is to start out the MCP server and make it to be had to an AI shopper.

On the backside of your record, upload the primary serve as that connects the server the use of the STDIO delivery:

async serve as primary() {
  const delivery = new StdioServerTransport();
  watch for server.attach(delivery);
  console.error("Kinsta MCP Server working on stdio");
}

primary().catch((error) => {
  console.error("Deadly error:", error);
  procedure.go out(1);
});

This tells the MCP server to concentrate for requests over same old enter and output. It makes the server discoverable by way of MCP-compatible desktop purchasers.

One essential element here’s logging. As a result of this server communicates over STDIO, all logs will have to pass to stderr. Writing to stdout can intrude with MCP messages and smash the relationship.

Subsequent, construct the undertaking:

npm run construct

This compiles the TypeScript information into the construct listing and makes the access level executable.

As soon as the construct finishes, the server is able to be introduced by way of an MCP shopper. You’ll be able to get entry to the complete code on GitHub.

Checking out your server with Claude for Desktop

To make use of your MCP server, Claude for Desktop must understand how to release it. Open the Claude Desktop configuration record:

~/Library/Software Give a boost to/Claude/claude_desktop_config.json

Create the record if it doesn’t exist already. When you’re the use of VS Code, you’ll be able to open it immediately from the terminal:

code ~/Library/Software Give a boost to/Claude/claude_desktop_config.json

Throughout the record, upload your MCP server underneath the mcpServers key. For instance:

{
  "mcpServers": {
    "kinsta": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/mcp-server-demo-kinsta-api/build/index.js"],
      "env": {
        "KINSTA_API_KEY": "your-api-key-here",
        "KINSTA_COMPANY_ID": "your-company-id-here"
      }
    }
  }
}

This configuration tells Claude for Desktop that there’s an MCP server named kinsta, it must be introduced the use of Node.js, and the access level is the constructed index.js record.

Be certain the trail issues to the compiled record within the construct listing, no longer the TypeScript supply. Save the record and restart Claude for Desktop.

Verifying the relationship

As soon as Claude restarts, open a brand new chat. Click on the + icon subsequent to the enter box, then hover over Connectors. You must see your MCP server indexed.

Registering an MCP server in Claude to enable tool access.
Registering an MCP server in Claude.

With the server hooked up, you’ll be able to get started the use of it instantly. Claude comes to a decision which software to make use of, passes the specified enter, and returns the outcome as simple textual content.

Updating a WordPress plugin using an MCP-powered workflow in Claude.
Updating a WordPress plugin the use of an MCP-powered workflow in Claude.

A unique solution to paintings with the equipment you have already got

What’s converting at this time isn’t the underlying equipment. APIs are nonetheless APIs. Web hosting platforms nonetheless paintings the similar manner. What’s converting is how we engage with them.

AI equipment are beginning to really feel much less like chat bins and extra like interfaces. This MCP server is a small instance of that shift. It doesn’t introduce new features. It exposes present ones in some way that matches how other people in reality paintings.

The place this is going subsequent is as much as you. You could stay issues easy and read-only. You could upload extra automation with approvals and guardrails. Or you may attach the similar server to different equipment on your workflow.

As you discover new equipment and workflows like this, having a cast website hosting basis issues. The very last thing you wish to have is to lose time coping with downtime or efficiency problems as an alternative of establishing and bettering your websites.

Kinsta supplies controlled website hosting for WordPress that assists in keeping your websites working reliably, even while you’re offline. You’ll be able to discover our website hosting plans or communicate to our gross sales workforce to seek out the best plan for you.

The put up Create your personal MCP Server to regulate WordPress website hosting with AI gave the impression first on Kinsta®.

WP Hosting

[ continue ]