Skip to main content
Glama

Letta MCP Server

by oculairmedia
clone-agent.js6.53 kB
import fs from 'fs/promises'; // Use promises for async file operations import path from 'path'; import os from 'os'; // To get temporary directory import FormData from 'form-data'; // Assuming form-data is available import { createLogger } from '../../core/logger.js'; const logger = createLogger('clone_agent'); /** * Tool handler for cloning an agent */ export async function handleCloneAgent(server, args) { if (!args?.source_agent_id) { server.createErrorResponse('Missing required argument: source_agent_id'); } if (!args?.new_agent_name) { server.createErrorResponse('Missing required argument: new_agent_name'); } const sourceAgentId = args.source_agent_id; const newAgentName = args.new_agent_name; const overrideTools = args.override_existing_tools ?? true; // Default override const projectId = args.project_id; // Optional project ID for the new agent let tempFilePath = ''; try { const headers = server.getApiHeaders(); const encodedSourceAgentId = encodeURIComponent(sourceAgentId); // --- Step 1: Export the source agent --- logger.info(`Exporting source agent ${sourceAgentId}...`); const exportResponse = await server.api.get(`/agents/${encodedSourceAgentId}/export`, { headers, }); const agentConfig = exportResponse.data; if (!agentConfig || typeof agentConfig !== 'object') { throw new Error('Received invalid data from agent export endpoint.'); } logger.info(`Source agent ${sourceAgentId} exported successfully.`); // --- Step 2: Modify the configuration for the new agent --- agentConfig.name = newAgentName; // Set the new name // Optionally clear fields that shouldn't be copied or might cause conflicts // delete agentConfig.id; // ID will be assigned on import // delete agentConfig.created_at; // delete agentConfig.updated_at; // Consider if message history should be copied or cleared // agentConfig.messages = []; // agentConfig.message_ids = []; const agentJsonString = JSON.stringify(agentConfig, null, 2); // --- Step 3: Save modified config to a temporary file --- // Use os.tmpdir() which should work inside Docker if /tmp is writable tempFilePath = path.join(os.tmpdir(), `agent_clone_temp_${Date.now()}.json`); logger.info(`Saving temporary config to ${tempFilePath}...`); await fs.writeFile(tempFilePath, agentJsonString); logger.info('Temporary config saved.'); // --- Step 4: Import the modified configuration --- logger.info(`Importing new agent '${newAgentName}' from ${tempFilePath}...`); const importHeaders = server.getApiHeaders(); delete importHeaders['Content-Type']; // Let FormData set the correct header const form = new FormData(); form.append('file', await fs.readFile(tempFilePath), path.basename(tempFilePath)); // Read file content for FormData const importParams = { append_copy_suffix: false, // We explicitly set the name, don't append suffix override_existing_tools: overrideTools, }; if (projectId) { importParams.project_id = projectId; } const importResponse = await server.api.post('/agents/import', form, { headers: { ...importHeaders, ...form.getHeaders(), }, params: importParams, }); const importedAgentState = importResponse.data; logger.info( `Agent '${newAgentName}' imported successfully with ID: ${importedAgentState.id}`, ); // --- Step 5: Cleanup temporary file --- await fs.unlink(tempFilePath); logger.info(`Cleaned up temporary file ${tempFilePath}.`); return { content: [ { type: 'text', text: JSON.stringify({ new_agent: importedAgentState, }), }, ], }; } catch (error) { logger.error('Error:', error.response?.data || error.message); // Attempt cleanup even on error if (tempFilePath) { try { await fs.unlink(tempFilePath); logger.info(`Cleaned up temporary file ${tempFilePath} after error.`); } catch (cleanupError) { logger.error(`Error cleaning up temporary file ${tempFilePath}:`, cleanupError); } } // Handle specific API errors if (error.response) { if (error.response.status === 404 && error.config.url.includes('/export')) { server.createErrorResponse(`Source agent not found: ${sourceAgentId}`); } if (error.response.status === 422 && error.config.url.includes('/import')) { server.createErrorResponse( `Validation error importing cloned agent: ${JSON.stringify(error.response.data)}`, ); } } server.createErrorResponse(`Failed to clone agent ${sourceAgentId}: ${error.message}`); } } /** * Tool definition for clone_agent */ export const cloneAgentDefinition = { name: 'clone_agent', description: 'Creates a new agent by cloning the configuration of an existing agent. Use list_agents to find source agent ID. Alternative to export_agent + import_agent workflow. Modify the clone with modify_agent afterwards.', inputSchema: { type: 'object', properties: { source_agent_id: { type: 'string', description: 'The ID of the agent to clone.', }, new_agent_name: { type: 'string', description: 'The name for the new cloned agent.', }, override_existing_tools: { type: 'boolean', description: 'Optional: If set to True, existing tools can get their source code overwritten by the tool definitions from the source agent. Defaults to true.', default: true, }, project_id: { type: 'string', description: 'Optional: The project ID to associate the new cloned agent with.', }, }, required: ['source_agent_id', 'new_agent_name'], }, };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oculairmedia/Letta-MCP-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server