How FTS5 Full-Text Search Enables Instant Node Discovery in n8n-MCP
n8n-MCP uses a SQLite FTS5 virtual table to index every n8n node's textual metadata, enabling sub-20ms full-text searches across thousands of core and community nodes with typo-tolerant fallback modes.
The n8n-MCP server acts as a bridge between AI assistants and the n8n automation platform, maintaining a comprehensive catalog of available integration nodes. To deliver instant answers to "find the node that does X" queries without scanning thousands of rows linearly, the project implements FTS5 full-text search via SQLite's virtual table mechanism, eliminating the performance penalties of traditional LIKE pattern matching.
The Database Architecture: Virtual Tables and Triggers
Creating the nodes_fts Virtual Table
In src/services/node-documentation-service.ts (lines 191-203), the service defines a virtual table that shadows the main nodes table:
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
node_type,
name,
display_name,
description,
category,
documentation_markdown,
aliases,
content=nodes,
content_rowid=id
);
The content=nodes and content_rowid=id directives link the FTS5 index to the source table, ensuring the virtual table references the original row data without duplication while maintaining a separate inverted index for text tokens.
Automatic Index Maintenance with Triggers
To keep the search index synchronized, the same file (lines 206-222) declares triggers that propagate changes from the main table:
- INSERT: Adds new rows to
nodes_ftsimmediately upon insertion intonodes - DELETE: Removes corresponding entries from the FTS5 index
- UPDATE: Replaces existing FTS5 records to reflect modified node metadata
This trigger-based approach ensures the full-text index remains consistent with the node catalog without requiring manual re-indexing or batch updates.
Runtime Search Implementation
The searchNodes Dispatcher Method
When the MCP server's search_nodes tool receives a query, the searchNodes method in src/mcp/server.ts (lines 1661-1670) first verifies FTS5 availability:
const ftsExists = this.db.prepare(`
SELECT name FROM sqlite_master
WHERE type='table' AND name='nodes_fts'
`).get();
if (ftsExists) {
return this.searchNodesFTS(normalizedQuery, limit, searchMode, options);
} else {
return this.searchNodesLIKE(normalizedQuery, limit, options);
}
If the virtual table exists, the engine routes to searchNodesFTS; otherwise, it degrades gracefully to the slower searchNodesLIKE implementation.
Constructing Ranked MATCH Queries
The core FTS5 implementation (lines 1675-1695) builds a boolean query and applies custom relevance scoring:
const nodes = this.db.prepare(`
SELECT n.*, rank
FROM nodes n
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
WHERE nodes_fts MATCH ?
${sourceFilter}
ORDER BY
CASE
WHEN LOWER(n.display_name) = LOWER(?) THEN 0
WHEN LOWER(n.display_name) LIKE LOWER(?) THEN 1
WHEN LOWER(n.node_type) LIKE LOWER(?) THEN 2
ELSE 3
END,
rank,
n.display_name
LIMIT ?
`).all(ftsQuery, cleanedQuery, `%${cleanedQuery}%`, `%${cleanedQuery}%`, limit);
This query prioritizes exact display name matches, then partial matches, then node type matches, finally falling back to the FTS5 internal rank value. The system specifically promotes popular core nodes—including HTTP Request, Webhook, Set, Code, and Slack—to ensure frequently-used integrations surface first in result sets.
Search Modes and Source Filtering
The implementation supports three distinct search behaviors via the mode parameter:
- OR mode (default): Treats query terms as alternatives, returning nodes matching any keyword using FTS5's implicit OR operator
- AND mode: Requires all terms to appear, constructing boolean expressions (
term1 AND term2) for the MATCH clause - FUZZY mode: Detects potential misspellings by first attempting an FTS5 search, then falling back to
LIKEpatterns with wildcards if no results return
Source filters (core, community, verified) append cheap SQL predicates to the WHERE clause, restricting results without rebuilding the query plan or affecting index utilization.
Initial Setup and Migration
For fresh installations or rebuilds, scripts/migrate-nodes-fts.ts (lines 30-51) creates the virtual table and bulk-populates it from existing node rows:
// Migration logic creates the virtual table and inserts existing nodes
db.exec(`
INSERT INTO nodes_fts(node_type, name, display_name, description, category, documentation_markdown, aliases)
SELECT node_type, name, display_name, description, category, documentation_markdown, aliases
FROM nodes
`);
This migration runs once during setup; thereafter, the triggers maintain synchronization automatically as the catalog updates.
Practical Code Examples
The following examples demonstrate how to query the node catalog using the MCP tool interface:
Basic keyword search using default OR mode:
await client.callTool('search_nodes', { query: 'webhook' });
// Returns the Webhook node as the top result
Exact phrase matching with AND mode:
await client.callTool('search_nodes', {
query: '"google sheets"',
mode: 'AND',
});
// Only nodes containing both "google" and "sheets" in indexed fields
Typo-tolerant search using FUZZY mode:
await client.callTool('search_nodes', {
query: 'slak',
mode: 'FUZZY',
});
// Finds the Slack node despite the misspelling via LIKE fallback
Filtered community search with result limits:
await client.callTool('search_nodes', {
query: 'scraping',
source: 'community',
limit: 5,
});
// Returns 5 most relevant community scraping integrations
Raw SQLite access for debugging or custom analytics:
const db = await getDatabase(); // MCP-provided SQLite handle
const rows = db.prepare(`
SELECT n.node_type, n.display_name, rank
FROM nodes n
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
WHERE nodes_fts MATCH ?
ORDER BY rank LIMIT 10
`).all('http request');
Key Implementation Files
src/services/node-documentation-service.ts: Defines thenodes_ftsvirtual table schema and maintenance triggers (lines 191-222)src/mcp/server.ts: Implements thesearchNodesdispatcher andsearchNodesFTSmethod with ranked MATCH queries (lines 1661-1695)scripts/migrate-nodes-fts.ts: Handles initial creation and population of the FTS5 table (lines 30-51)tests/integration/database/node-fts5-search.test.ts: Validates trigger behavior and search accuracy
Summary
- FTS5 virtual tables in SQLite provide the foundation for sub-20ms node discovery across the entire n8n catalog according to the czlonkowski/n8n-mcp source code
- Trigger-based synchronization automatically maintains the search index as nodes are inserted, updated, or deleted without manual intervention
- Intelligent fallback logic routes queries to
searchNodesFTSwhen available, degrading gracefully toLIKEsearches only when the virtual table is absent - Multi-tier ranking combines exact-match priority, FTS5 relevance scores, and popularity boosting for core nodes like HTTP Request and Webhook
- Flexible search modes support OR, AND, and FUZZY queries with optional source filtering for core, community, or verified nodes
Frequently Asked Questions
What makes FTS5 faster than LIKE queries for node discovery?
FTS5 uses inverted indices to map search terms directly to row locations, avoiding full-table scans. While LIKE '%term%' checks every row sequentially, the MATCH query in src/mcp/server.ts leverages the pre-built index to locate relevant nodes in logarithmic time, consistently delivering results under 20 milliseconds even across thousands of community nodes.
How does n8n-mcp handle typos in search queries?
When mode: 'FUZZY' is specified, the system first attempts an FTS5 MATCH query. If no results return, it automatically falls back to a LIKE pattern search with wildcards, as implemented in the searchNodes dispatcher. This dual-path approach ensures users find the intended node—such as locating "Slack" when typing "slak"—without requiring exact spelling.
Can I restrict searches to official n8n core nodes only?
Yes. The source parameter accepts 'core', 'community', or 'verified' values, which append SQL predicates to the WHERE clause in the MATCH query. Setting source: 'core' filters the result set to official n8n nodes before the ranking algorithm applies, ensuring only maintained integrations appear in results.
What happens if the FTS5 table is missing or corrupted?
The searchNodes method in src/mcp/server.ts explicitly checks for the nodes_fts table existence before routing to searchNodesFTS. If the table is absent—such as after a fresh clone without running migrations—the system routes requests to searchNodesLIKE, which uses standard SQL pattern matching. Administrators can rebuild the index by executing scripts/migrate-nodes-fts.ts to restore full-text performance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →