WordPressサイトを数多く管理していると、管理画面にアクセスして何度もボタンをクリックする作業は、できるだけ効率よく進めたいものです。

今回は、最近大きな注目を集めているModel Context Protocol(MCP)とKinsta APIを組み合わせて、多数のサイトを管理する制作会社の作業をどのくらい効率化できるのかを検証していきます。

具体的には、ClaudeのようなAIアシスタントをKinsta APIに接続し、制作会社が日常的に行うWordPress用サーバーの管理タスクを実行可能なMCPサーバーの構築例を、実践的な形でご紹介します。

今回構築するもの

今回構築するのは、AIアシスタントが以下のような操作を実行できる一連のツールを公開するMCPサーバーです。

  • アカウント配下のすべてのWordPressサイトを一覧表示

  • 特定のサイトにある環境を表示

  • 指定した環境のキャッシュをクリア

  • 既存のサイトを複製して新規サイトを立ち上げる

  • 古くなっている、または脆弱性のあるプラグインやテーマを確認する

  • 特定の環境でプラグインの更新を実行する

サーバーのセットアップが完了したら、MCPクライアント(今回の例ではClaude Desktop)に接続します。

プロンプト実行中に、ClaudeがMCPツールを呼び出して外部データを取得している様子
プロンプト実行中に、ClaudeがMCPツールを呼び出して外部データを取得している様子

いくつかのツールを呼び出したあと、次のようなレスポンスが返されます。

取得したツールデータをもとに、Claudeが構造化されたレスポンスを表示している様子
取得したツールデータをもとに、Claudeが構造化されたレスポンスを表示している様子

はじめに

実際のコーディングに入る前に、この構成の中でMCPがどのような役割を果たすのか、基本情報を理解しておくとわかりやすくなります。

MCPサーバーは、APIを置き換えたり、その動作を変更したりするものではなく、AIアシスタントと既存のAPIの間に配置される仕組みです。AIが必要に応じて呼び出せるツールのセットを公開します。それぞれのツールは、サイト一覧の取得やサイトのキャッシュクリアなど、特定の操作に対応します。

AIアシスタントで質問すると、その内容に関連するツールがあるかどうかが判断されます。関連するツールがあれば、AIがMCPサーバーを通じてそのツールを呼び出し、MCPサーバーがAPIと通信します。そして、その結果がシンプルなレスポンスとして返されます。すべてが自動的に実行されるわけではなく、公開したツールのみ利用可能になります。

今回の例では、MCPサーバーがKinsta APIと通信し、WordPress用サーバーの操作の一部だけをツールとして公開します。カスタムUIやバックグラウンド処理、特別なAI設定などは不要です。

前提条件

これからご紹介する手順は、以下の条件を前提とします。

  • 安定したNode.js環境
  • TypeScriptの基礎知識
  • APIアクセスが有効なMyKinstaアカウント
  • Kinsta APIキーと企業ID

MCPの事前知識、またAIモデルを構築したりトレーニングしたりする必要はありません。今回の例では、既存のツールをどのように連携させるかに焦点を当てます。

プロジェクトのセットアップ

まずは、本プロジェクト用のディレクトリを作成し、Node.jsアプリを初期化します。

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

次に、MCP SDKとプロジェクトで使用する必要最小限の依存パッケージをインストールします。

npm install @modelcontextprotocol/sdk zod@3
npm install -D typescript @types/node

次に基本的なプロジェクト構成を作成します。

mkdir src
touch src/index.ts

続いて、ビルドしたサーバーをNodeで実行できるようにpackage.jsonを更新します。

{
  "name": "kinsta-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for managing WordPress sites via the 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"
  }
}

最後に、プロジェクトのルートにtsconfig.jsonを追加します。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

これで準備が整い、MCPサーバー本体の構築を始められます。

MCPサーバーの構築

プロジェクトの準備が整ったら、いよいよMCPサーバー本体を構築していきます。

まずは必要なパッケージをインポートし、サーバーインスタンスを作成します。続いて、APIと通信するための簡単なヘルパー関数を追加します。その後、WordPress用サーバーの操作に直接対応するツールを登録していきます。

パッケージのインポートとサーバーを作成

src/index.tsを開き、ファイルの先頭に次のインポートを追加します。

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

これらは次の3つの役割を果たします。

  • McpServer:ツールを登録し、AIクライアントからのリクエストを処理するコアサーバー
  • StdioServerTransport:標準入力/標準出力(stdio)を通じてサーバーが通信できるようにする仕組み(多くのデスクトップAIクライアントがこの方法で接続)
  • zod:各ツールが受け取る入力を定義し、検証するために使用

続いて、APIと認証情報に関するいくつかの定数を定義します。

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;

続いて、MCPサーバーのインスタンスを作成します。

const server = new McpServer({
  name: "kinsta",
  version: "1.0.0",
});

サーバー名は、MCPクライアント内で表示される名前になります。バージョンの指定は必須ではありませんが、開発を進めていく中で更新を重ねる際に役立ちます。

APIリクエスト用のヘルパーを追加

これから作成するツールの多くは、APIに対してHTTPリクエストを送る必要があります。同じ処理を毎回書くのを避けるため、共通で使えるヘルパー関数を用意します。サーバーの設定コードの下に、次のコードを追加してください。

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 error (${response.status}): ${errorText}`);
  }

  return response.json() as Promise;
}

ツールの実装

MCPサーバーの主な役割は、ツールを公開することです。ツールは、AIアシスタントがユーザーの承認のもとで呼び出し、特定のタスクを実行するための関数です。

このサーバーでは、すべてのツールが以下の構成に従って定義されます。

  • ツール名(例:list_sites
  • 簡単な説明(AIアシスタントがどのタイミングで使うべきかを判断する助けになる)
  • 入力スキーマ(有効な入力がある場合にのみツールを実行するためのもの)
  • ハンドラー関数(APIを呼び出し、出力を整形する処理を記述)

AIアシスタントは、生のJSONをそのまま返すよりも、読みやすく整理された出力のほうが扱いやすくなるため、レスポンスは意図的にプレーンテキストで整形しています。

ツール1. サイト一覧の取得

このツールは、企業アカウント配下にあるすべてのWordPressサイトを取得します。複数のサイトを管理している場合、ほとんどの操作はサイトIDから始まるため、使用頻度の高いツールになります。

APIのレスポンスには各サイトの基本情報が含まれるため、ここでは扱いやすいようにシンプルなデータ構造を定義します。

interface Site {
  id: string;
  name: string;
  display_name: string;
  status: string;
  site_labels: Array;
}

interface ListSitesResponse {
  company: {
    sites: Site[];
  };
}

この定義をもとに、ツールを登録します。

server.registerTool(
  "list_sites",
  {
    description:
      "Get all WordPress sites for your company. Returns site IDs, names, and status.",
    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: "No sites found for this company." }
        ],
      };
    }

    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("nn");

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

このツールは入力を必要としないため、入力スキーマは空になります。ハンドラー内ではAPIを呼び出し、結果が空でないかを確認したうえで、読みやすいテキスト形式に整形して返します。

生のJSONをそのまま返すのではなく、チャット画面で扱いやすい短い要約を返すようにしています。これにより、AIアシスタントは追加のパース処理を行わなくても、「どのサイトを持っていますか?」「WordPressサイトを全部表示してください」といった質問にそのまま答えやすくなります。

ツール2. 環境の取得

サイトIDを取得した後によく行うことは、そのサイトの環境の確認です。このツールでは、指定したサイトに紐づくすべての環境を返します。これには、本番環境、ステージング環境、プレミアムステージング環境が含まれます。

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[];
  };
}

プライマリドメインやPHPバージョンなど、一部の項目は任意の値になるため、それに合わせて定義しています。ツール自体が受け取る入力はサイトIDのみです。

server.registerTool(
  "get_environments",
  {
    description:
      "Get environments (live, staging) for a specific site. Requires the site ID.",
    inputSchema: {
      site_id: z.string().describe("The site ID to get environments for"),
    },
  },
  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: "No environments found for this site." }
        ],
      };
    }

    const envList = envs
      .map((env) => {
        const domain = env.primaryDomain?.name || "No domain";
        const php = env.container_info?.php_engine_version || "Unknown";
        const type = env.is_premium
          ? "Premium Staging"
          : env.name === "live"
            ? "Live"
            : "Staging";

        return `• ${env.display_name} (${type})
  ID: ${env.id}
  Domain: ${domain}
  PHP: ${php}`;
      })
      .join("nn");

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

これは通常、キャッシュのクリア、サイトの複製、プラグインの更新といった操作を行う前の次のステップになります。

ツール3. サイトキャッシュのクリア

キャッシュのクリアは日常的によく行う作業ですが、非同期で実行される処理でもあります。実行すると、APIはすぐにオペレーションIDを返しますが、実際のキャッシュクリア処理はバックグラウンドで続行されます。

型定義とツール関数は次のとおりです。

interface OperationResponse {
  operation_id: string;
  message: string;
  status: number;
}

server.registerTool(
  "clear_site_cache",
  {
    description:
      "Clear the cache for a site environment. Requires the environment ID.",
    inputSchema: {
      environment_id: z
        .string()
        .describe("The environment ID to clear cache for"),
    },
  },
  async ({ environment_id }) => {
    const data = await kinstaRequest(
      "/sites/tools/clear-cache",
      {
        method: "POST",
        body: JSON.stringify({ environment_id }),
      }
    );

    return {
      content: [
        {
          type: "text",
          text: `Cache clear initiated!

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

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

処理の完了を待つのではなく、このツールはすぐにオペレーションIDを返します。これによりやり取りを高速に保つことができ、必要に応じてAIアシスタントが後から進行状況を確認することもできます。

ツール4. サイトの複製

サイトの複製は、特にテンプレートを使ったりクライアントサイトを立ち上げたりする際に、エージェンシーが頻繁に利用する操作のひとつです。最初からサイトを作成する代わりに、既存の環境をもとに新しいサイトを作成します。

レスポンスは先ほど使用したものと同じオペレーション形式を使うため、改めて定義する必要はありません。このツールでは、新規サイトの表示名と、複製元となる環境IDを入力として指定します。

server.registerTool(
  "clone_site",
  {
    description:
      "Clone an existing site environment to create a new site. Great for spinning up new client sites from a template.",
    inputSchema: {
      display_name: z
        .string()
        .describe("Name for the new cloned site"),
      source_env_id: z
        .string()
        .describe("The environment ID to clone from"),
    },
  },
  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: `Site clone initiated!

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

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

このツールは、ほかのツールと組み合わせて使うと特に便利です。たとえば、AIアシスタントがサイトを複製したあと、処理が完了したタイミングで環境一覧を取得したり、プラグインの状態を確認したりすることができます。

ツール5. オペレーションの状態確認

一部の操作は非同期で実行されるため、その進行状況を確認する手段も必要です。

interface OperationStatusResponse {
  status?: number;
  message?: string;
}

server.registerTool(
  "get_operation_status",
  {
    description:
      "Check the status of an async operation (cache clear, site clone, etc.)",
    inputSchema: {
      operation_id: z
        .string()
        .describe("The operation ID to check"),
    },
  },
  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: `Operation completed successfully!

Message: ${data.message || "Operation finished"}`,
          },
        ],
      };
    }

    if (response.status === 202) {
      return {
        content: [
          {
            type: "text",
            text: `Operation still in progress...

Message: ${data.message || "Processing"}`,
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: `Operation status: ${response.status}

Message: ${data.message || "Unknown status"}`,
        },
      ],
    };
  }
);

ツール6. 全サイトのプラグインを確認

多数のWordPressサイトを管理していると、プラグインまわりから状態のばらつきが生じやすくなります。このツールは、サイトごとではなく企業アカウント全体を対象にプラグインを確認できるようにすることで、その問題に対応します。

APIは多くの情報を返します。たとえば、各プラグインがどの環境にインストールされているか、更新が利用可能かどうか、さらにそのバージョンに脆弱性があると判定されているかどうかなど。こうしたデータを扱うには、以下のような型を定義します。

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[];
    };
  };
}

このツールは入力を必要としません。

server.registerTool(
  "get_plugins",
  {
    description:
      "Get all WordPress plugins across all sites. Shows which plugins have updates available or security vulnerabilities.",
    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: "No plugins found." }
        ],
      };
    }

    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) need update`
          : "✅ Up to date";

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

      return `• ${plugin.title} (${plugin.name})${vulnerable}
  Latest: ${plugin.latest_version || "unknown"}
  Installed on: ${plugin.environment_count} environment(s)
  ${status}`;
    }).join("nn");

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

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

ツール7. 全サイトのテーマを確認

テーマもプラグインと同じような問題を抱えやすいものの、確認される頻度はさらに低くなりがちです。このツールはプラグイン用のツールと同じように動作しますが、対象をWordPressテーマに絞っています。

レスポンスの構造もプラグイン用エンドポイントとほぼ同じで、テーマに固有の項目だけが含まれます。

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: number;
  update_count: number;
  environments: ThemeEnvironment[];
}

interface GetThemesResponse {
  company: {
    themes: {
      total: number;
      items: Theme[];
    };
  };
}

ツール8. プラグインの更新

問題を一覧で確認するだけでなく、最終的には対処も必要になります。このツールでは、特定の環境にある特定のプラグインを更新できます。

更新用エンドポイントは、先ほどと同じ非同期オペレーションの形式を返すため、ここでは改めて定義しません。

server.registerTool(
  "update_plugin",
  {
    description:
      "Update a specific plugin to a new version on a site environment.",
    inputSchema: {
      environment_id: z
        .string()
        .describe("The environment ID where the plugin is installed"),
      plugin_name: z
        .string()
        .describe("The plugin name/slug (e.g., 'akismet', 'elementor')"),
      update_version: z
        .string()
        .describe("The version to update to (e.g., '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 initiated!

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

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

キャッシュのクリアやサイトの複製と同様に、更新も非同期で実行されます。オペレーションIDを返すことで、AIアシスタントは更新が即座に完了したものとみなすのではなく、進行状況を追跡できます。

サーバーの起動

すべてのツールを登録したら、最後のステップとしてMCPサーバーを起動し、AIクライアントから利用できるようにします。

ファイルの末尾に、STDIOトランスポートを使ってサーバーに接続するmain関数を追加します。

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Kinsta MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

これにより、MCPサーバーは標準入力と標準出力を通じてリクエストを受け取るようになります。これで、MCPに対応したデスクトップクライアントからサーバーを利用できるようになります。

ここでひとつ重要なのが、ログの出力先です。このサーバーはSTDIO経由で通信するため、すべてのログはstderrに出力する必要があります。stdoutに書き込むとMCPメッセージと干渉し、接続が正常に機能しなくなる可能性があります。

続いて、プロジェクトをビルドします。

npm run build

これにより、TypeScriptファイルがbuildディレクトリにコンパイルされ、エントリーポイントを実行できるようになります。

ビルドが完了すれば、MCPクライアントからサーバーを起動できる状態になります。コード全体はGitHubで確認可能です。

Claude Desktopでサーバーをテストする

MCPサーバーを利用するには、Claude Desktopにどのようにサーバーを起動するかを設定する必要があります。まず、Claude Desktopの設定ファイルを開きます。

~/Library/Application Support/Claude/claude_desktop_config.json

まだファイルが存在しない場合は、新しく作成してください。VS Codeを使用している場合は、ターミナルから直接開くこともできます。

code ~/Library/Application Support/Claude/claude_desktop_config.json

ファイル内で、mcpServersキーの下にMCPサーバーを追加します。例えば、以下のように設定します。

{
  "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"
      }
    }
  }
}

この設定により、Claude Desktopに対して、kinstaという名前のMCPサーバーがあること、Node.jsで起動すること、そしてエントリーポイントがビルドされたindex.jsファイルであることを伝えます。

パスはTypeScriptのソースではなく、buildディレクトリ内のコンパイル済みファイルを指していることを確認してください。設定を保存したら、Claude Desktopを再起動します。

接続を確認する

Claudeを再起動したら、新しいチャットを開きます。入力欄の横にある+アイコンをクリックし、Connectorsにカーソルを合わせてください。すると、作成したMCPサーバーが一覧に表示されるはずです。

ClaudeでMCPサーバーを登録している様子
ClaudeでMCPサーバーを登録している様子

サーバーが接続されれば、すぐに使い始めることができます。Claudeが使用するツールを判断し、必要な入力を渡して、結果をプレーンテキストで返します。

ClaudeでMCPを活用したワークフローにより、WordPressプラグインを更新している様子
ClaudeでMCPを活用したワークフローにより、WordPressプラグインを更新している様子

既存ツールとの新しい付き合い方

いま変わりつつあるのは、ツールそのものではありません。APIやサーバーの仕組みは、基本的にこれまでと変わっていません。変わってきているのは、既存ツールとの関わり方です。

AIツールは、単なるチャットボックスというより、新しいインターフェースのような存在になりつつあります。このMCPサーバーは、その変化を示す小さな例にすぎません。新たな機能を追加するわけではなく、すでにある機能を、人が実際に作業する流れに合った形で利用できるようにするものです。

この先どのように活用していくかは、使い方次第です。シンプルに読み取り専用のツールとして使うこともできれば、承認フローやガードレールを加えて自動化を進めることも可能です。あるいは、同じサーバーをワークフロー内の他のツールと連携させることもできるはずです。

こうした新しいツールやワークフローを試していくには、安定したサーバー環境が欠かせません。サイトの改善や開発に集中するべき時間を、サイト停止やパフォーマンスの問題への対応に費やしてしまうのは避けたいものです。

Kinstaは、作業していない間でもサイトを安定して稼働させる、WordPress専用マネージドクラウドサーバーを提供しています。詳しくはプラン一覧をご覧いただくか、営業部門までお気軽にお問い合わせください。

Joel Olawanle Kinsta

Kinstaでテクニカルエディターとして働くフロントエンド開発者。オープンソースをこよなく愛する講師でもあり、JavaScriptとそのフレームワークを中心に200件以上の技術記事を執筆している。