Mengintegrasikan aplikasi App Service sebagai McP Server untuk GitHub Copilot Chat (Python)

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

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

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.

Prasyarat

Tutorial ini mengasumsikan Anda bekerja dengan sampel yang digunakan dalam Menyebarkan aplikasi web Python FastAPI dengan PostgreSQL di Azure.

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

Buka di GitHub Codespaces

Menambahkan server MCP ke aplikasi web Anda

  1. Di penjelajah codespace, buka src/pyproject.toml, tambahkan mcp[cli] ke daftar dependensi, seperti yang ditunjukkan dalam contoh berikut:

    dependencies = [
        ...
        "mcp[cli]",
    ]
    
  2. Di src/fastapi_app, buat file yang disebut mcp_server.py dan tempelkan kode inisialisasi server MCP berikut ke dalam file:

    import asyncio
    import contextlib
    from contextlib import asynccontextmanager
    
    from mcp.server.fastmcp import FastMCP
    from sqlalchemy.sql import func
    from sqlmodel import Session, select
    
    from .models import Restaurant, Review, engine
    
    # Create a FastMCP server. Use stateless_http=True for simple mounting. Default path is .../mcp
    mcp = FastMCP("RestaurantReviewsMCP", stateless_http=True)
    
    # Lifespan context manager to start/stop the MCP session manager with the FastAPI app
    @asynccontextmanager
    async def mcp_lifespan(app):
        async with contextlib.AsyncExitStack() as stack:
            await stack.enter_async_context(mcp.session_manager.run())
            yield
    
    # MCP tool: List all restaurants with their average rating and review count
    @mcp.tool()
    async def list_restaurants_mcp() -> list[dict]:
        """List restaurants with their average rating and review count."""
    
        def sync():
            with Session(engine) as session:
                statement = (
                    select(
                        Restaurant,
                        func.avg(Review.rating).label("avg_rating"),
                        func.count(Review.id).label("review_count"),
                    )
                    .outerjoin(Review, Review.restaurant == Restaurant.id)
                    .group_by(Restaurant.id)
                )
                results = session.exec(statement).all()
                rows = []
                for restaurant, avg_rating, review_count in results:
                    r = restaurant.dict()
                    r["avg_rating"] = float(avg_rating) if avg_rating is not None else None
                    r["review_count"] = review_count
                    r["stars_percent"] = (
                        round((float(avg_rating) / 5.0) * 100) if review_count > 0 and avg_rating is not None else 0
                    )
                    rows.append(r)
                return rows
    
        return await asyncio.to_thread(sync)
    
    # MCP tool: Get a restaurant and all its reviews by restaurant_id
    @mcp.tool()
    async def get_details_mcp(restaurant_id: int) -> dict:
        """Return the restaurant and its related reviews as objects."""
    
        def sync():
            with Session(engine) as session:
                restaurant = session.exec(select(Restaurant).where(Restaurant.id == restaurant_id)).first()
                if restaurant is None:
                    return None
                reviews = session.exec(select(Review).where(Review.restaurant == restaurant_id)).all()
                return {"restaurant": restaurant.dict(), "reviews": [r.dict() for r in reviews]}
    
        return await asyncio.to_thread(sync)
    
    # MCP tool: Create a new review for a restaurant
    @mcp.tool()
    async def create_review_mcp(restaurant_id: int, user_name: str, rating: int, review_text: str) -> dict:
        """Create a new review for a restaurant and return the created review dict."""
    
        def sync():
            with Session(engine) as session:
                review = Review()
                review.restaurant = restaurant_id
                review.review_date = __import__("datetime").datetime.now()
                review.user_name = user_name
                review.rating = int(rating)
                review.review_text = review_text
                session.add(review)
                session.commit()
                session.refresh(review)
                return review.dict()
    
        return await asyncio.to_thread(sync)
    
    # MCP tool: Create a new restaurant
    @mcp.tool()
    async def create_restaurant_mcp(restaurant_name: str, street_address: str, description: str) -> dict:
        """Create a new restaurant and return the created restaurant dict."""
    
        def sync():
            with Session(engine) as session:
                restaurant = Restaurant()
                restaurant.name = restaurant_name
                restaurant.street_address = street_address
                restaurant.description = description
                session.add(restaurant)
                session.commit()
                session.refresh(restaurant)
                return restaurant.dict()
    
        return await asyncio.to_thread(sync)
    

    Penginisialisasi FastMCP() membuat server MCP menggunakan pola mode stateless di MCP Python SDK. Secara default, titik akhir HTTP yang dapat dialirkan diatur ke /mcp subpath.

    • Dekorator @mcp.tool() menambahkan alat ke server MCP dengan implementasinya.
    • Deskripsi fungsi alat membantu agen panggilan untuk memahami cara menggunakan alat dan parameternya.

    Alat ini menduplikasi fungsionalitas ulasan restoran yang ada dalam aplikasi web FastAPI berbasis formulir. Jika mau, Anda dapat menambahkan lebih banyak alat untuk memperbarui dan menghapus fungsionalitas.

  3. Di src/fastapi_app/app.py, temukan baris untuk app = FastAPI() (baris 24) dan ganti dengan kode berikut:

    from .mcp_server import mcp, mcp_lifespan
    app = FastAPI(lifespan=mcp_lifespan)
    app.mount("/mcp", mcp.streamable_http_app())
    

    Kode ini memasang titik akhir HTTP server MCP yang dapat dialirkan ke aplikasi FastAPI yang ada di jalur /mcp. Bersama dengan jalur default titik akhir HTTP yang dapat dialirkan, jalur lengkapnya adalah /mcp/mcp.

Menguji server MCP secara lokal

  1. Di terminal codespace, jalankan aplikasi dengan perintah berikut:

    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r src/requirements.txt
    pip install -e src
    python3 src/fastapi_app/seed_data.py
    python3 -m uvicorn fastapi_app:app --reload --port=8000
    
  2. Pilih Buka di Browser, lalu tambahkan beberapa restoran dan ulasan.

    Biarkan uvicorn berjalan. Server MCP Anda sedang berjalan sekarang http://localhost:8000/mcp/mcp .

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

  4. Pilih tombol Alat , lalu pilih ikon Tambahkan Server MCP di sudut kanan atas popup.

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

  5. Pilih HTTP (HTTP atau peristiwa Server-Sent).

  6. Di Masukkan URL Server, ketik http://localhost:8000/mcp/mcp.

  7. Di Masukkan ID Server, ketik restaurant_ratings atau nama apa pun yang Anda suka.

  8. Pilih Pengaturan Ruang Kerja.

  9. Di jendela Obrolan Copilot baru, ketik sesuatu seperti "Tunjukkan peringkat restoran."

  10. 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 terlihat seperti ini dalam output 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>/mcp/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 Obrolan Salinan GitHub baru. Anda harus dapat melihat peringkat restoran, serta membuat restoran baru dan peringkat baru 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: Selalu validasi data masuk untuk mencegah input yang tidak valid atau berbahaya. Untuk aplikasi Python, gunakan pustaka seperti Pydantic untuk menerapkan aturan validasi data dengan model input khusus (seperti RestaurantCreate dan ReviewCreate). Lihat dokumentasi mereka untuk praktik terbaik dan detail implementasi.
  • 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 mengurangi kerentanan yang diketahui.

Sumber daya lainnya

Mengintegrasikan AI ke dalam aplikasi Azure App Service Anda