Skip to main content
Glama

Letta MCP Server

by oculairmedia
export-agent.test.js11.7 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; // Create mock instances that will be used in tests const mockFormDataInstance = { append: vi.fn(), getHeaders: vi.fn().mockReturnValue({ 'content-type': 'multipart/form-data' }), }; // Mock dependencies vi.mock('axios'); vi.mock('form-data', () => ({ default: vi.fn(() => mockFormDataInstance), })); import { handleExportAgent, exportAgentDefinition } from '../../tools/agents/export-agent.js'; import { createMockLettaServer } from '../utils/mock-server.js'; import { fixtures } from '../utils/test-fixtures.js'; import { expectValidToolResponse } from '../utils/test-helpers.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); describe('Export Agent', () => { let mockServer; let originalEnv; beforeEach(() => { mockServer = createMockLettaServer(); originalEnv = { XBACKBONE_URL: process.env.XBACKBONE_URL, XBACKBONE_TOKEN: process.env.XBACKBONE_TOKEN, }; // Clear mocks vi.clearAllMocks(); mockFormDataInstance.append.mockClear(); mockFormDataInstance.getHeaders.mockReturnValue({ 'content-type': 'multipart/form-data' }); axios.post.mockClear(); // Mock fs methods vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); vi.spyOn(fs, 'createReadStream').mockReturnValue({ pipe: vi.fn(), on: vi.fn(), }); }); afterEach(() => { process.env.XBACKBONE_URL = originalEnv.XBACKBONE_URL; process.env.XBACKBONE_TOKEN = originalEnv.XBACKBONE_TOKEN; vi.restoreAllMocks(); }); describe('Security Tests', () => { it('should not have hardcoded credentials in the code', async () => { const filePath = path.join(__dirname, '../../tools/agents/export-agent.js'); const content = fs.readFileSync(filePath, 'utf8'); expect(content).not.toContain('token_2ec2bee6249c1c7a9b363f7925768127'); expect(content).not.toContain('"https://100.80.70.44"'); }); it('should default upload_to_xbackbone to false', () => { const schema = exportAgentDefinition.inputSchema; const uploadProp = schema.properties.upload_to_xbackbone; expect(uploadProp.default).toBe(false); }); }); describe('Functionality Tests', () => { it('should export agent successfully', async () => { const agentData = fixtures.agent.basic; mockServer.api.get.mockResolvedValue({ data: agentData }); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', output_path: 'test-export.json', }); const data = expectValidToolResponse(result); expect(data.agent_id).toBe('agent-123'); expect(data.file_path).toContain('test-export.json'); expect(fs.writeFileSync).toHaveBeenCalledWith( expect.stringContaining('test-export.json'), JSON.stringify(agentData, null, 2), ); }); it('should return base64 data when requested', async () => { const agentData = fixtures.agent.basic; mockServer.api.get.mockResolvedValue({ data: agentData }); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', return_base64: true, }); const data = expectValidToolResponse(result); expect(data.base64_data).toBeDefined(); const decoded = Buffer.from(data.base64_data, 'base64').toString(); expect(JSON.parse(decoded)).toEqual(agentData); }); it('should not upload to XBackbone without credentials', async () => { delete process.env.XBACKBONE_URL; delete process.env.XBACKBONE_TOKEN; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); const result = await handleExportAgent(mockServer, { agent_id: 'test-agent', upload_to_xbackbone: true, }); const data = expectValidToolResponse(result); expect(data.xbackbone_url).toBeUndefined(); // The logger.warn is called directly in the tool, not through mockServer }); it('should handle agent not found error', async () => { const error = new Error('Not found'); error.response = { status: 404 }; mockServer.api.get.mockRejectedValue(error); await expect( handleExportAgent(mockServer, { agent_id: 'nonexistent' }), ).rejects.toThrow('Agent not found'); }); it('should handle file write errors', async () => { mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); fs.writeFileSync.mockImplementation(() => { throw new Error('Permission denied'); }); await expect(handleExportAgent(mockServer, { agent_id: 'agent-123' })).rejects.toThrow( /Failed to save agent export/, ); }); it('should upload to XBackbone successfully', async () => { process.env.XBACKBONE_URL = 'https://xbackbone.test'; process.env.XBACKBONE_TOKEN = 'test-token'; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); axios.post.mockResolvedValue({ status: 200, data: { url: 'https://xbackbone.test/file/abc123' }, }); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', upload_to_xbackbone: true, }); const data = expectValidToolResponse(result); expect(data.xbackbone_url).toBe('https://xbackbone.test/file/abc123'); expect(axios.post).toHaveBeenCalledWith( 'https://xbackbone.test/upload', mockFormDataInstance, expect.any(Object), ); expect(mockFormDataInstance.append).toHaveBeenCalledWith('token', 'test-token'); }); it('should handle XBackbone upload errors', async () => { process.env.XBACKBONE_URL = 'https://xbackbone.test'; process.env.XBACKBONE_TOKEN = 'test-token'; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); axios.post.mockRejectedValue(new Error('Network error')); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', upload_to_xbackbone: true, }); const data = expectValidToolResponse(result); expect(data.xbackbone_url).toBeUndefined(); expect(data.agent_id).toBe('agent-123'); expect(data.file_path).toBeDefined(); }); it('should handle XBackbone non-200 status', async () => { process.env.XBACKBONE_URL = 'https://xbackbone.test'; process.env.XBACKBONE_TOKEN = 'test-token'; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); axios.post.mockResolvedValue({ status: 400, data: { error: 'Bad request' }, }); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', upload_to_xbackbone: true, }); const data = expectValidToolResponse(result); expect(data.xbackbone_url).toBeUndefined(); }); it('should use provided XBackbone credentials over env vars', async () => { process.env.XBACKBONE_URL = 'https://env.xbackbone.test'; process.env.XBACKBONE_TOKEN = 'env-token'; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); axios.post.mockResolvedValue({ status: 200, data: { url: 'https://custom.xbackbone.test/file/xyz789' }, }); await handleExportAgent(mockServer, { agent_id: 'agent-123', upload_to_xbackbone: true, xbackbone_url: 'https://custom.xbackbone.test', xbackbone_token: 'custom-token', }); expect(axios.post).toHaveBeenCalledWith( 'https://custom.xbackbone.test/upload', mockFormDataInstance, expect.any(Object), ); expect(mockFormDataInstance.append).toHaveBeenCalledWith('token', 'custom-token'); }); it('should handle empty agent data', async () => { mockServer.api.get.mockResolvedValue({ data: null }); await expect(handleExportAgent(mockServer, { agent_id: 'agent-123' })).rejects.toThrow( 'Received empty data from agent export endpoint', ); }); it('should handle API errors', async () => { const error = new Error('API Error'); error.response = { status: 500, data: { detail: 'Internal server error' } }; mockServer.api.get.mockRejectedValue(error); await expect(handleExportAgent(mockServer, { agent_id: 'agent-123' })).rejects.toThrow( /Failed to export agent/, ); }); it('should handle missing agent_id', async () => { await expect(handleExportAgent(mockServer, {})).rejects.toThrow(); }); it('should use default output path when not provided', async () => { mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); const result = await handleExportAgent(mockServer, { agent_id: 'test-agent-456', }); const data = expectValidToolResponse(result); expect(data.file_path).toContain('agent_test-agent-456.json'); }); it('should handle XBackbone response without URL', async () => { process.env.XBACKBONE_URL = 'https://xbackbone.test'; process.env.XBACKBONE_TOKEN = 'test-token'; mockServer.api.get.mockResolvedValue({ data: fixtures.agent.basic }); axios.post.mockResolvedValue({ status: 200, data: { message: 'Success but no URL' }, // No url field }); const result = await handleExportAgent(mockServer, { agent_id: 'agent-123', upload_to_xbackbone: true, }); const data = expectValidToolResponse(result); expect(data.xbackbone_url).toBeUndefined(); }); }); describe('Tool Definition', () => { it('should have correct tool definition', () => { expect(exportAgentDefinition.name).toBe('export_agent'); expect(exportAgentDefinition.description).toContain('Export an agent'); expect(exportAgentDefinition.inputSchema.required).toEqual(['agent_id']); expect(exportAgentDefinition.inputSchema.properties).toHaveProperty('agent_id'); expect(exportAgentDefinition.inputSchema.properties).toHaveProperty('output_path'); expect(exportAgentDefinition.inputSchema.properties).toHaveProperty('return_base64'); expect(exportAgentDefinition.inputSchema.properties).toHaveProperty( 'upload_to_xbackbone', ); }); }); });

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