Supergateway는 MCP stdio 서버를 네트워크로 노출하는 프로토콜 변환기이지만, 보안 기능은 내장되어 있지 않습니다. 프로덕션 환경에서는 반드시 외부 보안 계층과 결합해야 합니다.
🎯 보안 아키텍처 한눈에
┌─────────────────────────────────────────────────────────────────┐
│ Claude Desktop / Cursor (클라이언트) │
│ "어제 #프로젝트 채널 요약해줘" │
│ ↓ (HTTPS + JWT/Bearer Token) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Reverse Proxy (Nginx / Caddy / Kong) │ │
│ │ - TLS 종단 (HTTPS) │ │
│ │ - JWT/OAuth 토큰 검증 │ │
│ │ - Rate Limiting (초당 요청 수 제한) │ │
│ │ - IP 화이트리스트 │ │
│ │ - 감사 로그 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ (내부망, 인증 완료된 트래픽만) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Supergateway (--oauth2Bearer 또는 --header) │ │
│ │ - 인증 토큰을 백엔드 MCP 서버로 전달 │ │
│ │ - 프로토콜 변환 (SSE ↔ stdio) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MCP Slack Server (stdio) │ │
│ │ - Slack API 연동 │ │
│ │ - 토큰 검증 로직 (선택) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Slack API │
└─────────────────────────────────────────────────────────────────┘
🔐 5 단계 보안 강화 전략
1️⃣ TLS 암호화 (필수) — 모든 트래픽 HTTPS 로
Supergateway 는 기본적으로 평문 HTTP를 사용합니다. 프로덕션에서는 반드시 TLS 종단이 필요합니다.
방법 A: Nginx 역방향 프록시 (권장)
Nginx 설치 (Ubuntu 예시):
sudo apt update && sudo apt install nginx -y
Nginx 설정 (/etc/nginx/sites-available/mcp-gateway):
server {
listen 80;
server_name mcp.yourcompany.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name mcp.yourcompany.com;
# TLS 인증서 (Let's Encrypt 권장)
ssl_certificate /etc/letsencrypt/live/mcp.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourcompany.com/privkey.pem;
# 현대적 TLS 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 보안 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
# Rate Limiting 영역
limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=10r/s;
location / {
# Rate Limiting 적용 (초당 10 요청, 버스트 20)
limit_req zone=mcp_limit burst=20 nodelay;
# Supergateway 로 프록시
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE/WebSocket 타임아웃
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# 건강 점검 엔드포인트 (모니터링용)
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
설정 활성화:
sudo ln -s /etc/nginx/sites-available/mcp-gateway /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Let’s Encrypt 인증서 발급:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d mcp.yourcompany.com
방법 B: Caddy (자동 TLS — 가장 간단)
Caddyfile (/etc/caddy/Caddyfile):
mcp.yourcompany.com {
reverse_proxy localhost:3000
# 보안 헤더
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options DENY
}
# Rate Limiting (Caddy 플러그인 필요)
@ratelimit {
rate 10r/s
burst 20
}
respond @ratelimit "Too Many Requests" 429
}
장점: 인증서 발급·갱신 완전 자동 (Zero-Config TLS).
2️⃣ 인증 (Authentication) — 토큰 없는 접근 차단
전략 A: Nginx + Basic Auth (간단한 팀용)
Nginx 설정 추가:
location / {
# Basic Auth
auth_basic "MCP Gateway";
auth_basic_user_file /etc/nginx/.htpasswd;
# ... 나머지 프록시 설정
proxy_pass http://localhost:3000;
}
사용자 생성:
sudo apt install apache2-utils -y
sudo htpasswd -c /etc/nginx/.htpasswd admin
# 비밀번호 입력 프롬프트
단점: 평문 비밀번호 전송 (반드시 HTTPS 와 함께 사용).
전략 B: Nginx + JWT 검증 (엔터프라이즈 권장)
JWT 검증 모듈 설치 (nginx-plus 또는 openresty):
# Ubuntu (OpenResty)
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo apt install -y openresty
Nginx 설정 (/etc/nginx/sites-available/mcp-gateway):
http {
# JWT 검증 (lua-resty-openidc 사용)
lua_package_path "/usr/share/lua/5.1/?.lua;;";
server {
listen 443 ssl;
server_name mcp.yourcompany.com;
# ... TLS 설정 ...
location / {
# JWT 검증
access_by_lua_block {
local openidc = require("resty.openidc")
local res, err = openidc.authenticate({
discovery = "https://auth.yourcompany.com/.well-known/openid-configuration",
client_id = "mcp-gateway",
client_secret = "your-client-secret",
redirect_uri = "https://mcp.yourcompany.com/callback",
scope = "openid profile mcp.access",
ssl_verify = "yes"
})
if err then
ngx.status = 401
ngx.say("Unauthorized: ", err)
ngx.exit(401)
end
# 검증된 JWT 정보를 헤더로 전달
ngx.req.set_header("X-User-Email", res.user.email)
ngx.req.set_header("X-User-Roles", res.user.roles)
}
# Supergateway 로 프록시 (JWT 정보 포함)
proxy_pass http://localhost:3000;
proxy_set_header X-User-Email $http_x_user_email;
proxy_set_header X-User-Roles $http_x_user_roles;
}
}
}
Supergateway 실행 (JWT 토큰을 백엔드로 전달):
npx -y supergateway \
--stdio "python mcp-slack-server.py" \
--port 3000 \
--header "X-Auth-Source: nginx-jwt"
전략 C: Supergateway 직접 인증 (–oauth2Bearer)
Supergateway 실행 (간단한 Bearer 토큰 검증):
npx -y supergateway \
--stdio "python mcp-slack-server.py" \
--port 3000 \
--oauth2Bearer "sk-mcp-your-secret-token-xyz123"
클라이언트 설정 (Claude Desktop):
{
"mcpServers": {
"slack-secure": {
"command": "npx",
"args": }
}
}
주의: 이 방식은 토큰이 클라이언트 설정 파일에 평문으로 저장됨. 팀 배포 시 .env 파일 + 시크릿 관리자 사용 권장.
3️⃣ 인가 (Authorization) — 사용자별 접근 제어
Nginx + JWT Claims 기반 RBAC
Nginx 설정 (역할별 접근 제어):
location / {
# JWT 검증 (위와 동일)
access_by_lua_block {
local openidc = require("resty.openidc")
local res, err = openidc.authenticate({...})
if err then
ngx.exit(401)
end
# 역할 추출
local roles = res.user.roles or ""
ngx.req.set_header("X-User-Roles", roles)
# 관리자만 접근 가능한 엔드포인트 차단
if string.match(ngx.var.uri, "/admin") and not string.match(roles, "admin") then
ngx.status = 403
ngx.say("Forbidden: Admin access required")
ngx.exit(403)
end
}
proxy_pass http://localhost:3000;
}
MCP 서버 내부 인가 (고급)
MCP 서버 코드에서 JWT 헤더를 검증하여 도구별 접근 제어 구현:
# mcp-slack-server.py
import os
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("slack-secure")
@server.tool("send_slack_message")
async def send_message(channel: str, message: str):
# JWT 헤더에서 역할 추출
auth_header = os.environ.get("HTTP_X_USER_ROLES", "")
# 관리자만 메시지 발송 가능
if "admin" not in auth_header:
raise PermissionError("send_slack_message requires admin role")
# 실제 Slack API 호출
from slack_sdk import WebClient
client = WebClient(token=os.environ)
return client.chat_postMessage(channel=channel, text=message)
@server.tool("read_slack_messages")
async def read_messages(channel: str, limit: int = 10):
# 모든 인증된 사용자는 읽기 가능
from slack_sdk import WebClient
client = WebClient(token=os.environ)
return client.conversations_history(channel=channel, limit=limit)
if __name__ == "__main__":
import asyncio
asyncio.run(stdio_server(server))
4️⃣ Rate Limiting — 남용 및 비용 폭주 방지
MCP 도구 호출은 비용이 많이 드는 LLM 연산을 트리거할 수 있습니다. Rate Limiting 은 필수입니다.
Nginx 기본 Rate Limiting
Nginx 설정:
http {
# 전역 Rate Limiting 존 정의 (초당 10 요청)
limit_req_zone $binary_remote_addr zone=mcp_global:10m rate=10r/s;
# 사용자별 Rate Limiting (JWT email 기준)
limit_req_zone $http_x_user_email zone=mcp_per_user:10m rate=5r/s;
server {
location / {
# 전역 제한 + 사용자별 제한 중첩 적용
limit_req zone=mcp_global burst=20 nodelay;
limit_req zone=mcp_per_user burst=10 nodelay;
# 제한 초과 시 응답
limit_req_status 429;
limit_req_log_level warn;
proxy_pass http://localhost:3000;
}
# 건강 점검은 Rate Limiting 제외
location /health {
limit_req off;
return 200 "healthy\n";
}
}
}
도구별 Rate Limiting (고급)
비싼 도구 (예: trigger_long_operation) 는 더 엄격하게 제한:
Nginx + Lua:
location / {
access_by_lua_block {
local body = ngx.req.get_body_data()
if body then
local json = require("cjson")
local data = json.decode(body)
local tool_name = data.params and data.params.name
-- 비싼 도구는 더 엄격하게 제한
if tool_name == "trigger_long_operation" then
local key = "tool:" .. tool_name .. ":user:" .. (ngx.var.http_x_user_email or "anonymous")
local count = ngx.shared.dict:get(key) or 0
if count >= 3 then
ngx.status = 429
ngx.say("Rate limit exceeded for tool: " .. tool_name)
ngx.exit(429)
end
ngx.shared.dict:incr(key, 1, 0)
ngx.shared.dict:expire(key, 60) -- 1 분 창
end
end
}
proxy_pass http://localhost:3000;
}
5️⃣ 감사 로그 & 모니터링 — 이상 탐지 및 규정 준수
Nginx 접근 로그 (확장 형식)
Nginx 설정:
http {
# MCP 특화 로그 형식
log_format mcp_json escape=json
'{'
'"time":"$time_iso8601",'
'"client_ip":"$remote_addr",'
'"user_email":"$http_x_user_email",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"bytes":$body_bytes_sent,'
'"referer":"$http_referer",'
'"user_agent":"$http_user_agent",'
'"request_time":$request_time'
'}';
server {
access_log /var/log/nginx/mcp-access.log mcp_json;
error_log /var/log/nginx/mcp-error.log warn;
# ... 나머지 설정
}
}
로그 분석 예시 (ELK 스택 또는 Datadog 연동):
# 최근 1 시간 동안 429 에러가 많은 사용자 top 5
cat /var/log/nginx/mcp-access.log | \
jq 'select(.status == 429) | .user_email' | \
sort | uniq -c | sort -rn | head -5
건강 점검 & 생존_probe
Nginx 설정:
location /health {
access_log off;
# Supergateway 건강 상태 확인
proxy_pass http://localhost:3000/health;
proxy_read_timeout 5s;
# 실패 시 503 반환
proxy_intercept_errors on;
error_page 502 503 504 = @health_fail;
}
location @health_fail {
return 503 "MCP Gateway unhealthy\n";
add_header Content-Type text/plain;
}
Kubernetes 생존_probe (예시):
livenessProbe:
httpGet:
path: /health
port: 443
scheme: HTTPS
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
📊 보안 설정 완전 예시 (프로덕션)
전체 아키텍처
Internet
↓ (HTTPS 443)
┌─────────────────────────────────────┐
│ Cloudflare / AWS WAF │
│ - DDoS 방어 │
│ - IP 평판 기반 차단 │
│ - 지리적 차단 (선택) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Nginx (Reverse Proxy) │
│ - TLS 종단 (Let's Encrypt) │
│ - JWT/OAuth 검증 │
│ - Rate Limiting (전역 + 사용자별) │
│ - 감사 로그 │
└─────────────────────────────────────┘
↓ (내부망, 인증 완료)
┌─────────────────────────────────────┐
│ Supergateway │
│ --oauth2Bearer 토큰 전달 │
│ --stdio MCP Slack Server │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ MCP Slack Server (stdio) │
│ - Slack API 연동 │
│ - 도구별 인가 (선택) │
└─────────────────────────────────────┘
1. Supergateway 실행 스크립트 (/opt/mcp/start.sh)
#!/bin/bash
set -e
# 환경 변수 로드
source /opt/mcp/.env
# Supergateway 시작 (systemd 서비스용)
exec npx -y supergateway \
--stdio "python /opt/mcp/mcp-slack-server.py" \
--port 3000 \
--outputTransport sse \
--header "X-Slack-Token: $SLACK_BOT_TOKEN" \
--header "X-Auth-Source: nginx" \
2>&1 | tee /var/log/mcp/supergateway.log
실행 권한 부여:
sudo chmod +x /opt/mcp/start.sh
2. systemd 서비스 등록 (/etc/systemd/system/mcp-gateway.service)
Description=MCP Slack Gateway (Supergateway)
After=network.target
Type=simple
User=mcp
Group=mcp
WorkingDirectory=/opt/mcp
EnvironmentFile=/opt/mcp/.env
ExecStart=/opt/mcp/start.sh
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mcp-gateway
# 보안 강화
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/mcp
WantedBy=multi-user.target
서비스 시작:
sudo systemctl daemon-reload
sudo systemctl enable mcp-gateway
sudo systemctl start mcp-gateway
sudo systemctl status mcp-gateway
3. Nginx 최종 설정 (/etc/nginx/sites-available/mcp-gateway)
# 전역 Rate Limiting 존
limit_req_zone $binary_remote_addr zone=mcp_global:10m rate=10r/s;
limit_req_zone $http_x_user_email zone=mcp_per_user:10m rate=5r/s;
# JWT 검증용 Lua 스크립트 경로
lua_package_path "/usr/share/lua/5.1/?.lua;;";
server {
listen 80;
server_name mcp.yourcompany.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name mcp.yourcompany.com;
# TLS
ssl_certificate /etc/letsencrypt/live/mcp.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourcompany.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 보안 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# MCP 특화 로그
access_log /var/log/nginx/mcp-access.log mcp_json;
error_log /var/log/nginx/mcp-error.log warn;
location / {
# JWT 검증 (Keycloak 예시)
access_by_lua_block {
local openidc = require("resty.openidc")
local res, err = openidc.authenticate({
discovery = "https://auth.yourcompany.com/.well-known/openid-configuration",
client_id = "mcp-gateway",
client_secret = os.getenv("KEYCLOAK_CLIENT_SECRET"),
redirect_uri = "https://mcp.yourcompany.com/callback",
scope = "openid profile mcp.access",
ssl_verify = "yes"
})
if err then
ngx.status = 401
ngx.say("{\"error\": \"Unauthorized\", \"message\": \"" .. err .. "\"}")
ngx.exit(401)
end
-- 사용자 정보 헤더로 전달
ngx.req.set_header("X-User-Email", res.user.email)
ngx.req.set_header("X-User-Roles", table.concat(res.user.roles or {}, ","))
}
# Rate Limiting
limit_req zone=mcp_global burst=20 nodelay;
limit_req zone=mcp_per_user burst=10 nodelay;
limit_req_status 429;
# Supergateway 로 프록시
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE/WebSocket 타임아웃
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# 에러 처리
proxy_intercept_errors on;
error_page 502 503 504 = @gateway_error;
}
location @gateway_error {
default_type application/json;
return 503 '{"error": "Service Unavailable", "message": "MCP Gateway is temporarily unavailable"}';
}
location /health {
access_log off;
proxy_pass http://localhost:3000/health;
proxy_read_timeout 5s;
add_header Content-Type text/plain;
}
location /metrics {
# Prometheus 용 메트릭 엔드포인트 (내부망만 접근)
allow 10.0.0.0/8;
allow 172.16.0.0/12;
deny all;
proxy_pass http://localhost:3000/metrics;
}
}
4. 환경 변수 관리 (/opt/mcp/.env)
# 절대 Git 에 커밋하지 말 것!
SLACK_BOT_TOKEN=xoxb-123456789012-...
KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret
MCP_AUTH_TOKEN=sk-mcp-your-secret-token-xyz123
권한 설정:
sudo chown mcp:mcp /opt/mcp/.env
sudo chmod 600 /opt/mcp/.env
🔍 보안 검증 체크리스트
배포 전 검증
- HTTPS 강제:
http://접근 시301리다이렉트 확인 - TLS 버전:
ssl_protocols TLSv1.2 TLSv1.3;설정 확인 - 인증 테스트: 토큰 없이 접근 시
401 Unauthorized반환 확인 - Rate Limiting: 초당 10 회 이상 요청 시
429 Too Many Requests확인 - 감사 로그:
/var/log/nginx/mcp-access.log에 JSON 로그 기록 확인 - 건강 점검:
/health엔드포인트에서200 OK반환 확인
정기 감사 (월 1 회)
- 토큰 순환: SLACK_BOT_TOKEN, MCP_AUTH_TOKEN 90 일마다 재발급
- 로그 분석: 이상한 패턴 (예: 특정 사용자의 급증한 요청) 탐지
- 권한 검토: JWT 역할 (
roles) 이 최소 권한 원칙 준수하는지 확인 - 보안 업데이트: Nginx, Supergateway, MCP 서버 최신 버전으로 업데이트
⚠️ 절대 하지 말아야 할 실수
| ❌ 실수 | ✅ 올바른 방법 |
|---|---|
| Supergateway 를 직접 인터넷에 노출 (포트 3000 개방) | 반드시 Nginx/Caddy 역방향 프록시 뒤에 배치 |
| 토큰을 설정 파일에 평문으로 기록 | 환경 변수 또는 시크릿 관리자 (AWS Secrets Manager, HashiCorp Vault) 사용 |
| Rate Limiting 없이 무제한 허용 | 전역 + 사용자별 이중 Rate Limiting 필수 |
| HTTP 로 토큰 전송 | HTTPS 필수 (TLS 1.2+) |
| 감사 로그를 남기지 않음 | JSON 형식으로 상세 로그 기록 + 중앙 집중식 수집 (ELK, Datadog) |
| 모든 사용자에게 관리자 권한 부여 | JWT Claims 기반 RBAC으로 도구별 접근 제어 |
📈 보안 강화 후 기대 효과
| 지표 | 강화 전 | 강화 후 |
|---|---|---|
| 무단 접근 시도 | 차단 불가 | 100% 차단 (JWT 검증) |
| DDoS 내성 | 취약 | 초당 10 요청 제한 (Rate Limiting) |
| 데이터 유출 위험 | 평문 전송 | TLS 암호화 (HTTPS) |
| 규정 준수 | 어려움 | 감사 로그로 GDPR/HIPAA 대응 가능 |
| 비용 폭주 | 무제한 호출 | 사용자별 할당량으로 LLM 비용 통제 |
“Supergateway 는 도구일 뿐, 보안은 당신의 책임입니다.”
3 시간 투자로 엔터프라이즈급 보안을 구축하세요. MCP 서버가 다루는 데이터는 회사의 신경계입니다.