MCP Server와 코드 실행: AI 에이전트의 토큰을 98% 절감하는 방법

  • 카카오톡 공유하기
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 링크 복사하기

Model Context Protocol(MCP)은 AI 에이전트를 외부 시스템에 연결하는 표준 프로토콜로, 2024년 11월 출시 이후 빠르게 업계 표준으로 자리잡았습니다. 커뮤니티에서는 수천 개의 MCP 서버를 개발했고, 모든 주요 프로그래밍 언어용 SDK가 제공되고 있습니다.

하지만 연결되는 도구가 늘어날수록 예상치 못한 문제가 발생했습니다. 도구가 많아질수록 에이전트가 느려지고, 비용이 급격히 증가하는 것입니다. Anthropic은 최근 이 문제에 대한 혁신적인 해결책을 제시했습니다. 바로 MCP 도구를 코드 실행 환경과 결합하는 방식입니다.

이 글에서는 MCP의 토큰 소비 문제와 그 해결 방법을 자세히 살펴보겠습니다.

MCP의 두 가지 토큰 병목 현상

1. 도구 정의가 컨텍스트 윈도우를 잠식

대부분의 MCP 클라이언트는 모든 도구 정의를 초기에 컨텍스트에 로드합니다. 각 도구는 다음과 같은 상세한 정보를 포함합니다:

문제는 수천 개의 도구에 연결된 에이전트는 사용자의 요청을 읽기도 전에 수십만 토큰을 처리해야 한다는 것입니다. 이는 응답 시간 증가와 비용 상승으로 직결됩니다.

2. 중간 결과가 모델을 반복적으로 통과

기존 MCP 방식에서 도구를 연결할 때 중간 결과가 모델의 컨텍스트를 반복적으로 통과해야 합니다.

예를 들어 “Google Drive에서 회의록을 다운로드해서 Salesforce 리드에 첨부해줘”라는 요청을 처리할 때:

같은 데이터가 두 번 컨텍스트를 통과하면서 총 100,000 토큰을 소비하게 됩니다. 더 큰 문서는 컨텍스트 윈도우 한계를 초과할 수 있으며, 모델이 긴 텍스트를 복사하면서 오류가 발생할 위험도 있습니다.

해결책: MCP 도구를 코드 API로 전환

Anthropic과 Cloudflare가 제안한 해법은 의외로 단순합니다. MCP 도구를 파일시스템의 코드 파일로 변환하는 것입니다.

파일 구조 예시

TypeScript 환경에서는 다음과 같은 구조를 만들 수 있습니다:

각 도구는 간단한 함수 파일이 됩니다:

// ./servers/google-drive/getDocument.ts
import { callMCPTool } from "../../../client.js";

interface GetDocumentInput {
  documentId: string;
}

interface GetDocumentResponse {
  content: string;
}

/* Google Drive에서 문서를 읽습니다 */
export async function getDocument(
  input: GetDocumentInput
): Promise<GetDocumentResponse> {
  return callMCPTool<GetDocumentResponse>(
    'google_drive__get_document', 
    input
  );
}

코드로 도구 연결하기

이제 에이전트는 코드를 작성하여 도구를 사용합니다:

// Google Docs에서 회의록을 읽어 Salesforce 리드에 추가
import * as gdrive from './servers/google-drive';
import * as salesforce from './servers/salesforce';

const transcript = (await gdrive.getDocument({ 
  documentId: 'abc123' 
})).content;

await salesforce.updateRecord({
  objectType: 'SalesMeeting',
  recordId: '00Q5f000001abcXYZ',
  data: { Notes: transcript }
});

회의록 내용은 코드 실행 환경 내에서만 흐르고 모델의 컨텍스트를 거치지 않습니다. 모델은 이 코드만 작성하면 되며, 실제 50,000 토큰짜리 회의록 내용은 transcript 변수에 저장되어 직접 Salesforce 호출로 전달됩니다.

극적인 토큰 절감 효과

Anthropic의 실험 결과, 이 방식으로 150,000 토큰을 2,000 토큰으로 줄이는 98.7% 절감 효과를 달성했습니다.

에이전트는 파일시스템을 탐색하여 필요한 도구만 찾아 사용합니다. 전체 도구 정의를 미리 로드할 필요가 없으니 토큰이 극적으로 줄어드는 것입니다.

코드 실행이 제공하는 추가 이점

1. 점진적 도구 발견 (Progressive Disclosure)

모델은 파일시스템 탐색에 능숙합니다. 도구를 파일로 제공하면 필요에 따라 정의를 읽을 수 있습니다.

또는 search_tools 도구를 추가하여 관련 정의만 찾을 수 있습니다. 예를 들어 “salesforce”를 검색하면 해당 도구만 로드합니다. 세부 수준 매개변수를 포함하여 에이전트가 필요한 정보 수준(이름만, 이름과 설명, 전체 스키마 포함)을 선택할 수 있게 하면 더욱 효율적입니다.

2. 컨텍스트 효율적인 데이터 처리

대용량 데이터를 다룰 때 코드에서 필터링과 변환을 수행한 후 결과만 모델에 전달할 수 있습니다:

// 코드 실행 없이 - 모든 행이 컨텍스트를 통과
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
10,000행이 컨텍스트에 반환되어 수동 필터링

// 코드 실행 사용 - 실행 환경에서 필터링
const allRows = await gdrive.getSheet({ sheetId: 'abc123' });
const pendingOrders = allRows.filter(row =>
  row["Status"] === 'pending'
);
console.log(`${pendingOrders.length}개의 대기 중인 주문 발견`);
console.log(pendingOrders.slice(0, 5)); // 확인용으로 처음 5개만 로깅

에이전트는 10,000행이 아닌 5행만 보면 됩니다. 집계, 여러 데이터 소스 간 조인, 특정 필드 추출 등에도 같은 패턴을 적용할 수 있습니다.

3. 강력하고 효율적인 제어 흐름

반복문, 조건문, 오류 처리를 익숙한 코드 패턴으로 처리할 수 있습니다:

// Slack에서 배포 완료 알림 대기
let found = false;
while (!found) {
  const messages = await slack.getChannelHistory({ 
    channel: 'C123456' 
  });
  found = messages.some(m => 
    m.text.includes('deployment complete')
  );
  if (!found) await new Promise(r => setTimeout(r, 5000));
}
console.log('배포 알림을 받았습니다');

매번 모델을 거쳐 도구를 호출하는 것보다 훨씬 효율적이고 빠릅니다. 조건문 트리를 작성하여 실행하면 모델이 if 문을 평가할 때까지 기다릴 필요가 없어 “첫 토큰까지의 지연 시간”도 절약됩니다.

4. 프라이버시 보호 작업

코드 실행을 사용하면 중간 결과가 기본적으로 실행 환경에 머물러 있습니다. 명시적으로 로깅하거나 반환한 것만 모델이 봅니다.

더 민감한 작업의 경우, 에이전트가 자동으로 민감한 데이터를 토큰화할 수 있습니다:

const sheet = await gdrive.getSheet({ sheetId: 'abc123' });
for (const row of sheet.rows) {
  await salesforce.updateRecord({
    objectType: 'Lead',
    recordId: row.salesforceId,
    data: {
      Email: row.email,
      Phone: row.phone,
      Name: row.name
    }
  });
}
console.log(`${sheet.rows.length}개의 리드를 업데이트했습니다`);

MCP 클라이언트가 데이터를 가로채어 모델에 도달하기 전에 PII(Personally Identifiable Information, 개인 식별 정보)를 토큰화합니다:

// 에이전트가 sheet.rows를 로깅했을 때 보는 내용:
[
  { salesforceId: '00Q...', email: '[EMAIL_1]', phone: '[PHONE_1]', name: '[NAME_1]' },
  { salesforceId: '00Q...', email: '[EMAIL_2]', phone: '[PHONE_2]', name: '[NAME_2]' },
  ...
]

실제 이메일, 전화번호, 이름은 Google Sheets에서 Salesforce로 흐르지만 모델을 통과하지 않습니다. 결정론적 보안 규칙을 정의하여 데이터 흐름을 제어할 수 있습니다.

5. 상태 지속성과 스킬

코드 실행과 파일시스템 접근을 통해 에이전트는 작업 간 상태를 유지할 수 있습니다:

const leads = await salesforce.query({
  query: 'SELECT Id, Email FROM Lead LIMIT 1000'
});
const csvData = leads.map(l => `${l.Id},${l.Email}`).join('\n');
await fs.writeFile('./workspace/leads.csv', csvData);

// 나중에 실행이 중단된 곳에서 재개
const saved = await fs.readFile('./workspace/leads.csv', 'utf-8');

에이전트는 작동하는 코드를 재사용 가능한 함수로 저장할 수도 있습니다:

// ./skills/save-sheet-as-csv.ts
import * as gdrive from './servers/google-drive';

export async function saveSheetAsCsv(sheetId: string) {
  const data = await gdrive.getSheet({ sheetId });
  const csv = data.map(row => row.join(',')).join('\n');
  await fs.writeFile(`./workspace/sheet-${sheetId}.csv`, csv);
  return `./workspace/sheet-${sheetId}.csv`;
}

// 나중에 어떤 에이전트 실행에서든 사용
import { saveSheetAsCsv } from './skills/save-sheet-as-csv';
const csvPath = await saveSheetAsCsv('abc123');

시간이 지남에 따라 에이전트는 자체 고수준 기능 도구 상자를 구축하여 진화하는 스캐폴딩을 만들어갑니다.

LLM은 코드 작성을 더 잘합니다

Cloudflare는 이를 명쾌하게 설명합니다: LLM은 실전 코드를 수백만 개의 오픈소스 프로젝트에서 학습했지만, 도구 호출은 제한된 합성 학습 데이터로만 훈련받았습니다.

도구 호출을 위해 특별히 훈련된 LLM에게 도구 호출을 시키는 것은, 셰익스피어에게 한 달간 중국어를 가르치고 중국어로 희곡을 쓰라고 하는 것과 같습니다. 최고의 작품이 나올 수 없죠.

도구가 너무 많거나 복잡하면 LLM이 올바른 도구를 선택하거나 올바르게 사용하는 데 어려움을 겪을 수 있습니다. 반면 LLM은 개발자에게 노출되는 전체 복잡한 API에 대해 코드를 작성하는 데는 큰 어려움이 없습니다.

실제 구현 사례: Cloudflare의 Code Mode

Cloudflare는 Agents SDK에 “Code Mode”를 제공했습니다. 아래는 그 SDK에 대한 설명입니다.:

import { codemode } from "agents/codemode/ai";

const {system, tools} = codemode({
  system: "You are a helpful assistant",
  tools: {
    // 도구 정의
  },
  // ...config
})

const stream = streamText({
  model: openai("gpt-5"),
  system,
  tools,
  messages: [
    { role: "user", content: "두 숫자를 더하는 함수를 작성해줘" }
  ]
})

이 변경으로 앱은 정의한 도구를 호출하는 코드를 생성하고 실행하기 시작합니다. MCP 서버도 포함됩니다.

<출처: https://blog.cloudflare.com/code-mode/>

V8 Isolate를 활용한 안전한 샌드박스

Cloudflare는 컨테이너 대신 V8 isolate를 사용하여 경량 샌드박스를 구현했습니다:

V8 Isolate sandbox?

V8 Isolate 샌드박스는 웹 브라우저에서 실행되는 악성 코드로부터 시스템을 보호하기 위해 V8 엔진의 메모리 영역을 격리하는 기술입니다. 이 기술은 V8의 메모리 손상으로 인한 공격이 프로세스의 다른 부분으로 확산되는 것을 막는 역할을 합니다.

  • Isolate는 몇 밀리초 안에 시작되며 몇 메가바이트의 메모리만 사용
  • 에이전트가 생성하는 모든 코드 스니펫마다 새로운 isolate 생성 가능
  • 재사용, 미리 준비(prewarming) 등을 걱정할 필요 없음
  • 마치 eval()을 직접 사용하는 것처럼 빠르지만 보안이 보장됨

샌드박스 코드는 인터넷에서 완전히 격리됩니다. 외부 세계에 대한 유일한 접근은 연결된 MCP 서버를 나타내는 TypeScript API를 통해서만 가능합니다.

고려사항

코드 실행은 자체적인 복잡성을 가져옵니다:

  • 안전한 샌드박스 환경 필요
  • 리소스 제한 및 모니터링 필요
  • 운영 오버헤드와 보안 고려사항 추가

다만, 토큰 비용 절감과 지연 시간 개선이 구현 비용보다 큰지 평가해야 합니다.

결론

MCP는 AI 에이전트를 외부 도구와 데이터에 연결하는 기반 프로토콜을 제공합니다. 하지만 너무 많은 서버가 연결되면 도구 정의와 결과가 과도한 토큰을 소비하여 에이전트 효율성이 떨어집니다.

여기서 제시된 문제들은 새로워 보이지만—컨텍스트 관리, 도구 조합, 상태 지속성—소프트웨어 엔지니어링에는 이미 알려진 해결책들이 있습니다. 코드 실행은 이러한 확립된 패턴을 에이전트에 적용하여 익숙한 프로그래밍 구조를 사용해 MCP 서버와 더욱 효율적으로 상호작용하게 합니다.

재정리하자면, MCP 도구간에 발생된 데이터들을 LLM 모델을 통과시키지 않고, 도구 사용에 필요한 API에 변수로 넘겨서 모델에게 직접 담겨오가는 과정을 줄이는 것이 핵심입니다. 아마도 이와 관련되는서는 다양한 구현 케이스와 방법이 있을 것으로 생각됩니다.

이 접근법을 구현한다면, MCP 커뮤니티와 결과를 공유하는 것을 권장합니다. 함께 AI 에이전트의 미래를 만들어가는 것입니다.


참고 자료


게시됨

카테고리

작성자

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다