이 자습서에서는 MCP(모델 컨텍스트 프로토콜)를 통해 FastAPI 앱의 기능을 노출하고, GitHub Copilot에 도구로 추가하고, 코필로트 채팅 에이전트 모드에서 자연어를 사용하여 앱과 상호 작용하는 방법을 알아봅니다.
웹 애플리케이션에 쇼핑, 호텔 예약 또는 데이터 관리와 같은 유용한 기능이 이미 있는 경우 다음과 같은 기능을 쉽게 사용할 수 있습니다.
- Visual Studio Code 또는 GitHub Codespaces의 GitHub Copilot 채팅 에이전트 모드와 같이 MCP 통합을 지원하는 모든 애플리케이션입니다.
- MCP 클라이언트를 사용하여 원격 도구에 액세스하는 사용자 지정 에이전트입니다.
웹앱에 MCP 서버를 추가하면 에이전트가 사용자 프롬프트에 응답할 때 앱의 기능을 이해하고 사용할 수 있습니다. 즉, 앱이 수행할 수 있는 모든 작업을 에이전트도 수행할 수 있습니다.
- 웹앱에 MCP 서버를 추가합니다.
- GitHub Copilot 채팅 에이전트 모드에서 로컬로 MCP 서버를 테스트합니다.
- AZURE App Service에 MCP 서버를 배포하고 GitHub Copilot 채팅에서 연결합니다.
필수 조건
이 자습서에서는 Azure에서 PostgreSQL을 사용하여 Python FastAPI 웹앱 배포에 사용되는 샘플을 사용하고 있다고 가정합니다.
최소한 GitHub Codespaces에서 샘플 애플리케이션 을 열고 실행 azd up하여 앱을 배포합니다.
웹앱에 MCP 서버 추가
다음 예제와 같이 코드스페이스 탐색기에서 src/pyproject.toml을 열고 종속성 목록에 추가
mcp[cli]합니다.dependencies = [ ... "mcp[cli]", ]src/fastapi_appmcp_server.py 파일을 만들고 다음 MCP 서버 초기화 코드를 파일에 붙여넣습니다.
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)FastMCP() 이니셜라이저는 MCP Python SDK에서 상태 비저장 모드 패턴을 사용하여 MCP 서버를 만듭니다. 기본적으로 스트리밍 가능한 HTTP 엔드포인트는 하위 경로로
/mcp설정됩니다.- 데코레이터는
@mcp.tool()해당 구현을 사용하여 MCP 서버에 도구를 추가합니다. - 도구 함수의 설명은 호출 에이전트가 도구 및 해당 매개 변수를 사용하는 방법을 이해하는 데 도움이 됩니다.
이 도구는 양식 기반 FastAPI 웹 애플리케이션의 기존 식당 리뷰 기능을 복제합니다. 원하는 경우 업데이트 및 삭제 기능에 대한 도구를 더 추가할 수 있습니다.
- 데코레이터는
src/fastapi_app/app.py(줄 24)의 줄을
app = FastAPI()찾아 다음 코드로 바꿉니다.from .mcp_server import mcp, mcp_lifespan app = FastAPI(lifespan=mcp_lifespan) app.mount("/mcp", mcp.streamable_http_app())이 코드는 MCP 서버의 스트리밍 가능한 HTTP 엔드포인트를 경로
/mcp의 기존 FastAPI 앱에 탑재합니다. 스트리밍 가능한 HTTP 엔드포인트의 기본 경로와 함께 전체 경로는 .입니다/mcp/mcp.
로컬에서 MCP 서버 테스트
코드스페이스 터미널에서 다음 명령을 사용하여 애플리케이션을 실행합니다.
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브라우저에서 열기를 선택한 다음, 몇 가지 레스토랑과 리뷰를 추가합니다.
실행 상태로 둡니다
uvicorn. MCP 서버가 현재 실행 중입니다http://localhost:8000/mcp/mcp.코드스페이스로 돌아가서 Copilot 채팅을 연 다음 프롬프트 상자에서 에이전트 모드를 선택합니다.
도구 단추를 선택한 다음 팝업의 오른쪽 위 모서리에서 MCP 서버 추가 아이콘을 선택합니다.
HTTP(HTTP 또는 Server-Sent 이벤트)를 선택합니다.
Enter Server URL에 .를 입력합니다http://localhost:8000/mcp/mcp.
Enter Server ID에 restaurant_ratings 또는 원하는 이름을 입력합니다.
작업 영역 설정을 선택합니다.
새 코필로트 채팅 창에 "식당 평점 표시"와 같은 내용을 입력합니다.
기본적으로 GitHub Copilot는 MCP 서버를 호출할 때 보안 확인을 표시합니다. 를 선택합니다 계속.
이제 MCP 도구 호출이 성공했음을 나타내는 응답이 표시됩니다.
App Service에 MCP 서버 배포
코드스페이스 터미널로 돌아가서 변경 내용을 커밋하여(GitHub Actions 메서드) 또는 실행
azd up(Azure 개발자 CLI 메서드)을 배포합니다.AZD 출력에서 앱의 URL을 찾습니다. URL은 AZD 출력에서 다음과 같습니다.
Deploying services (azd deploy) (✓) Done: Deploying service web - Endpoint: <app-url>
완료되면
azd up.vscode/mcp.json엽니다. URL을 .로 변경합니다<app-url>/mcp/mcp.수정된 MCP 서버 구성 위에서 시작을 선택합니다.
새 GitHub Copilot 채팅 창을 시작합니다. 당신은 레스토랑 등급을 볼 수있을뿐만 아니라, 코필로트 에이전트에서 새로운 레스토랑과 새로운 등급을 만들 수 있어야합니다.
보안 모범 사례
LLM(대규모 언어 모델)으로 구동되는 에이전트에서 MCP 서버를 호출하는 경우 프롬프트 삽입 공격에 유의해야 합니다. 다음 보안 모범 사례를 고려합니다.
- 인증 및 권한 부여: Microsoft Entra 인증을 사용하여 MCP 서버를 보호하여 권한 있는 사용자 또는 에이전트만 도구에 액세스할 수 있도록 합니다. 단계별 가이드는 Microsoft Entra 인증을 사용하여 Visual Studio Code에서 Azure App Service에 대한 보안 모델 컨텍스트 프로토콜 호출 을 참조하세요.
- 입력 유효성 검사 및 삭제: 항상 들어오는 데이터의 유효성을 검사하여 유효하지 않거나 악의적인 입력을 방지합니다. Python 앱의 경우 Pydantic 과 같은 라이브러리를 사용하여 전용 입력 모델(예: RestaurantCreate 및 ReviewCreate)을 사용하여 데이터 유효성 검사 규칙을 적용합니다. 모범 사례 및 구현 세부 정보는 해당 설명서를 참조하세요.
- HTTPS: 이 샘플은 기본적으로 HTTPS를 적용하고 전송 중인 데이터를 암호화하는 무료 TLS/SSL 인증서를 제공하는 Azure App Service를 사용합니다.
- 최소 권한 원칙: 사용 사례에 필요한 도구 및 데이터만 노출합니다. 필요한 경우가 아니면 중요한 작업을 노출하지 마세요.
- 속도 제한 및 제한: API Management 또는 사용자 지정 미들웨어를 사용하여 남용 및 서비스 거부 공격을 방지합니다.
- 로깅 및 모니터링: 감사 및 변칙 검색을 위한 MCP 엔드포인트의 로그 액세스 및 사용 의심스러운 활동을 모니터링합니다.
- CORS 구성: MCP 서버가 브라우저에서 액세스되는 경우 원본 간 요청을 신뢰할 수 있는 도메인으로 제한합니다. 자세한 내용은 CORS 사용을 참조하세요.
- 정기 업데이트: 알려진 취약성을 완화하기 위해 종속성을 최신 상태로 유지합니다.