이 자습서에서는 MCP(모델 컨텍스트 프로토콜)를 통해 Express.js 앱의 기능을 노출하고, GitHub Copilot에 도구로 추가하고, 코필로트 채팅 에이전트 모드에서 자연어를 사용하여 앱과 상호 작용하는 방법을 알아봅니다.
웹 애플리케이션에 쇼핑, 호텔 예약 또는 데이터 관리와 같은 유용한 기능이 이미 있는 경우 다음과 같은 기능을 쉽게 사용할 수 있습니다.
- Visual Studio Code 또는 GitHub Codespaces의 GitHub Copilot 채팅 에이전트 모드와 같이 MCP 통합을 지원하는 모든 애플리케이션입니다.
- MCP 클라이언트를 사용하여 원격 도구에 액세스하는 사용자 지정 에이전트입니다.
웹앱에 MCP 서버를 추가하면 에이전트가 사용자 프롬프트에 응답할 때 앱의 기능을 이해하고 사용할 수 있습니다. 즉, 앱이 수행할 수 있는 모든 작업을 에이전트도 수행할 수 있습니다.
- 웹앱에 MCP 서버를 추가합니다.
- GitHub Copilot 채팅 에이전트 모드에서 로컬로 MCP 서버를 테스트합니다.
- AZURE App Service에 MCP 서버를 배포하고 GitHub Copilot 채팅에서 연결합니다.
Prerequisites
이 자습서에서는 자습서 : Azure에 Node.js + MongoDB 웹앱 배포에 사용된 샘플을 사용 중이라고 가정합니다.
최소한 GitHub Codespaces에서 샘플 애플리케이션 을 열고 실행 azd up하여 앱을 배포합니다.
웹앱에 MCP 서버 추가
코드스페이스 터미널에서 필요한 npm 패키지를 프로젝트에 추가합니다.
npm install @modelcontextprotocol/sdk@latest zod-
- routes/index.js를 엽니다. 시나리오를 간단히 하기 위해 여기에 모든 MCP 서버 코드를 추가합니다.
경로/index.js맨 위에 다음 요구 사항을 추가합니다.
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { z } = require('zod');위의 파일
module.exports = router;맨 아래에 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, }); } } });이 경로는 MCP 서버 엔드포인트를
<url>/api/mcpMCP TypeScript SDK의 상태 비저장 모드 패턴으로 설정하고 사용합니다.-
server.registerTool()는 해당 구현을 사용하여 MCP 서버에 도구를 추가합니다. - SDK는 입력 유효성 검사에 zod 를 사용합니다.
-
description은(는) 구성 개체에서 그리고describe()은(는)inputSchema에서 도구 및 입력에 대해 사람이 읽을 수 있는 설명을 제공합니다. 호출 에이전트가 도구 및 해당 매개 변수를 사용하는 방법을 이해하는 데 도움이됩니다.
이 경로는 기존 경로의 CRUD(create-read-update-delete) 기능을 복제합니다. 이 기능은 필요하지 않지만 간결하게 유지합니다. 앱 논리를 모듈로 이동한 다음 모든 경로에서 모듈을 호출하는 것이 가장 좋습니다.
-
로컬에서 MCP 서버 테스트
코드스페이스 터미널에서 .를 사용하여 애플리케이션
npm start을 실행합니다.브라우저에서 열기를 선택한 다음 작업을 추가합니다.
실행 상태로 둡니다
npm start. MCP 서버가 현재 실행 중입니다http://localhost:3000/api/mcp.코드스페이스로 돌아가서 Copilot 채팅을 연 다음 프롬프트 상자에서 에이전트 모드를 선택합니다.
도구 단추를 선택한 다음, 드롭다운에서 추가 도구...를 선택합니다.
MCP 서버 추가를 선택합니다.
HTTP(HTTP 또는 Server-Sent 이벤트)를 선택합니다.
Enter Server URL 필드에 http://localhost:3000/api/mcp를 입력합니다.
Enter Server ID에 todos-mcp 또는 원하는 이름을 입력합니다.
작업 영역 설정을 선택합니다.
새 Copilot 채팅 창에 "할 일 목록을 보여줘"와 같은 내용을 입력합니다.
기본적으로 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>/api/mcp(으)로 변경합니다.수정된 MCP 서버 구성 위에서 시작을 선택합니다.
새 GitHub Copilot 채팅 창을 시작합니다. Copilot 에이전트에서 작업을 보고, 만들고, 업데이트하고, 삭제할 수 있어야 합니다.
보안 모범 사례
LLM(대규모 언어 모델)으로 구동되는 에이전트에서 MCP 서버를 호출하는 경우 프롬프트 삽입 공격에 유의해야 합니다. 다음 보안 모범 사례를 고려합니다.
- 인증 및 권한 부여: Microsoft Entra 인증을 사용하여 MCP 서버를 보호하여 권한 있는 사용자 또는 에이전트만 도구에 액세스할 수 있도록 합니다. 단계별 가이드는 Microsoft Entra 인증을 사용하여 Visual Studio Code에서 Azure App Service에 대한 보안 모델 컨텍스트 프로토콜 호출 을 참조하세요.
-
입력 유효성 검사 및 삭제: 이 자습서의 예제 코드는 입력 유효성 검사에 zod 를 사용하여 들어오는 데이터가 예상된 스키마와 일치하는지 확인합니다. 추가 보안을 위해 다음을 고려합니다.
- 특히 데이터베이스 쿼리 또는 출력에 사용되는 필드에 대해 처리하기 전에 모든 사용자 입력의 유효성을 검사하고 삭제합니다.
- 브라우저에서 API를 사용하는 경우 XSS(사이트 간 스크립팅)를 방지하기 위해 응답에서 출력을 이스케이프하세요.
- 예기치 않은 데이터를 방지하기 위해 모델에 엄격한 스키마 및 기본값을 적용합니다.
- HTTPS: 이 샘플은 기본적으로 HTTPS를 적용하고 전송 중인 데이터를 암호화하는 무료 TLS/SSL 인증서를 제공하는 Azure App Service를 사용합니다.
- 최소 권한 원칙: 사용 사례에 필요한 도구 및 데이터만 노출합니다. 필요한 경우가 아니면 중요한 작업을 노출하지 마세요.
- 속도 제한 및 제한: API Management 또는 사용자 지정 미들웨어를 사용하여 남용 및 서비스 거부 공격을 방지합니다.
- 로깅 및 모니터링: 감사 및 변칙 검색을 위한 MCP 엔드포인트의 로그 액세스 및 사용 의심스러운 활동을 모니터링합니다.
- CORS 구성: MCP 서버가 브라우저에서 액세스되는 경우 원본 간 요청을 신뢰할 수 있는 도메인으로 제한합니다. 자세한 내용은 CORS 사용을 참조하세요.
- 정기 업데이트: 알려진 취약성을 완화하기 위해 종속성을 최신 상태로 유지합니다.