Ejecución de Código con MCP: Construyendo Agentes Más Eficientes
Introducción
Una de las técnicas más prometedoras para mejorar la eficiencia de los agentes de IA es la ejecución de código. Este enfoque permite que los agentes interactúen con servidores MCP de manera más inteligente, reduciendo drásticamente el consumo de tokens.
En este artículo exploramos cómo implementar esta técnica basándonos en el artículo de ingeniería de Anthropic sobre el tema.
El problema: Sobrecarga de contexto
Los clientes MCP actuales enfrentan dos problemas fundamentales de eficiencia:
1. Sobrecarga de definiciones de herramientas
Los clientes MCP típicamente cargan todas las definiciones de herramientas en el contexto simultáneamente. Esto puede consumir cientos de miles de tokens antes de procesar cualquier solicitud real.
// Problema: Cargar todas las herramientas a la vez
const allTools = await mcpClient.listTools();
// Puede ser 100+ herramientas con schemas complejos
// = Miles de tokens consumidos innecesariamente
2. Resultados intermedios redundantes
Cuando un modelo debe pasar datos entre llamadas de herramientas, información como transcripciones completas fluye múltiples veces a través del contexto:
// Flujo ineficiente
const transcript = await getTranscript(); // 50,000 tokens
const summary = await summarize(transcript); // +50,000 tokens
const action = await createAction(summary); // +tokens
// Total: 100,000+ tokens desperdiciados
La solución: Servidores MCP como APIs ejecutables
La clave está en presentar los servidores MCP como APIs de código ejecutable en lugar de llamadas de herramientas directas. El agente escribe código que:
- Carga solo las definiciones necesarias
- Filtra y procesa datos localmente
- Devuelve resultados resumidos al modelo
Beneficios principales
Divulgación progresiva
Los modelos descubren herramientas navegando sistemas de archivos según necesidad:
// Estructura de descubrimiento
servers/
├── google-drive/
│ ├── getDocument.ts
│ └── listFiles.ts
└── salesforce/
├── updateRecord.ts
└── queryRecords.ts
// El agente solo carga lo que necesita
import { getDocument } from "./servers/google-drive/getDocument";
Eficiencia contextual
Los agentes filtran y transforman resultados en código antes de devolverlos:
// En lugar de devolver toda la transcripción al modelo
const transcript = await gdrive.getDocument({
documentId: 'abc123'
});
// Procesar localmente y devolver solo lo relevante
const actionItems = transcript
.split('\n')
.filter(line => line.includes('ACTION:'))
.map(line => line.replace('ACTION:', '').trim());
return actionItems; // Solo los items de acción
Privacidad mejorada
Los datos sensibles permanecen en el entorno de ejecución y nunca atraviesan el modelo:
// Los datos sensibles se procesan localmente
const customerData = await crm.getCustomer({ id: '123' });
const anonymized = {
tier: customerData.tier,
totalPurchases: customerData.purchases.length,
// Sin exponer: nombre, email, tarjeta, etc.
};
return anonymized;
Persistencia de estado
Los agentes mantienen estado entre operaciones y pueden guardar “skills” reutilizables:
// Skill reutilizable guardada
async function updateSalesforceFromGDrive(docId: string, recordId: string) {
const transcript = (await gdrive.getDocument({
documentId: docId
})).content;
await salesforce.updateRecord({
objectType: 'SalesMeeting',
recordId: recordId,
data: { Notes: transcript }
});
}
Implementación práctica
Estructura de proyecto
mcp-code-executor/
├── servers/
│ ├── google-drive/
│ │ └── getDocument.ts
│ ├── salesforce/
│ │ └── updateRecord.ts
│ └── index.ts # Registro de servidores
├── skills/
│ └── saved-skills.ts # Skills reutilizables
├── executor/
│ └── sandbox.ts # Entorno de ejecución
└── agent.ts # Lógica del agente
Ejemplo de servidor wrapeado
// servers/google-drive/getDocument.ts
export interface GetDocumentArgs {
documentId: string;
}
export interface GetDocumentResult {
content: string;
metadata: {
title: string;
lastModified: string;
};
}
export async function getDocument(
args: GetDocumentArgs
): Promise<GetDocumentResult> {
// Implementación real conectando a Google Drive API
const doc = await googleDriveAPI.files.get({
fileId: args.documentId,
fields: 'name,modifiedTime'
});
const content = await googleDriveAPI.files.export({
fileId: args.documentId,
mimeType: 'text/plain'
});
return {
content: content.data,
metadata: {
title: doc.data.name,
lastModified: doc.data.modifiedTime
}
};
}
Ejecutor con sandbox
// executor/sandbox.ts
import { VM } from 'vm2';
export async function executeCode(code: string, context: object) {
const vm = new VM({
timeout: 30000,
sandbox: {
...context,
console: {
log: (...args) => results.push({ type: 'log', data: args }),
error: (...args) => results.push({ type: 'error', data: args })
}
}
});
const results: any[] = [];
try {
const output = await vm.run(`
(async () => {
${code}
})()
`);
return { success: true, output, logs: results };
} catch (error) {
return { success: false, error: error.message, logs: results };
}
}
Flujo del agente
// agent.ts
async function processRequest(userRequest: string) {
// 1. El agente analiza la solicitud
const plan = await llm.analyze(userRequest);
// 2. Genera código para resolver la tarea
const code = await llm.generateCode(`
// Contexto disponible:
// - gdrive: Google Drive API
// - salesforce: Salesforce API
// Tarea: ${plan.description}
${plan.suggestedApproach}
`);
// 3. Ejecuta el código en sandbox
const result = await executeCode(code, {
gdrive,
salesforce,
// Solo las herramientas necesarias
});
// 4. Devuelve resultado resumido
return result.output;
}
Resultados de eficiencia
Los resultados son impresionantes:
| Métrica | Enfoque tradicional | Con ejecución de código |
|---|---|---|
| Tokens por tarea | ~100,000 | ~1,300 |
| Reducción | - | 98.7% |
| Latencia | Alta | Baja |
| Privacidad | Baja | Alta |
Ejemplo comparativo
Tarea: Actualizar Salesforce con notas de una reunión de Google Drive
Enfoque tradicional:
1. Cargar todas las herramientas (~50,000 tokens)
2. Llamar getDocument (~50,000 tokens de transcripción)
3. Pasar transcripción al modelo
4. Llamar updateRecord
Total: ~100,000+ tokens
Con ejecución de código:
const transcript = (await gdrive.getDocument({
documentId: 'abc123'
})).content;
await salesforce.updateRecord({
objectType: 'SalesMeeting',
recordId: '00Q5f000001abcXYZ',
data: { Notes: transcript }
});
Total: ~1,300 tokens (solo el código)
Consideraciones de seguridad
La ejecución de código introduce complejidad operativa. Es crítico implementar:
1. Sandboxing adecuado
const vm = new VM({
timeout: 30000, // Límite de tiempo
sandbox: {}, // Contexto aislado
eval: false, // Sin eval dinámico
wasm: false // Sin WebAssembly
});
2. Límites de recursos
const resourceLimits = {
maxMemory: 128 * 1024 * 1024, // 128MB
maxCPU: 10000, // 10 segundos
maxNetworkRequests: 100
};
3. Monitoreo y auditoría
// Logging de todas las ejecuciones
await auditLog.record({
timestamp: new Date(),
code: sanitize(code),
result: result.success ? 'success' : 'failure',
resourceUsage: {
memory: process.memoryUsage(),
duration: endTime - startTime
}
});
4. Permisos granulares
const permissions = {
'gdrive.read': true,
'gdrive.write': false,
'salesforce.read': true,
'salesforce.write': true,
'filesystem': false,
'network.external': false
};
Casos de uso ideales
1. Procesamiento de documentos largos
// Extraer solo información relevante de documentos extensos
const doc = await gdrive.getDocument({ id });
const relevantSections = doc.content
.split('##')
.filter(section => section.includes(keyword));
return relevantSections;
2. Agregación de múltiples fuentes
// Combinar datos de múltiples APIs sin pasar todo al modelo
const [sales, inventory, orders] = await Promise.all([
salesforce.query('SELECT * FROM Sales'),
erp.getInventory(),
orders.getPending()
]);
return {
lowStockItems: inventory.filter(i => i.stock < 10),
pendingOrdersValue: orders.reduce((sum, o) => sum + o.value, 0),
topProducts: sales.slice(0, 5)
};
3. Transformación de datos complejos
// Transformaciones que serían costosas en el modelo
const rawData = await api.getData();
const transformed = rawData
.map(normalizeFields)
.filter(isValid)
.reduce(aggregate, {});
return transformed;
Mejores prácticas
1. Cargar herramientas bajo demanda
// En lugar de cargar todo
const neededTools = plan.requiredTools;
const tools = await loadTools(neededTools);
2. Procesar datos localmente
// Filtrar antes de devolver al modelo
const filtered = data.filter(relevantCriteria);
const summarized = summarize(filtered);
return summarized;
3. Cachear resultados
const cache = new Map();
async function getCached(key, fetcher) {
if (!cache.has(key)) {
cache.set(key, await fetcher());
}
return cache.get(key);
}
4. Manejar errores graciosamente
try {
const result = await executeCode(code, context);
return result;
} catch (error) {
// Devolver error útil sin exponer detalles internos
return {
success: false,
error: 'Error procesando solicitud',
suggestion: 'Intenta reformular la tarea'
};
}
Conclusión
La ejecución de código con MCP representa un cambio de paradigma en cómo construimos agentes de IA:
- Eficiencia radical: Reducción de hasta 98.7% en consumo de tokens
- Mejor privacidad: Datos sensibles procesados localmente
- Mayor flexibilidad: Agentes que escriben código adaptado a cada tarea
- Escalabilidad: Menos carga en el modelo = más tareas procesables
Esta técnica es especialmente valiosa para:
- Procesamiento de documentos largos
- Integración con múltiples APIs
- Tareas que requieren transformación de datos
- Escenarios con datos sensibles
La complejidad adicional de implementar un entorno de ejecución seguro se compensa ampliamente con las mejoras en eficiencia y capacidad.
Post anterior: MCP Apps: Interfaces Interactivas
Recursos: