このチュートリアルでは、モデル コンテキスト プロトコル (MCP) を使用して Express.js アプリの機能を公開し、GitHub Copilot にツールとして追加し、Copilot Chat エージェント モードで自然言語を使用してアプリと対話する方法について説明します。
Web アプリケーションにショッピング、ホテル予約、データ管理などの便利な機能が既にある場合は、これらの機能を次の目的で簡単に利用できます。
- Visual Studio Code または GitHub Codespaces の GitHub Copilot Chat エージェント モードなど、 MCP 統合をサポートするアプリケーション。
- MCP クライアントを使用してリモート ツールにアクセスするカスタム エージェント。
MCP サーバーを Web アプリに追加することで、エージェントがユーザー プロンプトに応答したときにアプリの機能を理解して使用できるようになります。 つまり、アプリでできることは何でも、エージェントでも実行できます。
- MCP サーバーを Web アプリに追加します。
- GitHub Copilot Chat エージェント モードで MCP サーバーをローカルでテストします。
- MCP サーバーを Azure App Service にデプロイし、GitHub Copilot Chat でそれに接続します。
Prerequisites
このチュートリアルでは、「 チュートリアル: Node.js + MongoDB Web アプリを Azure にデプロイする」で使用するサンプルを使用していることを前提としています。
少なくとも、GitHub Codespaces で サンプル アプリケーション を開き、 azd upを実行してアプリをデプロイします。
MCP サーバーを Web アプリに追加する
codespace ターミナルで、必要な npm パッケージをプロジェクトに追加します。
npm install @modelcontextprotocol/sdk@latest zod-
- ルート/index.jsを開きます。 このシナリオをわかりやすくするために、ここにすべての MCP サーバー コードを追加します。
ルート/index.jsの先頭に、次の要件を追加します。
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { z } = require('zod');ファイルの下部
module.exports = router;、MCP サーバーの次のルートを追加します。router.post('/api/mcp', async function(req, res, next) { try { // Stateless server instance for each request const server = new McpServer({ name: "task-crud-server", version: "1.0.0" }); // Register tools server.registerTool( "create_task", { description: 'Create a new task', inputSchema: { taskName: z.string().describe('Name of the task to create') }, }, async ({ taskName }) => { const task = new Task({ taskName: taskName, createDate: new Date(), }); await task.save(); return { content: [ { type: 'text', text: `Task created: ${JSON.stringify(task)}` } ] }; } ); server.registerTool( "get_tasks", { description: 'Get all tasks' }, async () => { const tasks = await Task.find(); return { content: [ { type: 'text', text: `All tasks: ${JSON.stringify(tasks, null, 2)}` } ] }; } ); server.registerTool( "get_task", { description: 'Get a task by ID', inputSchema: { id: z.string().describe('Task ID') }, }, async ({ id }) => { try { const task = await Task.findById(id); if (!task) { throw new Error(); } return { content: [ { type: 'text', text: `Task: ${JSON.stringify(task)}` } ] }; } catch (error) { return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true }; } } ); server.registerTool( "update_task", { description: 'Update a task', inputSchema: { id: z.string().describe('Task ID'), taskName: z.string().optional().describe('New task name'), completed: z.boolean().optional().describe('Task completion status') }, }, async ({ id, taskName, completed }) => { try { const updateData = {}; if (taskName !== undefined) updateData.taskName = taskName; if (completed !== undefined) { updateData.completed = completed; if (completed === true) { updateData.completedDate = new Date(); } } const task = await Task.findByIdAndUpdate(id, updateData); if (!task) { throw new Error(); } return { content: [ { type: 'text', text: `Task updated: ${JSON.stringify(task)}` } ] }; } catch (error) { return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true }; } } ); server.registerTool( "delete_task", { description: 'Delete a task', inputSchema: { id: z.string().describe('Task ID to delete') }, }, async ({ id }) => { try { const task = await Task.findByIdAndDelete(id); if (!task) { throw new Error(); } return { content: [ { type: 'text', text: `Task deleted successfully: ${JSON.stringify(task)}` } ] }; } catch (error) { return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true }; } } ); // Create fresh transport for this request const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); // Clean up when request closes res.on('close', () => { transport.close(); server.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } });このルートは、MCP サーバー エンドポイントを
<url>/api/mcpに設定し、 MCP TypeScript SDK のステートレス モード パターンを使用します。-
server.registerTool()は、その実装を使用して MCP サーバーに ツール を追加します。 - SDK では、入力の検証に zod が使用されます。
-
descriptionの構成オブジェクトとdescribe()inputSchemaは、ツールと入力のための人間が判読できる説明を提供します。 これらは、呼び出し元エージェントがツールとそのパラメーターの使用方法を理解するのに役立ちます。
このルートは、既存のルートの create-read-update-delete (CRUD) 機能を複製しますが、これは不要ですが、わかりやすくするために保持します。 ベスト プラクティスは、アプリ ロジックをモジュールに移動してから、すべてのルートからモジュールを呼び出す方法です。
-
MCP サーバーをローカルでテストする
codespace ターミナルで、
npm startを使用してアプリケーションを実行します。[ ブラウザーで開く] を選択し、タスクを追加します。
npm startは実行したままにします。 MCP サーバーは現在、http://localhost:3000/api/mcpで実行されています。コードスペースに戻り、Copilot Chat を開き、プロンプト ボックスで [エージェント モード] を選択します。
[ ツール ] ボタンを選択し、ドロップダウンで [ その他のツールの追加 ]を選択します。
[ MCP サーバーの追加] を選択します。
HTTP (HTTP または Server-Sent イベント) を選択します。
[ サーバー URL の入力] に「 http://localhost:3000/api/mcp」と入力します。
[ サーバー ID の入力] に、 todos-mcp または任意の名前を入力します。
[ ワークスペースの設定] を選択します。
新しい Copilot チャット ウィンドウで、「やることリストを表示する」のように入力します。
既定では、MCP サーバーを呼び出すと、GitHub Copilot にセキュリティ確認が表示されます。 続行を選択します。
MCP ツールの呼び出しが成功したことを示す応答が表示されます。
MCP サーバーを App Service にデプロイする
codespace ターミナルに戻り、変更をコミットして変更をデプロイするか (GitHub Actions メソッド)、または
azd up(Azure Developer CLI メソッド) を実行します。AZD 出力で、アプリの URL を見つけます。 URL は、AZD の出力では次のようになります。
Deploying services (azd deploy) (✓) Done: Deploying service web - Endpoint: <app-url>
azd upが完了したら、.vscode/mcp.jsonを開きます。 URL を<app-url>/api/mcpに変更します。変更した MCP サーバー構成の上で、[開始] を選択 します。
新しい GitHub Copilot チャット ウィンドウを開始します。 Copilot エージェントでタスクを表示、作成、更新、および削除できる必要があります。
セキュリティのベスト プラクティス
MCP サーバーが大規模言語モデル (LLM) を使用するエージェントによって呼び出される場合は、 迅速なインジェクション 攻撃に注意してください。 次のセキュリティのベスト プラクティスを検討してください。
- 認証と承認: 承認されたユーザーまたはエージェントのみがツールにアクセスできるように、Microsoft Entra 認証を使用して MCP サーバーをセキュリティで保護します。 詳細なガイドについては、 Microsoft Entra 認証を使用した Visual Studio Code からの Azure App Service へのセキュリティで保護されたモデル コンテキスト プロトコルの呼び出し に関するページを参照してください。
-
入力の検証とサニタイズ: このチュートリアルのコード例では、入力の検証に zod を使用し、受信データが予想されるスキーマと一致することを確認します。 追加のセキュリティについては、次の点を考慮してください。
- 特にデータベース クエリまたは出力で使用されるフィールドについては、処理前にすべてのユーザー入力を検証してサニタイズします。
- API がブラウザーによって使用されている場合にクロスサイト スクリプティング (XSS) を防ぐために、応答で出力をエスケープする。
- モデルに厳密なスキーマと既定値を適用して、予期しないデータを回避する。
- HTTPS: このサンプルは、既定で HTTPS を適用し、転送中のデータを暗号化するための無料の TLS/SSL 証明書を提供する Azure App Service に依存しています。
- 最小限の特権の原則: ユース ケースに必要なツールとデータのみを公開します。 必要な場合を除き、機密性の高い操作を公開しないでください。
- レート制限と調整: API Management またはカスタム ミドルウェアを使用して、不正使用やサービス拒否攻撃を防ぎます。
- ログ記録と監視: 監査と異常検出のための MCP エンドポイントのログ アクセスと使用状況。 疑わしいアクティビティを監視します。
- CORS 構成: MCP サーバーがブラウザーからアクセスされる場合は、クロスオリジン要求を信頼されたドメインに制限します。 詳細については、「 CORS を有効にする」を参照してください。
- 定期的な更新: 既知の脆弱性を軽減するために、依存関係を最新の状態に保ちます。