エージェント JavaScript ツール

ウェブ機能: document.modelContext

Imperative API は、document.modelContext.registerTool() を使用して JavaScript ツールをプログラムで定義します。これは、現在のルートやユーザーの状態に基づいてツールを追加または削除する必要があるシングルページ アプリケーション(SPA)に最適です。

WebMCP は、プロデューサー / コンシューマー アーキテクチャで動作します。

  • ウェブページはツール プロデューサー: document.modelContext.registerTool() を使用してツールを定義して登録します。ウェブページに実装する必要があるのはこれだけです。エージェントにツールを公開するために、ページで getTools() または executeTool() を呼び出す必要はありません。
  • ブラウザ エージェントと拡張機能エージェントが主なコンシューマ: ブラウザによって、またはブラウザを介して提供されるエージェント(ブラウザの組み込みエージェント、ブラウザ拡張機能、DevTools、システムレベルのアシスタントなど)は、内部プラットフォーム メカニズムを介して、登録されたツールを帯域外で検出して呼び出します。
  • ページ内エージェントとテストハーネス: getTools() と executeTool() は、ページ内 JavaScript アシスタント(埋め込みチャット ウィジェットや <iframe> など)と、ページ コンテキスト内でツールを直接検出して呼び出す必要がある自動テストスイート専用に存在します。

登録とライフサイクル

ツールを登録するには、ツール定義オブジェクトと、オプションのオプション オブジェクト(ModelContextRegisterToolOptions)を渡します。このオブジェクトは、次の 2 つのオプション メンバーをサポートしています。

  • signal: 不要になったツールの登録を解除するために使用される AbortSignal。
  • exposedTo: ドキュメント ツリー内のどのドキュメントがフレーム境界を越えてツールを検出して実行できるかを制御するオリジン文字列の配列。

AbortController を使用したライフサイクル処理

WebMCP には unregisterTool() メソッドがありません。ツールの登録を解除するには、登録時に AbortSignal を渡し、ツールが不要になったときにそのシグナルを中止する必要があります。

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

登録シグナルを中止すると、ツールの登録のみが解除されます。すでに実行中の実行はキャンセルされません。長時間実行される作業を開始するツールは、以下で説明する実行ごとのシグナルも遵守する必要があります。そうしないと、登録解除によって孤立したリクエストと古い UI 更新が残ります。

exposedTo を使用してクロスオリジン ツールの公開を制御する

デフォルトでは、登録されたツールは、登録元のドキュメントと同一オリジンのフレームに対して非公開です。アプリケーションが <iframe> に埋め込まれている(またはサードパーティのウィジェットを埋め込んでいる)場合、祖先フレームまたは兄弟フレームがツールを検出して呼び出す必要がある場合は、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"],
});
  • 相互オプトイン ハンドシェイク: クロスオリジン ツール検出は、双方向のオプトインです。ツール プロバイダ フレームは exposedTo を介してコンシューマー オリジンを承認し、コンシューマー フレームは getTools({ fromOrigins: [...] }) を介してプロバイダ オリジンを明示的にリクエストする必要があります。fromOrigins がない場合、getTools() は同一オリジンのドキュメントのみをクエリします。
  • オリジンの照合: 登録書類自体のオリジンは常にアクセスできます。exposedTo で指定されたオリジンには、document.modelContext.getTools({ fromOrigins: [...] }) を介してツールを検出し、document.modelContext.executeTool() を介して実行する権限が付与されます。
  • セキュリティ要件: exposedTo のすべてのオリジンは、潜在的に信頼できる(HTTPS または localhost)必要があります。無効なオリジンまたは安全でないオリジンを渡すと、registerTool() は SecurityError で拒否されます。
  • スコープ: exposedTo は、ドキュメント ツリー内のフレーム間でページ内のクロスドキュメント検出を制御します。

パラメータの定義

パラメータ(params)は、inputSchema プロパティを使用して定義します。これは、ツールが想定する構造化データを記述する JSON スキーマ オブジェクトである必要があります。

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 }
});

ツールとパラメータの説明の作成

エージェントはすでに完全な inputSchema を受け取っているため、description ではツールの機能と使用するタイミングのみを記述する必要があります。パラメータ名、型、enum 値、デフォルト、制限を繰り返さないでください。代わりに、スキーマ(enum、default、minimum/maximum)で宣言します。パラメータの説明も同じルールに従います。値の意味を説明し、型や許可される値は説明しません。

ブラウザは inputSchema に対して引数を検証したり、default 値を適用したりしないため、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 },
});

実行パターン

execute コールバックは常に、入力オブジェクトと、AbortSignal - execute(inputObject, { signal }) を含むオプション オブジェクトの 2 つの引数を受け取ります。

async execute を使用するタイミング

ツールに Promise を返すオペレーションや、完了までに時間がかかるオペレーションが含まれている場合は、async を使用します。

  • ネットワーク呼び出し: API からデータを取得する。
  • 非同期ストレージ: IndexedDB へのアクセス。
  • 外部イベント: 特定の状態変化またはアニメーションの終了を待機しています。
async execute(input, { signal }) {
  const response = await fetch(`/api/data/${input.id}`, { signal });
  return await response.json();
}

execute(同期)を使用するケース

即時オペレーションには、標準の同期関数を使用します。同期ツールは、キャンセルが検出される前に完了まで実行されるため、シグナルを無視できます。

  • 純粋なロジック: メモリにすでに存在するデータの計算、フィルタリング、並べ替え。
  • 同期状態: localStorage または同期状態マネージャーからの読み取り。
execute(input) {
  return input.items.filter(item => item.active);
}

execute でのエラーの処理

execute がスローされた場合(または拒否された Promise が返された場合)、エージェントは一般的な実行エラーのみを受け取ります。エラー メッセージはエージェントに転送されません。ツールで、対応可能または回復可能なエラー(無効な入力、アイテムの欠落、HTTP ステータスの失敗など)が発生した場合は、throw new Error(...) を行わないでください。代わりに、ツールの通常の戻り値を介して構造化されたエラー ペイロードを返します。これにより、AI エージェントは診断の詳細を受け取り、自己修正または再試行できます。

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

実行がキャンセルされた場合(signal.aborted が true)にのみ再スローします。

処理中の実行をキャンセルする

キャンセルは、お客様またはエージェントが開始します。必須: ネットワーク リクエストやその他の長時間実行される非同期処理を実行するツールは、signal を遵守する必要があります。そうしないと、キャンセルされた実行がリソースを消費し続け、エージェントが移動した後に古いデータを UI に書き込む可能性があります。

  • シグナルを受け入れる API(fetch()、addEventListener()、ReadableStream リーダー): signal をそのまま渡します。
  • API は(setTimeout 、setIntervalポーリングループ、await -重いパイプライン): 呼び出しsignal.throwIfAborted()ステップ間、および/または拒否signal.addEventListener('abort', ...)。タイマーはシグナルを受け取らないため、中止リスナーでタイマーを自分でクリアする必要があります。
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 }
});

報告結果ツール

Chrome 154 以降、annotations は consequentialHint をサポートしています。旅行の予約、送金、メッセージの送信、データの削除など、リスクの高い操作、取り消し不可能な操作、実世界での操作を行うツールでは、true に設定します。これは、実行前にユーザーに明示的な確認を求めるようエージェントに指示します。

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.
  }
});

必須: consequentialHint は、強制メカニズムではなくヒントとして扱います。このヒント自体が実行をブロックすることはなく、このヒントより前にリリースされたブラウザではプロンプトは表示されません。アクションですでに保証されているアプリ内確認はそのままにします。

報告のデバッグツール

Chrome 156 以降、annotations は debugging をサポートしています。エンドユーザーのタスクではなく、デバッグやデベロッパー ツール用に構築されたツールの場合は、annotations.debugging を true に設定します。たとえば、内部アプリの状態のダンプ、フィーチャー トグルの切り替え、テストデータのシードなどです。これにより、エージェントはデベロッパー専用のツールと、ユーザーに代わって動作するツールを区別できます。デフォルトでは false に設定されます。ページ内エージェントとテストハーネスは、getTools() を介して RegisteredTool.annotations.debugging から読み取ることができます。

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.
  }
});

ページ内エージェントとテスト(getTools と executeTool)

ウェブページで AI エージェントにツールを公開するために、getTools() や executeTool() は必要ありません。document.modelContext.registerTool() を呼び出すだけで、ブラウザ エージェント、ブラウザ拡張機能、DevTools、システムレベルのアシスタントが、帯域外でツールを検出して実行できます。

getTools() メソッドと executeTool() メソッドは、JavaScript 環境内で実行されるコンシューマ専用です。

  1. ページ内 JavaScript エージェント: ページに直接埋め込まれた(または <iframe> 内で実行されている)AI アシスタント、コパイロット ウィジェット、チャット インターフェース。周囲のページまたはドキュメント ツリー内のアクセス可能なフレームが提供するツールを検出し、ユーザーに代わって呼び出す必要があります。
  2. 自動テストとデバッグ: 登録済みのツール メタデータを検査し、ライブの外部 LLM ハーネスを必要とせずにエージェント ツールの呼び出しをシミュレートするテストスイート(Playwright、Vitest、ブラウザ内単体テストなど)。

getTools() で利用可能なツールを検出する

ページ内エージェントは document.modelContext.getTools() を呼び出して、現在のドキュメントとドキュメント ツリー内のアクセス可能なフレームから RegisteredTool オブジェクトの配列を取得します。

Chrome 155 以降、RegisteredTool.inputSchema は JavaScript オブジェクト(登録時に提供されたスキーマのディープコピー)として直接返されます。このオブジェクトを変更しても、登録されているツールの実際のスキーマは変更されません。

// 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(", ")}`);
});

クロスオリジンの子孫 iframe 内に登録されているツール(登録時に exposedTo を介してアクセスを明示的に許可する必要がある)を検出するには、オプション ディクショナリで fromOrigins を渡します。

// 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"],
});

executeTool() を使用したツールの呼び出し

ページ内エージェントとテスト スクリプトは、document.modelContext.executeTool(tool, inputObject, options) を呼び出して登録済みのツールを実行します。ツールは、登録されたドキュメントで実行され、Promise はツールの execute コールバックから返された文字列化された結果に解決されます。

Chrome 155 以降、executeTool() は inputObject にプレーンな JavaScript オブジェクトを直接受け入れるようになり、ツールの execute コールバックが受け取る入力形式と一致するようになりました。

// 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 は拒否されます。

  • inputObject がオブジェクトではないか、JSON 文字列にシリアル化できない(循環参照や BigInt 値など)。JSON シリアル化によって省略された値(関数や undefined など)は、拒否されるのではなく、ツールが受け取るオブジェクトから省略されます。
  • 実行は options.signal を介して中止されます。
  • 実行前にツールの登録が解除されたか、未処理のランタイム エラーが発生しました。

ツール ファクトリー パターン

コンテキスト(ストアやアプリケーション インスタンスなど)をツールに渡すには、ファクトリ関数を使用します。

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

TypeScript の型

TypeScript プロジェクトでは、document.modelContext の宣言を手動で作成しないでください。公式の webmcp-types パッケージ(npm install --save-dev webmcp-types)をインストールし、tsconfig.json に追加します。これは自動的に取得されないためです。

{
  "compilerOptions": {
    "types": ["webmcp-types"]
  }
}
  • document.modelContext はオプションとして型指定されているため、使用する前に if (document.modelContext) で保護します。
  • インライン inputSchema を使用すると、execute 入力タイプはスキーマから推測されます。
  • WebMCP.ModelContextTool などの名前付き型はグローバル(インポートは不要)で、ツール ファクトリの型指定に役立ちます。

フレームワークのサポート

必須: アプリで React、Angular、Vue を使用している場合は、以下に記載されているフレームワークの公式 WebMCP 統合を通じてツールを登録します。コンポーネントから document.modelContext.registerTool() を呼び出したり、独自のライフサイクル ラッパーを作成したりしないでください。各統合では、機能検出、AbortSignal のクリーンアップ、コンポーネントまたはインジェクタのライフサイクルへのツールのスコープ設定がすでに処理されています。これらの API は試験運用版であるため、コードを記述する前に、パッケージのタイプまたはドキュメントで現在のシグネチャを確認してください。

フレームワーク パッケージ API ドキュメント
React 18+ use-webmcp-tool useWebMCP(options): ツールごとに 1 回の呼び出し npmjs.com/package/use-webmcp-tool
Angular @angular/core、@angular/forms/signals provideExperimentalWebMcpTools([...])(アプリまたはルート プロバイダ)、declareExperimentalWebMcpTool(...)(サービス内)、provideExperimentalWebMcpForms() + 信号フォームの experimentalWebMcpTool オプション angular.dev/ai/webmcp
Vue @vueuse/core useWebMCP(options) コンポーザブル vueuse.org/core/usewebmcp

これらの統合を使用する場合でも、このガイドのすべてのルールが適用されます。

  • ツール定義の形状を同じに保ちます(name、description、inputSchema、execute(input, { signal })、annotations)。
  • 説明とアノテーションのルールに従ってください。
  • execute 内で入力を検証し、デフォルトを適用します。
  • 長時間実行される処理に signal を渡します。
  • ツールを一部のタイミングでのみ提供するには、enabled(React、Vue)を渡します。フックまたはコンポーザブルの呼び出しを条件でラップしないでください。
  • 統合では、結果とエラーがラップされる場合があります({ content, isError } など)。エラーがエージェントに到達する方法に依存する前に、統合のドキュメントを確認してください。

API に関する注

  • annotations:(省略可)ツールのメタデータの辞書。
    • readOnlyHint: (省略可)ツールが状態を変更せず、データの読み取りのみを行う場合は、true に設定します。これにより、エージェントはツールを呼び出すタイミングを判断できます。
    • consequentialHint: (省略可、Chrome 154 以降)重大なアクション、不可逆的なアクション、現実世界のアクションの場合は true に設定して、エージェントが最初にユーザーの確認をリクエストするようにします。
    • untrustedContentHint: (省略可)ユーザー作成のテキストやサードパーティの API レスポンスなど、サイトが管理していないコンテンツをツールが返す場合は、true に設定します。
    • debugging: (省略可、Chrome 156 以降)ツールがエンドユーザーのタスクではなく、デバッグとデベロッパー ツールを目的としている場合は、true に設定します。デフォルト値は false です。
  • 登録オプション:
    • signal: (省略可)ツールが不要になったときに登録を解除するために使用される AbortSignal。
    • exposedTo: (省略可)ドキュメント ツリー内のどのドキュメントがフレーム境界を越えてツールを検出して実行できるかを制御する、安全なオリジン文字列の配列。
  • ツールの検出オプション(ModelContextGetToolOptions):
    • fromOrigins: (省略可)アクセス可能なフレームでクエリする安全なオリジン文字列の配列。fromOrigins を指定せずに getTools() を呼び出すと、同一オリジンのドキュメントのみがクエリされます。
  • 戻り値の形式: execute 関数は、JSON でシリアル化可能な任意の値(オブジェクト、配列、文字列、数値、ブール値)を返すことができます。execute からスローされた例外のメッセージはエージェントに転送されません。そのため、エージェントが読み取って対応できるように、ツールの戻り値を介して、対応可能なエラーを構造化されたエラー ペイロード({ error: "..." } など)として返します。
  • セキュア コンテキスト: WebMCP には HTTPS が必要です。exposedTo と fromOrigins のすべてのオリジンも、信頼できる可能性があります。
  • 非推奨 / 削除: navigator.modelContext(Chromium 150 で非推奨)、unregisterTool()、provideContext()、clearContext() のサポートを終了しました。

代替戦略

WebMCP Imperative API は、WebMCP をまだサポートしていないブラウザとの互換性を確保するために、機能検出とともに使用する必要があります。

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