search_nodes
Find entities and relations by semantic similarity to search queries, enabling semantic search for information retrieval.
Instructions
Search for entities and relations by semantic similarity.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query string. | |
| topK | No | Max number of results to return. | |
| threshold | No | Distance threshold for semantic filtering. |
Implementation Reference
- src/knowledge-graph-manager.js:168-217 (handler)Main handler implementation for search_nodes tool. Embeds the query, performs semantic vector search on entities, filters by similarity threshold, applies advanced relevance scoring, and returns top entities with their relations.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:175-200 (registration)Registration of the search_nodes tool in the MCP server, defining name, description, Zod input schema (query, topK, threshold), and async handler that invokes KnowledgeGraphManager.searchNodes and returns JSON response.// Tool: search_nodes 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 definition for search_nodes tool inputs: query (string), topK (int 1-100 default 8), threshold (float 0-1 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.') },
- Helper method called by searchNodes to apply contextual relevance scoring: fetches details, prepares context, scores via SearchContextManager, 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; }