Skip to content

Commit 9f57862

Browse files
committed
fix: search_tools natural language, get_tool/compare_tools case-insensitive slugs, compare UTM tags
1 parent 7a08685 commit 9f57862

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

index.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ const CATEGORIES = [
166166
const TOOL_DEFINITIONS = [
167167
{
168168
name: 'search_tools',
169-
description: 'Search 508+ software products by name, keyword, or use case. Returns name, category, rating, free plan availability, starting price, and ComparEdge URL.\n\nBEHAVIOR: Performs fuzzy matching across product names and categories. Returns up to `limit` results ranked by relevance. Each result includes a direct ComparEdge link.\n\nUSAGE GUIDELINES:\n- Use to discover tools when you do not know the exact slug.\n- Use before calling get_tool or get_pricing if the slug is uncertain.\n- Use for category browsing: query "crm", "ai coding", "project management".\n\nEXAMPLE QUERIES: "Find CRM tools", "search for Slack alternatives", "what project management tools exist?"',
169+
description: 'Search 495+ software products by name, keyword, category, or natural language query. Returns name, category, rating, free plan availability, starting price, and ComparEdge URL.\n\nBEHAVIOR: Scores each product against all meaningful keywords in the query (stopwords like "best", "find", "top" are ignored). Supports both exact product names and natural language queries.\n\nUSAGE GUIDELINES:\n- Use to discover tools when you do not know the exact slug.\n- Use before calling get_tool or get_pricing if the slug is uncertain.\n- Use for category browsing: query "crm", "ai coding", "project management".\n- Natural language works: "best CRM for startups" → extracts "crm" and "startups" keywords.\n\nEXAMPLE QUERIES: "notion", "CRM", "best CRM for startups", "project management free", "ai coding tools"',
170170
inputSchema: {
171171
type: 'object',
172172
properties: {
@@ -329,9 +329,15 @@ function fmtRow(label, v1, v2) {
329329

330330
async function searchTools(args) {
331331
const { query, limit = 5 } = args;
332-
const q = query.toLowerCase();
333332
const allTools = await getAllTools();
334333

334+
// Stopwords to ignore when splitting natural language queries
335+
const STOPWORDS = new Set(['find','best','top','what','are','the','for','a','an','to','in','of','and','or','with','use','using','good','great','cheap','free','open','source','tool','tools','software','app','apps','platform','service']);
336+
337+
// Split query into meaningful keywords, strip stopwords
338+
const qFull = query.toLowerCase();
339+
const qWords = qFull.split(/\s+/).filter(w => w.length > 1 && !STOPWORDS.has(w));
340+
335341
// Score each tool by relevance
336342
const scored = allTools
337343
.map(t => {
@@ -341,12 +347,22 @@ async function searchTools(args) {
341347
const desc = (t.description || '').toLowerCase();
342348
const cat = (t.categoryName || t.category || '').toLowerCase();
343349

344-
if (name === q) score += 100;
345-
else if (name.startsWith(q)) score += 60;
346-
else if (name.includes(q)) score += 40;
347-
if (slug.includes(q)) score += 30;
348-
if (desc.includes(q)) score += 20;
349-
if (cat.includes(q)) score += 15;
350+
// Exact full-query match (highest priority)
351+
if (name === qFull) score += 100;
352+
else if (name.startsWith(qFull)) score += 60;
353+
else if (name.includes(qFull)) score += 40;
354+
if (slug.includes(qFull)) score += 30;
355+
if (cat.includes(qFull)) score += 15;
356+
357+
// Individual keyword matches (natural language support)
358+
for (const w of qWords) {
359+
if (name === w) score += 50;
360+
else if (name.startsWith(w)) score += 30;
361+
else if (name.includes(w)) score += 20;
362+
if (slug.includes(w)) score += 15;
363+
if (cat.includes(w)) score += 10;
364+
if (desc.includes(w)) score += 5;
365+
}
350366

351367
return { t, score };
352368
})
@@ -374,7 +390,7 @@ async function searchTools(args) {
374390
}
375391

376392
async function getTool(args) {
377-
const { slug } = args;
393+
const slug = (args.slug || '').toLowerCase().trim();
378394
const [allTools, allPricing] = await Promise.all([getAllTools(), getAllPricing()]);
379395

380396
const t = allTools.find(x => x.slug === slug);
@@ -414,7 +430,8 @@ async function getTool(args) {
414430
}
415431

416432
async function compareTools(args) {
417-
const { tool1, tool2 } = args;
433+
const tool1 = (args.tool1 || '').toLowerCase().trim();
434+
const tool2 = (args.tool2 || '').toLowerCase().trim();
418435
const allTools = await getAllTools();
419436

420437
const t1 = allTools.find(x => x.slug === tool1);
@@ -449,9 +466,9 @@ async function compareTools(args) {
449466
p2.forEach(p => lines.push(` - ${p.name}: ${formatPrice(p.price)}`));
450467
}
451468

452-
lines.push(`\n${t1.name} URL: ${toolURL(tool1)}`);
453-
lines.push(`${t2.name} URL: ${toolURL(tool2)}`);
454-
lines.push(`Full comparison: ${SITE_BASE}/compare/${tool1}-vs-${tool2}`);
469+
lines.push(`\n${t1.name} URL: ${toolURL(tool1, true)}`);
470+
lines.push(`${t2.name} URL: ${toolURL(tool2, true)}`);
471+
lines.push(`Full comparison: ${SITE_BASE}/compare/${tool1}-vs-${tool2}?utm_source=mcp&utm_medium=ide&utm_campaign=comparedge-mcp`);
455472
return lines.join('\n');
456473
}
457474

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@comparedge/mcp-server",
3-
"version": "2.5.2",
3+
"version": "2.5.3",
44
"mcpName": "io.github.imkemit-ops/comparedge-mcp",
55
"description": "MCP server for ComparEdge: verified pricing, alternatives, and feature comparisons for 508+ SaaS and AI tools.",
66
"main": "index.js",

0 commit comments

Comments
 (0)