Skip to main content
Glama
knowall-ai

Neo4j Agent Memory MCP Server

by knowall-ai

search_memories

Search and retrieve information from a Neo4j knowledge graph using text queries, filters, and relationship depth to find stored memories.

Instructions

Search and retrieve memories from the knowledge graph

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoSearch text to find in any property (searches for ANY word - e.g. "Ben Weeks" finds memories containing "Ben" OR "Weeks")
labelNoFilter by memory label
depthNoRelationship depth to include, defaults to 1
order_byNoSort order such as created_at DESC, name ASC
limitNoMaximum results to return, defaults to 10, max 200
since_dateNoISO date string to filter memories created after this date (e.g., "2024-01-01" or "2024-01-01T00:00:00Z")

Implementation Reference

  • Core handler logic for the 'search_memories' tool. Validates arguments, implements flexible full-text search across all memory properties using JavaScript (word-based matching in arrays/objects), builds Neo4j queries for filtering by label/date, collects matching memory IDs, then fetches detailed results with optional connected nodes up to specified depth. Handles fallbacks and limits results.
    case 'search_memories': { if (!isSearchMemoriesArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search_memories arguments'); } const depth = args.depth ?? 1; const limit = Math.min(args.limit ?? 10, 200); let orderBy = 'memory.created_at DESC'; if (args.order_by) { const orderMatch = args.order_by.match(/^(memory\.|n\.)?([a-zA-Z_]+)\s+(ASC|DESC)$/i); if (orderMatch) { const property = orderMatch[2]; const direction = orderMatch[3].toUpperCase(); orderBy = `memory.${property} ${direction}`; } } const params: Record<string, any> = {}; if (args.query) params.query = args.query; if (args.label) params.label = args.label; if (args.since_date) params.since_date = args.since_date; // Try a different approach: execute simple queries and handle complexity in JavaScript const allMemoryIds = new Set<number>(); try { // First, get all memories that match the label and date filters (if any) let baseQuery = `MATCH (memory)`; const conditions = []; if (args.label) { conditions.push(`labels(memory)[0] = $label`); } if (args.since_date) { conditions.push(`memory.created_at >= $since_date`); } if (conditions.length > 0) { baseQuery += ` WHERE ` + conditions.join(' AND '); } baseQuery += ` RETURN id(memory) as id, properties(memory) as props`; const queryParams: Record<string, any> = {}; if (args.label) queryParams.label = args.label; if (args.since_date) queryParams.since_date = args.since_date; const allMemories = await neo4j.executeQuery(baseQuery, queryParams); // Filter in JavaScript for (const record of allMemories) { const props = record.props; // If query is empty, include all memories (they already match label/date filters) if (!args.query || args.query.trim() === '') { allMemoryIds.add(record.id); continue; } // Split query into words for more flexible matching const queryWords = args.query.toLowerCase().trim().split(/\s+/); let found = false; // Search through all properties for (const [key, value] of Object.entries(props)) { if (value === null || value === undefined) continue; const valueStr = Array.isArray(value) ? value.map(v => v?.toString() || '').join(' ').toLowerCase() : value.toString().toLowerCase(); // Check if ANY query word is found in this property for (const word of queryWords) { if (valueStr.includes(word)) { found = true; break; } } if (found) { allMemoryIds.add(record.id); break; } } } } catch (error) { console.error('Error in search:', error); // Fallback to a simpler query if the above fails const conditions = []; if (args.query && args.query.trim() !== '') { conditions.push('(memory.name CONTAINS $query OR memory.context CONTAINS $query OR memory.description CONTAINS $query)'); } if (args.label) { conditions.push('labels(memory)[0] = $label'); } if (args.since_date) { conditions.push('memory.created_at >= $since_date'); } let fallbackQuery = `MATCH (memory)`; if (conditions.length > 0) { fallbackQuery += ` WHERE ${conditions.join(' AND ')}`; } fallbackQuery += ` RETURN collect(DISTINCT id(memory)) as memoryIds`; const fallbackParams: Record<string, any> = {}; if (args.query) fallbackParams.query = args.query; if (args.label) fallbackParams.label = args.label; if (args.since_date) fallbackParams.since_date = args.since_date; const fallbackResults = await neo4j.executeQuery(fallbackQuery, fallbackParams); if (fallbackResults.length > 0 && fallbackResults[0].memoryIds) { fallbackResults[0].memoryIds.forEach((id: number) => allMemoryIds.add(id)); } } if (allMemoryIds.size === 0) { return { content: [ { type: 'text', text: JSON.stringify([], null, 2), }, ], }; } // Query 3: Fetch the actual memories with connections let finalQuery = ` MATCH (memory) WHERE id(memory) IN $memoryIds `; if (depth > 0) { finalQuery += ` OPTIONAL MATCH path = (memory)-[*1..${depth}]-(related) RETURN memory, collect(DISTINCT { memory: related, relationship: relationships(path)[0], distance: length(path) }) as connections ORDER BY ${orderBy} LIMIT ${limit} `; } else { finalQuery += ` RETURN memory, [] as connections ORDER BY ${orderBy} LIMIT ${limit}`; } const result = await neo4j.executeQuery(finalQuery, { memoryIds: Array.from(allMemoryIds) }); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; }
  • MCP protocol tool schema definition for 'search_memories', exported in the tools array. Defines input parameters with descriptions used for tool discovery and validation by MCP clients.
    { name: 'search_memories', description: 'Search and retrieve memories from the knowledge graph', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search text to find in any property (searches for ANY word - e.g. "Ben Weeks" finds memories containing "Ben" OR "Weeks")', }, label: { type: 'string', description: 'Filter by memory label', }, depth: { type: 'number', description: 'Relationship depth to include, defaults to 1', }, order_by: { type: 'string', description: 'Sort order such as created_at DESC, name ASC', }, limit: { type: 'number', description: 'Maximum results to return, defaults to 10, max 200', }, since_date: { type: 'string', description: 'ISO date string to filter memories created after this date (e.g., "2024-01-01" or "2024-01-01T00:00:00Z")', }, }, required: [], }, },
  • Internal TypeScript interface and type guard validator for SearchMemoriesArgs, imported and used in the handler for runtime argument validation.
    export interface SearchMemoriesArgs { query?: string; label?: string; depth?: number; order_by?: string; limit?: number; since_date?: string; } export interface CreateConnectionArgs { fromMemoryId: number; toMemoryId: number; type: string; properties?: Record<string, any>; } export interface UpdateMemoryArgs { nodeId: number; properties: Record<string, any>; } export interface UpdateConnectionArgs { fromMemoryId: number; toMemoryId: number; type: string; properties: Record<string, any>; } export interface DeleteMemoryArgs { nodeId: number; } export interface DeleteConnectionArgs { fromMemoryId: number; toMemoryId: number; type: string; } export interface ListMemoryLabelsArgs { // No arguments needed for this tool } export function isCreateMemoryArgs(args: unknown): args is CreateMemoryArgs { return typeof args === 'object' && args !== null && typeof (args as CreateMemoryArgs).label === 'string' && typeof (args as CreateMemoryArgs).properties === 'object'; } export function isSearchMemoriesArgs(args: unknown): args is SearchMemoriesArgs { if (typeof args !== 'object' || args === null) return false; const searchArgs = args as SearchMemoriesArgs; if (searchArgs.query !== undefined && typeof searchArgs.query !== 'string') return false; if (searchArgs.since_date !== undefined && typeof searchArgs.since_date !== 'string') return false; return true; }
  • src/server.ts:39-41 (registration)
    Registration of all tools (including search_memories) via the imported tools array in the MCP server's listTools request handler.
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools, }));

Latest Blog Posts

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/knowall-ai/mcp-neo4j-agent-memory'

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