Alat JavaScript Agentic

Imperative API menggunakan document.modelContext.registerTool() untuk menentukan alat JavaScript secara terprogram. Hal ini ideal untuk Aplikasi Web Satu Halaman (SPA) yang memerlukan penambahan atau penghapusan alat berdasarkan rute atau status pengguna saat ini.

WebMCP beroperasi pada arsitektur produsen/konsumen:

  • Halaman web adalah produsen alat: Anda menentukan dan mendaftarkan alat menggunakan document.modelContext.registerTool(). Hanya itu yang perlu diterapkan halaman web. Anda tidak perlu memanggil getTools() atau executeTool() di halaman untuk mengekspos alat ke agen.
  • Agen browser dan ekstensi adalah konsumen utama: Agen yang disediakan oleh atau melalui browser — termasuk agen bawaan browser, ekstensi browser, DevTools, dan asisten tingkat sistem — menemukan dan memanggil alat terdaftar di luar band melalui mekanisme platform internal.
  • Agen dalam halaman dan tes otomatis: getTools() dan executeTool() ada khusus untuk asisten JavaScript dalam halaman (misalnya, widget chat tersemat atau <iframe>) dan suite pengujian otomatis yang perlu menemukan dan memanggil alat langsung dalam konteks halaman.

Pendaftaran dan Siklus Proses

Alat didaftarkan dengan meneruskan objek definisi alat dan objek opsi opsional (ModelContextRegisterToolOptions), yang mendukung dua anggota opsional:

  • signal: AbortSignal yang digunakan untuk membatalkan pendaftaran alat saat tidak lagi diperlukan.
  • exposedTo: Array string origin yang mengontrol dokumen mana dalam hierarki dokumen yang diizinkan untuk menemukan dan menjalankan alat di seluruh batas frame.

Penanganan Siklus Proses dengan AbortController

WebMCP tidak menyediakan metode unregisterTool(). Untuk membatalkan pendaftaran alat, Anda harus meneruskan AbortSignal selama pendaftaran dan membatalkan sinyal tersebut saat alat tidak lagi diperlukan.

const controller = new AbortController();

await document.modelContext.registerTool({
  name: "get_user_preferences",
  description: "Retrieves the user's saved preferences.",
  inputSchema: { type: "object", properties: {} },
  execute() {
    const prefs = localStorage.getItem("user_prefs");
    return prefs ? JSON.parse(prefs) : { theme: "light" };
  },
  annotations: { readOnlyHint: true }
}, { signal: controller.signal });

// To unregister the tool (e.g., on component unmount):
controller.abort();

Membatalkan sinyal pendaftaran hanya akan membatalkan pendaftaran alat — tindakan ini tidak membatalkan eksekusi yang sudah berjalan. Alat yang memulai pekerjaan yang berjalan lama juga harus mematuhi sinyal per eksekusi yang dijelaskan di bawah, atau pembatalan pendaftaran akan meninggalkan permintaan tanpa induk dan update UI yang tidak berlaku.

Mengontrol eksposur alat lintas origin dengan exposedTo

Secara default, alat yang terdaftar bersifat pribadi untuk dokumen pendaftaran dan frame origin yang sama. Jika aplikasi Anda disematkan dalam <iframe> (atau menyematkan widget pihak ketiga) dan Anda memerlukan frame ancestor atau sibling untuk menemukan dan memanggil alat, tentukan origin yang diizinkan menggunakan exposedTo.

const controller = new AbortController();

await document.modelContext.registerTool({
  name: "get_cart_items",
  description: "Retrieves items currently in the user's shopping cart.",
  inputSchema: { type: "object", properties: {} },
  async execute(input, { signal }) {
    return await cartService.getItems({ signal });
  },
  annotations: { readOnlyHint: true },
}, {
  signal: controller.signal,
  exposedTo: ["https://checkout.example.com"],
});
  • Proses handshake keikutsertaan bersama: Penemuan alat lintas origin adalah keikutsertaan dua arah. Frame penyedia alat memberikan otorisasi ke origin konsumen melalui exposedTo, dan frame konsumen harus secara eksplisit meminta origin penyedia tersebut melalui getTools({ fromOrigins: [...] }). Tanpa fromOrigins, getTools() hanya akan mengkueri dokumen origin yang sama.
  • Pencocokan origin: Origin dokumen pendaftaran itu sendiri selalu memiliki akses. Origin yang ditentukan di exposedTo diberi izin untuk menemukan alat melalui document.modelContext.getTools({ fromOrigins: [...] }) dan menjalankannya melalui document.modelContext.executeTool().
  • Persyaratan keamanan: Setiap origin di exposedTo HARUS berpotensi tepercaya (HTTPS atau localhost). Meneruskan origin yang tidak valid atau tidak aman akan menyebabkan registerTool() menolak dengan SecurityError.
  • Cakupan: exposedTo mengontrol penemuan lintas dokumen dalam halaman di seluruh frame dalam hierarki dokumen.

Menentukan Parameter

Parameter (params) ditentukan menggunakan properti inputSchema. Objek ini harus berupa objek JSON Schema yang mendeskripsikan data terstruktur yang diharapkan alat.

await document.modelContext.registerTool({
  name: "calculate_area",
  description: "Calculates the area of a rectangle.",
  inputSchema: {
    type: "object",
    properties: {
      width: { type: "number", description: "The width of the rectangle." },
      height: { type: "number", description: "The height of the rectangle." }
    },
    required: ["width", "height"]
  },
  execute(input) {
    // input is { width: 10, height: 20 }
    return input.width * input.height;
  },
  annotations: { readOnlyHint: true }
});

Menulis Deskripsi Alat dan Parameter

Agen sudah menerima inputSchema lengkap, sehingga description hanya perlu menyatakan fungsi alat dan kapan harus menggunakannya. JANGAN mengulangi nama parameter, jenis, nilai enum, default, atau batas di dalamnya; deklarasikan dalam skema (enum, default, minimum/maximum). Deskripsi parameter mengikuti aturan yang sama: sebutkan arti nilai, bukan jenis atau nilai yang diizinkan.

Browser tidak memvalidasi argumen terhadap inputSchema atau menerapkan nilai default, jadi lakukan keduanya di execute.

const SORT_ORDERS = ["newest", "price_low_to_high"];

await document.modelContext.registerTool({
  name: "search_products",
  // Don't list the sort orders here, even via SORT_ORDERS: `enum` already does.
  description: "Searches the product catalog by keyword. Use when the user wants to find or compare products.",
  inputSchema: {
    type: "object",
    properties: {
      query: { type: "string", description: "Words to search for." },
      sort: { type: "string", enum: SORT_ORDERS, default: "newest", description: "Order of the results." },
    },
    required: ["query"],
  },
  async execute({ query, sort = "newest" }, { signal }) {
    if (typeof query !== "string" || !SORT_ORDERS.includes(sort)) {
      return { error: "Pass a search query and a supported sort order.", sortOrders: SORT_ORDERS };
    }
    return await productService.search({ query, sort }, { signal });
  },
  annotations: { readOnlyHint: true },
});

Pola Eksekusi

Callback execute selalu menerima dua argumen: objek input, dan objek opsi yang membawa AbortSignal — execute(inputObject, { signal }).

Kapan menggunakan async execute

Gunakan async jika alat melibatkan operasi yang menampilkan Promise atau memerlukan waktu untuk diselesaikan:

  • Panggilan jaringan: Mengambil data dari API.
  • Penyimpanan Asinkron: Mengakses IndexedDB.
  • Peristiwa Eksternal: Menunggu perubahan status atau animasi tertentu selesai.
async execute(input, { signal }) {
  const response = await fetch(`/api/data/${input.id}`, { signal });
  return await response.json();
}

Kapan harus menggunakan execute (Sinkron)

Gunakan fungsi sinkron standar untuk operasi langsung. Alat sinkron berjalan hingga selesai sebelum pembatalan dapat diamati, sehingga alat tersebut dapat mengabaikan sinyal:

  • Logika murni: Matematika, pemfilteran, atau pengurutan data yang sudah ada dalam memori.
  • Status sinkron: Membaca dari localStorage atau pengelola status sinkron.
execute(input) {
  return input.items.filter(item => item.active);
}

Menangani error di execute

Saat execute menampilkan (atau menampilkan promise yang ditolak), agen hanya menerima kegagalan eksekusi umum. Pesan error tidak diteruskan ke agen. Jika alat Anda mengalami error yang dapat ditindaklanjuti atau dipulihkan (seperti input tidak valid, item tidak ada, atau status HTTP gagal), JANGAN throw new Error(...). Sebagai gantinya, tampilkan payload error terstruktur melalui nilai yang ditampilkan normal alat tersebut sehingga agen AI menerima detail diagnostik dan dapat melakukan koreksi mandiri atau mencoba lagi:

execute({ itemName }) {
  const item = menuItems.find((entry) => entry.name.toLowerCase() === itemName.toLowerCase());
  if (!item) {
    // Return the error as a value instead of throwing, so the agent receives the message:
    return {
      error: `Menu item "${itemName}" not found.`,
      availableItems: menuItems.map((entry) => entry.name),
    };
  }
  return item;
}

Hanya tampilkan ulang saat eksekusi dibatalkan (signal.aborted adalah true).

Membatalkan eksekusi yang sedang berjalan

Pembatalan dimulai oleh pengguna atau agen. WAJIB: Alat apa pun yang melakukan permintaan jaringan atau pekerjaan asinkron lain yang berjalan lama HARUS mematuhi signal, jika tidak, eksekusi yang dibatalkan akan terus menggunakan resource dan dapat menulis data usang ke UI setelah agen melanjutkan.

  • API yang menerima sinyal (pembaca fetch(), addEventListener(), ReadableStream): teruskan signal secara langsung.
  • API yang tidak (setTimeout, setInterval, loop polling, pipeline yang berat await): panggil signal.throwIfAborted() di antara langkah-langkah, dan/atau tolak di signal.addEventListener('abort', ...). Timer tidak menerima sinyal, jadi Anda harus menghapusnya sendiri di pemroses pembatalan.
await document.modelContext.registerTool({
  name: "fetch_tool",
  description: "Fetch the text content of a URL and show it on the page.",
  inputSchema: {
    type: "object",
    properties: {
      url: { type: "string", description: "The URL to fetch" },
      priority: {
        type: "string",
        enum: ["high", "low", "auto"],
        description: "Relative network priority for the request",
      },
    },
    required: ["url"],
  },
  execute: async ({ url, priority }, { signal }) => {
    const output = document.querySelector("pre");
    output.textContent = "Loading…";
    try {
      // Passing `signal` aborts the network request the moment execution is cancelled.
      const response = await fetch(url, { priority, signal });
      if (!response.ok) {
        output.textContent = "";
        return { error: `Request failed with status ${response.status}` };
      }
      const text = await response.text();
      output.textContent = text;
      return text;
    } catch (err) {
      // Roll back the UI state this execution set, so a cancelled run leaves no trace.
      output.textContent = "";
      if (signal.aborted) {
        // The execution was cancelled: re-throw instead of returning an error value.
        throw err;
      }
      // Return other errors as a value instead of throwing, so the agent receives the message.
      return { error: err instanceof Error ? err.message : String(err) };
    }
  },
  annotations: { readOnlyHint: true }
});

Menandai Alat yang Berdampak Signifikan

Sejak Chrome 154, annotations mendukung consequentialHint. Tetapkan ke true untuk alat yang melakukan tindakan berisiko tinggi, tidak dapat diurungkan, atau berdampak di dunia nyata — memesan perjalanan, mentransfer uang, mengirim pesan, atau menghapus data. Tindakan ini memberi sinyal kepada agen untuk meminta konfirmasi pengguna secara eksplisit sebelum eksekusi.

await document.modelContext.registerTool({
  name: 'book_flight',
  description: 'Book a flight for the user with confirmed flight details.',
  inputSchema: {
    type: 'object',
    properties: {
      flightId: { type: 'string', description: 'ID of the flight to book' },
      passengers: { type: 'number', description: 'Number of tickets to purchase' },
    },
    required: ['flightId', 'passengers'],
  },
  execute: async ({ flightId, passengers }, { signal }) => {
    // Forward `signal` to the transaction API to honor cancellation:
    return await bookFlightReservation({ flightId, passengers }, { signal });
  },
  annotations: {
    readOnlyHint: false,      // This tool mutates state (it makes a booking).
    consequentialHint: true,  // Spends money and is not trivially reversible.
    untrustedContentHint: false, // Output is first-party data, not third-party content.
  }
});

WAJIB: Perlakukan consequentialHint sebagai petunjuk, bukan mekanisme penerapan. Petunjuk ini tidak memblokir eksekusi dengan sendirinya, dan browser yang lebih lama dari petunjuk ini tidak akan menampilkan dialog sama sekali. Pertahankan konfirmasi dalam aplikasi apa pun yang sudah dijamin oleh tindakan tersebut.

Alat Proses Debug Penandaan

Sejak Chrome 156, annotations mendukung debugging. Tetapkan annotations.debugging ke true untuk alat yang dibuat untuk proses debug dan alat developer, bukan tugas pengguna akhir. Contoh: membuang status aplikasi internal, mengaktifkan/menonaktifkan tombol fitur, atau menyisipkan data pengujian. Hal ini memungkinkan agen membedakan alat khusus developer dari alat yang dimaksudkan untuk bertindak bagi pengguna. Nilai defaultnya adalah false. Agen dalam halaman dan tes otomatis dapat membacanya dari RegisteredTool.annotations.debugging melalui getTools().

await document.modelContext.registerTool({
  name: "dump_app_state",
  description: "Returns a snapshot of the internal application store for debugging.",
  execute() {
    return appStore.getState();
  },
  annotations: {
    readOnlyHint: true, // Only reads state.
    debugging: true,    // Developer tooling, not an end-user action.
  }
});

Agen dan Pengujian Dalam Halaman (getTools dan executeTool)

Halaman web tidak memerlukan getTools() atau executeTool() untuk mengekspos alat ke agen AI. Cukup panggil document.modelContext.registerTool() untuk memungkinkan agen browser, ekstensi browser, DevTools, dan asisten tingkat sistem menemukan dan menjalankan alat Anda secara out-of-band.

Metode getTools() dan executeTool() ada khusus untuk konsumen yang berjalan di dalam lingkungan JavaScript:

  1. Agen JavaScript dalam halaman: Asisten AI, widget copilot, atau antarmuka chat yang disematkan langsung di halaman (atau berjalan di dalam <iframe>) yang perlu menemukan alat yang ditawarkan oleh halaman di sekitarnya atau frame yang dapat diakses di hierarki dokumen, dan memanggilnya atas nama pengguna.
  2. Pengujian dan proses debug otomatis: Suite pengujian (seperti Playwright, Vitest, atau pengujian unit dalam browser) yang memeriksa metadata alat yang terdaftar dan menyimulasikan pemanggilan alat agen tanpa memerlukan harness LLM eksternal yang aktif.

Menemukan alat yang tersedia dengan getTools()

Agen dalam halaman memanggil document.modelContext.getTools() untuk mengambil array objek RegisteredTool dari dokumen saat ini dan frame yang dapat diakses di hierarki dokumen.

Sejak Chrome 155, RegisteredTool.inputSchema ditampilkan langsung sebagai objek JavaScript (salinan mendalam dari skema yang diberikan saat pendaftaran). Mengubah objek ini tidak akan mengubah skema sebenarnya dari alat yang terdaftar.

// Example: An in-page AI assistant discovering tools available on the page:
const tools = await document.modelContext.getTools();

// In-page agents can listen for tools being registered or unregistered dynamically:
document.modelContext.addEventListener("toolchange", async () => {
  const updatedTools = await document.modelContext.getTools();
  console.log(`Active tools updated: ${updatedTools.map((t) => t.name).join(", ")}`);
});

Untuk menemukan alat yang terdaftar di dalam iframe turunan lintas origin (yang harus secara eksplisit memberikan akses melalui exposedTo selama pendaftaran), teruskan fromOrigins dalam kamus opsi:

// Query tools registered in the embedded cart frame that exposed them to this checkout origin:
const cartTools = await document.modelContext.getTools({
  fromOrigins: ["https://cart.example.com"],
});

Memanggil alat dengan executeTool()

Agen dalam halaman dan skrip pengujian memanggil document.modelContext.executeTool(tool, inputObject, options) untuk menjalankan alat yang terdaftar. Alat ini berjalan pada dokumen tempat alat tersebut didaftarkan, dan promise di-resolve ke hasil string yang ditampilkan oleh callback execute alat.

Sejak Chrome 155, executeTool() menerima objek JavaScript biasa secara langsung untuk inputObject, yang cocok dengan format input yang diterima callback execute alat Anda.

// 1. Discover the tool (typically done during agent initialization or test setup):
const tools = await document.modelContext.getTools();
const areaTool = tools.find((t) => t.name === "calculate_area");

if (areaTool) {
  const controller = new AbortController();
  // Example input payload matching the tool's inputSchema:
  const inputObject = { width: 10, height: 20 };
  const options = { signal: controller.signal };

  // 2. Pass input arguments as a JavaScript object, with a JSON string fallback for Chrome <155:
  let resultString;
  try {
    resultString = await document.modelContext.executeTool(areaTool, inputObject, options);
  } catch (err) {
    if (err?.message?.startsWith("Failed to parse input")) {
      resultString = await document.modelContext.executeTool(
        areaTool,
        JSON.stringify(inputObject),
        options
      );
    } else {
      // Re-throw real tool execution or cancellation errors:
      throw err;
    }
  }

  console.log("Execution output string:", resultString);
}

Promise yang ditampilkan akan ditolak jika:

  • inputObject bukan objek atau tidak dapat diserialisasi ke string JSON (misalnya, referensi sirkular atau nilai BigInt). Perhatikan bahwa nilai yang dihilangkan oleh serialisasi JSON (seperti fungsi dan undefined) akan dihilangkan dari objek yang diterima oleh alat, bukan menyebabkan penolakan.
  • Eksekusi dibatalkan melalui options.signal.
  • Alat tidak terdaftar sebelum eksekusi atau mengalami error runtime yang tidak tertangani.

Pola Tool Factory

Untuk meneruskan konteks (seperti penyimpanan atau instance aplikasi) ke alat Anda, gunakan fungsi factory.

export function createInventoryTool(inventoryManager) {
  return {
    name: "get_inventory",
    description: "Lists items in the inventory.",
    inputSchema: { type: "object", properties: {} },
    execute() {
      return inventoryManager.getItems();
    },
    annotations: { readOnlyHint: true }
  };
}

Jenis TypeScript

Dalam project TypeScript, JANGAN menulis deklarasi untuk document.modelContext secara manual. Instal paket webmcp-types resmi (npm install --save-dev webmcp-types) dan tambahkan ke tsconfig.json, karena paket ini tidak diambil secara otomatis:

{
  "compilerOptions": {
    "types": ["webmcp-types"]
  }
}
  • document.modelContext diketik sebagai opsional, jadi lindungi dengan if (document.modelContext) sebelum digunakan.
  • Dengan inputSchema inline, jenis input execute disimpulkan dari skema.
  • Jenis bernama seperti WebMCP.ModelContextTool bersifat global (tidak perlu diimpor), berguna untuk mengetik factory alat.

Dukungan framework

WAJIB: Jika aplikasi menggunakan React, Angular, atau Vue, daftarkan alat melalui integrasi WebMCP resmi framework tersebut, yang tercantum di bawah. JANGAN panggil document.modelContext.registerTool() dari komponen atau tulis wrapper siklus proses Anda sendiri. Setiap integrasi sudah menangani deteksi fitur, pembersihan AbortSignal, dan alat pencakupan ke siklus proses komponen atau injektor. API ini bersifat eksperimental, jadi sebelum menulis kode, cari tanda tangan saat ini di jenis atau dokumen paket.

Framework Paket API Dokumen
React 18+ use-webmcp-tool useWebMCP(options): satu panggilan per alat npmjs.com/package/use-webmcp-tool
Angular @angular/core, @angular/forms/signals provideExperimentalWebMcpTools([...]) (penyedia aplikasi atau rute), declareExperimentalWebMcpTool(...) (layanan internal), provideExperimentalWebMcpForms() + opsi experimentalWebMcpTool di Signal Forms angular.dev/ai/webmcp
Vue @vueuse/core Composable useWebMCP(options) vueuse.org/core/usewebmcp

Semua aturan dalam panduan ini tetap berlaku saat Anda menggunakan integrasi ini:

  • Pertahankan bentuk definisi alat yang sama: name, description, inputSchema, execute(input, { signal }), dan annotations.
  • Ikuti aturan untuk deskripsi dan anotasi.
  • Memvalidasi input dan menerapkan default di dalam execute.
  • Teruskan signal ke pekerjaan yang berjalan lama.
  • Untuk menawarkan alat hanya pada waktu tertentu, teruskan enabled (React, Vue). JANGAN gabungkan hook atau panggilan composable dalam suatu kondisi.
  • Integrasi dapat menggabungkan hasil dan error (misalnya sebagai { content, isError }). Periksa dokumen integrasi sebelum mengandalkan cara error mencapai agen.

Catatan API

  • annotations: (Opsional) Kamus untuk metadata alat.
    • readOnlyHint: (Opsional) Tetapkan ke true jika alat tidak mengubah status apa pun dan hanya membaca data. Hal ini membantu agen memutuskan kapan waktu yang aman untuk memanggil alat.
    • consequentialHint: (Opsional, Chrome 154+) Tetapkan ke true untuk tindakan berisiko tinggi, tidak dapat diurungkan, atau di dunia nyata sehingga agen meminta konfirmasi pengguna terlebih dahulu.
    • untrustedContentHint: (Opsional) Tetapkan ke true jika alat menampilkan konten yang tidak dikontrol situs Anda, seperti teks yang dihasilkan pengguna atau respons API pihak ketiga.
    • debugging: (Opsional, Chrome 156+) Tetapkan ke true jika alat ini ditujukan untuk proses debug dan alat developer, bukan tugas pengguna akhir. Default-nya adalah false.
  • Opsi Pendaftaran:
    • signal: (Opsional) AbortSignal yang digunakan untuk membatalkan pendaftaran alat saat tidak lagi diperlukan.
    • exposedTo: (Opsional) Array string origin aman yang mengontrol dokumen mana dalam hierarki dokumen yang diizinkan untuk menemukan dan menjalankan alat di seluruh batas frame.
  • Opsi Penemuan Alat (ModelContextGetToolOptions):
    • fromOrigins: (Opsional) Array string origin yang aman untuk dikueri dalam frame yang dapat diakses. Memanggil getTools() tanpa fromOrigins hanya akan mengkueri dokumen origin yang sama.
  • Format Return: Fungsi execute dapat menampilkan nilai apa pun yang dapat diubah menjadi format JSON (objek, array, string, angka, boolean). Pesan pengecualian yang ditampilkan dari execute tidak diteruskan ke agen, jadi tampilkan error yang dapat ditindaklanjuti sebagai payload error terstruktur (misalnya, { error: "..." }) melalui nilai yang ditampilkan alat agar agen dapat membaca dan menindaklanjutinya.
  • Konteks Aman: WebMCP memerlukan HTTPS. Semua origin di exposedTo dan fromOrigins juga harus berpotensi tepercaya.
  • Tidak Digunakan Lagi/Dihapus: navigator.modelContext (tidak digunakan lagi di Chromium 150), unregisterTool(), provideContext(), dan clearContext() tidak lagi didukung.

Strategi penggantian

WebMCP Imperative API harus digunakan dengan deteksi fitur untuk memastikan kompatibilitas dengan browser yang belum mendukung WebMCP.

if ('modelContext' in document) {
  // Register tools
}