@mcpskillsio/server 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +247 -1
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* - check_watched: Re-scan all watched repos
|
|
16
16
|
* - batch_check: Check up to 5 repos in one call (Pro)
|
|
17
17
|
* - auto_gate: "Should I install this?" → boolean + reason
|
|
18
|
+
* - build_stack: "What tools do I need?" → vetted stack from live trust data
|
|
18
19
|
*
|
|
19
20
|
* Free tier returns compact agent response.
|
|
20
21
|
* Full reports require MCPSKILLS_API_KEY env var.
|
|
@@ -30,6 +31,44 @@ import {
|
|
|
30
31
|
|
|
31
32
|
const API_BASE = "https://mcpskills.io/.netlify/functions";
|
|
32
33
|
const PACKAGES_URL = "https://mcpskills.io/.netlify/functions/packages";
|
|
34
|
+
const DATA_URL = "https://mcpskills.io/data/latest.json";
|
|
35
|
+
|
|
36
|
+
// --- Stack Builder: category definitions + keyword matching ---
|
|
37
|
+
// Mirrors data/registry.json categories but kept inline so the MCP server
|
|
38
|
+
// is fully self-contained (no fetch to a private data file).
|
|
39
|
+
const STACK_CATEGORIES = {
|
|
40
|
+
'ai-sdk': { name: 'AI SDK & LLM Tools', keywords: ['ai', 'llm', 'gpt', 'claude', 'anthropic', 'openai', 'chat', 'agent', 'embedding', 'langchain'] },
|
|
41
|
+
'mcp-server': { name: 'MCP Servers & Protocol', keywords: ['mcp', 'model-context-protocol', 'mcp-server', 'tool-server', 'skill'] },
|
|
42
|
+
'database': { name: 'Database & ORM', keywords: ['database', 'db', 'orm', 'sql', 'postgres', 'mysql', 'sqlite', 'prisma', 'drizzle', 'supabase', 'mongo'] },
|
|
43
|
+
'auth': { name: 'Authentication & Security', keywords: ['auth', 'authentication', 'login', 'oauth', 'jwt', 'session', 'nextauth', 'clerk', 'passport'] },
|
|
44
|
+
'payments': { name: 'Payments & Billing', keywords: ['payment', 'stripe', 'billing', 'checkout', 'subscription', 'commerce', 'lemon'] },
|
|
45
|
+
'ui': { name: 'UI Components & Design', keywords: ['ui', 'component', 'design', 'shadcn', 'radix', 'tailwind', 'css', 'frontend'] },
|
|
46
|
+
'email': { name: 'Email & Messaging', keywords: ['email', 'smtp', 'mail', 'resend', 'sendgrid', 'notification', 'messaging'] },
|
|
47
|
+
'testing': { name: 'Testing & Quality', keywords: ['test', 'testing', 'jest', 'vitest', 'playwright', 'cypress', 'e2e', 'unit-test'] },
|
|
48
|
+
'devops': { name: 'DevOps & Infrastructure', keywords: ['deploy', 'docker', 'ci', 'cd', 'infrastructure', 'kubernetes', 'monitoring', 'netlify', 'vercel'] },
|
|
49
|
+
'web-framework': { name: 'Web Frameworks', keywords: ['next', 'nextjs', 'express', 'fastify', 'hono', 'remix', 'nuxt', 'svelte', 'framework', 'server'] },
|
|
50
|
+
'validation': { name: 'Validation & Schema', keywords: ['validation', 'schema', 'zod', 'joi', 'yup', 'typebox', 'form'] },
|
|
51
|
+
'search': { name: 'Search & RAG', keywords: ['search', 'vector', 'rag', 'embedding', 'pinecone', 'chromadb', 'semantic', 'retrieval'] },
|
|
52
|
+
'scraping': { name: 'Web Scraping & Data', keywords: ['scrape', 'scraping', 'crawl', 'puppeteer', 'playwright', 'cheerio', 'data-extraction'] },
|
|
53
|
+
'file-system': { name: 'File System & Storage', keywords: ['file', 'storage', 's3', 'upload', 'filesystem', 'blob', 'bucket'] },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const STACK_CROSS_CATEGORY = {
|
|
57
|
+
'ai-sdk': ['mcp-server', 'database', 'search'],
|
|
58
|
+
'mcp-server': ['ai-sdk', 'auth', 'database'],
|
|
59
|
+
'database': ['auth', 'validation', 'web-framework'],
|
|
60
|
+
'auth': ['database', 'payments', 'web-framework'],
|
|
61
|
+
'payments': ['auth', 'email', 'web-framework'],
|
|
62
|
+
'ui': ['web-framework', 'testing', 'validation'],
|
|
63
|
+
'email': ['payments', 'auth'],
|
|
64
|
+
'testing': ['devops', 'ai-sdk'],
|
|
65
|
+
'devops': ['testing', 'database'],
|
|
66
|
+
'web-framework': ['database', 'auth', 'ui'],
|
|
67
|
+
'validation': ['database', 'ai-sdk'],
|
|
68
|
+
'search': ['ai-sdk', 'database'],
|
|
69
|
+
'scraping': ['database', 'ai-sdk'],
|
|
70
|
+
'file-system': ['database', 'devops'],
|
|
71
|
+
};
|
|
33
72
|
|
|
34
73
|
// --- API Client ---
|
|
35
74
|
|
|
@@ -357,7 +396,7 @@ function formatSafetyResult(data) {
|
|
|
357
396
|
const server = new Server(
|
|
358
397
|
{
|
|
359
398
|
name: "mcpskills",
|
|
360
|
-
version: "2.
|
|
399
|
+
version: "2.4.0",
|
|
361
400
|
},
|
|
362
401
|
{
|
|
363
402
|
capabilities: {
|
|
@@ -497,6 +536,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
497
536
|
required: ["repo"],
|
|
498
537
|
},
|
|
499
538
|
},
|
|
539
|
+
{
|
|
540
|
+
name: "build_stack",
|
|
541
|
+
description:
|
|
542
|
+
'Recommend a vetted stack of trusted tools for a described task. Describe what you\'re building (e.g., "Next.js app with auth, payments, and AI chat") and get back a curated list of the highest-scoring repos in each relevant category, pre-scored and ready to install. Returns tool names, trust scores, tiers, and install hints. Use this instead of guessing which tools to recommend — every suggestion is backed by live trust data.',
|
|
543
|
+
inputSchema: {
|
|
544
|
+
type: "object",
|
|
545
|
+
properties: {
|
|
546
|
+
description: {
|
|
547
|
+
type: "string",
|
|
548
|
+
description: 'What the user wants to build or accomplish. Can be a full sentence ("I need a Next.js app with auth and Stripe") or just keywords ("auth payments database mcp")',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
required: ["description"],
|
|
552
|
+
},
|
|
553
|
+
},
|
|
500
554
|
],
|
|
501
555
|
};
|
|
502
556
|
});
|
|
@@ -834,6 +888,198 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
834
888
|
return { content: [{ type: "text", text }] };
|
|
835
889
|
}
|
|
836
890
|
|
|
891
|
+
case "build_stack": {
|
|
892
|
+
const desc = args.description;
|
|
893
|
+
if (!desc || desc.trim().length === 0) {
|
|
894
|
+
return {
|
|
895
|
+
content: [{ type: "text", text: 'Describe what you\'re building. E.g., "Next.js app with auth, payments, and AI chat" or just "auth payments database".' }],
|
|
896
|
+
isError: true,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Parse description into lowercase tokens for keyword matching
|
|
901
|
+
const tokens = desc.toLowerCase()
|
|
902
|
+
.replace(/[^a-z0-9\s\-_.]/g, ' ')
|
|
903
|
+
.split(/\s+/)
|
|
904
|
+
.filter(t => t.length > 1);
|
|
905
|
+
|
|
906
|
+
// Match categories by keyword overlap
|
|
907
|
+
const matchedCategories = [];
|
|
908
|
+
for (const [catId, cat] of Object.entries(STACK_CATEGORIES)) {
|
|
909
|
+
const overlap = cat.keywords.filter(kw => tokens.some(t => t.includes(kw) || kw.includes(t)));
|
|
910
|
+
if (overlap.length > 0) {
|
|
911
|
+
matchedCategories.push({ id: catId, name: cat.name, matchCount: overlap.length, matchedKeywords: overlap });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// Sort by match strength
|
|
915
|
+
matchedCategories.sort((a, b) => b.matchCount - a.matchCount);
|
|
916
|
+
|
|
917
|
+
// If no direct matches, try to infer from common phrases
|
|
918
|
+
if (matchedCategories.length === 0) {
|
|
919
|
+
// Fallback: match any token that's a substring of any keyword
|
|
920
|
+
for (const [catId, cat] of Object.entries(STACK_CATEGORIES)) {
|
|
921
|
+
if (tokens.some(t => cat.name.toLowerCase().includes(t))) {
|
|
922
|
+
matchedCategories.push({ id: catId, name: cat.name, matchCount: 1, matchedKeywords: [] });
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (matchedCategories.length === 0) {
|
|
928
|
+
return {
|
|
929
|
+
content: [{
|
|
930
|
+
type: "text",
|
|
931
|
+
text: `Couldn't match "${desc}" to any tool categories. Try being more specific, e.g.:\n- "auth and payments for a Next.js app"\n- "database orm ai agent"\n- "mcp server testing devops"\n\nAvailable categories: ${Object.values(STACK_CATEGORIES).map(c => c.name).join(', ')}`,
|
|
932
|
+
}],
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Fetch the full scored dataset (CDN-cached, one call)
|
|
937
|
+
let allRepos = [];
|
|
938
|
+
try {
|
|
939
|
+
const res = await fetch(DATA_URL, {
|
|
940
|
+
headers: { 'Accept': 'application/json', 'User-Agent': 'mcpskills-mcp-server' },
|
|
941
|
+
});
|
|
942
|
+
if (res.ok) {
|
|
943
|
+
const data = await res.json();
|
|
944
|
+
allRepos = data.rows || [];
|
|
945
|
+
}
|
|
946
|
+
} catch {}
|
|
947
|
+
|
|
948
|
+
// Build keyword-to-category index for repo matching
|
|
949
|
+
// For each repo in the dataset, check if its description/key matches a category
|
|
950
|
+
function categorizeRepo(row) {
|
|
951
|
+
const searchText = `${row.key || ''} ${row.description || ''}`.toLowerCase();
|
|
952
|
+
const cats = [];
|
|
953
|
+
for (const [catId, cat] of Object.entries(STACK_CATEGORIES)) {
|
|
954
|
+
const hits = cat.keywords.filter(kw => searchText.includes(kw));
|
|
955
|
+
if (hits.length >= 1) cats.push(catId);
|
|
956
|
+
}
|
|
957
|
+
return cats;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// For each matched category, find the top-scored non-blocked repo
|
|
961
|
+
const stack = [];
|
|
962
|
+
const usedRepos = new Set();
|
|
963
|
+
|
|
964
|
+
for (const cat of matchedCategories) {
|
|
965
|
+
const candidates = allRepos
|
|
966
|
+
.filter(r => {
|
|
967
|
+
if (usedRepos.has(r.key)) return false;
|
|
968
|
+
if (r.tier === 'blocked') return false;
|
|
969
|
+
const repoCats = categorizeRepo(r);
|
|
970
|
+
return repoCats.includes(cat.id);
|
|
971
|
+
})
|
|
972
|
+
.sort((a, b) => (b.composite || 0) - (a.composite || 0));
|
|
973
|
+
|
|
974
|
+
const pick = candidates[0];
|
|
975
|
+
if (pick) {
|
|
976
|
+
usedRepos.add(pick.key);
|
|
977
|
+
stack.push({
|
|
978
|
+
category: cat.name,
|
|
979
|
+
categoryId: cat.id,
|
|
980
|
+
repo: pick.key,
|
|
981
|
+
score: pick.composite,
|
|
982
|
+
tier: pick.tier,
|
|
983
|
+
description: (pick.description || '').slice(0, 120),
|
|
984
|
+
});
|
|
985
|
+
} else {
|
|
986
|
+
stack.push({
|
|
987
|
+
category: cat.name,
|
|
988
|
+
categoryId: cat.id,
|
|
989
|
+
repo: null,
|
|
990
|
+
score: null,
|
|
991
|
+
tier: null,
|
|
992
|
+
description: 'No scored repos in this category yet',
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Add cross-category suggestions for any categories not already matched
|
|
998
|
+
const matchedIds = new Set(matchedCategories.map(c => c.id));
|
|
999
|
+
const suggestions = new Set();
|
|
1000
|
+
for (const cat of matchedCategories) {
|
|
1001
|
+
const adjacent = STACK_CROSS_CATEGORY[cat.id] || [];
|
|
1002
|
+
for (const adjId of adjacent) {
|
|
1003
|
+
if (!matchedIds.has(adjId) && !suggestions.has(adjId)) {
|
|
1004
|
+
suggestions.add(adjId);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const suggestedTools = [];
|
|
1010
|
+
for (const adjId of suggestions) {
|
|
1011
|
+
const catDef = STACK_CATEGORIES[adjId];
|
|
1012
|
+
if (!catDef) continue;
|
|
1013
|
+
const candidates = allRepos
|
|
1014
|
+
.filter(r => !usedRepos.has(r.key) && r.tier !== 'blocked' && categorizeRepo(r).includes(adjId))
|
|
1015
|
+
.sort((a, b) => (b.composite || 0) - (a.composite || 0));
|
|
1016
|
+
const pick = candidates[0];
|
|
1017
|
+
if (pick) {
|
|
1018
|
+
suggestedTools.push({
|
|
1019
|
+
category: catDef.name,
|
|
1020
|
+
repo: pick.key,
|
|
1021
|
+
score: pick.composite,
|
|
1022
|
+
tier: pick.tier,
|
|
1023
|
+
reason: `Often used alongside ${matchedCategories[0]?.name || 'your stack'}`,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
if (suggestedTools.length >= 3) break;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Compute stack trust score
|
|
1030
|
+
const scoredItems = stack.filter(s => typeof s.score === 'number');
|
|
1031
|
+
const avgScore = scoredItems.length > 0
|
|
1032
|
+
? +(scoredItems.reduce((sum, s) => sum + s.score, 0) / scoredItems.length).toFixed(1)
|
|
1033
|
+
: null;
|
|
1034
|
+
const allVerified = scoredItems.every(s => s.tier === 'verified');
|
|
1035
|
+
|
|
1036
|
+
// Format output
|
|
1037
|
+
const lines = [
|
|
1038
|
+
`# Recommended Stack`,
|
|
1039
|
+
``,
|
|
1040
|
+
`Based on: "${desc}"`,
|
|
1041
|
+
`Stack Trust Score: ${avgScore ?? '—'}/10${allVerified ? ' ✅ All Verified' : ''}`,
|
|
1042
|
+
``,
|
|
1043
|
+
`## Core Tools (${stack.length})`,
|
|
1044
|
+
];
|
|
1045
|
+
|
|
1046
|
+
for (const item of stack) {
|
|
1047
|
+
if (item.repo) {
|
|
1048
|
+
const icon = item.tier === 'verified' ? '✅' : item.tier === 'established' ? '🟡' : '⚪';
|
|
1049
|
+
lines.push(` ${icon} **${item.repo}** — ${item.score}/10 ${item.tier}`);
|
|
1050
|
+
if (item.description) lines.push(` ${item.description}`);
|
|
1051
|
+
lines.push(` Category: ${item.category}`);
|
|
1052
|
+
lines.push(` Score page: https://mcpskills.io/score/${encodeURI(item.repo)}`);
|
|
1053
|
+
} else {
|
|
1054
|
+
lines.push(` ❓ **${item.category}** — no scored repos yet`);
|
|
1055
|
+
}
|
|
1056
|
+
lines.push('');
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (suggestedTools.length > 0) {
|
|
1060
|
+
lines.push(`## You Might Also Need`);
|
|
1061
|
+
for (const sug of suggestedTools) {
|
|
1062
|
+
const icon = sug.tier === 'verified' ? '✅' : sug.tier === 'established' ? '🟡' : '⚪';
|
|
1063
|
+
lines.push(` ${icon} **${sug.repo}** — ${sug.score}/10 ${sug.tier}`);
|
|
1064
|
+
lines.push(` ${sug.reason}`);
|
|
1065
|
+
}
|
|
1066
|
+
lines.push('');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (apiKey) {
|
|
1070
|
+
lines.push(`---`);
|
|
1071
|
+
lines.push(`Full signal breakdown available for each tool via check_trust_score.`);
|
|
1072
|
+
} else {
|
|
1073
|
+
lines.push(`---`);
|
|
1074
|
+
lines.push(`Set MCPSKILLS_API_KEY for full 14-signal reports on each tool.`);
|
|
1075
|
+
lines.push(`Get a key at https://mcpskills.io`);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
lines.push('', 'Powered by mcpskills.io — every recommendation backed by live trust data.');
|
|
1079
|
+
|
|
1080
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
837
1083
|
default:
|
|
838
1084
|
return {
|
|
839
1085
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcpskillsio/server",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client. Accepts GitHub repos, npm packages, Smithery URLs, and OpenClaw skills.
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client. Accepts GitHub repos, npm packages, Smithery URLs, and OpenClaw skills. 15 signals (incl. OSV/KEV/EPSS vulnerability intelligence), safety scanning, OpenClaw frontmatter parsing + transparency scoring, recommendations, badges, monitoring, batch checking, auto-gate decisions, and stack building from live trust data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|