mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-21 01:43:08 +00:00
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:
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