- Published on
AutoGPT에 MCP 붙여 도구연결·권한격리 구현
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버/에이전트 자동화가 커질수록 문제는 “도구를 얼마나 많이 붙이느냐”가 아니라 “어떻게 안전하게 붙이느냐”로 이동합니다. AutoGPT 같은 에이전트 프레임워크는 웹 브라우징, 파일 I/O, DB 조회, 배포 트리거 등 다양한 액션을 수행할 수 있지만, 도구 호출이 늘어날수록 권한이 뭉개지고(과도한 토큰), 실패 지점이 늘고(부분 실패), 사고 시 추적이 어려워집니다(감사 로그 부재).
이 글에서는 AutoGPT에 MCP(Model Context Protocol)를 붙여 도구 연결을 표준화하고, 권한을 격리하는 구현 전략을 다룹니다. 핵심은 다음 두 가지입니다.
- MCP를 “도구 버스”로 사용해, 에이전트는 도구의 내부 구현을 모르고도 호출한다
- 도구 실행은 별도 프로세스/컨테이너에서 수행하며, 스코프 기반 권한과 감사 로그를 강제한다
왜 MCP인가: 도구 연결을 표준화하는 이유
MCP는 LLM/에이전트가 외부 도구(툴, 리소스, 프롬프트 템플릿 등)를 접근할 때의 인터페이스를 표준화하려는 프로토콜입니다. 특정 벤더 SDK나 프레임워크에 종속되지 않고, “도구 목록 조회 → 스키마 기반 호출 → 결과 수신” 흐름을 일관되게 만들 수 있습니다.
AutoGPT 관점에서 MCP를 붙이면 이점이 큽니다.
- 도구 추가/교체 비용 감소: 도구가 늘어도 에이전트 코드가 덜 바뀜
- 권한 모델을 프로토콜 레이어에서 강제: “이 에이전트는 이 도구의 이 기능만” 같은 정책을 중앙화
- 감사/관측성 확보: 모든 도구 호출이 MCP 게이트웨이를 통과하므로 로깅/트레이싱이 쉬움
목표 아키텍처: Agent, MCP Gateway, Tool Runners
권장 구조는 3층입니다.
- AutoGPT(Agent): 계획 수립과 호출 결정만 담당
- MCP Gateway(중앙 게이트): 인증/인가, 스코프 검사, 레이트 리밋, 로깅, 라우팅
- Tool Runner(격리 실행기): 실제 도구 실행. 프로세스/컨테이너/서버리스로 분리
이렇게 분리하면 “에이전트 프로세스가 곧 root 권한”이 되는 최악의 결합을 피할 수 있습니다.
권한 격리의 핵심 원칙
- 최소 권한(Least Privilege): 도구별로 가능한 액션을 쪼개고 스코프를 세분화
- 실행 격리(Isolation): 파일 시스템, 네트워크, 자격 증명, CPU/메모리를 분리
- 감사 가능성(Auditability): 누가/언제/무엇을/왜 호출했는지 남김
- 실패 내성(Resilience): 부분 실패 재시도, 타임아웃, 서킷 브레이커
MCP로 도구 연결: “스키마 기반 호출”을 강제하기
도구 호출을 안전하게 만들려면, 자연어로 “DB 조회해줘”가 아니라 명시적 스키마가 필요합니다. 예를 들어 query_mysql 도구를 만들 때 입력을 구조화합니다.
- 허용 쿼리 타입:
SELECT만 - 허용 스키마/테이블: 화이트리스트
- 최대 결과 행 수 제한
이 제약을 MCP 도구 스키마로 표현하면, 에이전트가 실수로 DROP 같은 위험한 쿼리를 만들 가능성을 크게 낮출 수 있습니다.
아래는 개념적인 예시입니다(실제 MCP 서버 구현체마다 세부 API는 다를 수 있지만, **핵심은 “입력 스키마와 정책을 서버가 강제”**한다는 점입니다).
// tools/mysqlTool.ts (개념 예시)
import { z } from "zod";
export const QueryMySQLInput = z.object({
sql: z.string().min(1),
maxRows: z.number().int().min(1).max(500).default(200),
timeoutMs: z.number().int().min(100).max(5000).default(1500)
});
// 정책: SELECT만 허용
function assertSelectOnly(sql: string) {
const normalized = sql.trim().toUpperCase();
if (!normalized.startsWith("SELECT ")) {
throw new Error("Only SELECT queries are allowed");
}
}
export async function queryMySQL(raw: unknown, ctx: { tenantId: string }) {
const input = QueryMySQLInput.parse(raw);
assertSelectOnly(input.sql);
// 추가 정책: 테이블 화이트리스트, WHERE 강제, LIMIT 강제 등도 가능
// 실제 실행은 격리된 Runner에서 수행하도록 분리하는 것이 안전
return {
rows: [],
meta: {
tenantId: ctx.tenantId,
maxRows: input.maxRows
}
};
}
권한 모델 설계: 스코프, 정책, 임시 토큰
도구 권한 격리는 “API 키를 숨긴다” 수준으로 끝나지 않습니다. 다음을 분리해야 합니다.
- 에이전트 정체성(Who): 어떤 에이전트/워크플로우인가
- 호출 의도(Why): 어떤 작업 티켓/요청에 의해 호출되는가
- 권한 스코프(What): 어떤 도구의 어떤 액션을 호출할 수 있는가
- 실행 환경(Where): 어떤 네트워크/파일 시스템에서 실행되는가
추천 스코프 예시
tool.mysql.readonlytool.github.issue.writetool.k8s.readonlytool.http.fetch.allowed_domains
여기서 중요한 건 tool.http.fetch.allowed_domains처럼 정책 파라미터가 포함된 스코프입니다. 단순히 “HTTP 가능”은 너무 큽니다.
MCP Gateway에서 인가 체크(예시)
// gateway/authorize.ts
type Scope = string;
type AuthContext = {
agentId: string;
tenantId: string;
scopes: Scope[];
};
export function requireScope(ctx: AuthContext, required: Scope) {
if (!ctx.scopes.includes(required)) {
throw new Error(`Forbidden: missing scope ${required}`);
}
}
export function authorizeToolCall(ctx: AuthContext, toolName: string) {
// 단순 예시: 도구별로 스코프 매핑
const map: Record<string, Scope> = {
"query_mysql": "tool.mysql.readonly",
"create_github_issue": "tool.github.issue.write"
};
const required = map[toolName];
if (!required) throw new Error("Unknown tool");
requireScope(ctx, required);
}
실무에서는 여기에 다음을 추가합니다.
- 테넌트/프로젝트 경계(tenant boundary)
- 도구별 쿼터(일/분당 호출 수)
- 입력 값 검증(허용 도메인, 허용 리포지토리 등)
실행 격리: Tool Runner를 컨테이너로 분리하기
권한 격리의 결정타는 “도구 실행을 에이전트 프로세스 밖으로 빼는 것”입니다. Tool Runner를 컨테이너로 분리하면 아래를 강제할 수 있습니다.
- 읽기 전용 파일 시스템
- 아웃바운드 네트워크 제한(egress allowlist)
- CPU/메모리 제한
- OS 권한 축소(non-root)
Docker 기반 Runner 예시
# docker-compose.yml (개념 예시)
services:
mcp-gateway:
image: myorg/mcp-gateway:latest
ports:
- "8080:8080"
tool-runner-mysql:
image: myorg/tool-runner-mysql:latest
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
environment:
- MYSQL_HOST=mysql.internal
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
메모리 제한을 두는 이유는 단순 안정성뿐 아니라, 에이전트가 유도한 “대용량 응답”으로 Runner가 죽는 것을 막기 위함입니다. 컨테이너가 반복 재시작되는 상황은 쿠버네티스에서 CrashLoopBackOff로 이어질 수 있는데, 원인 분석과 대응 패턴은 아래 글도 함께 참고하면 좋습니다.
네트워크/도메인 격리: “웹 접근” 도구가 가장 위험하다
AutoGPT류 에이전트에서 가장 위험한 도구는 보통 http_fetch나 브라우저 자동화입니다. 이유는 다음과 같습니다.
- SSRF(내부망 접근) 가능성
- 인증 토큰/메타데이터 엔드포인트 노출 가능성
- 데이터 유출 경로가 다양함
대응은 “허용 도메인 allowlist + 내부 IP 대역 차단 + 리다이렉트 제한”이 기본입니다.
// tools/httpFetchPolicy.ts
import dns from "node:dns/promises";
import net from "node:net";
const ALLOWED_HOSTS = ["api.github.com", "example.com"];
function isPrivateIp(ip: string) {
// 단순 예시: RFC1918 일부만 체크
return ip.startsWith("10.") || ip.startsWith("192.168.") || ip.startsWith("172.16.");
}
export async function assertAllowedUrl(urlStr: string) {
const url = new URL(urlStr);
if (!ALLOWED_HOSTS.includes(url.hostname)) {
throw new Error("Host not allowed");
}
const resolved = await dns.lookup(url.hostname);
if (net.isIP(resolved.address) && isPrivateIp(resolved.address)) {
throw new Error("Private IP is not allowed");
}
}
관측성과 감사 로그: “도구 호출”을 이벤트로 남겨라
권한 격리가 제대로 작동하는지 확인하려면 로그가 필요합니다. 추천 이벤트 스키마는 다음을 포함합니다.
requestId/traceIdagentId,tenantId,userId(있다면)toolName,toolVersioninputHash(원문 전체를 남기기 어렵다면 해시)policyDecision(허용/차단 사유)durationMs,status,errorType
또한 “LLM이 어떤 근거로 도구를 호출했는지”를 남기고 싶다면, 프롬프트 전체를 저장하기보다는 요약된 근거만 남기는 편이 안전합니다(PII/시크릿 혼입 위험).
실패·재시도 설계: 부분 실패가 기본값이다
도구 호출은 외부 시스템에 의존하므로 실패가 기본입니다. 특히 여러 도구를 연쇄 호출하는 AutoGPT 플로우에서는 “부분 실패 후 재시도”가 필수입니다.
- 타임아웃: 도구별로 상한을 강제
- 재시도: 멱등성(idempotency) 있는 호출만 제한적으로
- 백오프: 지수 백오프 + 지터
- 부분 실패: 성공한 단계는 재실행하지 않도록 체크포인트
대량 호출이나 큐 지연, 429 처리 같은 운영 패턴은 아래 글의 전략이 그대로 응용됩니다.
간단한 재시도 래퍼 예시는 다음과 같습니다.
// gateway/retry.ts
export async function withRetry<T>(fn: () => Promise<T>, opts?: {
retries?: number;
baseDelayMs?: number;
}) {
const retries = opts?.retries ?? 2;
const base = opts?.baseDelayMs ?? 200;
let lastErr: unknown;
for (let i = 0; i <= retries; i++) {
try {
return await fn();
} catch (e) {
lastErr = e;
if (i === retries) break;
const delay = base * Math.pow(2, i) + Math.floor(Math.random() * 50);
await new Promise(r => setTimeout(r, delay));
}
}
throw lastErr;
}
실전 체크리스트: “붙였다”가 아니라 “운영된다”의 기준
마지막으로, AutoGPT에 MCP를 붙여 도구 연결·권한 격리를 구현할 때 운영 관점에서 확인할 항목을 정리합니다.
필수
- 도구는 MCP Gateway를 통해서만 접근 가능(우회 경로 차단)
- 도구별 스코프와 정책(도메인/테이블/리포지토리 allowlist 등)
- Tool Runner 격리(컨테이너, non-root, read-only FS, egress 제한)
- 모든 호출에
traceId부여 + 감사 로그 저장 - 타임아웃/레이트리밋/재시도 기본값 정의
권장
- 정책 테스트(허용/차단 케이스를 단위 테스트로)
- 입력 스키마 버저닝(도구 버전별 호환성)
- 민감 정보 마스킹(로그/에러 메시지)
- 서킷 브레이커로 장애 전파 차단
마무리
AutoGPT에 MCP를 붙이는 작업은 “도구를 더 많이 연결하기 위한 편의 기능”이 아니라, 도구 호출을 표준화하고 통제 가능한 경계로 밀어 넣는 보안/운영 아키텍처에 가깝습니다. 에이전트는 똑똑해질수록 더 많은 권한을 요구하지만, 시스템은 그 반대로 더 잘게 쪼개고 더 강하게 격리해야 안전해집니다.
정리하면 다음 한 줄이 핵심입니다.
- 에이전트는 계획만, MCP Gateway는 통제만, Tool Runner는 실행만
이 구조로 가면 도구가 늘어도 권한이 무너지지 않고, 사고가 나도 원인을 추적할 수 있으며, 재시도/타임아웃 같은 운영 기본기가 자연스럽게 따라옵니다.