search_nodes
Find entities and relations by keyword or semantic similarity using hybrid search. Specify query, mode, and threshold to retrieve relevant results from the Memento MCP server.
Instructions
Search for entities and relations by keyword or semantic similarity. Supports hybrid mode.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Search mode to use. | keyword |
| query | Yes | Search query string. | |
| threshold | No | Distance threshold for semantic filtering. | |
| topK | No | Max number of results to return. |
Implementation Reference
- src/knowledge-graph-manager.js:168-217 (handler)Core handler function performing semantic embedding search on observations, cosine similarity filtering, and relevance re-ranking using context-aware scoring.async searchNodes({ query, topK = 10, threshold = 0.75, includeScoreDetails = false, scoringProfile = 'balanced' }) { try { const [ rawVectorBuf ] = await this.embedTexts([ query ]); const rawVector = Array.from(new Float32Array(rawVectorBuf.buffer, rawVectorBuf.byteOffset, rawVectorBuf.byteLength / 4)); const unitVector = normalizeVector(rawVector); const limit = Math.max(topK * 2, topK + 5); const rows = await this.#repository.semanticSearch(unitVector, limit); const ids = rows .filter(r => Number(r.similarity) >= Number(threshold)) .slice(0, topK) .map(r => r.entity_id); if (!ids.length) { return { entities: [], relations: [] } } return this.#applyScoring(ids, query, includeScoreDetails, scoringProfile); } catch (error) { console.error(`Search error:`, error?.message ?? error); throw error; } function normalizeVector(v) { let sum = 0; for (let i = 0; i < v.length; i += 1) { sum += v[i] * v[i]; } const norm = Math.sqrt(sum) if (!isFinite(norm) || norm === 0) { return v; } const out = new Array(v.length) for (let i = 0; i < v.length; i += 1) { out[i] = v[i] / norm; } return out; } }
- src/server.js:176-199 (registration)Registers the MCP tool 'search_nodes' with input schema validation using Zod and delegates execution to KnowledgeGraphManager.searchNodes.this.tool( 'search_nodes', 'Search for entities and relations by semantic similarity.', { query: z.string().describe('Search query string.'), topK: z.number().int().min(1).max(100) .optional() .default(8) .describe('Max number of results to return.'), threshold: z.number().min(0).max(1) .optional() .default(0.35) .describe('Distance threshold for semantic filtering.') }, async (args) => ({ content: [{ type: 'text', text: JSON.stringify( await this.#knowledgeGraphManager.searchNodes(args), null, 2 ) }] })
- src/server.js:180-189 (schema)Zod schema defining input parameters: query (string), topK (number, default 8), threshold (number, default 0.35).query: z.string().describe('Search query string.'), topK: z.number().int().min(1).max(100) .optional() .default(8) .describe('Max number of results to return.'), threshold: z.number().min(0).max(1) .optional() .default(0.35) .describe('Distance threshold for semantic filtering.') },
- Private helper method that fetches entity details, prepares search context, applies relevance scoring, updates access stats, and merges scores with full graph details.async #applyScoring(entityIds, query, includeScoreDetails, scoringProfile) { if (!entityIds?.length) { return { entities: [], relations: [] }; } const entityData = await this.#repository.fetchEntitiesWithDetails(entityIds); const normalized = entityData.map(row => ({ ...row, entity_id: String(row.entity_id) })); const searchContext = await this.#searchContextManager.prepareSearchContext(query, { contextSize: 5, preloadDepth: 2 }); const scored = await this.#searchContextManager.scoreSearchResults(normalized, searchContext, { includeComponents: includeScoreDetails, scoringProfile }); scored.sort((a, b) => (b.score || 0) - (a.score || 0)); const foundIds = scored.map(row => Number(row.entity_id)); if (foundIds.length) { await this.#searchContextManager.updateAccessStats(foundIds); } const entityNames = scored.map(row => row.name); const fullDetails = await this.openNodes(entityNames); if (includeScoreDetails) { const withScores = fullDetails.entities.map((entity, index) => ({ ...entity, score: scored[index]?.score, scoreComponents: scored[index]?.scoreComponents })); return { entities: withScores, relations: fullDetails.relations }; } return fullDetails; }