yeznable
[ MCP ] ChatGPT + AWS + Terraform 본문
지난 포스팅에서 Render를 활용해 간단하게 무료로 MCP 서버를 만들고 Custom GPT에 연결하는 실습을 했다.
[ MCP ] ChatGPT + MCP 서버
지난번에 아래 글에서 Claude with MCPs에 대해 소개했었다. [ 따라잡기 ] Claude with MCPs (0) - 소개AI를 활용하는 데에 뒤쳐지지 말아야겠다는 생각에 호기롭게 [ 따라잡기 ]라는 말머리도 달아 윈드서프
yeznable-blog.tistory.com
이게 가능하다면 AWS 서버도 ChatGPT랑 자연어로 관리할 수 있는게 아닌가 싶어서 테스트 해보았고 Terraform까지 활용하여 다음과 같이 동작시킬 수 있었다.
Render에서도 테스트 했었던 서버 파일구조 관리 기능은 다음과 같이 동작한다.
GPT한테 물어봐서 작성된 코드를 GPT가 직접 파일로 작성하고 실행하도록 요청할 수 있다.
GPT에게 자연어로 요청해서 Terraform 설치, 필요한 파일 작성까지 먼저 준비했다.
이후 Terraform 명령어를 활용해 새로운 EC2 인스턴스를 생성하고 삭제하도록 할 수 있었다.
이런 MCP + AWS 구조를 갖추기 위해서는 다음 조건을 만족해야 한다.
이 조건들에는 어느정도 과금이 들어가는 조건들도 있다.
- 도메인 구매
- 탄력적 IP(Elastic IP)를 통해 MCP 서버로 활용할 EC2 인스턴스의 IP 고정
- Route 53을 통한 MCP 서버의 Elastic IP에 도메인 연결
- 도메인에 HTTPS 접속이 가능하도록 설정(nginx+certbot)
- FastAPI로 커스텀 GPT와 MCP 서버가 연결될 수 있도록 설정
- MCP 서버의 인바운드 규칙 설정(FastAPI를 위한 8000번 포트)
나는 FastAPI를 아래와 같이 설정해서 활용한다.
GitHub - yejoonlee/YEZNABLE_MCP_SERVER
Contribute to yejoonlee/YEZNABLE_MCP_SERVER development by creating an account on GitHub.
github.com
이 저장소의 소스를 clone 한다고 바로 활용할 수 있는건 아니고 .env 파일을 다음과 같이 작성해두어야 한다.
SERVER_URL=https://<구매한 도메인 주소>
SECRET_TOKEN=<FastAPI에 요청할 때 쓸 토큰>
GRAPH_FILE_PATH=<그래프 파일을 저장할 위치>
Custom GPT 생성 설정은 다음과 같이 했다.
지침 설정
당신은 서버 관리 보조 역할을 수행합니다. FastAPI로 구성된 원격 MCP 서버와 연동되어 있으며, 다음과 같은 기능을 수행할 수 있습니다:
1. 명령어 실행:
- 사용자가 요청한 명령어를 MCP 서버에 전송하여 백그라운드로 실행하거나 결과를 수집해 출력합니다.
2. 파일/디렉터리 관리:
- 사용자의 요청에 따라 파일을 생성하거나, 삭제, 복사, 이름 변경할 수 있습니다.
3. 사용 시 주의사항:
- MCP 서버는 리눅스 기반 Bash 쉘을 사용합니다.
- 경로를 지정할 때는 항상 전체 경로 또는 홈 디렉토리 기준으로 상대 경로를 명확히 전달해야 합니다.
- 중요한 시스템 디렉토리를 조작하지 않도록 주의합니다 (`/etc`, `/bin`, `/usr` 등).
요청이 가능하면 적절한 API를 호출해 실제로 수행하세요. 그렇지 않은 경우에는 설명과 안내만 제공하세요.
작업의 인증 설정은 API 키로 설정하고 내가 FastAPI에 활용한 방법인 Bearer를 선택한 뒤 위의 .env에도 적어놓은 토큰을 입력해서 저장한다.
스키마는 다음과 같다.
{
"openapi": "3.1.0",
"info": {
"title": "MCP Control Server",
"version": "1.0.0",
"description": "A FastAPI server that executes shell commands and manages files remotely"
},
"servers": [
{
"url": "https://<구매하고 설정을 마친 도메인 주소>"
}
],
"paths": {
"/run": {
"post": {
"operationId": "run_command",
"summary": "Run a shell command without returning output",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "Shell command to execute"
}
},
"required": [
"command"
]
}
}
}
},
"responses": {
"200": {
"description": "Command sent"
}
}
}
},
"/run-and-capture": {
"post": {
"operationId": "run_command_and_capture",
"summary": "Run a shell command and return the output",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "Shell command to execute"
}
},
"required": [
"command"
]
}
}
}
},
"responses": {
"200": {
"description": "Command output returned"
}
}
}
},
"/file/create": {
"post": {
"operationId": "create_file",
"summary": "Create a file with optional content",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path where the file will be created"
},
"content": {
"type": "string",
"description": "Content to write in the file"
}
},
"required": [
"path"
]
}
}
}
},
"responses": {
"200": {
"description": "File created"
}
}
}
},
"/file/delete": {
"post": {
"operationId": "delete_file",
"summary": "Delete a file or directory",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file or directory"
}
},
"required": [
"path"
]
}
}
}
},
"responses": {
"200": {
"description": "File or directory deleted"
}
}
}
},
"/file/rename": {
"post": {
"operationId": "rename_file",
"summary": "Rename or move a file or directory",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Original file or directory path"
},
"new_path": {
"type": "string",
"description": "New file or directory path"
}
},
"required": [
"path",
"new_path"
]
}
}
}
},
"responses": {
"200": {
"description": "Renamed or moved successfully"
}
}
}
},
"/file/copy": {
"post": {
"operationId": "copy_file",
"summary": "Copy a file or directory",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Source path"
},
"new_path": {
"type": "string",
"description": "Destination path"
}
},
"required": [
"path",
"new_path"
]
}
}
}
},
"responses": {
"200": {
"description": "File or directory copied"
}
}
}
}
}
}
원래는 MCP 서버만 제어하도록 생각하고 있었다.
그래서 인스턴스를 무료티어로 만들어서 커스텀 GPT와 연결해 제어가 되는지 테스트 해봤다.
테스트 완료 후 스펙업을 해서 활용할 생각이었는데 Terraform을 활용하면 무료 티어 인스턴스로 MCP 서버를 운영하고 필요에 따라서 Terraform으로 새로운 인스턴스를 만들어 쓸 수 있겠다는 생각이다.
'하는 일 > ai' 카테고리의 다른 글
[ MCP ] ChatGPT + MCP 서버 (0) | 2025.06.05 |
---|---|
[ gradio ] 간단한 AI 모델 서빙을 위한 툴 (0) | 2025.06.04 |
[ 따라잡기 ] Claude with MCPs (1) - 실습 후기 (0) | 2025.04.03 |
[ 따라잡기 ] Claude with MCPs (0) - 소개 (0) | 2025.04.02 |
[ Windsurf ] 윈드서프 파이썬 프로젝트에 이용해보기 (2) - Cascade랑 놀기 (0) | 2025.01.26 |