MCPアプリDevelopment
このドキュメントを使用して概念情報を理解し、具体的なガイドラインに従って、HTMLとJavaScriptでWorkato MCPアプリを構築します。 MCPアプリのDevelopmentには、SDKコネクション、アプリ内からのツールの呼び出し、Content Security Policy設定、設計パターン、デバッグが含まれます。
MCPアプリの仕組み
MCPアプリは、MCPサーバーがクライアントに提供する単一のHTMLドキュメントです。 クライアントはチャット内のサンドボックス化されたiframeでそれをレンダリングします。
理解すべき最も重要な概念は、LLMとアプリの関係です。
- LLMはリンクされたツールを1回呼び出し、アプリを起動します。 LLMがレンダリングに関与するのはこのときだけです。
- アプリの読み込み後、アプリはSDKを通じてツールを直接呼び出します。 LLMはこれらの呼び出しを仲介しません。
- アプリがインタラクションサーフェスです。 ユーザーは、追加のLLMターンを通じてではなく、アプリ内でデータを表示、並べ替え、フィルターし、操作します。
リンクされたツールは、アプリを開くためにLLMによって1回呼び出され、その後はアプリが引き継ぎます。 これにより、ツールの設計方法とアプリコードの記述方法が決まります。 リンクされたツールの入力スキーマに必要なのは、完全なインタラクションペイロードではなく、アプリの起動に必要なパラメーターのみです。
例が必要な場合
ChatGPT、Claude、CursorなどのLLMでGitHubリポジトリの画像を直接表示、パン、ズームできるMCPアプリを作成する方法についてのステップバイステップガイドは、GitHubインタラクティブ画像MCPアプリユースケースを参照してください。
前提条件
MCPアプリコードを記述する前に、次の設定があることを確認します。
- 実行中のレシピによって支えられた、少なくとも1つのツールを持つMCPサーバー。 詳細については、MCPサーバーの作成を参照してください。
- ツールにリンクされたMCPアプリ。 詳細については、MCPアプリの追加を参照してください。
- Claudeなど、MCPアプリをサポートするクライアント。
MCPアプリの構造
MCPアプリには、最低限次の3つの部分が必要です。
- SDKインポート
- コネクション
- レンダリングロジック
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Load external libraries from a CDN. Every external domain must be
declared in the Content security policy. -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<div id="app">Connecting...</div>
<!-- type="module" is required. Top-level await only works in a module script. -->
<script type="module">
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js';
const app = new App({ name: 'My App', version: '1.0.0' });
app.ontoolresult = () => {};
// Await the connection before calling any tool or rendering data.
await app.connect();
document.getElementById('app').textContent = 'Connected';
</script>
</body>
</html>SDKコネクション
MCPアプリは、MCP apps SDK(@modelcontextprotocol/ext-apps)を使用してクライアントと通信します。 これには次の設定が必要です。
- scriptタグには
type="module"を使用する必要があります。 トップレベルのawait app.connect()は、通常のscriptでは構文エラーをスローします。 app.connect()は、ハンドシェイク、ホストコンテキスト、およびセッションを処理します。 ツールを呼び出す前、またはツール結果に依存するデータをレンダリングする前に、これをawaitしてください。 コネクションが解決する前にレンダリングすると、エラーが表示されずに失敗します。- SDKのデフォルトのツール結果レンダリングを抑制するには、接続する前に
app.ontoolresultを設定します。 - インポートURLでSDKを特定のバージョンに固定します。 現在の安定版リリースについては、cdn.jsdelivr.netのドキュメントを参照してください。
MCPアプリからツールを呼び出す
MCPは、接続後にapp.callServerToolを使用して同じMCPサーバー上のツールを呼び出します。 Workatoはツール結果を2つのレイヤーでラップするため、ラップを解除する必要があります。
async function callTool(toolName, params) {
const raw = await app.callServerTool({ name: toolName, arguments: params });
// Layer 1 — MCP envelope. Prefer the typed structuredContent.
// Fall back to the text content as a JSON string.
let parsed = raw && raw.structuredContent;
if (!parsed) {
const text = raw && raw.content && raw.content[0] && raw.content[0].text;
parsed = text ? JSON.parse(text) : raw;
}
// Layer 2 — Workato Skill wrapper. Skills wrap the recipe output under a
// `result` key. API recipes don't. This normalizes both.
return parsed && parsed.result ? parsed.result : parsed;
}
// Example: load and render a list.
const data = await callTool('search_records', { query: '' });
renderRecords(data.items);ツールを呼び出すには、次のガイドラインを使用します。
app.callServerTool({ name, arguments })を使用します。 引数はargumentsフィールドに入ります。- ツール名は、大文字と小文字の区別を含め、ツールと完全に一致する必要があります。 Workatoはアセット名からツール名を派生させ、大文字と小文字を保持します。
Search Recordsという名前のスキルは、search_recordsではなく、ツールSearch_Recordsになります。 呼び出しを記述する前に、AI Hub>Enterprise MCP>[server]>ツールで正確な名前を確認してください。 raw.itemsを直接読み取ると、undefinedが返されます。 データはエンベロープ内にネストされています。 常に両方のレイヤーのラップを解除します。
Content Security Policyの設定
MCPアプリは、厳格なContent Security Policy(CSP)を持つサンドボックス化されたiframe内で実行されます。 アプリがスクリプト、スタイル、フォント、画像、ネットワークリクエスト、またはネストされたフレームのために接続するすべての外部ドメインを宣言する必要があります。宣言されていない場合、ブラウザーはそれをエラー表示なしでブロックします。
MCPアプリを追加または編集するときに、Content security policyセクションを展開し、各外部ドメインを関連するフィールドに追加します。
| フィールド | 制御対象 |
|---|---|
| 接続ドメイン | ネットワークリクエスト(fetch、XHR、WebSocket) |
| リソースドメイン | スクリプト、スタイル、フォント、画像 |
| フレームドメイン | ネストされたiframe。 たとえば、埋め込み動画。 |
| Base URIドメイン | ドキュメントに許可されたbase URI |
最も一般的な間違いは、SDKまたはCSSフレームワークをcdn.jsdelivr.netから読み込む際に、それをリソースドメインに追加しないことです。 その結果、明らかなエラーなしに空白のアプリになります。 アプリが空白またはスタイル未適用でレンダリングされる場合は、CSPを確認してください。
使用するすべてのドメインを追加
アプリが画像CDNから画像を埋め込む場合、またはストリーミングサービスから動画を埋め込む場合は、それらのドメインも追加します。 読み込みに失敗するドメインを見つけるには、ブラウザーの開発者ツールのネットワークタブを使用します。
デザインパターン
トークン効率の高いツール
MCPアプリのアーキテクチャにより、LLMが見るものとユーザーが見るものを分離できます。 LLMに必要なのは、リクエストをルーティングしてアプリを起動するための十分な情報だけです。 アプリにデータを入力するリッチデータは、LLMを完全にバイパスできます。
これにより、次のツールタイプを設計できます。
- LLMが呼び出す起動ツール。 これは、完全なデータセットではなく、件数やステータスなどの軽量な確認応答を返します。
- 完全なペイロードを取得するために、アプリが
app.connect()から呼び出すデータツール。
これにより、大規模なデータセットをLLMのコンテキストから除外でき、リストアプリや詳細アプリでのトークン利用状況とレイテンシーが削減されます。 チャット内でLLMがデータを再記述しないように、起動ツールの説明にレンダリング指示を追加します。
このツールの結果は、チャット内でインタラクティブなMCPアプリとしてレンダリングされます。MCPアプリが応答です。返された項目をテキスト返信で一覧表示、要約、または再記述しないでください。
### クライアント側のデータ派生 {: #client-side-data-derivation :}
フィルターオプションやカテゴリ数などの表示専用データは、レシピではなくMCPアプリのJavaScriptで計算します。レシピは生レコードを返し、アプリが残りを派生させます。これにより、レシピがシンプルに保たれ、レシピFormulaエンジンの制約を回避できます。
```javascript
// Derive filter options from the records the app already has.
function deriveFilters(items) {
return [
{ name: 'Status', values: [...new Set(items.map(i => i.status))] },
{ name: 'Priority', values: [...new Set(items.map(i => i.priority))] }
].filter(g => g.values.length > 0);
}キーボードインタラクション
MCPアプリは、デフォルトのキーボード動作を継承しません。 次のように、ユーザーが期待するインタラクションを追加します。
- クリック可能なカードを
tabindex="0"でフォーカス可能にし、クリックだけでなくEnterとSpaceにも応答するようにします。 - コメントまたはメッセージフィールドでは、Enterで送信し、Shift+Enterで改行を挿入します。
- Escapeでモーダルを閉じます。
チャットパネル内でサイズ変更
ホストが必要な権限を付与しないため、ブラウザーのfullscreen APIはMCPアプリのiframe内でブロックされます。 ユーザーがより広いスペースを使えるように、ボタンでMCpアプリのmax-heightをコンパクトな上限と100vhの間で切り替えます。 MCPアプリはiframeを離れることなく、利用可能なチャットパネル全体に拡張されます。
テストとデバッグ
このセクションを使用して、MCPアプリをテストおよびデバッグします。
Development中にデバッグパネルを使用する
開発中は、SDKコネクションの状態と生のツール結果を確認できるように、表示可能なログ要素をアプリに追加します。 プロダクションに進む前に、次の項目を確認して完了してください。
- HTMLからデバッグ要素を削除します。
- 関連するスタイルを削除します。
- ログ関数の本体を、何もしないように置き換えます。
クライアントはチャットごとにアプリをキャッシュする
MCPアプリコードを編集して保存した後、クライアントは既存のチャット内のMCPアプリを更新しません。 MCPアプリは、チャットで最初にレンダリングされた時点で最新だったバージョンに固定されます。 最新のコードを読み込むには、新しいチャットを開始する必要があります。 ブラウザータブを再読み込みするだけでは不十分です。
デプロイされたコードの確認
どのコードがライブであるかを確認するには、MCPアプリのヘッダーに表示可能なバージョンマーカーを追加し、保存するたびに更新します。 アプリをトリガーするときに、最新のデプロイを見ているかどうかをマーカーで一目で確認できます。
プロダクションチェックリスト
MCPアプリを共有する前に、次を確認します。
- リンクされたすべてのツールの背後にあるレシピが実行中であること。
callServerTool呼び出し内のツール名が、大文字と小文字の区別を含め、サーバーと完全に一致していること。- すべての外部ドメインがContent Security Policyで宣言されていること。
- 起動ツールの説明が、あいまいな機能ラベルではなく、明示的なトリガー条件であること。
- デバッグパネルとすべてのバージョンマーカーが削除されていること。
- 新しいチャットでエンドツーエンドのテストを行ったこと。
既知の制限事項
MCPアプリには次の制限があります。
- Fullscreenは使用できません: ブラウザーのfullscreen APIは、サンドボックス化されたiframe内でブロックされます。 代わりにサイズ変更トグルを使用します。
- アプリの状態は保持されません: MCPアプリはサンドボックス化されたiframe内で実行されます。 チャット履歴は保持されますが、保存済みフィルターやフォーム入力など、MCPアプリの内部状態はナビゲーション時にリセットされます。 状態を保持する必要がある場合は、データストアに支えられたツールを通じて状態を保持します。
- 検証済みユーザーアクセスとトークン認証は相互に排他的です: 検証済みユーザーアクセスを必要とするツールは、トークン認証を使用できません。 利用可能なアクセス方法については、MCP認証を参照してください。
最終更新日: