Model Context Protocol 기반의 코드 실행 방식을 활용하면 에이전트가 더 많은 도구를 다루면서도 토큰 사용량은 오히려 줄일 수 있으며, 컨텍스트 오버헤드를 최대 98.7%까지 절감할 수 있습니다.
모델 컨텍스트 프로토콜(Model Context Protocol, MCP)은 AI 에이전트와 외부 시스템을 연결하기 위한 개방형 표준입니다. 기존에는 에이전트를 도구나 데이터에 연결하려면 조합마다 별도의 커스텀 통합을 구축해야 했습니다. 이로 인해 생태계가 파편화되고 중복 작업이 늘어나면서, 진정한 의미의 연결된 시스템을 확장하기가 어려웠습니다. MCP는 범용 프로토콜로 이 문제를 해결합니다. 개발자가 에이전트에 MCP를 한 번만 구현하면, 방대한 통합 생태계 전체에 접근할 수 있습니다.
2024년 11월 MCP 출시 이후 채택 속도는 매우 빨랐습니다. 커뮤니티에서 수천 개의 MCP 서버를 구축했고, 주요 프로그래밍 언어를 모두 지원하는 SDK가 제공되며, 업계 전반에서 MCP를 에이전트와 도구·데이터를 연결하는 사실상의 표준으로 받아들이고 있습니다.
현재 개발자들은 수십 개의 MCP 서버에 걸쳐 수백에서 수천 개의 도구에 접근하는 에이전트를 일상적으로 구축하고 있습니다. 그러나 연결된 도구 수가 늘어날수록, 모든 도구 정의를 미리 로드하고 중간 결과를 컨텍스트 윈도우에 전달하는 과정에서 에이전트 속도가 느려지고 비용이 증가합니다.
이 글에서는 코드 실행을 활용해 에이전트가 MCP 서버와 더 효율적으로 상호작용하는 방법을 살펴봅니다. 더 많은 도구를 처리하면서도 토큰 사용량은 줄이는 접근법입니다.
MCP 사용 규모가 커지면, 에이전트의 비용과 지연 시간을 증가시키는 두 가지 대표적인 패턴이 나타납니다.
대부분의 MCP 클라이언트는 모든 도구 정의를 사전에 컨텍스트에 직접 로드하여, 직접 도구 호출(direct tool-calling) 구문으로 모델에 노출합니다. 이러한 도구 정의는 다음과 같은 형태입니다.
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도구 설명이 컨텍스트 윈도우에서 차지하는 공간이 커질수록 응답 시간과 비용이 함께 증가합니다. 에이전트가 수천 개의 도구에 연결된 경우, 사용자 요청을 읽기도 전에 수십만 토큰을 먼저 처리해야 합니다.
대부분의 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 서버를 직접 도구 호출이 아닌 코드 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 코드 실행을 활용하면, 에이전트가 도구를 필요할 때만 로드하고, 데이터를 모델에 전달하기 전에 필터링하며, 복잡한 로직을 한 번에 처리할 수 있어 컨텍스트를 훨씬 효율적으로 사용할 수 있습니다. 또한 보안과 상태 관리 측면에서도 이점이 있습니다.
모델은 파일 시스템 탐색에 능숙합니다. 도구를 파일 시스템 상의 코드로 제공하면, 모델이 모든 도구 정의를 처음에 한꺼번에 읽는 대신 필요할 때마다 읽어올 수 있습니다.
또 다른 방법으로, 서버에 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 명령을 번갈아 실행하는 것보다 훨씬 효율적입니다.
또한 조건 분기 트리를 코드로 작성해 한 번에 실행하면 "첫 토큰 생성까지의 지연 시간(time to first token)"도 줄일 수 있습니다. 모델이 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 커뮤니티에 경험을 공유해 주시기 바랍니다.
이 글은 Adam Jones와 Conor Kelly가 작성했습니다. 초고에 피드백을 주신 Jeremy Fox, Jerome Swannack, Stuart Ritchie, Molly Vorwerck, Matt Samuels, Maggie Vo에게 감사드립니다.