@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.
Files changed (2) hide show
  1. package/index.js +247 -1
  2. 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.2.0",
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.2.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. 14 signals, safety scanning, recommendations, badges, monitoring, batch checking, and auto-gate decisions.",
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": {