Skip to main content
Glama
README.md
# Video Transcriber MCP

영상 URL의 음성을 OpenAI Speech-to-Text로 전사하는 MCP 서버입니다.
공개 HTTPS 영상 파일을 임시 다운로드하고 FFmpeg로 오디오를 추출한 뒤, OpenAI 전사 결과를 MCP tool의 구조화된 JSON으로 반환합니다.

- Python 3.13
- 공식 [MCP Python SDK v2](https://github.com/modelcontextprotocol/python-sdk): `mcp==2.2.0`, `MCPServer`
- OpenAI Python SDK `3.13.0`, `client.audio.transcriptions.create()`
- 기본 모델: `gpt-transcribe`, 기본 언어: `ko`
- Streamable HTTP: `/mcp`, 상태 확인: `/health`
- `0.0.0.0:$PORT` 바인딩, `PORT` 기본값 `10000`

## 지원 범위

`https://cdn.example.com/video.mp4`처럼 **영상 파일을 직접 반환하는 URL**을 사용합니다. 서명된 다운로드 URL도 사용할 수 있습니다. URL의 query와 fragment는 반환 메타데이터에서 제거됩니다.

틱톡·유튜브 등의 웹페이지, 로그인 페이지, HLS/DASH 재생목록, DRM 영상은 지원하지 않습니다. 영상 화면에 포함된 글자를 OCR하는 기능이 아니라 **영상의 음성 전사**입니다. ChatGPT에 직접 첨부한 MP4를 자동 전달하는 기능은 이번 버전에 포함하지 않습니다.

## 환경변수

| 변수 | 기본값 | 설명 |
| --- | --- | --- |
| `OPENAI_API_KEY` | 없음 | 실제 전사 호출에 필요. Render dashboard 또는 실행 환경에서만 설정 |
| `PORT` | `10000` | 서버 수신 포트 |
| `MAX_DOWNLOAD_MB` | `200` | 다운로드 제한, MiB 단위. 최대 1024 |
| `DOWNLOAD_TIMEOUT_SECONDS` | `180` | DNS·리다이렉트·본문을 포함한 전체 다운로드 제한 |
| `FFMPEG_TIMEOUT_SECONDS` | `180` | FFmpeg 실행 제한 |
| `OPENAI_TIMEOUT_SECONDS` | `180` | OpenAI 전사 요청 제한 |
| `MCP_ALLOWED_HOSTS` | 없음 | 커스텀 도메인 사용 시 허용할 Host 목록, 쉼표로 구분 |

Render에서는 자동 제공되는 `RENDER_EXTERNAL_HOSTNAME`을 HTTP Host 허용 목록에 추가합니다. `*.onrender.com` 전체를 허용하지 않습니다. 커스텀 도메인 사용 시 `MCP_ALLOWED_HOSTS=transcriber.example.com`처럼 정확한 호스트를 지정하세요.

API 키가 없어도 import, 서버 시작, `/health`, MCP 초기화와 도구 목록 조회는 가능합니다. 실제 전사 도구를 호출할 때만 `MISSING_API_KEY` 오류를 반환합니다. `.env`를 자동으로 읽지 않습니다.

## 로컬 실행

Python 3.13과 FFmpeg를 설치하고 `ffmpeg -version`으로 확인하세요.

```bash
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python server.py
```

Windows PowerShell:

```powershell
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
$env:PORT = "10000"
python server.py
```

실제 전사는 실행 환경에 `OPENAI_API_KEY`가 설정되어 있어야 합니다. 키를 소스 파일이나 셸 스크립트에 넣지 마세요. 값 없이 서버를 시작해도 아래의 무과금 테스트를 실행할 수 있습니다.

## Docker 실행

```bash
docker build -t video-transcriber-mcp .
docker run --rm -p 10000:10000 -e PORT=10000 video-transcriber-mcp
```

위 명령은 키 없이 기동 상태만 확인합니다. 호스트 환경에 이미 키를 안전하게 설정했다면 값을 명령줄에 쓰지 않고 전달합니다:

```bash
docker run --rm -p 10000:10000 -e PORT=10000 -e OPENAI_API_KEY video-transcriber-mcp
```

이미지는 `python:3.13-slim` 기반으로 FFmpeg와 CA 인증서를 설치하고 apt 캐시를 제거합니다. 실행은 root가 아닌 사용자로 하며, 이미지에는 `server.py`와 의존성 파일만 복사합니다.

## Render 배포

1. **New > Web Service**를 선택합니다.
2. GitHub repository **kypvalor/video-transcriber-mcp**를 연결합니다.
3. **Runtime: Docker**를 선택합니다. Dockerfile 경로는 `./Dockerfile`입니다.
4. Environment에 다음을 설정합니다:
   - `OPENAI_API_KEY=<Render dashboard에서 직접 입력>`
   - `PORT=10000` (선택 사항)
5. Health Check Path를 **`/health`**로 설정합니다.
6. 배포합니다. 별도 Docker Command/Start Command 재정의는 필요하지 않습니다.
7. 배포 후 MCP URL:

   ```text
   https://<service-name>.onrender.com/mcp
   ```

8. 아래 테스트에서 로컬 URL을 배포 URL로 바꾸어 `/health`, MCP 초기화·도구 조회를 확인합니다.

큰 영상은 FFmpeg CPU·임시 디스크와 서비스 요청 시간 제한의 영향을 받습니다. 한 프로세스에서 동시에 한 건만 처리하고 나머지 요청에는 `SERVER_BUSY`를 반환합니다. 먼저 짧은 영상으로 서비스 자원을 확인하세요. 자동 확장 인스턴스 사이의 전역 비용 한도는 제공하지 않습니다.

## MCP tool

### `transcribe_video`

입력:

```json
{
  "video_url": "https://cdn.example.com/video.mp4",
  "language": "ko"
}
```

성공 결과:

```json
{
  "success": true,
  "language": "ko",
  "transcript": "전사된 전체 텍스트",
  "source_url": "https://cdn.example.com/video.mp4",
  "audio_format": "mp3"
}
```

실패 결과는 `success: false`, 빈 `transcript`, 그리고 `error: {"code": "...", "message": "..."}`를 포함합니다. 클라이언트는 MCP 프로토콜의 성공 여부뿐 아니라 **`success` 필드도 확인**해야 합니다. 대표 코드는 `UNSAFE_URL`, `DOWNLOAD_TOO_LARGE`, `DOWNLOAD_TIMEOUT`, `FFMPEG_ERROR`, `FFMPEG_TIMEOUT`, `AUDIO_TOO_LARGE`, `MISSING_API_KEY`, `OPENAI_ERROR`, `SERVER_BUSY`입니다.

## MCP 테스트

### 1. 자동 테스트 — 실제 OpenAI 호출 없음

```bash
python -m py_compile server.py
python -m unittest discover -s tests -p "test_*.py" -v
python tests/smoke_mcp.py
```

단위 테스트는 네트워크·OpenAI를 모의 응답으로 대체합니다. FFmpeg가 있으면 1초 테스트 영상을 직접 만들어 MP3 추출도 검증합니다. 없으면 해당 테스트만 skip합니다.

`smoke_mcp.py`는 **키를 제거한 자식 프로세스**로 서버를 띄우고, `/health`, HTTP Host 차단, 공식 MCP v2 클라이언트의 초기화·도구 조회·키 미설정 응답을 검증한 후 프로세스를 종료합니다. 저장된 키나 실제 OpenAI 서비스를 사용하지 않습니다.

GitHub Actions의 **Docker and MCP checks**도 동일한 Dockerfile로 이미지를 빌드하고, non-root 컨테이너 안에서 테스트와 실제 HTTP MCP 통신을 검증합니다. CI에는 OpenAI 키를 등록할 필요가 없습니다.

### 2. Health check

```bash
curl http://localhost:10000/health
```

응답: `{"status":"ok"}`. 이 응답은 서버 생존 여부이며 OpenAI 키·결제 권한을 보증하지 않습니다.

### 3. 공식 MCP v2 클라이언트

서버를 켠 상태에서 아래 코드를 실행합니다. 도구 목록 조회는 OpenAI를 호출하지 않습니다.

```python
import asyncio
from mcp import Client

async def main():
    async with Client("http://localhost:10000/mcp", read_timeout_seconds=600) as client:
        tools = await client.list_tools()
        print([tool.name for tool in tools.tools])  # ["transcribe_video"]

asyncio.run(main())
```

실제 전사 테스트는 같은 `async with` 안에서 다음을 실행합니다. 실제 공개 영상 URL과 서버 측 키가 필요하며 **OpenAI 사용료가 발생**합니다.

```python
result = await client.call_tool(
    "transcribe_video",
    {"video_url": "https://your-cdn.example/video.mp4", "language": "ko"},
)
print(result.structured_content)
```

선택적으로 `npx @modelcontextprotocol/inspector`를 실행하고 Streamable HTTP의 URL에 `/mcp`를 입력할 수 있습니다. 브라우저 주소창으로 `/mcp`를 열었을 때의 GET 응답만으로 MCP 동작 여부를 판단하지 마세요. 초기화와 `tools/list`가 실제 검증 기준입니다.

## 보안 및 제한

- **API key를 GitHub에 절대 저장하지 마세요.** 노출된 키는 재사용하지 말고 OpenAI에서 폐기하세요. 서버는 현재 실행 환경의 `OPENAI_API_KEY`만 사용합니다.
- `.env`, `.env.*`, 인증서·키 파일, 캐시·임시 파일은 Git에서 제외합니다. `.dockerignore`도 허용 목록 방식입니다.
- HTTPS 443 포트만 허용하며 URL 인증정보와 localhost·사설·예약·루프백·링크로컬·멀티캐스트 주소를 거부합니다. DNS 응답 중 내부 IP가 하나라도 있으면 거부합니다.
- DNS 검사 뒤 **검증된 IP에 연결하고 원래 Host/TLS SNI를 유지**합니다. HTTP 프록시 환경변수를 다운로드에 사용하지 않으며, 최대 5번의 리다이렉트마다 같은 검사를 적용합니다.
- Content-Length를 먼저 확인하고, 헤더가 없거나 작게 보고되어도 실제 다운로드 바이트 수를 제한합니다. 다운로드 시간·서버 응답 대기 시간도 제한합니다.
- FFmpeg는 `shell=True` 없이 실행하며, 네트워크 프로토콜과 재생목록 입력을 차단합니다. 오디오만 MP3 mono / 16 kHz / 64 kbps로 추출합니다.
- OpenAI의 25 MB 제한보다 작은 **24 MB** 오디오 제한을 둡니다. 초과 시 잘린 오디오를 전사하지 않고 오류를 반환합니다.
- 임시 파일은 성공·실패·요청 취소 시 정리하고 FFmpeg 시간 초과·취소 시 자식 프로세스를 종료합니다. OS가 강제로 종료된 경우의 정리는 임시 컨테이너 파일시스템 정책을 따릅니다.
- API 키·전사 원문·전체 다운로드 URL·FFmpeg 원본 오류를 애플리케이션 로그에 출력하지 않습니다. HTTP SDK debug 로그를 켜지 마세요.
- 이번 서버에는 MCP 사용자 인증이 포함되지 않습니다. **접근 가능한 호출자는 서버의 OpenAI 비용을 발생시킬 수 있습니다.** 공개 운영 시 인증 게이트웨이/접근 제한을 적용하고 OpenAI 프로젝트 지출 한도를 설정하세요. HTTP Host 검사는 사용자 인증을 대신하지 않습니다.

## 공식 문서

- [MCP Python SDK v2](https://github.com/modelcontextprotocol/python-sdk)
- [MCPServer API](https://py.sdk.modelcontextprotocol.io/api/mcp/server/)
- [OpenAI File transcription](https://developers.openai.com/api/docs/guides/speech-to-text)
- [Render Docker](https://render.com/docs/docker)
- [Render environment variables](https://render.com/docs/environment-variables)

고정된 버전으로 검증한 뒤 의존성을 업데이트하세요. OpenAI 모델 접근 가능 여부는 계정마다 다르므로 모델명이 유효하더라도 실제 계정 권한을 별도로 확인해야 합니다.