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_fts immediately upon insertion into nodes
  • 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 LIKE patterns 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

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 searchNodesFTS when available, degrading gracefully to LIKE searches 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →