Bagikan melalui


Mengintegrasikan aplikasi App Service sebagai McP Server untuk GitHub Copilot Chat (Node.js)

Dalam tutorial ini, Anda akan mempelajari cara mengekspos fungsionalitas aplikasi Express.js melalui Model Context Protocol (MCP), menambahkannya sebagai alat ke GitHub Copilot, dan berinteraksi dengan aplikasi Anda menggunakan bahasa natural dalam mode agen Obrolan Copilot.

Cuplikan layar memperlihatkan GitHub Copilot memanggil server Todos MCP yang dihosting di Azure App Service.

Jika aplikasi web Anda sudah memiliki fitur yang berguna, seperti belanja, pemesanan hotel, atau manajemen data, mudah untuk membuat kemampuan tersebut tersedia untuk:

  • Aplikasi apa pun yang mendukung integrasi MCP, seperti mode agen GitHub Copilot Chat di Visual Studio Code atau di GitHub Codespaces.
  • Agen kustom yang mengakses alat jarak jauh dengan menggunakan klien MCP.

Dengan menambahkan server MCP ke aplikasi web, Anda memungkinkan agen untuk memahami dan menggunakan kemampuan aplikasi saat merespons permintaan pengguna. Ini berarti apa pun yang dapat dilakukan aplikasi Anda, agen juga dapat melakukannya.

  • Tambahkan server MCP ke aplikasi web Anda.
  • Uji server MCP secara lokal dalam mode agen GitHub Copilot Chat.
  • Sebarkan server MCP ke Azure App Service dan sambungkan di GitHub Copilot Chat.

Prerequisites

Tutorial ini mengasumsikan Anda bekerja dengan sampel yang digunakan dalam Tutorial: Menyebarkan aplikasi web Node.js + MongoDB ke Azure.

Minimal, buka aplikasi sampel di GitHub Codespaces dan sebarkan aplikasi dengan menjalankan azd up.

Menambahkan server MCP ke aplikasi web Anda

  1. Di terminal codespace, tambahkan paket npm yang diperlukan ke proyek Anda:

    npm install @modelcontextprotocol/sdk@latest zod
    
    1. Buka rute/index.js. Untuk kesederhanaan skenario, Anda akan menambahkan semua kode server MCP Anda di sini.
  2. Di bagian atas rute/index.js, tambahkan persyaratan berikut:

    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
    const { z } = require('zod');
    
  3. Di bagian bawah file, di atas module.exports = router; tambahkan rute berikut untuk server 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,
          });
        }
      }
    });
    

    Rute ini mengatur titik akhir server MCP Anda ke <url>/api/mcp dan menggunakan pola mode stateless di MCP TypeScript SDK.

    • server.registerTool() menambahkan alat beserta implementasinya ke server MCP.
    • SDK menggunakan zod untuk validasi input.
    • description dalam objek konfigurasi dan describe() dalam inputSchema memberikan deskripsi yang dapat dibaca manusia untuk alat dan input. Mereka membantu agen panggilan untuk memahami cara menggunakan alat dan parameternya.

    Rute ini menggandakan fungsionalitas membuat-membaca-memperbarui-menghapus (CRUD) dari rute yang ada, yang sebenarnya tidak diperlukan, tetapi akan tetap dipertahankan demi kemudahan. Praktik terbaik adalah memindahkan logika aplikasi ke modul, lalu memanggil modul dari semua rute.

Menguji server MCP secara lokal

  1. Di terminal codespace, jalankan aplikasi dengan npm start.

  2. Pilih Buka di Browser, lalu tambahkan tugas.

    Biarkan npm start berjalan. Server MCP Anda sedang berjalan di http://localhost:3000/api/mcp sekarang.

  3. Kembali ke codespace, buka Obrolan Copilot, lalu pilih Mode agen di kotak perintah.

  4. Pilih tombol Alat , lalu pilih Tambahkan Alat Lainnya... di menu dropdown.

    Cuplikan layar memperlihatkan cara menambahkan server MCP dalam mode agen GitHub Copilot Chat.

  5. Pilih Tambahkan Server MCP.

  6. Pilih HTTP (HTTP atau Event Server-Sent).

  7. Di Masukkan URL Server, ketik http://localhost:3000/api/mcp.

  8. Di Masukkan ID Server, ketik todos-mcp atau nama apa pun yang Anda suka.

  9. Pilih Pengaturan Ruang Kerja.

  10. Di dalam jendela Obrolan Copilot baru, ketik sesuatu seperti "Tunjukkan daftar tugas."

  11. Secara default, GitHub Copilot menunjukkan konfirmasi keamanan saat Anda memanggil server MCP. Pilih Lanjutkan.

    Cuplikan layar memperlihatkan pesan keamanan default dari pemanggilan MCP di GitHub Copilot Chat.

    Anda sekarang akan melihat respons yang menunjukkan bahwa panggilan alat MCP berhasil.

    Cuplikan layar memperlihatkan bahwa respons dari panggilan alat MCP di jendela GitHub Copilot Chat.

Menyebarkan server MCP Anda ke App Service

  1. Kembali ke terminal codespace, sebarkan perubahan Anda dengan menerapkan perubahan Anda (metode GitHub Actions) atau jalankan azd up (metode Azure Developer CLI).

  2. Di output AZD, temukan URL aplikasi Anda. URL tampak seperti ini dalam keluaran AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. Setelah azd up selesai, buka .vscode/mcp.json. Ubah URL menjadi <app-url>/api/mcp.

  4. Di atas konfigurasi server MCP Anda yang dimodifikasi, pilih Mulai.

    Cuplikan layar memperlihatkan cara memulai server MCP secara manual dari file mcp.json lokal.

  5. Mulai jendela baru GitHub Copilot Chat. Anda harus dapat melihat, membuat, memperbarui, dan menghapus tugas di agen Copilot.

Praktik terbaik keamanan

Ketika server MCP Anda dipanggil oleh agen yang didukung oleh model bahasa besar (LLM), waspadai serangan injeksi yang cepat . Pertimbangkan praktik terbaik keamanan berikut:

  • Autentikasi dan Otorisasi: Amankan server MCP Anda dengan autentikasi Microsoft Entra untuk memastikan hanya pengguna atau agen yang berwenang yang dapat mengakses alat Anda. Lihat Panggilan Secure Model Context Protocol ke Azure App Service dari Visual Studio Code dengan autentikasi Microsoft Entra untuk panduan langkah demi langkah.
  • Validasi dan Sanitasi Input: Contoh kode dalam tutorial ini menggunakan zod untuk validasi input, memastikan bahwa data masuk cocok dengan skema yang diharapkan. Untuk keamanan tambahan, pertimbangkan:
    • Memvalidasi dan membersihkan semua input pengguna sebelum diproses, terutama untuk bidang yang digunakan dalam kueri atau output database.
    • Melakukan escaping pada output dalam respons untuk mencegah scripting lintas situs (XSS) jika API Anda digunakan oleh browser.
    • Menerapkan skema ketat dan nilai default dalam model Anda untuk menghindari data yang tidak terduga.
  • HTTPS: Sampel bergantung pada Azure App Service, yang memberlakukan HTTPS secara default dan menyediakan sertifikat TLS/SSL gratis untuk mengenkripsi data saat transit.
  • Prinsip Hak Istimewa Terkecil: Hanya mengekspos alat dan data yang diperlukan untuk kasus penggunaan Anda. Hindari mengekspos operasi sensitif kecuali diperlukan.
  • Pembatasan dan Pembatasan Laju: Gunakan API Management atau middleware kustom untuk mencegah penyalahgunaan dan penolakan serangan layanan.
  • Pengelogan dan Pemantauan: Akses log dan penggunaan titik akhir MCP untuk audit dan deteksi anomali. Pantau aktivitas yang mencurigakan.
  • Konfigurasi CORS: Membatasi permintaan lintas asal ke domain tepercaya jika server MCP Anda diakses dari browser. Untuk informasi selengkapnya, lihat Mengaktifkan CORS.
  • Pembaruan Reguler: Selalu perbarui dependensi Anda untuk memitigasi kerentanan yang diketahui.

Sumber daya lainnya

Mengintegrasikan AI ke dalam aplikasi Azure App Service Anda