Skip to main content
Glama

Letta MCP Server

by oculairmedia
update-memory-block.test.js19.9 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { handleUpdateMemoryBlock, updateMemoryBlockToolDefinition, } from '../../../tools/memory/update-memory-block.js'; import { createMockLettaServer } from '../../utils/mock-server.js'; import { expectValidToolResponse } from '../../utils/test-helpers.js'; describe('Update Memory Block', () => { let mockServer; beforeEach(() => { mockServer = createMockLettaServer(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('Tool Definition', () => { it('should have correct tool definition', () => { expect(updateMemoryBlockToolDefinition.name).toBe('update_memory_block'); expect(updateMemoryBlockToolDefinition.description).toContain( 'Update the contents and metadata', ); expect(updateMemoryBlockToolDefinition.inputSchema.required).toEqual(['block_id']); expect(updateMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty( 'block_id', ); expect(updateMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('value'); expect(updateMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty( 'metadata', ); expect(updateMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty( 'agent_id', ); }); }); describe('Functionality Tests', () => { it('should update memory block value successfully', async () => { const updatedBlock = { id: 'block-123', name: 'Updated Block', label: 'persona', value: 'Updated content for the memory block', limit: 5000, metadata: { version: '1.0' }, updated_at: '2024-01-02T00:00:00Z', }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'block-123', value: 'Updated content for the memory block', }); // Verify API call expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/block-123', { value: 'Updated content for the memory block', }, expect.objectContaining({ headers: expect.any(Object), }), ); // Verify response const data = expectValidToolResponse(result); expect(data.id).toBe('block-123'); expect(data.value).toBe('Updated content for the memory block'); expect(data.updated_at).toBe('2024-01-02T00:00:00Z'); }); it('should update memory block metadata successfully', async () => { const newMetadata = { version: '2.0', tags: ['updated', 'important'], last_modified_by: 'user-123', }; const updatedBlock = { id: 'block-456', name: 'Metadata Update Block', label: 'system', value: 'Original content', metadata: newMetadata, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'block-456', metadata: newMetadata, }); // Verify only metadata was sent expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/block-456', { metadata: newMetadata, }, expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.metadata).toEqual(newMetadata); expect(data.value).toBe('Original content'); // Value unchanged }); it('should update both value and metadata', async () => { const newValue = 'Completely new content'; const newMetadata = { version: '3.0', complete_update: true, }; const updatedBlock = { id: 'block-789', name: 'Complete Update Block', label: 'human', value: newValue, metadata: newMetadata, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'block-789', value: newValue, metadata: newMetadata, }); // Verify both fields were sent expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/block-789', { value: newValue, metadata: newMetadata, }, expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.value).toBe(newValue); expect(data.metadata).toEqual(newMetadata); }); it('should update with agent_id authorization', async () => { const agentId = 'agent-auth-123'; const updatedBlock = { id: 'agent-block', value: 'Agent authorized update', }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'agent-block', value: 'Agent authorized update', agent_id: agentId, }); // Verify user_id header was included expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/agent-block', expect.any(Object), expect.objectContaining({ headers: expect.objectContaining({ user_id: agentId, }), }), ); const data = expectValidToolResponse(result); expect(data.value).toBe('Agent authorized update'); }); it('should handle updating to empty string value', async () => { // Empty string is considered as falsy, so the tool will reject it await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'empty-block', value: '', }), ).rejects.toThrow('Either value or metadata must be provided'); }); it('should handle updating to empty metadata object', async () => { const updatedBlock = { id: 'empty-metadata-block', value: 'Content', metadata: {}, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'empty-metadata-block', metadata: {}, }); expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/empty-metadata-block', { metadata: {}, }, expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.metadata).toEqual({}); }); it('should handle very large value updates', async () => { const largeValue = 'Y'.repeat(10000); // 10k characters const updatedBlock = { id: 'large-update-block', value: largeValue, limit: 10000, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'large-update-block', value: largeValue, }); const data = expectValidToolResponse(result); expect(data.value).toBe(largeValue); expect(data.value.length).toBe(10000); }); it('should preserve undefined fields during update', async () => { const originalBlock = { id: 'preserve-block', name: 'Original Name', label: 'persona', value: 'New value only', metadata: { original: true }, limit: 5000, }; mockServer.api.patch.mockResolvedValueOnce({ data: originalBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'preserve-block', value: 'New value only', // Not updating metadata }); // Verify only value was sent in update expect(mockServer.api.patch).toHaveBeenCalledWith( '/blocks/preserve-block', { value: 'New value only', // metadata should not be included }, expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.name).toBe('Original Name'); expect(data.label).toBe('persona'); expect(data.metadata).toEqual({ original: true }); }); it('should handle complex nested metadata updates', async () => { const complexMetadata = { level1: { level2: { level3: { deep_value: 'nested content', array: [1, 2, { nested: true }], }, }, }, timestamps: { created: '2024-01-01T00:00:00Z', modified: '2024-01-02T00:00:00Z', }, flags: { active: true, reviewed: false, priority: 'high', }, }; const updatedBlock = { id: 'complex-metadata-block', value: 'Content', metadata: complexMetadata, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'complex-metadata-block', metadata: complexMetadata, }); const data = expectValidToolResponse(result); expect(data.metadata).toEqual(complexMetadata); expect(data.metadata.level1.level2.level3.deep_value).toBe('nested content'); }); it('should handle special characters in updates', async () => { const specialValue = 'Line 1\nLine 2\tTabbed\r\nWindows line\n\nDouble space\n"Quoted" text'; const specialMetadata = { unicode: '你好世界 🌍', emoji: '🚀🎉🔧', symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?', }; const updatedBlock = { id: 'special-chars-block', value: specialValue, metadata: specialMetadata, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'special-chars-block', value: specialValue, metadata: specialMetadata, }); const data = expectValidToolResponse(result); expect(data.value).toBe(specialValue); expect(data.metadata.unicode).toBe('你好世界 🌍'); }); }); describe('Error Handling', () => { it('should throw error for missing block_id', async () => { await expect( handleUpdateMemoryBlock(mockServer, { value: 'Some value', }), ).rejects.toThrow('Missing required argument: block_id'); }); it('should throw error for null block_id', async () => { await expect( handleUpdateMemoryBlock(mockServer, { block_id: null, value: 'Some value', }), ).rejects.toThrow('Missing required argument: block_id'); }); it('should throw error when neither value nor metadata provided', async () => { await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'block-123', }), ).rejects.toThrow('Either value or metadata must be provided'); }); it('should throw error for undefined args', async () => { await expect(handleUpdateMemoryBlock(mockServer, undefined)).rejects.toThrow( 'Missing required argument: block_id', ); }); it('should handle 404 error when block not found', async () => { const error = new Error('Not found'); error.response = { status: 404, data: { error: 'Memory block not found' }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'non-existent-block', value: 'New value', }), ).rejects.toThrow('Not found'); }); it('should handle 403 forbidden error', async () => { const error = new Error('Forbidden'); error.response = { status: 403, data: { error: 'Cannot update this memory block' }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'protected-block', value: 'Attempted update', }), ).rejects.toThrow('Forbidden'); }); it('should handle 422 validation error', async () => { const error = new Error('Validation failed'); error.response = { status: 422, data: { error: 'Validation error', details: { value: 'Value exceeds maximum length of 5000 characters', }, }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'block-123', value: 'X'.repeat(6000), }), ).rejects.toThrow('Validation failed'); }); it('should handle network errors', async () => { const error = new Error('Network error: Connection timeout'); mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'block-123', value: 'Update value', }), ).rejects.toThrow('Network error'); }); it('should handle server errors', async () => { const error = new Error('Internal server error'); error.response = { status: 500, data: { error: 'Database update failed' }, }; mockServer.api.patch.mockRejectedValueOnce(error); await expect( handleUpdateMemoryBlock(mockServer, { block_id: 'block-123', metadata: { update: 'failed' }, }), ).rejects.toThrow('Internal server error'); }); }); describe('Edge Cases', () => { it('should handle empty string block_id gracefully', async () => { await expect( handleUpdateMemoryBlock(mockServer, { block_id: '', value: 'Some value', }), ).rejects.toThrow('Missing required argument: block_id'); }); it('should allow updating with null metadata values', async () => { const metadataWithNulls = { field1: null, field2: 'value', field3: null, }; const updatedBlock = { id: 'null-metadata-block', value: 'Content', metadata: metadataWithNulls, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'null-metadata-block', metadata: metadataWithNulls, }); const data = expectValidToolResponse(result); expect(data.metadata.field1).toBeNull(); expect(data.metadata.field2).toBe('value'); expect(data.metadata.field3).toBeNull(); }); it('should handle UUID format block IDs in updates', async () => { const uuidBlockId = '550e8400-e29b-41d4-a716-446655440000'; const updatedBlock = { id: uuidBlockId, value: 'Updated UUID block', }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: uuidBlockId, value: 'Updated UUID block', }); expect(mockServer.api.patch).toHaveBeenCalledWith( `/blocks/${uuidBlockId}`, expect.any(Object), expect.any(Object), ); const data = expectValidToolResponse(result); expect(data.id).toBe(uuidBlockId); }); it('should handle updating blocks at character limit', async () => { const maxValue = 'Z'.repeat(5000); // Exactly at limit const updatedBlock = { id: 'max-limit-block', value: maxValue, limit: 5000, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'max-limit-block', value: maxValue, }); const data = expectValidToolResponse(result); expect(data.value.length).toBe(5000); }); it('should handle concurrent metadata updates correctly', async () => { // Simulate a scenario where metadata might have version conflicts const versionedMetadata = { version: 5, last_update_timestamp: Date.now(), update_count: 10, }; const updatedBlock = { id: 'versioned-block', value: 'Content', metadata: versionedMetadata, }; mockServer.api.patch.mockResolvedValueOnce({ data: updatedBlock }); const result = await handleUpdateMemoryBlock(mockServer, { block_id: 'versioned-block', metadata: versionedMetadata, }); const data = expectValidToolResponse(result); expect(data.metadata.version).toBe(5); expect(data.metadata.update_count).toBe(10); }); }); });

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