mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-18 16:33:13 +00:00
Compare commits
1 Commits
v2.20.7
...
fix/missin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e583261d37 |
90
CHANGELOG.md
90
CHANGELOG.md
@@ -7,96 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [2.20.7] - 2025-10-22
|
|
||||||
|
|
||||||
### 🔄 Dependencies
|
|
||||||
|
|
||||||
**Updated n8n to v1.116.2**
|
|
||||||
|
|
||||||
Updated all n8n dependencies to the latest compatible versions:
|
|
||||||
- `n8n`: 1.115.2 → 1.116.2
|
|
||||||
- `n8n-core`: 1.114.0 → 1.115.1
|
|
||||||
- `n8n-workflow`: 1.112.0 → 1.113.0
|
|
||||||
- `@n8n/n8n-nodes-langchain`: 1.114.1 → 1.115.1
|
|
||||||
|
|
||||||
**Database Rebuild:**
|
|
||||||
- Rebuilt node database with 542 nodes from updated n8n packages
|
|
||||||
- All 542 nodes loaded successfully from both n8n-nodes-base (439 nodes) and @n8n/n8n-nodes-langchain (103 nodes)
|
|
||||||
- Documentation mapping completed for all nodes
|
|
||||||
|
|
||||||
**Testing:**
|
|
||||||
- Changes validated in CI/CD pipeline with full test suite (705 tests)
|
|
||||||
- Critical nodes validated: httpRequest, code, slack, agent
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
|
||||||
|
|
||||||
**FTS5 Search Ranking - Exact Match Prioritization**
|
|
||||||
|
|
||||||
Fixed critical bug in production search where exact matches weren't appearing first in search results.
|
|
||||||
|
|
||||||
#### Problem
|
|
||||||
- SQL ORDER BY clause was `ORDER BY rank, CASE ... END` (wrong order)
|
|
||||||
- FTS5 rank sorted first, CASE statement only acted as tiebreaker
|
|
||||||
- Since FTS5 ranks are always unique, CASE boosting never applied
|
|
||||||
- Additionally, CASE used case-sensitive comparison failing to match nodes like "Webhook" when searching "webhook"
|
|
||||||
- Result: Searching "webhook" returned "Webflow Trigger" first, actual "Webhook" node ranked 4th
|
|
||||||
|
|
||||||
#### Root Cause Analysis
|
|
||||||
**SQL Ordering Issue:**
|
|
||||||
```sql
|
|
||||||
-- BEFORE (Broken):
|
|
||||||
ORDER BY rank, CASE ... END -- rank first, CASE never used
|
|
||||||
-- Result: webhook ranks 4th (-9.64 rank)
|
|
||||||
-- Top 3: webflowTrigger (-10.20), vonage (-10.09), renameKeys (-10.01)
|
|
||||||
|
|
||||||
-- AFTER (Fixed):
|
|
||||||
ORDER BY CASE ... END, rank -- CASE first, exact matches prioritized
|
|
||||||
-- Result: webhook ranks 1st (CASE priority 0)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Case-Sensitivity Issue:**
|
|
||||||
- Old: `WHEN n.display_name = ?` (case-sensitive, fails on "Webhook" vs "webhook")
|
|
||||||
- New: `WHEN LOWER(n.display_name) = LOWER(?)` (case-insensitive, matches correctly)
|
|
||||||
|
|
||||||
#### Fixed
|
|
||||||
|
|
||||||
**1. Production Code** (`src/mcp/server.ts` lines 1278-1295)
|
|
||||||
- Changed ORDER BY from: `rank, CASE ... END`
|
|
||||||
- To: `CASE WHEN LOWER(n.display_name) = LOWER(?) ... END, rank`
|
|
||||||
- Added case-insensitive comparison with LOWER() function
|
|
||||||
- Exact matches now consistently appear first in search results
|
|
||||||
|
|
||||||
**2. Test Files Updated**
|
|
||||||
- `tests/integration/database/node-fts5-search.test.ts` (lines 137-160)
|
|
||||||
- `tests/integration/ci/database-population.test.ts` (lines 206-234)
|
|
||||||
- Both updated to match corrected SQL logic with case-insensitive comparison
|
|
||||||
- Tests now accurately validate production search behavior
|
|
||||||
|
|
||||||
#### Impact
|
|
||||||
|
|
||||||
**Search Quality:**
|
|
||||||
- ✅ Exact matches now always rank first (webhook, http, code, etc.)
|
|
||||||
- ✅ Case-insensitive matching works correctly (Webhook = webhook = WEBHOOK)
|
|
||||||
- ✅ Better user experience - predictable search results
|
|
||||||
- ✅ SQL query more efficient (correct ordering at database level)
|
|
||||||
|
|
||||||
**Performance:**
|
|
||||||
- Same or better performance (less JavaScript sorting needed)
|
|
||||||
- Database does the heavy lifting with correct ORDER BY
|
|
||||||
- JavaScript sorting still provides additional relevance refinement
|
|
||||||
|
|
||||||
**Testing:**
|
|
||||||
- All 705 tests passing (703 passed + 2 fixed)
|
|
||||||
- Comprehensive testing by n8n-mcp-tester agent
|
|
||||||
- Code review approved with minor optimization suggestions for future
|
|
||||||
|
|
||||||
**Verified Search Results:**
|
|
||||||
- "webhook" → nodes-base.webhook (1st)
|
|
||||||
- "http" → nodes-base.httpRequest (1st)
|
|
||||||
- "code" → nodes-base.code (1st)
|
|
||||||
- "slack" → nodes-base.slack (1st)
|
|
||||||
- All case variations work correctly (WEBHOOK, Webhook, webhook)
|
|
||||||
|
|
||||||
## [2.20.6] - 2025-10-21
|
## [2.20.6] - 2025-10-21
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|||||||
@@ -192,5 +192,4 @@ NEVER proactively create documentation files (*.md) or README files. Only create
|
|||||||
- When the user asks to review issues, you should use GH CLI to get the issue and all the comments
|
- When the user asks to review issues, you should use GH CLI to get the issue and all the comments
|
||||||
- When the task can be divided into separated subtasks, you should spawn separate sub-agents to handle them in paralel
|
- When the task can be divided into separated subtasks, you should spawn separate sub-agents to handle them in paralel
|
||||||
- Use the best sub-agent for the task as per their descriptions
|
- Use the best sub-agent for the task as per their descriptions
|
||||||
- Do not use hyperbolic or dramatic language in comments and documentation
|
- Do not use hyperbolic or dramatic language in comments and documentation
|
||||||
- Add to every commit and PR: Concieved by Romuald Członkowski - and then link to www.aiadvisors.pl/en. Don't add it in conversations
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
[](https://www.npmjs.com/package/n8n-mcp)
|
[](https://www.npmjs.com/package/n8n-mcp)
|
||||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||||
[](https://github.com/n8n-io/n8n)
|
[](https://github.com/n8n-io/n8n)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||||
|
|
||||||
|
|||||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
5997
package-lock.json
generated
5997
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.20.7",
|
"version": "2.20.6",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -140,15 +140,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@n8n/n8n-nodes-langchain": "^1.115.1",
|
"@n8n/n8n-nodes-langchain": "^1.114.1",
|
||||||
"@supabase/supabase-js": "^2.57.4",
|
"@supabase/supabase-js": "^2.57.4",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-rate-limit": "^7.1.5",
|
"express-rate-limit": "^7.1.5",
|
||||||
"lru-cache": "^11.2.1",
|
"lru-cache": "^11.2.1",
|
||||||
"n8n": "^1.116.2",
|
"n8n": "^1.115.2",
|
||||||
"n8n-core": "^1.115.1",
|
"n8n-core": "^1.114.0",
|
||||||
"n8n-workflow": "^1.113.0",
|
"n8n-workflow": "^1.112.0",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"sql.js": "^1.13.0",
|
"sql.js": "^1.13.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp-runtime",
|
"name": "n8n-mcp-runtime",
|
||||||
"version": "2.20.7",
|
"version": "2.20.6",
|
||||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1276,20 +1276,20 @@ export class N8NDocumentationMCPServer {
|
|||||||
try {
|
try {
|
||||||
// Use FTS5 with ranking
|
// Use FTS5 with ranking
|
||||||
const nodes = this.db.prepare(`
|
const nodes = this.db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
n.*,
|
n.*,
|
||||||
rank
|
rank
|
||||||
FROM nodes n
|
FROM nodes n
|
||||||
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
|
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
|
||||||
WHERE nodes_fts MATCH ?
|
WHERE nodes_fts MATCH ?
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE
|
rank,
|
||||||
WHEN LOWER(n.display_name) = LOWER(?) THEN 0
|
CASE
|
||||||
WHEN LOWER(n.display_name) LIKE LOWER(?) THEN 1
|
WHEN n.display_name = ? THEN 0
|
||||||
WHEN LOWER(n.node_type) LIKE LOWER(?) THEN 2
|
WHEN n.display_name LIKE ? THEN 1
|
||||||
|
WHEN n.node_type LIKE ? THEN 2
|
||||||
ELSE 3
|
ELSE 3
|
||||||
END,
|
END,
|
||||||
rank,
|
|
||||||
n.display_name
|
n.display_name
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`).all(ftsQuery, cleanedQuery, `%${cleanedQuery}%`, `%${cleanedQuery}%`, limit) as (NodeRow & { rank: number })[];
|
`).all(ftsQuery, cleanedQuery, `%${cleanedQuery}%`, `%${cleanedQuery}%`, limit) as (NodeRow & { rank: number })[];
|
||||||
|
|||||||
@@ -205,20 +205,9 @@ describe.skipIf(!dbExists)('Database Content Validation', () => {
|
|||||||
|
|
||||||
it('MUST have FTS5 index properly ranked', () => {
|
it('MUST have FTS5 index properly ranked', () => {
|
||||||
const results = db.prepare(`
|
const results = db.prepare(`
|
||||||
SELECT
|
SELECT node_type, rank FROM nodes_fts
|
||||||
n.node_type,
|
|
||||||
rank
|
|
||||||
FROM nodes n
|
|
||||||
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
|
|
||||||
WHERE nodes_fts MATCH 'webhook'
|
WHERE nodes_fts MATCH 'webhook'
|
||||||
ORDER BY
|
ORDER BY rank
|
||||||
CASE
|
|
||||||
WHEN LOWER(n.display_name) = LOWER('webhook') THEN 0
|
|
||||||
WHEN LOWER(n.display_name) LIKE LOWER('%webhook%') THEN 1
|
|
||||||
WHEN LOWER(n.node_type) LIKE LOWER('%webhook%') THEN 2
|
|
||||||
ELSE 3
|
|
||||||
END,
|
|
||||||
rank
|
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
`).all();
|
`).all();
|
||||||
|
|
||||||
@@ -226,7 +215,7 @@ describe.skipIf(!dbExists)('Database Content Validation', () => {
|
|||||||
'CRITICAL: FTS5 ranking not working. Search quality will be degraded.'
|
'CRITICAL: FTS5 ranking not working. Search quality will be degraded.'
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Exact match should be in top results (using production boosting logic with CASE-first ordering)
|
// Exact match should be in top results
|
||||||
const topNodes = results.slice(0, 3).map((r: any) => r.node_type);
|
const topNodes = results.slice(0, 3).map((r: any) => r.node_type);
|
||||||
expect(topNodes,
|
expect(topNodes,
|
||||||
'WARNING: Exact match "nodes-base.webhook" not in top 3 ranked results'
|
'WARNING: Exact match "nodes-base.webhook" not in top 3 ranked results'
|
||||||
|
|||||||
@@ -136,25 +136,14 @@ describe('Node FTS5 Search Integration Tests', () => {
|
|||||||
describe('FTS5 Search Quality', () => {
|
describe('FTS5 Search Quality', () => {
|
||||||
it('should rank exact matches higher', () => {
|
it('should rank exact matches higher', () => {
|
||||||
const results = db.prepare(`
|
const results = db.prepare(`
|
||||||
SELECT
|
SELECT node_type, rank FROM nodes_fts
|
||||||
n.node_type,
|
|
||||||
rank
|
|
||||||
FROM nodes n
|
|
||||||
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
|
|
||||||
WHERE nodes_fts MATCH 'webhook'
|
WHERE nodes_fts MATCH 'webhook'
|
||||||
ORDER BY
|
ORDER BY rank
|
||||||
CASE
|
|
||||||
WHEN LOWER(n.display_name) = LOWER('webhook') THEN 0
|
|
||||||
WHEN LOWER(n.display_name) LIKE LOWER('%webhook%') THEN 1
|
|
||||||
WHEN LOWER(n.node_type) LIKE LOWER('%webhook%') THEN 2
|
|
||||||
ELSE 3
|
|
||||||
END,
|
|
||||||
rank
|
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
`).all();
|
`).all();
|
||||||
|
|
||||||
expect(results.length).toBeGreaterThan(0);
|
expect(results.length).toBeGreaterThan(0);
|
||||||
// Exact match should be in top results (using production boosting logic with CASE-first ordering)
|
// Exact match should be in top results
|
||||||
const topResults = results.slice(0, 3).map((r: any) => r.node_type);
|
const topResults = results.slice(0, 3).map((r: any) => r.node_type);
|
||||||
expect(topResults).toContain('nodes-base.webhook');
|
expect(topResults).toContain('nodes-base.webhook');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user