Condividi tramite


Integrare un'app del Servizio app come server MCP per GitHub Copilot Chat (Python)

In questa esercitazione si apprenderà come esporre le funzionalità di un'app FastAPI tramite il protocollo MCP (Model Context Protocol), aggiungerlo come strumento a GitHub Copilot e interagire con l'app usando il linguaggio naturale in modalità agente di Copilot Chat.

Screenshot che mostra la risposta della chiamata allo strumento MCP nella finestra di GitHub Copilot Chat.

Se l'applicazione Web dispone già di funzionalità utili, ad esempio shopping, prenotazione hotel o gestione dei dati, è facile rendere disponibili queste funzionalità per:

Aggiungendo un server MCP all'app Web, si abilita un agente per comprendere e usare le funzionalità dell'app quando risponde alle richieste degli utenti. Ciò significa che qualsiasi operazione che l'app può fare, la può fare anche l'agente.

  • Aggiungere un server MCP all'app Web.
  • Testare il server MCP in locale in modalità agente di GitHub Copilot Chat.
  • Distribuire il server MCP nel servizio app di Azure e connetterlo in GitHub Copilot Chat.

Prerequisiti

Questa esercitazione presuppone che si stia usando l'esempio usato in Distribuire un'app Web FastAPI Python con PostgreSQL in Azure.

Aprire almeno l'applicazione di esempio in GitHub Codespaces e distribuire l'app eseguendo azd up.

Aprire in GitHub Codespaces

Aggiungere un server MCP all'app Web

  1. In explorer di Codespace, aprire src/pyproject.toml, aggiungere mcp[cli] all'elenco delle dipendenze, come illustrato nell'esempio seguente:

    dependencies = [
        ...
        "mcp[cli]",
    ]
    
  2. In src/fastapi_app, creare un file denominato mcp_server.py e incollare il codice di inizializzazione del server MCP seguente nel 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)
    

    L'inizializzatore FastMCP() crea un server MCP usando il modello di modalità senza stato in SDK di MCP Python. Per impostazione predefinita, l'endpoint HTTP di flusso è impostato sul percorso secondario /mcp.

    • L'elemento Decorator @mcp.tool() aggiunge uno strumento al server MCP con la relativa implementazione.
    • La descrizione della funzione strumento consente all'agente chiamante di comprendere come usare lo strumento e i relativi parametri.

    Gli strumenti duplicano la funzionalità di recensione dei ristoranti esistente nell'applicazione Web FastAPI basata su modulo. Se si vuole, è possibile aggiungere altri strumenti per la funzionalità di aggiornamento ed eliminazione.

  3. In src/fastapi_app/app.py, trovare la riga per app = FastAPI() (riga 24) e sostituirla con il codice seguente:

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

    Questo codice monta l'endpoint HTTP trasmissibile del server MCP all'app FastAPI esistente nel percorso /mcp. Insieme al percorso predefinito dell'endpoint HTTP trasmissibile, il percorso completo è /mcp/mcp.

Testare il server MCP in locale

  1. Nel terminale codespace, eseguire l'applicazione con i comandi seguenti:

    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. Selezionare Apri nel browser, quindi aggiungere alcuni ristoranti e recensioni.

    Lasciare uvicorn in esecuzione. Ora il server MCP è in esecuzione in http://localhost:8000/mcp/mcp.

  3. Nel codespace, aprire Copilot Chat e quindi selezionare Modalità agente nella casella di richiesta.

  4. Selezionare il pulsante Strumenti, quindi selezionare l'icona Aggiungi server MCP nell'angolo in alto a destra del popup.

    Screenshot che mostra come aggiungere un server MCP in modalità agente di GitHub Copilot Chat.

  5. Selezionare HTTP (HTTP o Eventi inviati dal server).

  6. In Immettere l'URL del server, digitare http://localhost:8000/mcp/mcp.

  7. In Immettere l'ID server, digitare restaurant_ratings o qualsiasi nome desiderato.

  8. Selezionare Impostazioni area di lavoro.

  9. In una nuova finestra chat di Copilot digitare qualcosa di simile a "Mostrami le classificazioni del ristorante".

  10. Per impostazione predefinita, GitHub Copilot mostra una conferma di sicurezza quando si richiama un server MCP. Seleziona Continua.

    Screenshot che mostra il messaggio di sicurezza predefinito da una chiamata MCP in GitHub Copilot Chat.

    Verrà visualizzata una risposta che indica che la chiamata allo strumento MCP ha esito positivo.

    Screenshot che mostra la risposta della chiamata allo strumento MCP nella finestra di GitHub Copilot Chat.

Distribuire il server MCP nel servizio app

  1. Tornare al terminale codespace, distribuire le modifiche eseguendo il commit delle modifiche (metodo GitHub Actions) o eseguendo azd up (metodo dell'interfaccia della riga di comando per sviluppatori di Azure).

  2. Nell'output AZD, trovare l'URL dell'app. L'URL è simile al seguente nell'output AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. Al termine di azd up, aprire .vscode/mcp.json. Modificare l'URL in <app-url>/mcp/mcp.

  4. Sopra la configurazione del server MCP modificata, selezionare Avvia.

    Screenshot che mostra come avviare manualmente un server MCP dal file mcp.json locale.

  5. Avviare una nuova finestra di GitHub Copilot Chat. Dovrebbe essere possibile visualizzare le valutazioni dei ristoranti, così come creare nuovi ristoranti e nuove valutazioni nell'agente Copilot.

Procedure consigliate per la sicurezza

Quando il server MCP viene chiamato da un agente basato su modelli linguistici di grandi dimensioni (LLM), prestare attenzione agli attacchi di prompt injection. Considerare le seguenti procedure consigliate per la sicurezza:

  • Autenticazione e autorizzazione: proteggere il server MCP con l'autenticazione Microsoft Entra per garantire che solo gli utenti o gli agenti autorizzati possano accedere agli strumenti. Per una guida dettagliata, vedere Proteggere le chiamate MCP (Model Context Protocol) al Servizio app di Azure da Visual Studio Code con l'autenticazione di Microsoft Entra.
  • Convalida e bonifica dell'input: convalidare sempre i dati in ingresso per evitare input non validi o dannosi. Per le app Python, usare librerie quali Pydantic per applicare regole di convalida dei dati con modelli di input dedicati, ad esempio RestaurantCreate e ReviewCreate. Per informazioni dettagliate sulle procedure consigliate e sull'implementazione, vedere la documentazione.
  • HTTPS: l'esempio si basa sul servizio app di Azure, che applica HTTPS per impostazione predefinita e fornisce certificati TLS/SSL gratuiti per crittografare i dati in transito.
  • Principio dei privilegi minimi: esporre solo gli strumenti e i dati necessari per il caso d'uso. Evitare di esporre operazioni sensibili, a meno che non sia necessario.
  • Limitazione della velocità e limitazione delle richieste: usare Gestione API o middleware personalizzato per evitare abusi e attacchi di tipo denial-of-service.
  • Registrazione e monitoraggio: accesso e utilizzo dei log degli endpoint MCP per il controllo e il rilevamento delle anomalie. Monitorare l'attività sospetta.
  • Configurazione CORS: limitare le richieste tra le origini ai domini attendibili se si accede al server MCP dai browser. Per altre informazioni, vedere Abilitare CORS.
  • Aggiornamenti regolari: mantenere aggiornate le dipendenze per attenuare le vulnerabilità note.

Altre risorse

Integrare l'intelligenza artificiale nelle applicazioni di Servizio app di Azure