이 자습서에서는 FastAPI 및 MCP Python SDK를 사용하여 작업 관리 도구를 노출하는 MCP(모델 컨텍스트 프로토콜) 서버를 빌드합니다. 서버를 Azure Container Apps에 배포하고 VS Code의 GitHub Copilot 채팅에서 연결합니다.
이 자습서에서는 다음을 수행합니다.
- MCP 도구를 노출하는 FastAPI 앱 만들기
- GitHub Copilot를 사용하여 로컬로 MCP 서버 테스트
- Azure Container Apps에 앱 컨테이너화 및 배포
- 배포된 MCP 서버에 GitHub Copilot 연결
필수 조건
- 활성 구독이 있는 Azure 계정. 체험 계정 만들기
- Azure CLI 버전 2.62.0 이상.
- Python 3.10 이상.
- GitHub Copilot 확장이 있는 Visual Studio Code.
- Docker Desktop (선택 사항 - 컨테이너를 로컬로 테스트하는 데만 필요).
앱 스캐폴드 만들기
이 섹션에서는 FastAPI 및 MCP Python SDK를 사용하여 새 Python 프로젝트를 만듭니다.
프로젝트 디렉터리를 만들고 가상 환경을 설정합니다.
mkdir tasks-mcp-server && cd tasks-mcp-server python -m venv .venv source .venv/bin/activate만들기
requirements.txt:fastapi>=0.115.0 uvicorn>=0.30.0 mcp[cli]>=1.2.0종속성을 설치합니다.
pip install -r requirements.txt메모리 내 데이터 저장소를 만들
task_store.py.from dataclasses import dataclass, field from datetime import datetime, timezone @dataclass class TaskItem: id: int title: str description: str is_complete: bool = False created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) def to_dict(self) -> dict: return { "id": self.id, "title": self.title, "description": self.description, "is_complete": self.is_complete, "created_at": self.created_at.isoformat(), } class TaskStore: def __init__(self): self._tasks: list[TaskItem] = [ TaskItem(1, "Buy groceries", "Milk, eggs, bread"), TaskItem(2, "Write docs", "Draft the MCP tutorial", True), ] self._next_id = 3 def get_all(self) -> list[dict]: return [t.to_dict() for t in self._tasks] def get_by_id(self, task_id: int) -> dict | None: task = next((t for t in self._tasks if t.id == task_id), None) return task.to_dict() if task else None def create(self, title: str, description: str) -> dict: task = TaskItem(self._next_id, title, description) self._next_id += 1 self._tasks.append(task) return task.to_dict() def toggle_complete(self, task_id: int) -> dict | None: task = next((t for t in self._tasks if t.id == task_id), None) if task is None: return None task.is_complete = not task.is_complete return task.to_dict() def delete(self, task_id: int) -> bool: task = next((t for t in self._tasks if t.id == task_id), None) if task is None: return False self._tasks.remove(task) return True # For demonstration only — not thread-safe. store = TaskStore()데이터 클래스는
TaskItemserialization 메서드를 사용하여to_dict()데이터 모델을 정의합니다. 클래스는TaskStore샘플 데이터로 미리 채워진 메모리 내 목록을 관리하고 CRUD 메서드를 제공합니다. 모듈 수준store싱글톤은 간단히 하기 위해 애플리케이션 간에 공유됩니다.
MCP 도구 정의
이 섹션에서는 AI 모델이 FastAPI 애플리케이션에서 MCP 서버를 호출하고 탑재할 수 있는 MCP 도구를 정의합니다.
만들기
mcp_server.py:from mcp.server.fastmcp import FastMCP from task_store import store mcp = FastMCP("TasksMCP", stateless_http=True) @mcp.tool() async def list_tasks() -> list[dict]: """List all tasks with their ID, title, description, and completion status.""" return store.get_all() @mcp.tool() async def get_task(task_id: int) -> dict | None: """Get a single task by its numeric ID. Args: task_id: The numeric ID of the task to retrieve. """ return store.get_by_id(task_id) @mcp.tool() async def create_task(title: str, description: str) -> dict: """Create a new task with the given title and description. Returns the created task. Args: title: A short title for the task. description: A detailed description of what the task involves. """ return store.create(title, description) @mcp.tool() async def toggle_task_complete(task_id: int) -> str: """Toggle a task's completion status between complete and incomplete. Args: task_id: The numeric ID of the task to toggle. """ task = store.toggle_complete(task_id) if task: status = "complete" if task["is_complete"] else "incomplete" return f"Task {task['id']} is now {status}." return f"Task with ID {task_id} not found." @mcp.tool() async def delete_task(task_id: int) -> str: """Delete a task by its numeric ID. Args: task_id: The numeric ID of the task to delete. """ if store.delete(task_id): return f"Task {task_id} deleted." return f"Task with ID {task_id} not found."주요 정보:
-
FastMCP("TasksMCP", stateless_http=True)는 Python SDK에서 무상태 HTTP 패턴을 사용하여 MCP 서버를 생성합니다. 스트리밍 가능한 HTTP 엔드포인트는 기본적으로 하위 경로로 설정/mcp됩니다. - 각
@mcp.tool()함수는 호출할 수 없는 도구가 됩니다. 함수 문서 문자열 및 매개 변수 주석은 AI 모델이 각 도구를 사용하는 방법을 이해하는 데 도움이 됩니다.
-
app.py을(를) 만듭니다. 이 파일은 MCP 서버를 탑재하는 FastAPI 애플리케이션을 정의합니다.from contextlib import AsyncExitStack, asynccontextmanager from fastapi import FastAPI from fastapi.responses import JSONResponse from mcp_server import mcp @asynccontextmanager async def lifespan(app: FastAPI): async with AsyncExitStack() as stack: await stack.enter_async_context(mcp.session_manager.run()) yield app = FastAPI(lifespan=lifespan) app.mount("/", mcp.streamable_http_app()) @app.get("/health") async def health(): return JSONResponse({"status": "healthy"})MCP 서버 앱은 루트(
/)에 탑재됩니다. SDK의 스트리밍 가능한 HTTP 엔드포인트는 기본값으로/mcp설정되므로 전체 엔드포인트 경로는 .입니다/mcp.Container Apps 상태 프로브에는 별도의
/health엔드포인트가 사용됩니다. MCP 엔드포인트는 JSON-RPC POST 요청을 예상하며 상태 검사로 적합하지 않습니다.
로컬에서 MCP 서버 테스트
Azure에 배포하기 전에 MCP 서버가 로컬로 실행하고 GitHub Copilot에서 연결하여 작동하는지 확인합니다.
애플리케이션을 시작합니다.
uvicorn app:app --reload --port 8080VS Code를 연 다음 , Copilot 채팅 을 열고 에이전트 모드를 선택합니다.
도구 단추를 선택한 다음, 추가 도구 추가...를 선택합니다.>MCP 서버를 추가합니다.
HTTP(HTTP 또는 Server-Sent 이벤트)를 선택합니다.
서버 URL을 입력합니다.
http://localhost:8080/mcp서버 ID를 입력합니다.
tasks-mcp작업 영역 설정을 선택합니다.
새 Copilot 채팅 프롬프트에서 다음을 입력 합니다. "모든 작업 표시"
부조종사에서 MCP 도구 확인 메시지를 표시할 때 계속 을 선택합니다.
메모리 내 저장소에서 반환된 작업 목록이 표시됩니다.
팁 (조언)
"PR을 검토할 작업 만들기", "작업 1을 완료로 표시" 또는 "작업 2 삭제"와 같은 다른 프롬프트를 사용해 보세요.
애플리케이션 컨테이너화
Azure에 배포하기 전에 로컬에서 테스트할 수 있도록 애플리케이션을 Docker 컨테이너로 패키지합니다.
Dockerfile를 만듭니다.FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8080 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]Dockerfile은 Python 3.12 슬림 기본 이미지를 사용하고, 종속성을
requirements.txt설치한 다음, 애플리케이션 코드를 복사합니다. Uvicorn은 포트 8080에서 FastAPI 앱을 제공합니다.컨테이너가 로컬로 빌드되고 실행되는지 확인합니다.
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-server상태 엔드포인트가 응답하는지 확인합니다.
curl http://localhost:8080/health
Azure Container Apps에 배포
애플리케이션을 컨테이너화한 후 Azure CLI를 사용하여 Azure Container Apps에 배포합니다. 이 az containerapp up 명령은 클라우드에서 컨테이너 이미지를 빌드하므로 이 단계를 위해 컴퓨터에 Docker가 필요하지 않습니다.
환경 변수 설정:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-py"리소스 그룹 및 Container Apps 환경을 만듭니다.
az group create --name $RESOURCE_GROUP --location $LOCATION az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATION컨테이너 앱을 배포합니다.
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080CORS 구성:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"비고
프로덕션의 경우 와일드카드 원본을 신뢰할 수 있는 특정 원본으로 대체합니다. Container Apps에서 보안 MCP 서버를 참조하세요.
배포 확인:
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
배포된 서버에 GitHub Copilot 연결
이제 MCP 서버가 Azure에서 실행 중이므로 배포된 엔드포인트에 GitHub Copilot를 연결하도록 VS Code를 구성합니다.
생성 또는 업데이트
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "http", "url": "https://<your-app-fqdn>/mcp" } } }배포 출력에서
<your-app-fqdn>을 FQDN으로 교체하십시오.VS Code에서 에이전트 모드에서 코필로트 채팅을 엽니다.
tasks-mcp-server이(가) 도구 목록에 표시되는지 확인합니다. 필요한 경우 시작을 선택합니다."스테이징 환경을 배포하는 작업 만들기"와 같은 프롬프트를 사용하여 테스트합니다.
대화형 사용을 위한 크기 조정 구성
기본적으로 Azure Container Apps는 0개의 복제본으로 확장할 수 있습니다. 코필로트와 같은 대화형 클라이언트를 제공하는 MCP 서버의 경우 콜드 시작으로 인해 눈에 띄는 지연이 발생합니다. 하나 이상의 인스턴스를 계속 실행하도록 최소 복제본 수를 설정합니다.
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
보안 고려 사항
이 자습서에서는 간단히 하기 위해 인증되지 않은 MCP 서버를 사용합니다. 프로덕션 환경에서 MCP 서버를 실행하기 전에 다음 권장 사항을 검토합니다. LLM(대규모 언어 모델)에서 구동되는 에이전트가 MCP 서버를 호출하는 경우 프롬프트 삽입 공격에 유의해야 합니다.
- 인증 및 권한 부여: Microsoft Entra ID를 사용하여 MCP 서버를 보호합니다. Container Apps에서 보안 MCP 서버를 참조하세요.
- 입력 유효성 검사: 항상 도구 매개 변수의 유효성을 검사합니다. Pydantic을 사용하여 도구 입력에 대한 데이터 유효성 검사를 적용합니다.
- HTTPS: Azure Container Apps는 기본적으로 자동 TLS 인증서를 사용하여 HTTPS를 적용합니다.
- 최소 권한: 사용 사례에 필요한 도구만 노출합니다. 확인 없이 파괴적인 작업을 수행하는 도구를 사용하지 않습니다.
- CORS: 프로덕션 환경에서 허용된 원본을 신뢰할 수 있는 도메인으로 제한합니다.
- 로깅 및 모니터링: 감사를 위한 MCP 도구 호출을 기록합니다. Azure Monitor 및 Log Analytics를 사용합니다.
자원을 정리하세요
이 애플리케이션을 계속 사용하지 않으려면 리소스 그룹을 삭제하여 이 자습서에서 만든 모든 리소스를 제거합니다.
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait