더 효율적인 AI 에이전트 구축
TMThttps://www.anthropic.com/engineering/code-execution-with-mcp
도구를 직접 호출하면 각 정의와 결과가 컨텍스트를 소모합니다. 에이전트는 대신 코드를 작성해 도구를 호출할 때 더 잘 확장됩니다. MCP로 동작하는 방식은 다음과 같습니다.
Model Context Protocol (MCP)는 AI 에이전트를 외부 시스템에 연결하기 위한 오픈 표준입니다. 에이전트를 도구와 데이터에 연결하는 일은 전통적으로 각 쌍마다 커스텀 통합이 필요해 단편화와 중복 노력이 발생했고, 이는 진정으로 연결된 시스템을 확장하기 어렵게 만들었습니다. MCP는 범용 프로토콜을 제공합니다—개발자는 에이전트에 MCP를 한 번만 구현하면 통합 생태계 전체를 활용할 수 있습니다.
2024년 11월 MCP 출시 이후 채택이 빠르게 진행되었습니다: 커뮤니티는 수천 개의 MCP 서버를 구축했고, 주요 프로그래밍 언어용 SDK가 제공되며, 업계는 도구와 데이터를 에이전트에 연결하는 사실상의 표준으로 MCP를 채택했습니다.
오늘날 개발자들은 수십 개의 MCP 서버에 걸쳐 수백, 수천 개의 도구에 접근하는 에이전트를 일상적으로 구축합니다. 하지만 연결된 도구 수가 증가할수록 모든 도구 정의를 미리 로드하고 중간 결과를 컨텍스트 윈도우를 통해 전달하는 방식은 에이전트를 느리게 하고 비용을 증가시킵니다.
이 블로그에서는 코드 실행이 에이전트가 MCP 서버와 더 효율적으로 상호작용하도록 해, 더 적은 토큰으로 더 많은 도구를 처리하는 방법을 살펴봅니다.
도구로 인한 과도한 토큰 소비는 에이전트 효율을 떨어뜨립니다
MCP 사용이 확장되면서 에이전트 비용과 지연 시간을 증가시킬 수 있는 일반적인 패턴이 두 가지 있습니다:
- 도구 정의가 컨텍스트 윈도우를 과부하시킵니다.
- 중간 도구 결과가 추가 토큰을 소비합니다.
1. 도구 정의가 컨텍스트 윈도우를 과부하시킵니다
대부분의 MCP 클라이언트는 모든 도구 정의를 직접 컨텍스트에 미리 로드하여, 모델에 직접 도구 호출 문법으로 노출합니다. 이러한 도구 정의는 다음과 같을 수 있습니다:
gdrive.getDocument
Description: Retrieves a document from Google Drive
Parameters:
documentId (required, string): The ID of the document to retrieve
fields (optional, string): Specific fields to return
Returns: Document object with title, body content, metadata, permissions, etc.salesforce.updateRecord
Description: Updates a record in Salesforce
Parameters:
objectType (required, string): Type of Salesforce object (Lead, Contact, Account, etc.)
recordId (required, string): The ID of the record to update
data (required, object): Fields to update with their new values
Returns: Updated record object with confirmation도구 설명은 더 많은 컨텍스트 윈도우 공간을 차지하여 응답 시간과 비용을 증가시킵니다. 에이전트가 수천 개의 도구에 연결된 경우, 요청을 읽기 전에 수십만 토큰을 처리해야 할 수 있습니다.
2. 중간 도구 결과가 추가 토큰을 소비합니다
대부분의 MCP 클라이언트는 모델이 MCP 도구를 직접 호출할 수 있도록 합니다. 예를 들어, 에이전트에게 “Google Drive에서 내 미팅 기록을 다운로드해서 Salesforce 리드에 첨부해줘”라고 요청할 수 있습니다.
모델은 다음과 같은 호출을 수행합니다:
TOOL CALL: gdrive.getDocument(documentId: "abc123")
→ returns "Discussed Q4 goals...\n[full transcript text]"
(loaded into model context)
TOOL CALL: salesforce.updateRecord(
objectType: "SalesMeeting",
recordId: "00Q5f000001abcXYZ",
data: { "Notes": "Discussed Q4 goals...\n[full transcript text written out]" }
)
(model needs to write entire transcript into context again)모든 중간 결과는 모델을 통해 전달되어야 합니다. 이 예시에서는 전체 호출 기록이 두 번 흐릅니다. 2시간짜리 영업 미팅의 경우 추가로 50,000 토큰을 처리해야 할 수 있습니다. 더 큰 문서는 컨텍스트 윈도우 한계를 초과해 워크플로우가 중단될 수 있습니다.
큰 문서나 복잡한 데이터 구조를 다룰 때, 모델이 도구 호출 간에 데이터를 복사하는 과정에서 실수를 저지를 가능성이 더 커질 수 있습니다.
MCP 클라이언트가 MCP 서버와 LLM과 어떻게 동작하는지에 대한 이미지. MCP 클라이언트는 도구 정의를 모델의 컨텍스트 윈도우에 로드하고, 각 도구 호출과 결과가 작업 사이마다 모델을 통과하도록 메시지 루프를 오케스트레이션합니다.
MCP와 함께하는 코드 실행은 컨텍스트 효율을 개선합니다
에이전트용 코드 실행 환경이 점점 보편화되면서, 해결책은 MCP 서버를 직접 도구 호출이 아닌 코드 API로 제공하는 것입니다. 그러면 에이전트가 MCP 서버와 상호작용하는 코드를 작성할 수 있습니다. 이 접근은 두 가지 문제를 모두 해결합니다: 에이전트는 필요한 도구만 로드하고, 결과를 모델에 전달하기 전에 실행 환경에서 데이터를 처리할 수 있습니다.
이를 구현하는 방법은 여러 가지가 있습니다. 한 가지 접근은 연결된 MCP 서버에서 제공하는 모든 도구의 파일 트리를 생성하는 것입니다. TypeScript를 사용한 구현은 다음과 같습니다:
servers
├── google-drive
│ ├── getDocument.ts
│ ├── ... (other tools)
│ └── index.ts
├── salesforce
│ ├── updateRecord.ts
│ ├── ... (other tools)
│ └── index.ts
└── ... (other servers)그 다음 각 도구를 파일 하나에 대응시키면, 다음과 같습니다:
// ./servers/google-drive/getDocument.ts
import { callMCPTool } from "../../../client.js";
interface GetDocumentInput {
documentId: string;
}
interface GetDocumentResponse {
content: string;
}
/* Read a document from Google Drive */
export async function getDocument(
input: GetDocumentInput
): Promise<GetDocumentResponse> {
return callMCPTool<GetDocumentResponse>("google_drive__get_document", input);
}위의 Google Drive에서 Salesforce로의 예시는 다음 코드가 됩니다:
// Read transcript from Google Docs and add to Salesforce prospect
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 },
});에이전트는 파일 시스템을 탐색하여 도구를 발견합니다: ./servers/ 디렉터리를 나열해 사용 가능한 서버(예: google-drive, salesforce)를 찾고, 필요한 도구의 개별 파일(예: getDocument.ts, updateRecord.ts)을 읽어 각 도구의 인터페이스를 이해합니다. 이를 통해 에이전트는 현재 작업에 필요한 정의만 로드할 수 있습니다. 이 방식은 토큰 사용량을 150,000 토큰에서 2,000 토큰으로 줄여—시간과 비용을 98.7% 절감합니다.
Cloudflare는 MCP와 함께하는 코드 실행을 “Code Mode”라고 부르며 유사한 결과를 발표했습니다. 핵심 통찰은 동일합니다: LLM은 코드를 작성하는 데 능숙하며, 개발자는 이 강점을 활용해 에이전트가 MCP 서버와 더 효율적으로 상호작용하도록 구축해야 합니다.
MCP와 함께하는 코드 실행의 이점
MCP와 함께하는 코드 실행은 도구를 온디맨드로 로드하고, 결과를 모델에 전달하기 전에 데이터를 필터링하며, 복잡한 로직을 단일 단계로 실행함으로써 에이전트가 컨텍스트를 더 효율적으로 사용하게 합니다. 또한 이 접근에는 보안과 상태 관리 측면의 이점도 있습니다.
점진적 공개(Progressive disclosure)
모델은 파일 시스템을 탐색하는 데 능숙합니다. 도구를 파일 시스템 상의 코드로 제시하면, 모델은 모든 정의를 한 번에 읽지 않고 필요할 때마다 도구 정의를 온디맨드로 읽을 수 있습니다.
또는 서버에 search_tools 도구를 추가해 관련 정의를 찾을 수도 있습니다. 예를 들어, 앞서 사용한 가상의 Salesforce 서버를 사용할 때, 에이전트는 “salesforce”를 검색하고 현재 작업에 필요한 도구만 로드합니다. search_tools 도구에 상세 수준(예: 이름만, 이름+설명, 스키마를 포함한 전체 정의)을 선택할 수 있는 파라미터를 포함하면, 에이전트가 컨텍스트를 절약하면서 도구를 효율적으로 찾는 데 도움이 됩니다.
컨텍스트 효율적인 도구 결과
대규모 데이터셋을 다룰 때, 에이전트는 결과를 모델에 반환하기 전에 코드에서 필터링 및 변환할 수 있습니다. 10,000행짜리 스프레드시트를 가져오는 상황을 고려해 보겠습니다:
// Without code execution - all rows flow through context
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
→ returns 10,000 rows in context to filter manually
// With code execution - filter in the execution environment
const allRows = await gdrive.getSheet({ sheetId: 'abc123' });
const pendingOrders = allRows.filter(row =>
row["Status"] === 'pending'
);
console.log(`Found ${pendingOrders.length} pending orders`);
console.log(pendingOrders.slice(0, 5)); // Only log first 5 for review에이전트는 10,000개 행 대신 5개 행만 확인합니다. 유사한 패턴을 사용해 집계, 여러 데이터 소스 간 조인, 특정 필드만 추출 등을 수행하면서도 컨텍스트 윈도우를 부풀리지 않을 수 있습니다.
더 강력하고 컨텍스트 효율적인 제어 흐름
루프, 조건문, 에러 처리는 개별 도구 호출을 체이닝하는 대신 익숙한 코드 패턴으로 수행할 수 있습니다. 예를 들어, 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("Deployment notification received");이 접근은 MCP 도구 호출과 sleep 커맨드를 에이전트 루프를 통해 번갈아 실행하는 것보다 더 효율적입니다.
또한, 실행될 조건 트리를 코드로 작성할 수 있다는 점은 “첫 토큰까지의 시간” 지연을 줄여줍니다: 모델이 if-문을 평가하기를 기다리는 대신, 코드 실행 환경이 이를 수행하게 할 수 있습니다.
프라이버시를 보존하는 작업
에이전트가 MCP와 함께 코드 실행을 사용할 때, 중간 결과는 기본적으로 실행 환경 내부에 유지됩니다. 이렇게 하면 에이전트는 당신이 명시적으로 로그를 남기거나 반환한 것만 보게 되므로, 모델의 컨텍스트에 들어가기를 원치 않는 데이터가 워크플로우를 통해 흐르더라도 모델 컨텍스트에는 포함되지 않습니다.
더 민감한 작업의 경우, 에이전트 하니스가 자동으로 민감 데이터를 토큰화할 수 있습니다. 예를 들어, 스프레드시트에서 고객 연락처를 Salesforce로 가져와야 한다고 가정해 봅니다. 에이전트는 다음과 같이 작성합니다:
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(`Updated ${sheet.rows.length} leads`);MCP 클라이언트는 데이터가 모델에 도달하기 전에 이를 가로채 PII를 토큰화합니다:
// What the agent would see, if it logged the sheet.rows:
[
{ salesforceId: '00Q...', email: '[EMAIL_1]', phone: '[PHONE_1]', name: '[NAME_1]' },
{ salesforceId: '00Q...', email: '[EMAIL_2]', phone: '[PHONE_2]', name: '[NAME_2]' },
...
]그런 다음 데이터가 다른 MCP 도구 호출에서 공유될 때, MCP 클라이언트의 조회를 통해 비토큰화됩니다. 실제 이메일 주소, 전화번호, 이름은 Google Sheets에서 Salesforce로 흐르지만, 모델을 통과하지는 않습니다. 이는 에이전트가 민감 데이터를 실수로 로그로 남기거나 처리하는 일을 방지합니다. 또한 이를 사용해 데이터가 어디서 어디로 흐를 수 있는지 결정적인 보안 규칙을 정의할 수 있습니다.
상태 지속성과 스킬
파일 시스템 접근이 가능한 코드 실행은 에이전트가 작업 간 상태를 유지할 수 있게 합니다. 에이전트는 중간 결과를 파일에 기록하여, 작업을 재개하고 진행 상황을 추적할 수 있습니다:
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);
// Later execution picks up where it left off
const saved = await fs.readFile("./workspace/leads.csv", "utf-8");에이전트는 자체 코드를 재사용 가능한 함수로 영속화할 수도 있습니다. 에이전트가 특정 작업을 위한 동작하는 코드를 개발하면, 그 구현을 저장해 미래에 재사용할 수 있습니다:
// In ./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`;
}
// Later, in any agent execution:
import { saveSheetAsCsv } from "./skills/save-sheet-as-csv";
const csvPath = await saveSheetAsCsv("abc123");이는 Skills 개념과 밀접하게 연결됩니다. 모델이 전문 작업에서 성능을 개선하기 위해 참조하고 사용할 수 있는 재사용 지침, 스크립트, 리소스 폴더입니다. 이러한 저장된 함수에 SKILL.md 파일을 추가하면, 모델이 참조하고 사용할 수 있는 구조화된 스킬이 생성됩니다. 시간이 지남에 따라, 이는 에이전트가 더 높은 수준의 능력을 갖춘 도구 상자를 구축하게 하며, 가장 효과적으로 작업하는 데 필요한 스캐폴딩을 발전시킵니다.
코드 실행은 자체 복잡성을 도입한다는 점에 유의하세요. 에이전트가 생성한 코드를 실행하려면 적절한 샌드박싱, 리소스 제한, 모니터링을 갖춘 안전한 실행 환경이 필요합니다. 이러한 인프라 요구사항은 직접 도구 호출이 회피하는 운영 오버헤드와 보안 고려사항을 추가합니다. 코드 실행의 이점—토큰 비용 감소, 지연 시간 감소, 개선된 도구 합성—은 이러한 구현 비용과 저울질되어야 합니다.
요약
MCP는 에이전트가 많은 도구와 시스템에 연결할 수 있도록 하는 기초적인 프로토콜을 제공합니다. 그러나 너무 많은 서버가 연결되면, 도구 정의와 결과가 과도한 토큰을 소비해 에이전트 효율을 떨어뜨립니다.
여기서의 많은 문제가 새롭게 느껴지더라도—컨텍스트 관리, 도구 합성, 상태 지속성—소프트웨어 엔지니어링에서 이미 알려진 해법이 있습니다. 코드 실행은 이러한 확립된 패턴을 에이전트에 적용하여, 에이전트가 MCP 서버와 더 효율적으로 상호작용하도록 익숙한 프로그래밍 구성요소를 사용하게 합니다. 이 접근을 구현한다면, MCP 커뮤니티 MCP 커뮤니티에 여러분의 결과를 공유하길 권합니다.