Skip to main content
Glama

Letta MCP Server

by oculairmedia
attach-memory-block.test.js21.2 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { handleAttachMemoryBlock, attachMemoryBlockToolDefinition, } from '../../../tools/memory/attach-memory-block.js'; import { createMockLettaServer } from '../../utils/mock-server.js'; import { expectValidToolResponse } from '../../utils/test-helpers.js'; describe('Attach Memory Block', () => { let mockServer; beforeEach(() => { mockServer = createMockLettaServer(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('Tool Definition', () => { it('should have correct tool definition', () => { expect(attachMemoryBlockToolDefinition.name).toBe('attach_memory_block'); expect(attachMemoryBlockToolDefinition.description).toContain( 'Attach a memory block to an agent', ); expect(attachMemoryBlockToolDefinition.inputSchema.required).toEqual([ 'block_id', 'agent_id', ]); expect(attachMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty( 'block_id', ); expect(attachMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty( 'agent_id', ); expect(attachMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('label'); }); }); describe('Functionality Tests', () => { it('should attach memory block successfully with minimal args', async () => { const blockId = 'block-123'; const agentId = 'agent-456'; const mockBlock = { id: blockId, name: 'Test Memory Block', label: 'persona', value: 'I am a helpful assistant', }; const mockAgent = { id: agentId, name: 'Test Agent', }; // Mock block verification mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); // Mock attachment (patch returns empty) mockServer.api.patch.mockResolvedValueOnce({ data: {} }); // Mock agent info retrieval mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); // Verify block verification call expect(mockServer.api.get).toHaveBeenCalledWith( `/blocks/${blockId}`, expect.objectContaining({ headers: expect.objectContaining({ user_id: agentId, }), }), ); // Verify attachment call expect(mockServer.api.patch).toHaveBeenCalledWith( `/agents/${agentId}/core-memory/blocks/attach/${blockId}`, {}, expect.objectContaining({ headers: expect.objectContaining({ user_id: agentId, }), }), ); // Verify agent info call expect(mockServer.api.get).toHaveBeenCalledWith( `/agents/${agentId}`, expect.objectContaining({ headers: expect.objectContaining({ user_id: agentId, }), }), ); // Verify response const data = expectValidToolResponse(result); expect(data.agent_id).toBe(agentId); expect(data.agent_name).toBe('Test Agent'); expect(data.block_id).toBe(blockId); expect(data.block_name).toBe('Test Memory Block'); expect(data.label).toBe('persona'); // Uses block's label }); it('should attach with custom label override', async () => { const blockId = 'block-custom'; const agentId = 'agent-custom'; const mockBlock = { id: blockId, name: 'Original Label Block', label: 'persona', // Original label value: 'Content', }; const mockAgent = { id: agentId, name: 'Custom Label Agent', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, label: 'human', // Override label }); const data = expectValidToolResponse(result); expect(data.label).toBe('human'); // Should use provided label }); it('should handle block without name', async () => { const blockId = 'nameless-block'; const agentId = 'agent-nameless'; const mockBlock = { id: blockId, // No name field label: 'system', value: 'System content', }; const mockAgent = { id: agentId, name: 'Agent for Nameless', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.block_name).toBe('Unnamed Block'); }); it('should handle agent without name', async () => { const blockId = 'block-noagent'; const agentId = 'nameless-agent'; const mockBlock = { id: blockId, name: 'Block for Nameless Agent', label: 'persona', }; const mockAgent = { id: agentId, // No name field }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.agent_name).toBe('Unknown'); }); it('should use default label when block has no label', async () => { const blockId = 'no-label-block'; const agentId = 'agent-default-label'; const mockBlock = { id: blockId, name: 'No Label Block', // No label field value: 'Content without label', }; const mockAgent = { id: agentId, name: 'Default Label Agent', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.label).toBe('custom'); // Default when no label }); it('should attach different types of memory blocks', async () => { const labels = ['persona', 'human', 'system', 'custom_type']; for (const label of labels) { const blockId = `block-${label}`; const agentId = `agent-${label}`; const mockBlock = { id: blockId, name: `${label} Block`, label: label, value: `Content for ${label}`, }; const mockAgent = { id: agentId, name: `${label} Agent`, }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.label).toBe(label); } }); it('should include user_id header in all API calls', async () => { const blockId = 'auth-block'; const agentId = 'auth-agent'; mockServer.api.get.mockResolvedValueOnce({ data: { id: blockId } }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: { id: agentId } }); await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); // Verify all calls included user_id header expect(mockServer.api.get).toHaveBeenCalledTimes(2); expect(mockServer.api.patch).toHaveBeenCalledTimes(1); // All calls should have user_id header const allCalls = [...mockServer.api.get.mock.calls, ...mockServer.api.patch.mock.calls]; allCalls.forEach((call) => { const headers = call[1]?.headers || call[2]?.headers; expect(headers).toHaveProperty('user_id', agentId); }); }); it('should handle attaching template blocks', async () => { const blockId = 'template-block'; const agentId = 'template-agent'; const mockBlock = { id: blockId, name: 'Template Block', label: 'system', value: 'Template with {{variable}}', is_template: true, template_variables: ['variable'], }; const mockAgent = { id: agentId, name: 'Template Using Agent', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.block_id).toBe(blockId); expect(data.agent_id).toBe(agentId); }); }); describe('Error Handling', () => { it('should throw error for missing block_id', async () => { await expect( handleAttachMemoryBlock(mockServer, { agent_id: 'agent-123', }), ).rejects.toThrow('Missing required argument: block_id'); }); it('should throw error for missing agent_id', async () => { await expect( handleAttachMemoryBlock(mockServer, { block_id: 'block-123', }), ).rejects.toThrow('Missing required argument: agent_id'); }); it('should throw error for missing both required args', async () => { await expect(handleAttachMemoryBlock(mockServer, {})).rejects.toThrow( 'Missing required argument: block_id', ); }); it('should handle block not found error', async () => { const error = new Error('Block not found'); error.response = { status: 404, data: { error: 'Memory block not found' }, }; mockServer.api.get.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: 'non-existent-block', agent_id: 'agent-123', }), ).rejects.toThrow('Block not found'); }); it('should handle agent not found error during attachment', async () => { const blockId = 'block-123'; const agentId = 'non-existent-agent'; // Block exists mockServer.api.get.mockResolvedValueOnce({ data: { id: blockId, name: 'Test Block' }, }); // Attachment fails - agent not found const error = new Error('Agent not found'); error.response = { status: 404, data: { error: 'Agent not found' }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }), ).rejects.toThrow('Agent not found'); }); it('should handle attachment conflict error', async () => { const blockId = 'conflict-block'; const agentId = 'conflict-agent'; // Block exists mockServer.api.get.mockResolvedValueOnce({ data: { id: blockId, name: 'Conflict Block' }, }); // Attachment fails - already attached const error = new Error('Conflict'); error.response = { status: 409, data: { error: 'Memory block already attached to agent' }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }), ).rejects.toThrow('Conflict'); }); it('should handle forbidden access error', async () => { const blockId = 'forbidden-block'; const agentId = 'forbidden-agent'; const error = new Error('Forbidden'); error.response = { status: 403, data: { error: 'Not authorized to access this block' }, }; mockServer.api.get.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }), ).rejects.toThrow('Forbidden'); }); it('should handle error when retrieving agent info after attachment', async () => { const blockId = 'block-123'; const agentId = 'agent-error'; // Block verification succeeds mockServer.api.get.mockResolvedValueOnce({ data: { id: blockId, name: 'Test Block' }, }); // Attachment succeeds mockServer.api.patch.mockResolvedValueOnce({ data: {} }); // Agent info retrieval fails const error = new Error('Failed to get agent'); error.response = { status: 500 }; mockServer.api.get.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }), ).rejects.toThrow('Failed to get agent'); }); it('should handle network errors', async () => { const error = new Error('Network error: Connection refused'); mockServer.api.get.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: 'block-123', agent_id: 'agent-123', }), ).rejects.toThrow('Network error'); }); it('should handle server errors', async () => { const error = new Error('Internal server error'); error.response = { status: 500, data: { error: 'Database error' }, }; mockServer.api.get.mockRejectedValueOnce(error); await expect( handleAttachMemoryBlock(mockServer, { block_id: 'block-123', agent_id: 'agent-123', }), ).rejects.toThrow('Internal server error'); }); }); describe('Edge Cases', () => { it('should handle empty string IDs gracefully', async () => { await expect( handleAttachMemoryBlock(mockServer, { block_id: '', agent_id: 'agent-123', }), ).rejects.toThrow('Missing required argument: block_id'); await expect( handleAttachMemoryBlock(mockServer, { block_id: 'block-123', agent_id: '', }), ).rejects.toThrow('Missing required argument: agent_id'); }); it('should handle UUID format IDs', async () => { const blockId = '550e8400-e29b-41d4-a716-446655440000'; const agentId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; const mockBlock = { id: blockId, name: 'UUID Block', label: 'system', }; const mockAgent = { id: agentId, name: 'UUID Agent', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); expect(mockServer.api.patch).toHaveBeenCalledWith( `/agents/${agentId}/core-memory/blocks/attach/${blockId}`, {}, expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.block_id).toBe(blockId); expect(data.agent_id).toBe(agentId); }); it('should handle special characters in names', async () => { const blockId = 'special-block'; const agentId = 'special-agent'; const mockBlock = { id: blockId, name: 'Block with "quotes" & symbols <tag>', label: 'persona', }; const mockAgent = { id: agentId, name: 'Agent: Special édition 🚀', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.block_name).toBe('Block with "quotes" & symbols <tag>'); expect(data.agent_name).toBe('Agent: Special édition 🚀'); }); it('should handle attaching shared blocks', async () => { const blockId = 'shared-block'; const agentId = 'new-agent'; const mockBlock = { id: blockId, name: 'Shared System Config', label: 'system', value: 'Shared configuration', shared: true, agents: [ { id: 'agent-1', name: 'Agent One' }, { id: 'agent-2', name: 'Agent Two' }, ], }; const mockAgent = { id: agentId, name: 'New Agent', }; mockServer.api.get.mockResolvedValueOnce({ data: mockBlock }); mockServer.api.patch.mockResolvedValueOnce({ data: {} }); mockServer.api.get.mockResolvedValueOnce({ data: mockAgent }); const result = await handleAttachMemoryBlock(mockServer, { block_id: blockId, agent_id: agentId, }); const data = expectValidToolResponse(result); expect(data.block_id).toBe(blockId); expect(data.agent_id).toBe(agentId); }); }); });

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