WebMCP入門|メモ検索をAIから呼べるWebアプリを作る

黄色い背景に「WebMCP」「AIがメモを探す」の文字と、OPFSのメモ2件を囲む虫眼鏡を配置した図 生成AI・Web開発
カテゴリー
生成AI・Web開発
公開日
2026.09.09

はじめに

自分で作ったWebページの検索機能を、AIからも使えるようにしたい。そんなときに試せるのが、ページの機能を「ツール」として公開するWebMCPです。

この記事は、HTMLのフォームとJavaScriptの関数を学んだ人に向けた実装入門です。架空の学習メモを検索するアプリを作り、通常の検索ボタンとWebMCPから同じ結果を得るところまで進めます。Codexから公開ツールを呼ぶ例も紹介します。

WebMCPは実験的なブラウザAPIです。 この記事のコードはChrome 152の試用機能を対象にしています。一般的なブラウザで常に使える前提にはせず、WebMCPが無効なときも画面の検索が動く構成にします。

作るものと到達点

完成するのは、HTML・CSS・JavaScriptの3ファイルからなる「メモ検索ラボ」です。

  • 架空メモ4件を、タイトルと本文の部分一致で検索する。
  • 同じ検索関数を、画面とsearch_notesツールの両方で使う。
  • 空入力や不正な件数を拒否し、検索結果がない場合と区別する。
  • Chromeでツールを直接呼び、Codexからも結果を取得する。
OPFSを検索し、n1とn4の2件が表示されたメモ検索ラボ

メモの編集・永続保存、意味検索、ログインは扱いません。外部ライブラリやAPIキーもアプリには組み込みません。まず、既存の関数をツールにする流れを一つ理解することが目標です。

仕組み:画面とツールで検索処理を共有する

検索ボタンを押すと、画面はsearchNotes()を呼びます。WebMCPでは、ツール名、説明、入力の形、実行する関数をブラウザへ登録します。ツールが呼ばれたときも、同じsearchNotes()へ入力を渡します。

フォーム → searchNotes() → 画面へ表示
Codex → ブラウザのWebMCP → search_notes → searchNotes() → 結果を返す

ツールを使うAIは、説明を読み、例えば「OPFSのメモを2件探す」という依頼をquery: "OPFS"limit: 2に変換します。実際の部分一致検索をするのは、AIではなくアプリのJavaScriptです。

WebMCPと、Node.jsなどで動かすMCPサーバーは、実行場所と接続方法が異なります。また、WebMCPにローカルLLMが付属するわけではありません。ツールの実行場所がブラウザでも、接続するAIの推論やデータ処理が外部サービスで行われる場合があります。

公式のImperative APIでは、document.modelContext.registerTool()でJavaScriptの機能を登録します。古い解説にあるnavigator.modelContextと混ぜず、対象ブラウザで利用できるAPIを確認してください。

必要な環境

用途 必要なもの
ファイル作成 テキストエディター、WindowsのPowerShell
ローカル配信 Python 3。ここではpyコマンドを使用
WebMCPの試用 Chrome 152、WebMCP for testingを有効化
画面からの通常検索 JavaScriptのES Modulesを使えるブラウザ
AIからの呼び出し ページのWebMCPツールを扱えるCodexのブラウザ環境

通常検索とブラウザからの直接呼び出しには、有料AIサービスは必要ありません。Codexの利用には、利用中のアカウント・契約・使用量制限が適用されます。この記事ではAPIキーを作成したり、有料APIへ直接リクエストしたりしません。

Pythonがまだない場合は、Pythonのインストールと環境設定を先に進めてください。py --versionでバージョンが表示されれば、この手順を実行できます。

手順1:3つのファイルを作る

作業用フォルダーに、次の構成でファイルを保存します。文字コードはUTF-8です。拡張子が.html.txtなどにならないよう、エクスプローラーでファイルの拡張子を表示しておきます。

webmcp-note-search/
  index.html
  style.css
  app.js

index.html:検索画面を用意する

type="module"でJavaScriptを読み込みます。通常の検索と、開発用のツール呼び出しボタンを別に置いています。後者は、AIを使わずにWebMCPの経路だけを調べるためのものです。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>メモ検索ラボ | Kanimiso-Lab</title>
  <link rel="icon" href="data:,">
  <link rel="stylesheet" href="style.css">
  <script type="module" src="app.js"></script>
</head>
<body>
  <main>
    <header><span class="brand">KANIMISO-LAB / WEBMCP</span><span class="badge">練習用デモ</span></header>
    <p class="eyebrow">検索する。ツールとして呼び出す。</p>
    <h1>メモ検索ラボ</h1>
    <p class="intro">同じ検索処理を、画面とWebMCPから使います。<br>登録されているのは、架空の学習メモ4件です。</p>
    <p id="api-status" class="status" role="status">WebMCPの状態を確認しています…</p>
    <section aria-labelledby="search-heading" class="panel">
      <h2 id="search-heading">メモを探す</h2>
      <form id="search-form" novalidate>
        <div class="fields">
          <label>検索語<input id="query" name="query" type="search" value="OPFS" maxlength="80" placeholder="例:OPFS、Python、MCP"></label>
          <label class="limit">表示件数<select id="limit" name="limit"><option value="2">2件まで</option><option value="4">4件まで</option></select></label>
          <button type="submit">検索する</button>
        </div>
      </form>
      <p id="result-status" role="status" aria-live="polite">検索語を入力してください。</p>
      <ul id="results" class="results"></ul>
    </section>
    <details class="panel">
      <summary>WebMCPから同じ検索を試す</summary>
      <p>上の入力をJSONにして、ブラウザに登録した <code>search_notes</code> を直接呼び出します。このボタンはAIを使いません。</p>
      <button id="tool-search" type="button" disabled>ツールで検索する</button>
      <pre id="tool-output" aria-live="polite">呼び出し結果がここに表示されます。</pre>
    </details>
    <footer>検索は部分一致です。データの保存・編集機能はありません。<br>このデモ自身は外部APIに接続しません。AIや拡張機能と接続すると、検索結果が接続先へ渡る場合があります。</footer>
  </main>
</body>
</html>

style.css:画面を整える

表示を整えるCSSです。ブラウザ幅が狭いときは検索欄を折り返します。

:root {
  font-family:system-ui,"Yu Gothic",Meiryo,sans-serif;
  color:#173b37;
  background:#f4f6f2;
  font-synthesis:none;
  line-height:1.7
}
* {
  box-sizing:border-box
}
body {
  margin:0
}
main {
  max-width:960px;
  margin:auto;
  padding:40px 32px
}
header {
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:16px;
  border-bottom:1px solid #cbd9cf;
  padding-bottom:22px
}
.brand {
  font-size:13px;
  letter-spacing:.12em;
  font-weight:750
}
.badge {
  font-size:12px;
  border:1px solid #b7cabc;
  border-radius:30px;
  padding:3px 12px
}
.eyebrow {
  margin:32px 0 4px;
  font-size:14px;
  color:#456b60
}
h1 {
  font-size:clamp(32px,5vw,48px);
  line-height:1.25;
  letter-spacing:-.03em;
  margin:0 0 16px
}
h2 {
  font-size:20px;
  margin:0 0 20px
}
.intro {
  color:#465e54;
  margin-bottom:20px
}
.status {
  font-size:14px;
  border-left:4px solid #40826d;
  padding:10px 16px;
  background:#e5eee6
}
.panel {
  background:#fff;
  border:1px solid #d3ded4;
  border-radius:14px;
  padding:24px;
  margin-top:24px
}
.fields {
  display:flex;
  gap:16px;
  align-items:end
}
label {
  display:block;
  font-size:13px;
  font-weight:650;
  flex:1
}
.limit {
  flex:0 0 130px
}
input,select,button {
  font:inherit;
  border-radius:7px;
  min-height:46px
}
input,select {
  display:block;
  width:100%;
  border:1px solid #91a69b;
  padding:8px 12px;
  margin-top:6px;
  color:#183b36;
  background:#fff
}
button {
  border:0;
  padding:10px 24px;
  background:#205a49;
  color:#fff;
  font-weight:650;
  cursor:pointer
}
button:disabled {
  background:#697b73;
  cursor:not-allowed
}
button:hover:enabled {
  background:#164535
}
:focus-visible {
  outline:3px solid #a86312;
  outline-offset:3px
}
#result-status {
  font-size:14px;
  color:#42665a;
  margin:20px 0 8px
}
.results {
  list-style:none;
  padding:0;
  margin:0
}
.results li {
  border-top:1px solid #e1e8e0;
  padding:14px 0
}
.results h3 {
  font-size:17px;
  margin:0 0 3px
}
.results p {
  color:#53655e;
  font-size:14px;
  margin:0
}
.results small {
  color:#557366
}
summary {
  font-size:16px;
  font-weight:650;
  cursor:pointer
}
details p {
  font-size:14px
}
pre {
  background:#f1f5f0;
  padding:16px;
  border-radius:6px;
  white-space:pre-wrap;
  overflow-wrap:anywhere;
  font-size:13px;
  color:#173b37
}
code {
  font-family:ui-monospace,Consolas,monospace
}
footer {
  font-size:12px;
  color:#54685c;
  margin-top:24px
}
@media(max-width:580px) {
  main {
    padding:24px 16px
  }
  .fields {
    flex-wrap:wrap;
    gap:12px
  }
  .fields label:first-child {
    flex-basis:100%
  }
  .limit {
    flex:1
  }
  .panel {
    padding:18px
  }
  .brand {
    font-size:10px;
    letter-spacing:.06em
  }
  .badge {
    white-space:nowrap
  }
  .intro br {
    display:none
  }
}

app.js:検索とツール登録を実装する

まずは次の全体を保存してください。このあと、入力の確認、登録、呼び出しの部分を順番に解説します。

// 架空の学習データ。実在する個人情報を入れないでください。
const notes = Object.freeze([
  { id: "n1", title: "OPFSでメモを保存", body: "OPFSはオリジン専用のファイル保存領域。保存と読み込みを練習する。" },
  { id: "n2", title: "Pythonの仮想環境", body: "venvで環境を分け、pipでライブラリを管理する。" },
  { id: "n3", title: "MCPツールの入力確認", body: "ツールの引数は、実行する関数でも型と値を確認する。" },
  { id: "n4", title: "OPFSの保存先", body: "OPFSのデータはオリジンごとに分かれる。ポートの違いにも注意する。" }
].map(Object.freeze));

export function searchNotes(input) {
  // JSON Schemaの宣言だけに依存せず、実行時にも確認します。
  if (!input || typeof input !== "object" || Array.isArray(input)) {
    throw new TypeError("入力はオブジェクトにしてください。");
  }
  if (Object.keys(input).some(key => !["query", "limit"].includes(key))) {
    throw new TypeError("queryとlimit以外の項目は指定できません。");
  }
  if (typeof input.query !== "string") {
    throw new TypeError("queryは文字列にしてください。");
  }
  const query = input.query.trim();
  if (query.length === 0 || query.length > 80) {
    throw new RangeError("検索語は空白を除いて1〜80文字にしてください。");
  }
  const limit = input.limit === undefined ? 2 : input.limit;
  if (!Number.isInteger(limit) || limit < 1 || limit > 4) {
    throw new RangeError("limitは1〜4の整数にしてください。");
  }
  const word = query.toLowerCase();
  const matches = notes.filter(note =>
    `${note.title}\n${note.body}`.toLowerCase().includes(word)
  );
  return { query, total: matches.length, notes: matches.slice(0, limit).map(note => ({ ...note })) };
}

export const noteTool = {
  name: "search_notes",
  description: "架空の学習メモを検索します。queryには探すキーワードを1つ指定してください。タイトルと本文の大文字小文字を区別しない部分一致です。保存や編集は行いません。",
  inputSchema: {
    type: "object",
    properties: {
      query: { type: "string", minLength: 1, maxLength: 80, description: "検索語。例:OPFS、Python、MCP。自然言語の依頼文全体ではなくキーワードを渡します。" },
      limit: { type: "integer", minimum: 1, maximum: 4, description: "最大表示件数。省略すると2。" }
    },
    required: ["query"],
    additionalProperties: false
  },
  annotations: { readOnlyHint: true, untrustedContentHint: true },
  execute: async input => JSON.stringify(searchNotes(input))
};

// 画面の検索はWebMCPの有効・無効に関係なく利用できます。
const form = document.querySelector("#search-form");
const resultStatus = document.querySelector("#result-status");
const results = document.querySelector("#results");
const toolButton = document.querySelector("#tool-search");
const toolOutput = document.querySelector("#tool-output");
const apiStatus = document.querySelector("#api-status");

function readInput() {
  return { query: form.elements.query.value, limit: Number(form.elements.limit.value) };
}

function renderResult(result) {
  results.replaceChildren();
  resultStatus.textContent = `「${result.query}」:${result.total}件見つかりました(${result.notes.length}件表示)。`;
  for (const note of result.notes) {
    const item = document.createElement("li");
    const title = document.createElement("h3");
    const body = document.createElement("p");
    const id = document.createElement("small");
    title.textContent = note.title;
    body.textContent = note.body;
    id.textContent = note.id;
    item.append(title, body, id);
    results.append(item);
  }
}

form.addEventListener("submit", event => {
  event.preventDefault();
  try {
    renderResult(searchNotes(readInput()));
  } catch (error) {
    results.replaceChildren();
    resultStatus.textContent = error.message;
  }
});

async function setupWebMCP() {
  try {
    const context = document.modelContext;
    if (!context || typeof context.registerTool !== "function") {
      apiStatus.textContent = "WebMCPは利用できません。通常の検索は使えます。";
      return;
    }
    await context.registerTool(noteTool);
    apiStatus.textContent = "WebMCP:search_notesを登録しました。";
    if (typeof context.getTools !== "function" || typeof context.executeTool !== "function") {
      apiStatus.textContent += " このブラウザでは画面からの直接呼び出しに対応していません。";
      return;
    }
    toolButton.disabled = false;
    toolButton.addEventListener("click", async () => {
      toolButton.disabled = true;
      try {
        const tool = (await context.getTools()).find(item => item.name === noteTool.name);
        if (!tool) throw new Error("search_notesが見つかりません。ページを再読み込みしてください。");
        // 対象ChromeのexecuteToolには、入力をJSON文字列で渡します。
        const raw = await context.executeTool(tool, JSON.stringify(readInput()));
        if (raw === null) throw new Error("ページ遷移が発生したため、結果を取得できませんでした。");
        const result = JSON.parse(raw);
        renderResult(result);
        toolOutput.textContent = JSON.stringify(result, null, 2);
      } catch (error) {
        results.replaceChildren();
        resultStatus.textContent = "ツールでの検索に失敗しました。";
        toolOutput.textContent = `${error.name}: ${error.message}`;
      } finally {
        toolButton.disabled = false;
      }
    });
  } catch (error) {
    apiStatus.textContent = `WebMCPの登録に失敗しました:${error.name}。通常の検索は使えます。`;
  }
}

await setupWebMCP();

手順2:ローカルサーバーで開く

作ったフォルダーをエクスプローラーで開き、アドレスバーへpowershellと入力してEnterを押します。そのフォルダーを作業場所としてPowerShellが開きます。

py -m http.server 8765 --bind 127.0.0.1

PowerShellは開いたままにして、Chromeでhttp://127.0.0.1:8765/を開きます。index.htmlをダブルクリックするfile://の開き方は使いません。JavaScriptモジュールとWebMCPをローカルHTTP経由で試すためです。

127.0.0.1は自分のPCを指します。ここでは開発サーバーを自分のPCからだけ接続できるようにしています。終了するときは、サーバーを動かしたPowerShellでCtrl+Cを押します。

最初はWebMCPが無効でも構いません。「検索する」を押し、OPFSを含むメモが2件表示されることを確かめてください。

手順3:WebMCPを有効にする

Chromeでchrome://flags/#enable-webmcp-testingを開き、WebMCP for testingEnabledに変更します。表示される再起動操作でChromeを再起動し、デモのURLを開き直します。作業中のタブは先に保存してください。

画面に「WebMCP:search_notesを登録しました。」と表示されれば、登録処理まで進んでいます。

公式にはChrome 149からのOrigin Trialも案内されていますが、この記事はローカルでのflagによる試用に限定します。実験が終わったら、同じ設定をDefaultへ戻してChromeを再起動してください。Codex内のブラウザは別の環境なので、通常のChromeのflagを変えれば必ずそちらも変わるわけではありません。

解説:検索処理をツールにするときの3つの要点

1.入力の形はJSON Schema、実際の確認は関数で行う

inputSchemaは、ツールに渡す入力の形を説明します。queryは文字列、limitは1〜4の整数です。

ただし、宣言だけに任せず、searchNotes()でも確認しています。画面以外から呼ばれたときは、フォームの選択肢やmaxlengthを通らないからです。空白だけの検索語、数値として渡された検索語、余計な項目などを実行前に拒否します。

検索は大文字小文字を区別しない部分一致です。「Pythonの環境を分ける方法を教えて」のような文全体を渡しても、その文字列を含むメモがなければ0件です。自然言語から適切な検索語を選ぶ処理と、アプリの検索処理を混同しないようにします。

なお、コードの文字数判定はJavaScriptのlengthです。絵文字などは見た目の1文字が複数として数えられる場合があります。

2.検索結果はデータとして返す

executeは、検索結果をJSON.stringify()で文字列にして返します。返す値には、一致したメモの総件数を表すtotalと、件数制限を適用したnotesがあります。OPFSが2件一致してもlimit: 1なら、totalは2、返すメモは1件です。

readOnlyHintは、このツールがデータを書き換えないことを説明するヒントです。権限制御の代わりにはなりません。untrustedContentHintは、メモなどの内容を指示ではなくデータとして扱うためのヒントです。本物のメモを使う際は、返す情報の範囲をさらに検討してください。

画面への表示にはtextContentを使っています。検索語やメモをHTMLとして組み立てないので、文字列にタグが入っても、そのまま実行する設計にはなっていません。

3.ツールの登録失敗で通常検索を止めない

通常検索のイベントを先に登録し、WebMCPの準備を別のtryブロックで行います。APIがない場合や登録に失敗した場合でも、通常検索はそのまま使えます。

画面の「ツールで検索する」ボタンは、getTools()でツールを探し、executeTool()で呼びます。対象Chromeでは、入力をJSON文字列で渡します。返ってきた文字列をJSONに戻し、通常検索と同じ表示関数に渡しています。

動作確認:画面とWebMCPの結果を比べる

OPFSを入力し、通常の「検索する」を押します。次に「WebMCPから同じ検索を試す」を開き、「ツールで検索する」を押してください。

どちらも、n1「OPFSでメモを保存」とn4「OPFSの保存先」の2件が表示されます。ツール側ではJSONも表示されます。

WebMCPの直接呼び出しでOPFSのメモ2件とJSONの結果が表示された画面

開発者ツールのConsoleで経路を確認したい場合は、自分で作ったこのデモのページで次を実行します。

const tools = await document.modelContext.getTools();
const tool = tools.find(item => item.name === "search_notes");
if (!tool) throw new Error("search_notesが見つかりません。");
const result = await document.modelContext.executeTool(
  tool,
  JSON.stringify({ query: "OPFS", limit: 2 })
);
console.log(JSON.parse(result));

これはツールの直接呼び出しです。この結果だけでは、AIが自然言語を理解してツールを選べた証拠にはなりません。 また、Consoleからの呼び出しはデータを返すだけで、画面の検索結果を更新しません。画面の開発用ボタンには、結果を表示する処理を別に書いています。

試す入力 期待される結果
{"query":"OPFS","limit":2} totalは2、n1とn4
{"query":" pYtHoN "} 前後の空白を除き、n2が1件
{"query":"OPFS","limit":1} totalは2、表示対象はn1の1件
{"query":"Rust"} totalは0、notesは空配列
{"query":" "} 空白だけの入力として拒否
{"query":123} queryの型違いとして拒否
{"query":"OPFS","limit":0} 件数の範囲外として拒否

実装の検証では、ネイティブAPIの正常系5ケース、不正入力12ケース、同名登録と不正JSONの2ケースを確認しました。通常検索は、WebMCPを無効にしたChromeとEdgeでも利用できました。これは試した環境・入力についての結果であり、全ブラウザや全AIクライアントへの対応保証ではありません。

Codexから公開ツールを呼ぶ

Codex側でページのWebMCPツールを取得・呼び出しできるブラウザ機能が利用できることが前提です。CLIにURLを貼るだけで必ず使える、という意味ではありません。利用できる機能やプランが異なる場合は、先にブラウザでの直接呼び出しまで確認してください。

  1. ローカルサーバーを起動したまま、Codexでこの作業を扱うタスクを開きます。
  2. 次のように依頼します。
  3. 実行記録に、ページのsearch_notesツールと入力・戻り値があるかを確認します。
アプリ内ブラウザで http://127.0.0.1:8765/ を開いてください。
ページが公開しているWebMCPツールを使い、OPFSについてのメモを2件探してください。
検索ボタンのクリックやソースコードの読み取りではなく、ツールの戻り値を基に答えてください。

この制作では、Codexのアプリ内ブラウザがページのsearch_notesを検出し、query: "OPFS", limit: 2で呼び出して、n1とn4を取得しました。続けてPythonの1件検索とRustの該当なしも、CodexのWebMCP接続経由で確認しました。

検証は、制作中のCodexに用意した検索ケースを実行させたものです。未知の依頼への正答率、独立した新規会話での再現率、一般的なブラウザ操作より速いかどうかは測っていません。「AIなら必ず正しく探せる」とは結論づけません。

Codexから直接ツールを呼んだ場合、ページの検索欄や結果一覧は自動では変わりません。このツールは検索結果を返すだけで、表示を変更しないからです。結果はCodexが取得したツールの戻り値で確認します。

トラブル対処

WebMCPが利用できないと表示される

通常の検索が動くなら、まずChromeのバージョン、試用flag、再起動、URLを確認します。file://ではなく、指定したローカルHTTPのURLを開いてください。企業管理のブラウザでは、設定変更が制限されている場合があります。

登録したのにCodexから見つからない

通常Chromeのタブと、Codexが開いたブラウザのタブは別です。Codexが実際に開いたURLと、そのページで登録が完了しているかを確かめます。登録できても、クライアント側がWebMCPに対応していなければ呼び出せません。

不正な入力でUnknownErrorが返る

対象Chromeでは、検索関数のTypeErrorRangeErrorが、呼び出し側ではUnknownErrorとして返るケースがありました。Consoleには「queryは文字列にしてください」など、関数が投げた理由が表示されます。JSONの構文エラーも区別して確認してください。

意図的に失敗例を試した際のConsoleエラーは、診断の一部です。正常な検索だけを行った場合には、アプリ由来の予期しないConsoleエラーは確認されませんでした。

Duplicate tool nameになる

同じページでsearch_notesを二度登録すると、対象ChromeではInvalidStateErrorになります。サンプルはページ読み込み時に一度だけ登録します。Consoleで登録コードを繰り返し実行した場合はページを再読み込みしてください。画面の再描画ごとに登録するアプリでは、登録の寿命を別途設計する必要があります。

サーバーを起動できない、app.jsが404になる

ポート8765が使用中なら、別の空いているポートを指定し、ブラウザとCodexへ渡すURLも同じ番号に変更します。app.jsが404なら、PowerShellを開いたフォルダーとファイル名を見直してください。

制約と向かない用途

このデモは小さなデータを対象にした部分一致検索です。大量データ、曖昧な質問への意味検索、複数人の権限管理にはそのまま使えません。

また、読み取り専用でも情報漏えいが起こらないとは限りません。アプリ自身の通信はローカルファイルの取得だけですが、接続するAIや拡張機能にはメモ本文が渡り得ます。まずは架空データを使い、本物のデータを返す前に、接続先のデータの扱いと公開範囲を確認してください。

クロスオリジンの公開設定はこのコードには追加していません。注文・削除などの処理へ発展させる場合は、入力確認に加え、認可、利用者の確認、再実行時の扱いを設計する必要があります。詳しくは公式のツール安全性ガイドを参照してください。

WebMCPは実験的な機能なので、全利用者が使う必須機能や、無人で動き続ける処理の土台として採用する前には、対応環境と運用方法を検討してください。

まとめ

WebMCPでは、JavaScriptの関数を、説明と入力の形を持つツールとしてブラウザへ登録できます。今回のメモ検索では、通常の画面とツールが同じ関数を呼ぶため、結果を照合しながら実装を確かめられました。Codexからのツール呼び出しも確認できました。

最初は読み取り専用の小さな機能を一つ選び、直接呼び出し、AI接続、入力エラーを分けて試してみてください。

次にメモを保存したい場合は、OPFS入門へ進めます。サーバー側で使うMCPとの違いを整理したい場合は、MCP TypeScript SDK v2入門も参考になります。

参考リンク

サンプルコードはKanimiso-LabのMIT Licenseによる教材です。メモは架空データです。本文中の操作画面は、このサンプルを実際に動かしたスクリーンショットです。アイキャッチはメモ検索を表す図解です。

コメント

タイトルとURLをコピーしました