iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Modern Web

WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站系列 第 23 篇

Day 23|WebMCP 要怎麼接 PHP?把 WordPress 拆成 JS Tool、REST API 與權限檢查

  • 分享至 

  • xImage
  •  

本篇重點

前兩天整理了登入、權限與跨 Origin 的邊界,今天把這些觀念接到 WordPress。

這次做一個 get_current_profile:Agent 呼叫頁面上的 JavaScript Tool,Tool 再向 WordPress REST API 發出請求,由 PHP 檢查登入與權限,回傳目前帳號的 ID 與顯示名稱。

先看這次的分工

Agent
→ JavaScript Tool:get_current_profile
→ fetch:WordPress REST API
→ WordPress:Cookie、Nonce 與權限檢查
→ 目前使用者資料
→ JSON 回傳給 Tool

JavaScript 負責工具名稱、描述、輸入格式與呼叫方式。PHP 負責判斷身分、檢查權限與讀取資料。既有 WordPress 功能可以沿用,不需要另外重寫一套後端。

圖片 1|JavaScript 提供 Tool,WordPress 負責登入、權限與資料。
https://ithelp.ithome.com.tw/upload/images/20261002/20121296eBXhBnaAL8.png

先安裝範例外掛

外掛目錄如下:

wp-content/plugins/wp-webmcp-lab/
├── wp-webmcp-lab.php
└── assets/
    ├── webmcp.js
    └── webmcp.css
  1. 進入 WordPress 後台的「外掛 → 安裝外掛 → 上傳外掛」。
  2. 選擇 wp-webmcp-lab-0.1.0.zip,按「立即安裝」,再按「啟用」。
  3. 在網站首頁網址後面加上 ?webmcp_lab=1,開啟 Day 23 測試頁。
  4. 使用已啟用 WebMCP 的 Chrome,確認畫面出現「已註冊 get_current_profile」。

這一天的程式放在 WordPress 外掛內。測試頁只在指定網址載入 Tool;網站其他頁面維持原本的功能。

PHP 建立查詢入口

自訂路由在 rest_api_init 註冊。以下是外掛中的登入、權限與資料回傳核心:

function wp_webmcp_lab_profile_permission() {
    if ( ! is_user_logged_in() ) {
        return new WP_Error(
            'webmcp_not_logged_in',
            '請先登入 WordPress。',
            array( 'status' => 401 )
        );
    }

    if ( ! current_user_can( 'read' ) ) {
        return new WP_Error(
            'webmcp_forbidden',
            '目前帳號沒有讀取權限。',
            array( 'status' => 403 )
        );
    }

    return true;
}

add_action( 'rest_api_init', function () {
    register_rest_route( 'webmcp/v1', '/profile', array(
        'methods' => WP_REST_Server::READABLE,
        'permission_callback' => 'wp_webmcp_lab_profile_permission',
        'callback' => function () {
            $user = wp_get_current_user();
            $response = rest_ensure_response( array(
                'id' => $user->ID,
                'name' => $user->display_name,
            ) );
            $response->header( 'Cache-Control', 'no-store, private' );
            return $response;
        },
    ) );
} );

permission_callback 先檢查是否登入,再檢查帳號是否具有 read 權限。通過之後,才讀取目前帳號的資料。

這個入口沒有 userId 參數,呼叫者不能自行指定另一個帳號。回傳內容也只包含 ID 與顯示名稱。

把 REST 網址與 Nonce 交給 JavaScript

在 wp_enqueue_scripts 中載入 JS,並傳入請求需要的設定。以下是外掛的載入核心:

add_action( 'wp_enqueue_scripts', function () {
    if ( ! wp_webmcp_lab_is_demo() ) {
        return;
    }

    wp_enqueue_script(
        'wp-webmcp-lab',
        plugin_dir_url( __FILE__ ) . 'assets/webmcp.js',
        array(),
        '0.1.0',
        true
    );

    wp_localize_script( 'wp-webmcp-lab', 'wpWebMCP', array(
        'profileUrl' => esc_url_raw( rest_url( 'webmcp/v1/profile' ) ),
        'nonce' => is_user_logged_in() ? wp_create_nonce( 'wp_rest' ) : '',
    ) );
} );

wp_webmcp_lab_is_demo() 是外掛提供的測試頁判斷函式,用來檢查網址中的 webmcp_lab=1。完整外掛另外包含測試頁與樣式;上面的片段用來說明載入設定。

API 網址由 rest_url() 產生,不在 JS 中拼接 /wp-json/。一般永久連結的網址會包含 index.php?rest_route=/webmcp/v1/profile,同樣可以使用。

Cookie、Nonce 與權限各做什麼?

項目 本篇的用途
登入 Cookie 讓 WordPress 驗證登入帳號
wp_rest Nonce 配合 Cookie 驗證 REST 請求,提供 CSRF 防護
current_user_can('read') 檢查目前帳號是否具備此功能需要的權限

Nonce 放在 X-WP-Nonce 請求標頭。它不會替帳號增加權限;登入驗證與功能權限仍由 WordPress 後端檢查。

使用 Cookie 驗證 REST API 時,若沒有提供 Nonce,WordPress 會把這次請求視為未登入。因此,登入後要重新載入測試頁,讓頁面取得登入狀態對應的設定。

JavaScript 註冊 Tool

下面整理外掛的請求與註冊核心。完整 webmcp.js 另外把結果同步顯示在「最後一次請求」,並讓頁面按鈕共用同一個請求函式。

(() => {
  async function profile() {
    try {
      const endpoint = new URL(wpWebMCP.profileUrl, location.href);
      if (endpoint.origin !== location.origin) {
        throw new Error('REST API 與頁面必須使用相同 Origin。');
      }

      const headers = { Accept: 'application/json' };
      if (wpWebMCP.nonce) {
        headers['X-WP-Nonce'] = wpWebMCP.nonce;
      }

      const response = await fetch(endpoint, {
        credentials: 'same-origin',
        cache: 'no-store',
        headers,
      });
      const body = await response.json();

      return {
        httpStatus: response.status,
        status: response.ok ? 'success' : 'error',
        ...(response.ok
          ? { profile: body }
          : { code: body.code, message: body.message }),
      };
    } catch (error) {
      return { status: 'request_error', message: error.message };
    }
  }

  async function register() {
    if (!document.modelContext?.registerTool) return;

    await document.modelContext.registerTool({
      name: 'get_current_profile',
      description:
        'Read the ID and display name of the currently signed-in WordPress user. ' +
        'Requires WordPress login. Does not read other users or modify data.',
      inputSchema: {
        type: 'object',
        properties: {},
        additionalProperties: false,
      },
      annotations: { readOnlyHint: true },
      execute: async (args) => {
        if (!args || typeof args !== 'object' ||
            Array.isArray(args) || Object.keys(args).length) {
          return JSON.stringify({ status: 'invalid_input' });
        }
        return JSON.stringify(await profile());
      },
    });
  }

  register().catch(console.error);
})();

這裡使用一般 JS 檔案,把 await 放在 async 函式內,再執行註冊。請求失敗時保留 HTTP 狀態與 WordPress 錯誤碼,方便分辨未登入、權限不足與請求本身失敗。

readOnlyHint 描述這個 Tool 的用途;真正能否讀取資料,仍由 PHP 的權限檢查決定。

登入後執行 Tool

  1. 在測試頁按「登入 WordPress」,登入測試帳號。
  2. 回到測試頁,確認「目前身分」顯示帳號名稱。
  3. 開啟 WebMCP Inspector,在 Tool 選擇 get_current_profile。
  4. Input Arguments 填入 {},按 Execute Tool。
  5. 檢查結果中的 httpStatus: 200、status: success,以及 profile 裡的 id 與 name。

這次直接執行指定 Tool,不需要在 User Prompt 填入文字。頁面下方會同步顯示這次請求;從 Inspector 執行時,來源為 get_current_profile。

圖片 2|登入後執行 get_current_profile,取得目前帳號的 ID 與顯示名稱。
https://ithelp.ithome.com.tw/upload/images/20261002/201212966iM7rlshLw.png

登出後再執行一次

  1. 按測試頁的「登出 WordPress」。
  2. 回到測試頁,確認目前身分變成「Guest/未登入」。
  3. 在 Inspector 選擇相同 Tool,輸入 {},再次按 Execute Tool。
  4. 核對回傳內容:
{
  "httpStatus": 401,
  "status": "error",
  "code": "webmcp_not_logged_in",
  "message": "請先登入 WordPress。"
}

測試頁在未登入時仍會註冊 Tool,讓這個案例直接呈現後端的登入檢查:工具可以被看見,但讀取帳號資料的請求會被拒絕。

圖片 3|未登入仍能看見 Tool,但 WordPress 回傳 401,拒絕讀取使用者資料。
https://ithelp.ithome.com.tw/upload/images/20261002/20121296T5TddWEFri.png

頁面的「直接查詢 REST API」按鈕與 Tool 共用請求函式,適合核對 API 回應;完整 Tool 流程則使用 Inspector 的 Execute Tool。

什麼時候重用原生 REST API?

WordPress 已有文章查詢入口,例如 wp/v2/posts。如果原生入口的資料與權限規則符合需求,就讓 Tool 呼叫它。

需要縮小回傳欄位、整合多個查詢,或加入特定業務規則時,再建立自己的路由。本篇使用 webmcp/v1/profile,回傳範圍固定為目前帳號的 ID 與顯示名稱。

功能權限留在 PHP 檢查

本篇使用 read。如果之後增加發布文章的功能,就要依操作檢查 publish_posts;涉及特定文章時,還要檢查該文章的操作權限。

前端可以決定要顯示哪些 Tool,後端則必須在每次請求時驗證權限。即使有人繞過頁面直接呼叫 REST API,也要經過相同檢查。

可帶走的重點

  1. JavaScript 把網站功能包成 Agent 能呼叫的 Tool。
  2. Tool 透過 REST API 使用既有的 WordPress 功能。
  3. Cookie 驗證登入身分,Nonce 配合 REST 請求驗證,Capability 決定功能權限。
  4. API 網址交給 rest_url() 產生,避免永久連結格式不同造成錯誤。
  5. 未登入案例回傳 401,讓錯誤原因能直接被看見。

下一篇接著把 WordPress 文章搜尋包成 search_posts。

參考資料


上一篇
Day 22|Iframe 裡的 Tool 誰說了算?跨 Origin WebMCP 權限實驗
系列文
WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言