chore: update n8n to v1.101.1
- Updated n8n from 1.100.1 to 1.101.1 - Updated n8n-core from 1.99.0 to 1.100.0 - Updated n8n-workflow from 1.97.0 to 1.98.0 - Updated @n8n/n8n-nodes-langchain from 1.99.0 to 1.100.1 - Rebuilt node database with 528 nodes - All validation tests passing - Bumped version to 2.7.12 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
12
README.md
12
README.md
@@ -2,9 +2,9 @@
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 525+ workflow automation nodes.
|
||||
@@ -13,7 +13,7 @@ A Model Context Protocol (MCP) server that provides AI assistants with comprehen
|
||||
|
||||
n8n-MCP serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively. It provides structured access to:
|
||||
|
||||
- 📚 **525 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 📚 **528 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 🔧 **Node properties** - 99% coverage with detailed schemas
|
||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||
- 📄 **Documentation** - 90% coverage from official n8n docs (including AI nodes)
|
||||
@@ -568,10 +568,10 @@ npm run dev:http # HTTP dev mode
|
||||
|
||||
## 📊 Metrics & Coverage
|
||||
|
||||
Current database coverage (n8n v1.100.1):
|
||||
Current database coverage (n8n v1.101.1):
|
||||
|
||||
- ✅ **525/525** nodes loaded (100%)
|
||||
- ✅ **520** nodes with properties (99%)
|
||||
- ✅ **528/528** nodes loaded (100%)
|
||||
- ✅ **520** nodes with properties (98.5%)
|
||||
- ✅ **470** nodes with documentation (90%)
|
||||
- ✅ **263** AI-capable tools detected
|
||||
- ✅ **AI Agent & LangChain nodes** fully documented
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.7.12] - 2025-07-10
|
||||
|
||||
### Updated
|
||||
- **n8n Dependencies**: Updated to latest versions for compatibility and new features
|
||||
- n8n: 1.100.1 → 1.101.1
|
||||
- n8n-core: 1.99.0 → 1.100.0
|
||||
- n8n-workflow: 1.97.0 → 1.98.0
|
||||
- @n8n/n8n-nodes-langchain: 1.99.0 → 1.100.1
|
||||
- **Node Database**: Rebuilt with 528 nodes from updated n8n packages
|
||||
- All validation tests passing with updated dependencies
|
||||
|
||||
## [2.7.11] - 2025-07-10
|
||||
|
||||
### Enhanced
|
||||
@@ -554,6 +565,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Basic n8n and MCP integration
|
||||
- Core workflow automation features
|
||||
|
||||
[2.7.12]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.11...v2.7.12
|
||||
[2.7.11]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.10...v2.7.11
|
||||
[2.7.10]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.8...v2.7.10
|
||||
[2.7.8]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.5...v2.7.8
|
||||
|
||||
5341
package-lock.json
generated
5341
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",
|
||||
"version": "2.7.11",
|
||||
"version": "2.7.12",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
@@ -93,14 +93,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.13.2",
|
||||
"@n8n/n8n-nodes-langchain": "^1.99.0",
|
||||
"@n8n/n8n-nodes-langchain": "^1.100.1",
|
||||
"axios": "^1.10.0",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"n8n": "^1.100.1",
|
||||
"n8n-core": "^1.99.0",
|
||||
"n8n-workflow": "^1.97.0",
|
||||
"n8n": "^1.101.1",
|
||||
"n8n-core": "^1.100.0",
|
||||
"n8n-workflow": "^1.98.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"uuid": "^10.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.7.10",
|
||||
"version": "2.7.12",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
48
scripts/debug-fuzzy.ts
Normal file
48
scripts/debug-fuzzy.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
async function debugFuzzy() {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Get the actual implementation
|
||||
const serverAny = server as any;
|
||||
|
||||
// Test nodes we expect to find
|
||||
const testNodes = [
|
||||
{ node_type: 'nodes-base.slack', display_name: 'Slack', description: 'Consume Slack API' },
|
||||
{ node_type: 'nodes-base.webhook', display_name: 'Webhook', description: 'Handle webhooks' },
|
||||
{ node_type: 'nodes-base.httpRequest', display_name: 'HTTP Request', description: 'Make HTTP requests' },
|
||||
{ node_type: 'nodes-base.emailSend', display_name: 'Send Email', description: 'Send emails' }
|
||||
];
|
||||
|
||||
const testQueries = ['slak', 'webook', 'htpp', 'emial'];
|
||||
|
||||
console.log('Testing fuzzy scoring...\n');
|
||||
|
||||
for (const query of testQueries) {
|
||||
console.log(`\nQuery: "${query}"`);
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
for (const node of testNodes) {
|
||||
const score = serverAny.calculateFuzzyScore(node, query);
|
||||
const distance = serverAny.getEditDistance(query, node.display_name.toLowerCase());
|
||||
console.log(`${node.display_name.padEnd(15)} - Score: ${score.toFixed(0).padStart(4)}, Distance: ${distance}`);
|
||||
}
|
||||
|
||||
// Test actual search
|
||||
console.log('\nActual search result:');
|
||||
const result = await server.executeTool('search_nodes', {
|
||||
query: query,
|
||||
mode: 'FUZZY',
|
||||
limit: 5
|
||||
});
|
||||
console.log(`Found ${result.results.length} results`);
|
||||
if (result.results.length > 0) {
|
||||
console.log('Top result:', result.results[0].displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugFuzzy().catch(console.error);
|
||||
114
scripts/debug-template-search.ts
Normal file
114
scripts/debug-template-search.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* Debug template search issues
|
||||
*/
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
import { TemplateRepository } from '../src/templates/template-repository';
|
||||
|
||||
async function debug() {
|
||||
console.log('🔍 Debugging template search...\n');
|
||||
|
||||
const db = await createDatabaseAdapter('./data/nodes.db');
|
||||
|
||||
// Check FTS5 support
|
||||
const hasFTS5 = db.checkFTS5Support();
|
||||
console.log(`FTS5 support: ${hasFTS5}`);
|
||||
|
||||
// Check template count
|
||||
const templateCount = db.prepare('SELECT COUNT(*) as count FROM templates').get() as { count: number };
|
||||
console.log(`Total templates: ${templateCount.count}`);
|
||||
|
||||
// Check FTS5 tables
|
||||
const ftsTables = db.prepare(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type IN ('table', 'virtual') AND name LIKE 'templates_fts%'
|
||||
ORDER BY name
|
||||
`).all() as { name: string }[];
|
||||
|
||||
console.log('\nFTS5 tables:');
|
||||
ftsTables.forEach(t => console.log(` - ${t.name}`));
|
||||
|
||||
// Check FTS5 content
|
||||
if (hasFTS5) {
|
||||
try {
|
||||
const ftsCount = db.prepare('SELECT COUNT(*) as count FROM templates_fts').get() as { count: number };
|
||||
console.log(`\nFTS5 entries: ${ftsCount.count}`);
|
||||
} catch (error) {
|
||||
console.log('\nFTS5 query error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Test template repository
|
||||
console.log('\n📋 Testing TemplateRepository...');
|
||||
const repo = new TemplateRepository(db);
|
||||
|
||||
// Test different searches
|
||||
const searches = ['webhook', 'api', 'automation'];
|
||||
|
||||
for (const query of searches) {
|
||||
console.log(`\n🔎 Searching for "${query}"...`);
|
||||
|
||||
// Direct SQL LIKE search
|
||||
const likeResults = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM templates
|
||||
WHERE name LIKE ? OR description LIKE ?
|
||||
`).get(`%${query}%`, `%${query}%`) as { count: number };
|
||||
console.log(` LIKE search matches: ${likeResults.count}`);
|
||||
|
||||
// Repository search
|
||||
try {
|
||||
const repoResults = repo.searchTemplates(query, 5);
|
||||
console.log(` Repository search returned: ${repoResults.length} results`);
|
||||
if (repoResults.length > 0) {
|
||||
console.log(` First result: ${repoResults[0].name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` Repository search error:`, error);
|
||||
}
|
||||
|
||||
// Direct FTS5 search if available
|
||||
if (hasFTS5) {
|
||||
try {
|
||||
const ftsQuery = `"${query}"`;
|
||||
const ftsResults = db.prepare(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM templates t
|
||||
JOIN templates_fts ON t.id = templates_fts.rowid
|
||||
WHERE templates_fts MATCH ?
|
||||
`).get(ftsQuery) as { count: number };
|
||||
console.log(` Direct FTS5 matches: ${ftsResults.count}`);
|
||||
} catch (error) {
|
||||
console.log(` Direct FTS5 error:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if templates_fts is properly synced
|
||||
if (hasFTS5) {
|
||||
console.log('\n🔄 Checking FTS5 sync...');
|
||||
try {
|
||||
// Get a few template IDs and check if they're in FTS
|
||||
const templates = db.prepare('SELECT id, name FROM templates LIMIT 5').all() as { id: number, name: string }[];
|
||||
|
||||
for (const template of templates) {
|
||||
try {
|
||||
const inFTS = db.prepare('SELECT rowid FROM templates_fts WHERE rowid = ?').get(template.id);
|
||||
console.log(` Template ${template.id} "${template.name.substring(0, 30)}...": ${inFTS ? 'IN FTS' : 'NOT IN FTS'}`);
|
||||
} catch (error) {
|
||||
console.log(` Error checking template ${template.id}:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' FTS sync check error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
debug().catch(console.error);
|
||||
}
|
||||
|
||||
export { debug };
|
||||
130
scripts/migrate-nodes-fts.ts
Normal file
130
scripts/migrate-nodes-fts.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as path from 'path';
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
import { logger } from '../src/utils/logger';
|
||||
|
||||
/**
|
||||
* Migrate existing database to add FTS5 support for nodes
|
||||
*/
|
||||
async function migrateNodesFTS() {
|
||||
logger.info('Starting nodes FTS5 migration...');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'nodes.db');
|
||||
const db = await createDatabaseAdapter(dbPath);
|
||||
|
||||
try {
|
||||
// Check if nodes_fts already exists
|
||||
const tableExists = db.prepare(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='nodes_fts'
|
||||
`).get();
|
||||
|
||||
if (tableExists) {
|
||||
logger.info('nodes_fts table already exists, skipping migration');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Creating nodes_fts virtual table...');
|
||||
|
||||
// Create the FTS5 virtual table
|
||||
db.prepare(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
||||
node_type,
|
||||
display_name,
|
||||
description,
|
||||
documentation,
|
||||
operations,
|
||||
content=nodes,
|
||||
content_rowid=rowid,
|
||||
tokenize='porter'
|
||||
)
|
||||
`).run();
|
||||
|
||||
// Populate the FTS table with existing data
|
||||
logger.info('Populating nodes_fts with existing data...');
|
||||
|
||||
const nodes = db.prepare('SELECT rowid, * FROM nodes').all() as any[];
|
||||
logger.info(`Migrating ${nodes.length} nodes to FTS index...`);
|
||||
|
||||
const insertStmt = db.prepare(`
|
||||
INSERT INTO nodes_fts(rowid, node_type, display_name, description, documentation, operations)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
for (const node of nodes) {
|
||||
insertStmt.run(
|
||||
node.rowid,
|
||||
node.node_type,
|
||||
node.display_name,
|
||||
node.description || '',
|
||||
node.documentation || '',
|
||||
node.operations || ''
|
||||
);
|
||||
}
|
||||
|
||||
// Create triggers to keep FTS in sync
|
||||
logger.info('Creating synchronization triggers...');
|
||||
|
||||
db.prepare(`
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_fts_insert AFTER INSERT ON nodes
|
||||
BEGIN
|
||||
INSERT INTO nodes_fts(rowid, node_type, display_name, description, documentation, operations)
|
||||
VALUES (new.rowid, new.node_type, new.display_name, new.description, new.documentation, new.operations);
|
||||
END
|
||||
`).run();
|
||||
|
||||
db.prepare(`
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_fts_update AFTER UPDATE ON nodes
|
||||
BEGIN
|
||||
UPDATE nodes_fts
|
||||
SET node_type = new.node_type,
|
||||
display_name = new.display_name,
|
||||
description = new.description,
|
||||
documentation = new.documentation,
|
||||
operations = new.operations
|
||||
WHERE rowid = new.rowid;
|
||||
END
|
||||
`).run();
|
||||
|
||||
db.prepare(`
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_fts_delete AFTER DELETE ON nodes
|
||||
BEGIN
|
||||
DELETE FROM nodes_fts WHERE rowid = old.rowid;
|
||||
END
|
||||
`).run();
|
||||
|
||||
// Test the FTS search
|
||||
logger.info('Testing FTS search...');
|
||||
|
||||
const testResults = db.prepare(`
|
||||
SELECT n.* FROM nodes n
|
||||
JOIN nodes_fts ON n.rowid = nodes_fts.rowid
|
||||
WHERE nodes_fts MATCH 'webhook'
|
||||
ORDER BY rank
|
||||
LIMIT 5
|
||||
`).all();
|
||||
|
||||
logger.info(`FTS test search found ${testResults.length} results for 'webhook'`);
|
||||
|
||||
// Persist if using sql.js
|
||||
if ('persist' in db) {
|
||||
logger.info('Persisting database changes...');
|
||||
(db as any).persist();
|
||||
}
|
||||
|
||||
logger.info('✅ FTS5 migration completed successfully!');
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Migration failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration
|
||||
migrateNodesFTS().catch(error => {
|
||||
logger.error('Migration error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
162
scripts/test-fts5-search.ts
Normal file
162
scripts/test-fts5-search.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
interface SearchTest {
|
||||
query: string;
|
||||
mode?: 'OR' | 'AND' | 'FUZZY';
|
||||
description: string;
|
||||
expectedTop?: string[];
|
||||
}
|
||||
|
||||
async function testFTS5Search() {
|
||||
console.log('Testing FTS5 Search Implementation\n');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const tests: SearchTest[] = [
|
||||
{
|
||||
query: 'webhook',
|
||||
description: 'Basic search - should return Webhook node first',
|
||||
expectedTop: ['nodes-base.webhook']
|
||||
},
|
||||
{
|
||||
query: 'http call',
|
||||
description: 'Multi-word OR search - should return HTTP Request node first',
|
||||
expectedTop: ['nodes-base.httpRequest']
|
||||
},
|
||||
{
|
||||
query: 'send message',
|
||||
mode: 'AND',
|
||||
description: 'AND mode - only nodes with both "send" AND "message"',
|
||||
},
|
||||
{
|
||||
query: 'slak',
|
||||
mode: 'FUZZY',
|
||||
description: 'FUZZY mode - should find Slack despite typo',
|
||||
expectedTop: ['nodes-base.slack']
|
||||
},
|
||||
{
|
||||
query: '"email trigger"',
|
||||
description: 'Exact phrase search with quotes',
|
||||
},
|
||||
{
|
||||
query: 'http',
|
||||
mode: 'FUZZY',
|
||||
description: 'FUZZY mode with common term',
|
||||
expectedTop: ['nodes-base.httpRequest']
|
||||
},
|
||||
{
|
||||
query: 'google sheets',
|
||||
mode: 'AND',
|
||||
description: 'AND mode - find Google Sheets node',
|
||||
expectedTop: ['nodes-base.googleSheets']
|
||||
},
|
||||
{
|
||||
query: 'webhook trigger',
|
||||
mode: 'OR',
|
||||
description: 'OR mode - should return nodes with either word',
|
||||
}
|
||||
];
|
||||
|
||||
let passedTests = 0;
|
||||
let failedTests = 0;
|
||||
|
||||
for (const test of tests) {
|
||||
console.log(`\n${test.description}`);
|
||||
console.log(`Query: "${test.query}" (Mode: ${test.mode || 'OR'})`);
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
try {
|
||||
const results = await server.executeTool('search_nodes', {
|
||||
query: test.query,
|
||||
mode: test.mode,
|
||||
limit: 5
|
||||
});
|
||||
|
||||
if (!results.results || results.results.length === 0) {
|
||||
console.log('❌ No results found');
|
||||
if (test.expectedTop) {
|
||||
failedTests++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found ${results.results.length} results:`);
|
||||
results.results.forEach((node: any, index: number) => {
|
||||
const marker = test.expectedTop && index === 0 && test.expectedTop.includes(node.nodeType) ? ' ✅' : '';
|
||||
console.log(` ${index + 1}. ${node.nodeType} - ${node.displayName}${marker}`);
|
||||
});
|
||||
|
||||
// Verify search mode is returned
|
||||
if (results.mode) {
|
||||
console.log(`\nSearch mode used: ${results.mode}`);
|
||||
}
|
||||
|
||||
// Check expected results
|
||||
if (test.expectedTop) {
|
||||
const firstResult = results.results[0];
|
||||
if (test.expectedTop.includes(firstResult.nodeType)) {
|
||||
console.log('✅ Test passed: Expected node found at top');
|
||||
passedTests++;
|
||||
} else {
|
||||
console.log('❌ Test failed: Expected node not at top');
|
||||
console.log(` Expected: ${test.expectedTop.join(' or ')}`);
|
||||
console.log(` Got: ${firstResult.nodeType}`);
|
||||
failedTests++;
|
||||
}
|
||||
} else {
|
||||
// Test without specific expectations
|
||||
console.log('✅ Search completed successfully');
|
||||
passedTests++;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ Error: ${error}`);
|
||||
failedTests++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('FTS5 Feature Tests');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
// Test FTS5-specific features
|
||||
console.log('\n1. Testing relevance ranking...');
|
||||
const webhookResult = await server.executeTool('search_nodes', {
|
||||
query: 'webhook',
|
||||
limit: 10
|
||||
});
|
||||
console.log(` Primary "Webhook" node position: #${webhookResult.results.findIndex((r: any) => r.nodeType === 'nodes-base.webhook') + 1}`);
|
||||
|
||||
console.log('\n2. Testing fuzzy matching with various typos...');
|
||||
const typoTests = ['webook', 'htpp', 'slck', 'googl sheet'];
|
||||
for (const typo of typoTests) {
|
||||
const result = await server.executeTool('search_nodes', {
|
||||
query: typo,
|
||||
mode: 'FUZZY',
|
||||
limit: 1
|
||||
});
|
||||
if (result.results.length > 0) {
|
||||
console.log(` "${typo}" → ${result.results[0].displayName} ✅`);
|
||||
} else {
|
||||
console.log(` "${typo}" → No results ❌`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log(`Test Summary: ${passedTests} passed, ${failedTests} failed`);
|
||||
console.log('='.repeat(50));
|
||||
|
||||
process.exit(failedTests > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
testFTS5Search().catch(error => {
|
||||
console.error('Test execution failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
76
scripts/test-fuzzy-fix.ts
Normal file
76
scripts/test-fuzzy-fix.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
async function testFuzzyFix() {
|
||||
console.log('Testing FUZZY mode fix...\n');
|
||||
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Test 1: FUZZY mode with typo
|
||||
console.log('Test 1: FUZZY mode with "slak" (typo for "slack")');
|
||||
const fuzzyResult = await server.executeTool('search_nodes', {
|
||||
query: 'slak',
|
||||
mode: 'FUZZY',
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log(`Results: ${fuzzyResult.results.length} found`);
|
||||
if (fuzzyResult.results.length > 0) {
|
||||
console.log('✅ FUZZY mode now finds results!');
|
||||
fuzzyResult.results.forEach((node: any, i: number) => {
|
||||
console.log(` ${i + 1}. ${node.nodeType} - ${node.displayName}`);
|
||||
});
|
||||
} else {
|
||||
console.log('❌ FUZZY mode still not working');
|
||||
}
|
||||
|
||||
// Test 2: AND mode with explanation
|
||||
console.log('\n\nTest 2: AND mode with "send message"');
|
||||
const andResult = await server.executeTool('search_nodes', {
|
||||
query: 'send message',
|
||||
mode: 'AND',
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log(`Results: ${andResult.results.length} found`);
|
||||
if (andResult.searchInfo) {
|
||||
console.log('✅ AND mode now includes search info:');
|
||||
console.log(` ${andResult.searchInfo.message}`);
|
||||
console.log(` Tip: ${andResult.searchInfo.tip}`);
|
||||
}
|
||||
|
||||
console.log('\nFirst 5 results:');
|
||||
andResult.results.slice(0, 5).forEach((node: any, i: number) => {
|
||||
console.log(` ${i + 1}. ${node.nodeType} - ${node.displayName}`);
|
||||
});
|
||||
|
||||
// Test 3: More typos
|
||||
console.log('\n\nTest 3: More FUZZY tests');
|
||||
const typos = ['htpp', 'webook', 'slck', 'emial'];
|
||||
|
||||
for (const typo of typos) {
|
||||
const result = await server.executeTool('search_nodes', {
|
||||
query: typo,
|
||||
mode: 'FUZZY',
|
||||
limit: 1
|
||||
});
|
||||
|
||||
if (result.results.length > 0) {
|
||||
console.log(`✅ "${typo}" → ${result.results[0].displayName}`);
|
||||
} else {
|
||||
console.log(`❌ "${typo}" → No results`);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
testFuzzyFix().catch(error => {
|
||||
console.error('Test failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
38
scripts/test-fuzzy-simple.ts
Normal file
38
scripts/test-fuzzy-simple.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
async function testSimple() {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Just test one query
|
||||
const result = await server.executeTool('search_nodes', {
|
||||
query: 'slak',
|
||||
mode: 'FUZZY',
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log('Query: "slak" (FUZZY mode)');
|
||||
console.log(`Results: ${result.results.length}`);
|
||||
|
||||
if (result.results.length === 0) {
|
||||
// Let's check with a lower threshold
|
||||
const serverAny = server as any;
|
||||
const slackNode = {
|
||||
node_type: 'nodes-base.slack',
|
||||
display_name: 'Slack',
|
||||
description: 'Consume Slack API'
|
||||
};
|
||||
const score = serverAny.calculateFuzzyScore(slackNode, 'slak');
|
||||
console.log(`\nSlack node score for "slak": ${score}`);
|
||||
console.log('Current threshold: 400');
|
||||
console.log('Should it match?', score >= 400 ? 'YES' : 'NO');
|
||||
} else {
|
||||
result.results.forEach((r: any, i: number) => {
|
||||
console.log(`${i + 1}. ${r.displayName}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
testSimple().catch(console.error);
|
||||
46
scripts/test-http-search.ts
Normal file
46
scripts/test-http-search.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
#\!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
async function testHttpSearch() {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
console.log('Testing search for "http"...\n');
|
||||
|
||||
const result = await server.executeTool('search_nodes', {
|
||||
query: 'http',
|
||||
limit: 50 // Get more results to see where HTTP Request is
|
||||
});
|
||||
|
||||
console.log(`Total results: ${result.results.length}\n`);
|
||||
|
||||
// Find HTTP Request node in results
|
||||
const httpRequestIndex = result.results.findIndex((r: any) =>
|
||||
r.nodeType === 'nodes-base.httpRequest'
|
||||
);
|
||||
|
||||
if (httpRequestIndex === -1) {
|
||||
console.log('❌ HTTP Request node NOT FOUND in results\!');
|
||||
} else {
|
||||
console.log(`✅ HTTP Request found at position ${httpRequestIndex + 1}`);
|
||||
}
|
||||
|
||||
console.log('\nTop 10 results:');
|
||||
result.results.slice(0, 10).forEach((r: any, i: number) => {
|
||||
console.log(`${i + 1}. ${r.nodeType} - ${r.displayName}`);
|
||||
});
|
||||
|
||||
// Also check LIKE search directly
|
||||
console.log('\n\nTesting LIKE search fallback:');
|
||||
const serverAny = server as any;
|
||||
const likeResult = await serverAny.searchNodesLIKE('http', 20);
|
||||
|
||||
console.log(`LIKE search found ${likeResult.results.length} results`);
|
||||
console.log('Top 5 LIKE results:');
|
||||
likeResult.results.slice(0, 5).forEach((r: any, i: number) => {
|
||||
console.log(`${i + 1}. ${r.nodeType} - ${r.displayName}`);
|
||||
});
|
||||
}
|
||||
|
||||
testHttpSearch().catch(console.error);
|
||||
113
scripts/test-mcp-search.ts
Normal file
113
scripts/test-mcp-search.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* Test MCP search behavior
|
||||
*/
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
import { TemplateService } from '../src/templates/template-service';
|
||||
import { TemplateRepository } from '../src/templates/template-repository';
|
||||
|
||||
async function testMCPSearch() {
|
||||
console.log('🔍 Testing MCP search behavior...\n');
|
||||
|
||||
// Set MCP_MODE to simulate Docker environment
|
||||
process.env.MCP_MODE = 'stdio';
|
||||
console.log('Environment: MCP_MODE =', process.env.MCP_MODE);
|
||||
|
||||
const db = await createDatabaseAdapter('./data/nodes.db');
|
||||
|
||||
// Test 1: Direct repository search
|
||||
console.log('\n1️⃣ Testing TemplateRepository directly:');
|
||||
const repo = new TemplateRepository(db);
|
||||
|
||||
try {
|
||||
const repoResults = repo.searchTemplates('webhook', 5);
|
||||
console.log(` Repository search returned: ${repoResults.length} results`);
|
||||
if (repoResults.length > 0) {
|
||||
console.log(` First result: ${repoResults[0].name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Repository search error:', error);
|
||||
}
|
||||
|
||||
// Test 2: Service layer search (what MCP uses)
|
||||
console.log('\n2️⃣ Testing TemplateService (MCP layer):');
|
||||
const service = new TemplateService(db);
|
||||
|
||||
try {
|
||||
const serviceResults = await service.searchTemplates('webhook', 5);
|
||||
console.log(` Service search returned: ${serviceResults.length} results`);
|
||||
if (serviceResults.length > 0) {
|
||||
console.log(` First result: ${serviceResults[0].name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Service search error:', error);
|
||||
}
|
||||
|
||||
// Test 3: Test with empty query
|
||||
console.log('\n3️⃣ Testing with empty query:');
|
||||
try {
|
||||
const emptyResults = await service.searchTemplates('', 5);
|
||||
console.log(` Empty query returned: ${emptyResults.length} results`);
|
||||
} catch (error) {
|
||||
console.log(' Empty query error:', error);
|
||||
}
|
||||
|
||||
// Test 4: Test getTemplatesForTask (which works)
|
||||
console.log('\n4️⃣ Testing getTemplatesForTask (control):');
|
||||
try {
|
||||
const taskResults = await service.getTemplatesForTask('webhook_processing');
|
||||
console.log(` Task search returned: ${taskResults.length} results`);
|
||||
if (taskResults.length > 0) {
|
||||
console.log(` First result: ${taskResults[0].name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Task search error:', error);
|
||||
}
|
||||
|
||||
// Test 5: Direct SQL queries
|
||||
console.log('\n5️⃣ Testing direct SQL queries:');
|
||||
try {
|
||||
// Count templates
|
||||
const count = db.prepare('SELECT COUNT(*) as count FROM templates').get() as { count: number };
|
||||
console.log(` Total templates: ${count.count}`);
|
||||
|
||||
// Test LIKE search
|
||||
const likeResults = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM templates
|
||||
WHERE name LIKE '%webhook%' OR description LIKE '%webhook%'
|
||||
`).get() as { count: number };
|
||||
console.log(` LIKE search for 'webhook': ${likeResults.count} results`);
|
||||
|
||||
// Check if FTS5 table exists
|
||||
const ftsExists = db.prepare(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='templates_fts'
|
||||
`).get() as { name: string } | undefined;
|
||||
console.log(` FTS5 table exists: ${ftsExists ? 'Yes' : 'No'}`);
|
||||
|
||||
if (ftsExists) {
|
||||
// Test FTS5 search
|
||||
try {
|
||||
const ftsResults = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM templates t
|
||||
JOIN templates_fts ON t.id = templates_fts.rowid
|
||||
WHERE templates_fts MATCH 'webhook'
|
||||
`).get() as { count: number };
|
||||
console.log(` FTS5 search for 'webhook': ${ftsResults.count} results`);
|
||||
} catch (ftsError) {
|
||||
console.log(` FTS5 search error:`, ftsError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Direct SQL error:', error);
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
testMCPSearch().catch(console.error);
|
||||
}
|
||||
|
||||
export { testMCPSearch };
|
||||
136
scripts/test-search-improvements.ts
Normal file
136
scripts/test-search-improvements.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
|
||||
interface SearchTestCase {
|
||||
query: string;
|
||||
expectedTop: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
async function testSearchImprovements() {
|
||||
console.log('Testing search improvements...\n');
|
||||
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const testCases: SearchTestCase[] = [
|
||||
{
|
||||
query: 'webhook',
|
||||
expectedTop: ['nodes-base.webhook'],
|
||||
description: 'Primary webhook node should appear first'
|
||||
},
|
||||
{
|
||||
query: 'http',
|
||||
expectedTop: ['nodes-base.httpRequest'],
|
||||
description: 'HTTP Request node should appear first'
|
||||
},
|
||||
{
|
||||
query: 'http call',
|
||||
expectedTop: ['nodes-base.httpRequest'],
|
||||
description: 'HTTP Request node should appear first for "http call"'
|
||||
},
|
||||
{
|
||||
query: 'slack',
|
||||
expectedTop: ['nodes-base.slack'],
|
||||
description: 'Slack node should appear first'
|
||||
},
|
||||
{
|
||||
query: 'email',
|
||||
expectedTop: ['nodes-base.emailSend', 'nodes-base.gmail', 'nodes-base.emailReadImap'],
|
||||
description: 'Email-related nodes should appear first'
|
||||
},
|
||||
{
|
||||
query: 'http request',
|
||||
expectedTop: ['nodes-base.httpRequest'],
|
||||
description: 'HTTP Request node should appear first for exact name'
|
||||
}
|
||||
];
|
||||
|
||||
let passedTests = 0;
|
||||
let failedTests = 0;
|
||||
|
||||
for (const testCase of testCases) {
|
||||
try {
|
||||
console.log(`\nTest: ${testCase.description}`);
|
||||
console.log(`Query: "${testCase.query}"`);
|
||||
|
||||
const results = await server.executeTool('search_nodes', {
|
||||
query: testCase.query,
|
||||
limit: 10
|
||||
});
|
||||
|
||||
if (!results.results || results.results.length === 0) {
|
||||
console.log('❌ No results found');
|
||||
failedTests++;
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found ${results.results.length} results`);
|
||||
console.log('Top 5 results:');
|
||||
|
||||
const top5 = results.results.slice(0, 5);
|
||||
top5.forEach((node: any, index: number) => {
|
||||
const isExpected = testCase.expectedTop.includes(node.nodeType);
|
||||
const marker = index === 0 && isExpected ? '✅' : index === 0 && !isExpected ? '❌' : '';
|
||||
console.log(` ${index + 1}. ${node.nodeType} - ${node.displayName} ${marker}`);
|
||||
});
|
||||
|
||||
// Check if any expected node appears in top position
|
||||
const firstResult = results.results[0];
|
||||
if (testCase.expectedTop.includes(firstResult.nodeType)) {
|
||||
console.log('✅ Test passed: Expected node found at top position');
|
||||
passedTests++;
|
||||
} else {
|
||||
console.log('❌ Test failed: Expected nodes not at top position');
|
||||
console.log(` Expected one of: ${testCase.expectedTop.join(', ')}`);
|
||||
console.log(` Got: ${firstResult.nodeType}`);
|
||||
failedTests++;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ Test failed with error: ${error}`);
|
||||
failedTests++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log(`Test Summary: ${passedTests} passed, ${failedTests} failed`);
|
||||
console.log('='.repeat(50));
|
||||
|
||||
// Test the old problematic queries to ensure improvement
|
||||
console.log('\n\nTesting Original Problem Scenarios:');
|
||||
console.log('=====================================\n');
|
||||
|
||||
// Test webhook query that was problematic
|
||||
console.log('1. Testing "webhook" query (was returning service-specific webhooks first):');
|
||||
const webhookResult = await server.executeTool('search_nodes', { query: 'webhook', limit: 10 });
|
||||
const webhookFirst = webhookResult.results[0];
|
||||
if (webhookFirst.nodeType === 'nodes-base.webhook') {
|
||||
console.log(' ✅ SUCCESS: Primary Webhook node now appears first!');
|
||||
} else {
|
||||
console.log(` ❌ FAILED: Got ${webhookFirst.nodeType} instead of nodes-base.webhook`);
|
||||
console.log(` First 3 results: ${webhookResult.results.slice(0, 3).map((r: any) => r.nodeType).join(', ')}`);
|
||||
}
|
||||
|
||||
// Test http call query
|
||||
console.log('\n2. Testing "http call" query (was not finding HTTP Request easily):');
|
||||
const httpCallResult = await server.executeTool('search_nodes', { query: 'http call', limit: 10 });
|
||||
const httpCallFirst = httpCallResult.results[0];
|
||||
if (httpCallFirst.nodeType === 'nodes-base.httpRequest') {
|
||||
console.log(' ✅ SUCCESS: HTTP Request node now appears first!');
|
||||
} else {
|
||||
console.log(` ❌ FAILED: Got ${httpCallFirst.nodeType} instead of nodes-base.httpRequest`);
|
||||
console.log(` First 3 results: ${httpCallResult.results.slice(0, 3).map((r: any) => r.nodeType).join(', ')}`);
|
||||
}
|
||||
|
||||
process.exit(failedTests > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
testSearchImprovements().catch(error => {
|
||||
console.error('Test execution failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user