@mcpskillsio/server 1.0.1 → 2.1.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 (3) hide show
  1. package/README.md +37 -5
  2. package/index.js +433 -45
  3. package/package.json +6 -4
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client.
4
4
 
5
- 12 signals across 4 dimensions with safety scanning for prompt injection, credential theft, and supply chain attacks.
5
+ 14 signals across 4 dimensions with safety scanning for prompt injection, credential theft, and supply chain attacks.
6
6
 
7
7
  ## Install
8
8
 
@@ -68,11 +68,43 @@ Browse curated, pre-scored skill packages organized by use case.
68
68
  "Show me safe AI skill packages for full-stack development"
69
69
  ```
70
70
 
71
+ ### `get_badge`
72
+
73
+ Generate an SVG trust badge URL for your README.
74
+
75
+ ```
76
+ "Get a trust badge for my repo anthropics/anthropic-sdk-typescript"
77
+ ```
78
+
79
+ ### `watch_repo`
80
+
81
+ Start monitoring a repo for trust score changes (requires API key).
82
+
83
+ ```
84
+ "Watch modelcontextprotocol/servers for score changes"
85
+ ```
86
+
87
+ ### `batch_check`
88
+
89
+ Score up to 5 repos in a single call (Pro tier).
90
+
91
+ ```
92
+ "Batch check these repos: anthropics/anthropic-sdk-typescript, langchain-ai/langchainjs"
93
+ ```
94
+
95
+ ### `auto_gate`
96
+
97
+ Get a boolean go/no-go decision with reasoning.
98
+
99
+ ```
100
+ "Should I install this MCP server? 21st-dev/magic-mcp"
101
+ ```
102
+
71
103
  ## Full Reports
72
104
 
73
105
  Free tier returns trust tier + dimension scores (same as mcpskills.io free scans).
74
106
 
75
- For full 12-signal reports with detailed safety findings inside your IDE, set your API key:
107
+ For full 14-signal reports with detailed safety findings inside your IDE, set your API key:
76
108
 
77
109
  ```bash
78
110
  export MCPSKILLS_API_KEY=your_key_here
@@ -85,10 +117,10 @@ Get your API key at [mcpskills.io/api](https://mcpskills.io/api).
85
117
  The server calls the mcpskills.io trust scoring API, which:
86
118
 
87
119
  1. Fetches repo data from GitHub API and OpenSSF Scorecard
88
- 2. Scores 12 signals across 4 dimensions (Alive, Legit, Solid, Usable)
89
- 3. Detects AI skills/MCP servers and activates Skills Mode
120
+ 2. Scores 14 signals across 4 dimensions (Alive, Legit, Solid, Usable)
121
+ 3. Detects AI skills/MCP servers and activates Skills Mode (+2 bonus signals)
90
122
  4. Runs 5 safety scans based on ClawHavoc and ToxicSkills attack patterns
91
- 5. Assigns a trust tier: Verified (7.5), Established (4.5), New, or Blocked
123
+ 5. Assigns a trust tier: Verified (>=7.0), Established (>=4.5), New, or Blocked
92
124
 
93
125
  ## License
94
126
 
package/index.js CHANGED
@@ -10,10 +10,15 @@
10
10
  * - check_trust_score: Score any GitHub repo
11
11
  * - scan_safety: Run safety-only scan for AI skills
12
12
  * - list_packages: Browse curated safe skill packages
13
+ * - get_badge: Get trust badge URL for READMEs
14
+ * - watch_repo: Monitor a repo for score changes
15
+ * - check_watched: Re-scan all watched repos
16
+ * - batch_check: Check up to 5 repos in one call (Pro)
17
+ * - auto_gate: "Should I install this?" → boolean + reason
13
18
  *
14
- * Free tier returns tier + dimension scores.
15
- * Full reports (all 12 signals + safety findings) require an API key.
16
- * Get your key at https://mcpskills.io/api
19
+ * Free tier returns compact agent response.
20
+ * Full reports require MCPSKILLS_API_KEY env var.
21
+ * Get your key at https://mcpskills.io
17
22
  */
18
23
 
19
24
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -29,7 +34,10 @@ const PACKAGES_URL = "https://mcpskills.io/.netlify/functions/packages";
29
34
  // --- API Client ---
30
35
 
31
36
  async function fetchScore(repo, apiKey) {
32
- const headers = { "Content-Type": "application/json" };
37
+ const headers = {
38
+ "Content-Type": "application/json",
39
+ "Accept": "application/json",
40
+ };
33
41
  if (apiKey) headers["X-API-Key"] = apiKey;
34
42
 
35
43
  const res = await fetch(`${API_BASE}/score`, {
@@ -38,6 +46,16 @@ async function fetchScore(repo, apiKey) {
38
46
  body: JSON.stringify({ repo }),
39
47
  });
40
48
 
49
+ if (res.status === 429) {
50
+ const err = await res.json().catch(() => ({}));
51
+ throw new Error(`Rate limit exceeded. ${err.upgrade || 'Set MCPSKILLS_API_KEY for more scans.'}`);
52
+ }
53
+
54
+ if (res.status === 401 || res.status === 403) {
55
+ const err = await res.json().catch(() => ({}));
56
+ throw new Error(err.error || `Auth error: ${res.status}`);
57
+ }
58
+
41
59
  if (!res.ok) {
42
60
  const err = await res.json().catch(() => ({ error: res.statusText }));
43
61
  throw new Error(err.error || `API error: ${res.status}`);
@@ -60,42 +78,68 @@ function formatTier(tier) {
60
78
  }
61
79
 
62
80
  function formatDimensions(dims) {
81
+ if (!dims) return " (dimensions unavailable)";
63
82
  return [
64
- ` Alive: ${dims.alive}/10 (maintained?)`,
65
- ` Legit: ${dims.legit}/10 (credible author?)`,
66
- ` Solid: ${dims.solid}/10 (secure?)`,
67
- ` Usable: ${dims.usable}/10 (good docs?)`,
83
+ ` Alive: ${dims.alive ?? '?'}/10 (maintained?)`,
84
+ ` Legit: ${dims.legit ?? '?'}/10 (credible author?)`,
85
+ ` Solid: ${dims.solid ?? '?'}/10 (secure?)`,
86
+ ` Usable: ${dims.usable ?? '?'}/10 (good docs?)`,
68
87
  ].join("\n");
69
88
  }
70
89
 
71
- function formatFreeResult(data) {
90
+ function formatRecommendations(recs) {
91
+ if (!recs || (!recs.alternatives?.length && !recs.crossCategory?.length)) return [];
92
+
93
+ const lines = ["", "## Recommendations"];
94
+
95
+ if (recs.alternatives?.length > 0) {
96
+ lines.push("");
97
+ lines.push(`**Alternatives in ${recs.categories?.[0]?.name || "this category"}:**`);
98
+ for (const alt of recs.alternatives) {
99
+ const scoreStr = alt.score ? ` — ${alt.score}/10 ${alt.tier ? formatTier(alt.tier) : ""}` : " — not yet scored";
100
+ lines.push(` → **${alt.repo}** (${alt.label})${scoreStr}`);
101
+ }
102
+ }
103
+
104
+ if (recs.crossCategory?.length > 0) {
105
+ lines.push("");
106
+ lines.push("**You might also need:**");
107
+ for (const sug of recs.crossCategory) {
108
+ const scoreStr = sug.score ? ` (${sug.score}/10)` : "";
109
+ lines.push(` → **${sug.repo}**${scoreStr} — ${sug.label} (${sug.reason})`);
110
+ }
111
+ }
112
+
113
+ return lines;
114
+ }
115
+
116
+ // --- Agent Response Formatting ---
117
+
118
+ function formatAgentResponse(data) {
119
+ // Compact agent response (from free tier API)
120
+ const rec = data.recommendation || (data.safe ? 'install' : 'caution');
121
+ const icon = data.safe ? '✅' : data.recommendation === 'blocked' ? '🚫' : '⚠️';
122
+
72
123
  const lines = [
73
- `# Trust Score: ${data.repo}`,
74
- "",
75
- `**Score:** ${data.composite}/10`,
76
- `**Tier:** ${formatTier(data.tier)}`,
77
- `**Mode:** ${data.mode === "skills" ? "Skills Mode (AI skill detected)" : "Standard Mode"}`,
78
- "",
79
- `## Dimensions`,
80
- formatDimensions(data.dimensions),
81
- "",
82
- `⭐ ${data.meta.stars.toLocaleString()} stars | 🍴 ${data.meta.forks.toLocaleString()} forks | 📄 ${data.meta.license}`,
124
+ `${icon} ${data.safe ? 'SAFE to install' : 'CAUTION'} — ${formatTier(data.tier)} (${data.score}/10)`,
125
+ `Recommendation: ${rec}`,
83
126
  ];
84
127
 
85
- if (data.mode === "skills" && data.skill) {
86
- lines.push("", `## Skill Info`);
87
- lines.push(` Type: ${data.skill.type}`);
88
- lines.push(` Safety: ${data.skill.safety?.summary || "Not scanned"}`);
128
+ if (data.flags?.length > 0) {
129
+ lines.push(`Flags: ${data.flags.join(', ')}`);
89
130
  }
90
131
 
91
- lines.push(
92
- "",
93
- "---",
94
- "🔒 Full report (all 12 signals + safety findings) → https://mcpskills.io",
95
- "Set MCPSKILLS_API_KEY for full reports in-context."
96
- );
132
+ if (data.reasoning) {
133
+ lines.push(`Analysis: ${data.reasoning}`);
134
+ }
97
135
 
98
- return lines.join("\n");
136
+ if (data.certified) {
137
+ lines.push('🏅 Certified Safe by MCP Skills');
138
+ }
139
+
140
+ lines.push('', 'Set MCPSKILLS_API_KEY for full 14-signal breakdown.');
141
+
142
+ return lines.join('\n');
99
143
  }
100
144
 
101
145
  function formatFullResult(data) {
@@ -118,6 +162,8 @@ function formatFullResult(data) {
118
162
  issue_responsiveness: "Issue Response",
119
163
  author_credibility: "Author Credibility",
120
164
  community_adoption: "Community Adoption",
165
+ contributor_diversity: "Contributor Diversity",
166
+ download_adoption: "Download Adoption",
121
167
  security_posture: "Security Posture",
122
168
  dependency_health: "Dependency Health",
123
169
  readme_quality: "README Quality",
@@ -125,23 +171,41 @@ function formatFullResult(data) {
125
171
  skill_spec_compliance: "Spec Compliance",
126
172
  license_clarity: "License Clarity",
127
173
  tool_safety: "Tool Safety",
174
+ supply_chain_safety: "Supply Chain Safety",
128
175
  };
129
176
 
130
- for (const [key, val] of Object.entries(data.signals)) {
177
+ for (const [key, val] of Object.entries(data.signals || {})) {
131
178
  const label = signalLabels[key] || key;
132
- const bar = "█".repeat(Math.round(val)) + "░".repeat(10 - Math.round(val));
133
- lines.push(` ${bar} ${val}/10 ${label}`);
179
+ const v = typeof val === 'number' && !isNaN(val) ? val : 0;
180
+ const bar = "█".repeat(Math.round(v)) + "░".repeat(10 - Math.round(v));
181
+ lines.push(` ${bar} ${v}/10 ${label}`);
134
182
  }
135
183
 
136
184
  lines.push(
137
185
  "",
138
- `⭐ ${data.meta.stars.toLocaleString()} stars | 🍴 ${data.meta.forks.toLocaleString()} forks | 📄 ${data.meta.license}`
186
+ `⭐ ${(data.meta?.stars || 0).toLocaleString()} stars | 🍴 ${(data.meta?.forks || 0).toLocaleString()} forks | 📄 ${data.meta?.license || 'Unknown'}`
139
187
  );
140
188
 
141
189
  if (data.mode === "skills" && data.skill) {
142
190
  lines.push("", `## Skill Details`);
143
191
  lines.push(` Type: ${data.skill.type}`);
144
- lines.push(` Indicators: ${data.skill.indicators.join(", ")}`);
192
+ if (data.skill.indicators) lines.push(` Indicators: ${data.skill.indicators.join(", ")}`);
193
+
194
+ // OpenClaw frontmatter
195
+ if (data.skill.frontmatter) {
196
+ lines.push("", ` ### SKILL.md Frontmatter`);
197
+ const fm = data.skill.frontmatter;
198
+ if (fm.name) lines.push(` Name: ${fm.name}`);
199
+ if (fm.version) lines.push(` Version: ${fm.version}`);
200
+ if (fm.description) lines.push(` Description: ${fm.description}`);
201
+ if (data.skill.transparency?.bonus > 0) {
202
+ lines.push(` 🛡️ Transparency bonus: +${data.skill.transparency.bonus} to tool_safety`);
203
+ const d = data.skill.transparency.details;
204
+ if (d.hasSecuritySection) lines.push(` ✓ Declares security posture`);
205
+ if (d.hasAllowedTools) lines.push(` ✓ Lists allowed tools (${d.allowedToolsCount})`);
206
+ if (d.hasRequires) lines.push(` ✓ Declares requirements`);
207
+ }
208
+ }
145
209
 
146
210
  if (data.skill.specChecks) {
147
211
  lines.push("", ` ### Spec Compliance`);
@@ -167,6 +231,10 @@ function formatFullResult(data) {
167
231
  lines.push("", `## ⚠️ Disqualifiers: ${data.disqualifiers.join(", ")}`);
168
232
  }
169
233
 
234
+ if (data.recommendations) {
235
+ lines.push(...formatRecommendations(data.recommendations));
236
+ }
237
+
170
238
  lines.push("", `Scanned at: ${data.scannedAt}`);
171
239
  lines.push("Powered by mcpskills.io");
172
240
 
@@ -175,14 +243,15 @@ function formatFullResult(data) {
175
243
 
176
244
  function formatSafetyResult(data) {
177
245
  if (data.mode !== "skills" || !data.skill) {
246
+ const score = data.composite ?? data.score ?? '?';
178
247
  return [
179
248
  `# Safety Scan: ${data.repo}`,
180
249
  "",
181
250
  "This repo was not detected as an AI skill or MCP server.",
182
251
  "Safety scanning only runs on repos identified as skills.",
183
252
  "",
184
- `Mode: ${data.mode}`,
185
- `Score: ${data.composite}/10 | Tier: ${formatTier(data.tier)}`,
253
+ `Mode: ${data.mode || 'standard'}`,
254
+ `Score: ${score}/10 | Tier: ${formatTier(data.tier)}`,
186
255
  ].join("\n");
187
256
  }
188
257
 
@@ -190,7 +259,7 @@ function formatSafetyResult(data) {
190
259
  `# Safety Scan: ${data.repo}`,
191
260
  "",
192
261
  `**Skill Type:** ${data.skill.type}`,
193
- `**Safety Score:** ${data.signals.tool_safety}/10`,
262
+ `**Safety Score:** ${data.signals?.tool_safety ?? '?'}/10`,
194
263
  `**Status:** ${data.skill.safety?.summary || "Unknown"}`,
195
264
  "",
196
265
  ];
@@ -239,7 +308,7 @@ function formatSafetyResult(data) {
239
308
  const server = new Server(
240
309
  {
241
310
  name: "mcpskills",
242
- version: "1.0.0",
311
+ version: "2.1.0",
243
312
  },
244
313
  {
245
314
  capabilities: {
@@ -255,7 +324,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
255
324
  {
256
325
  name: "check_trust_score",
257
326
  description:
258
- "Score any GitHub repo for trustworthiness. Returns a trust score (0-10) across 4 dimensions: Alive (maintained?), Legit (credible author?), Solid (secure?), Usable (good docs?). AI skills and MCP servers get enhanced scanning with 5 safety checks. Set MCPSKILLS_API_KEY env var for full 12-signal reports.",
327
+ "Score any GitHub repo for trustworthiness. Returns a trust score (0-10) across 4 dimensions: Alive (maintained?), Legit (credible author?), Solid (secure?), Usable (good docs?). AI skills and MCP servers get enhanced scanning with 5 safety checks. Set MCPSKILLS_API_KEY env var for full 14-signal reports.",
259
328
  inputSchema: {
260
329
  type: "object",
261
330
  properties: {
@@ -299,6 +368,86 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
299
368
  },
300
369
  },
301
370
  },
371
+ {
372
+ name: "get_badge",
373
+ description:
374
+ "Get a trust badge URL for any GitHub repo. Returns a shields.io-style SVG badge showing the trust score and tier. Embed in READMEs to show verified trust status. Badge auto-updates hourly.",
375
+ inputSchema: {
376
+ type: "object",
377
+ properties: {
378
+ repo: {
379
+ type: "string",
380
+ description: 'GitHub repo in "owner/repo" format',
381
+ },
382
+ },
383
+ required: ["repo"],
384
+ },
385
+ },
386
+ {
387
+ name: "watch_repo",
388
+ description:
389
+ "Start monitoring a repo for trust score changes. You'll be alerted when a repo's score changes significantly (±0.3 points or tier change). Requires a paid API key.",
390
+ inputSchema: {
391
+ type: "object",
392
+ properties: {
393
+ repo: {
394
+ type: "string",
395
+ description: 'GitHub repo in "owner/repo" format',
396
+ },
397
+ email: {
398
+ type: "string",
399
+ description: "Email address for alerts",
400
+ },
401
+ },
402
+ required: ["repo", "email"],
403
+ },
404
+ },
405
+ {
406
+ name: "check_watched",
407
+ description:
408
+ "Re-scan all watched repos and check for score changes. Returns any repos whose trust score changed significantly since last check.",
409
+ inputSchema: {
410
+ type: "object",
411
+ properties: {
412
+ email: {
413
+ type: "string",
414
+ description: "Email address used when watching repos",
415
+ },
416
+ },
417
+ required: ["email"],
418
+ },
419
+ },
420
+ {
421
+ name: "batch_check",
422
+ description:
423
+ "Check up to 5 repos in one call. Returns a trust assessment for each repo. Requires a Pro API key (MCPSKILLS_API_KEY). Great for bulk-vetting dependencies.",
424
+ inputSchema: {
425
+ type: "object",
426
+ properties: {
427
+ repos: {
428
+ type: "array",
429
+ items: { type: "string" },
430
+ description: 'Array of GitHub repos in "owner/repo" format (max 5)',
431
+ },
432
+ },
433
+ required: ["repos"],
434
+ },
435
+ },
436
+ {
437
+ name: "auto_gate",
438
+ description:
439
+ 'Should I install this? Returns a simple go/no-go decision with reasoning. The fastest way to check if a repo is safe to use. Returns { proceed: true/false, reason: "..." }.',
440
+ inputSchema: {
441
+ type: "object",
442
+ properties: {
443
+ repo: {
444
+ type: "string",
445
+ description: 'GitHub repo in "owner/repo" format',
446
+ },
447
+ },
448
+ required: ["repo"],
449
+ },
450
+ },
302
451
  ],
303
452
  };
304
453
  });
@@ -325,9 +474,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
325
474
  }
326
475
 
327
476
  const data = await fetchScore(repo, apiKey);
328
- const formatted = apiKey
329
- ? formatFullResult(data)
330
- : formatFreeResult(data);
477
+
478
+ // Determine if we got a full response or agent compact response
479
+ let formatted;
480
+ if (data.signals && data.dimensions) {
481
+ // Full paid response
482
+ formatted = formatFullResult(data);
483
+ } else if (data.safe !== undefined) {
484
+ // Agent compact response
485
+ formatted = formatAgentResponse(data);
486
+ } else {
487
+ // Human free response — format it for the agent
488
+ formatted = `${formatTier(data.tier)} ${data.repo} — ${data.composite}/10\n\nSet MCPSKILLS_API_KEY for full signal breakdown.`;
489
+ }
331
490
 
332
491
  return { content: [{ type: "text", text: formatted }] };
333
492
  }
@@ -383,9 +542,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
383
542
  lines.push("");
384
543
  const items = pkg.skills || pkg.repos || [];
385
544
  for (const item of items) {
386
- const name = item.name || `${item.owner}/${item.repo}`;
545
+ const itemName = item.name || `${item.owner}/${item.repo}`;
387
546
  const desc = item.description || item.role || "";
388
- lines.push(` - **${name}** — ${desc}`);
547
+ lines.push(` - **${itemName}** — ${desc}`);
389
548
  }
390
549
  lines.push("");
391
550
  }
@@ -394,6 +553,235 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
394
553
  return { content: [{ type: "text", text: lines.join("\n") }] };
395
554
  }
396
555
 
556
+ case "get_badge": {
557
+ const repo = args.repo;
558
+ if (!repo || !repo.includes("/")) {
559
+ return {
560
+ content: [{ type: "text", text: 'Invalid repo format. Use "owner/repo".' }],
561
+ isError: true,
562
+ };
563
+ }
564
+
565
+ const badgeUrl = `https://mcpskills.io/.netlify/functions/badge?repo=${encodeURIComponent(repo)}`;
566
+ const reportUrl = `https://mcpskills.io/?repo=${encodeURIComponent(repo)}`;
567
+
568
+ const lines = [
569
+ `# Trust Badge for ${repo}`,
570
+ "",
571
+ "## Badge URL",
572
+ `\`${badgeUrl}\``,
573
+ "",
574
+ "## Embed in README.md",
575
+ "```markdown",
576
+ `[![MCP Skills Trust Score](${badgeUrl})](${reportUrl})`,
577
+ "```",
578
+ "",
579
+ "## Embed in HTML",
580
+ "```html",
581
+ `<a href="${reportUrl}"><img src="${badgeUrl}" alt="MCP Skills Trust Score" /></a>`,
582
+ "```",
583
+ "",
584
+ "The badge auto-updates hourly. It shows the current trust score, tier, and links to the full report.",
585
+ "",
586
+ "---",
587
+ "Verified by mcpskills.io",
588
+ ];
589
+
590
+ return { content: [{ type: "text", text: lines.join("\n") }] };
591
+ }
592
+
593
+ case "watch_repo": {
594
+ const repo = args.repo;
595
+ const email = args.email;
596
+ if (!repo || !repo.includes("/")) {
597
+ return {
598
+ content: [{ type: "text", text: 'Invalid repo format. Use "owner/repo".' }],
599
+ isError: true,
600
+ };
601
+ }
602
+ if (!email || !email.includes("@")) {
603
+ return {
604
+ content: [{ type: "text", text: "Invalid email address." }],
605
+ isError: true,
606
+ };
607
+ }
608
+
609
+ const headers = { "Content-Type": "application/json" };
610
+ if (apiKey) headers["X-API-Key"] = apiKey;
611
+
612
+ const res = await fetch(`${API_BASE}/monitor`, {
613
+ method: "POST",
614
+ headers,
615
+ body: JSON.stringify({ action: "watch", repo, email }),
616
+ });
617
+ const data = await res.json();
618
+
619
+ if (res.status === 403) {
620
+ return {
621
+ content: [{ type: "text", text: data.error || "Monitoring requires a paid plan. Get one at https://mcpskills.io" }],
622
+ isError: true,
623
+ };
624
+ }
625
+
626
+ const lines = [
627
+ `# Watching ${repo}`,
628
+ "",
629
+ `**Current Score:** ${data.currentScore ?? "Scanning..."}`,
630
+ `**Current Tier:** ${data.currentTier ? formatTier(data.currentTier) : "Unknown"}`,
631
+ `**Total Watched:** ${data.totalWatched || 1}`,
632
+ "",
633
+ `You'll be notified at ${email} when this repo's trust score changes by ±0.3 points or its tier changes.`,
634
+ "",
635
+ "Use **check_watched** to manually re-scan all your watched repos.",
636
+ ];
637
+
638
+ return { content: [{ type: "text", text: lines.join("\n") }] };
639
+ }
640
+
641
+ case "check_watched": {
642
+ const email = args.email;
643
+ if (!email || !email.includes("@")) {
644
+ return {
645
+ content: [{ type: "text", text: "Invalid email address." }],
646
+ isError: true,
647
+ };
648
+ }
649
+
650
+ const headers = { "Content-Type": "application/json" };
651
+ if (apiKey) headers["X-API-Key"] = apiKey;
652
+
653
+ const res = await fetch(`${API_BASE}/monitor`, {
654
+ method: "POST",
655
+ headers,
656
+ body: JSON.stringify({ action: "check", email }),
657
+ });
658
+ const data = await res.json();
659
+
660
+ const lines = [
661
+ `# Monitoring Report`,
662
+ "",
663
+ `**Repos Checked:** ${data.checked || 0}`,
664
+ `**Changes Detected:** ${data.changes?.length || 0}`,
665
+ "",
666
+ ];
667
+
668
+ if (data.changes?.length > 0) {
669
+ lines.push("## Score Changes");
670
+ for (const c of data.changes) {
671
+ const arrow = c.delta > 0 ? "↑" : "↓";
672
+ const emoji = c.delta > 0 ? "📈" : "📉";
673
+ lines.push(` ${emoji} **${c.repo}** ${c.oldScore} → ${c.newScore} (${arrow}${Math.abs(c.delta)})`);
674
+ if (c.tierChanged) {
675
+ lines.push(` Tier: ${c.oldTier} → ${c.newTier}`);
676
+ }
677
+ }
678
+ } else {
679
+ lines.push("✅ All watched repos are stable. No significant changes.");
680
+ }
681
+
682
+ lines.push("", "---", "Powered by mcpskills.io");
683
+ return { content: [{ type: "text", text: lines.join("\n") }] };
684
+ }
685
+
686
+ case "batch_check": {
687
+ const repos = args.repos;
688
+ if (!Array.isArray(repos) || repos.length === 0) {
689
+ return {
690
+ content: [{ type: "text", text: "Provide an array of repos to check." }],
691
+ isError: true,
692
+ };
693
+ }
694
+
695
+ if (!apiKey) {
696
+ return {
697
+ content: [{ type: "text", text: "batch_check requires a Pro API key. Set MCPSKILLS_API_KEY env var.\nGet one at https://mcpskills.io" }],
698
+ isError: true,
699
+ };
700
+ }
701
+
702
+ const batch = repos.slice(0, 5);
703
+ const results = [];
704
+
705
+ for (const repo of batch) {
706
+ if (!repo.includes("/")) {
707
+ results.push(`❌ ${repo} — invalid format`);
708
+ continue;
709
+ }
710
+ try {
711
+ const data = await fetchScore(repo, apiKey);
712
+ if (data.safe !== undefined) {
713
+ const icon = data.safe ? '✅' : data.recommendation === 'blocked' ? '🚫' : '⚠️';
714
+ results.push(`${icon} ${repo} — ${data.tier} (${data.score}/10) → ${data.recommendation}`);
715
+ } else if (data.signals) {
716
+ results.push(`${formatTier(data.tier)} ${repo} — ${data.composite}/10`);
717
+ } else {
718
+ results.push(`${formatTier(data.tier)} ${repo} — ${data.composite}/10`);
719
+ }
720
+ } catch (err) {
721
+ results.push(`❌ ${repo} — ${err.message}`);
722
+ }
723
+ }
724
+
725
+ const lines = [
726
+ `# Batch Trust Check (${batch.length} repos)`,
727
+ "",
728
+ ...results,
729
+ "",
730
+ `Checked ${batch.length} of ${repos.length} repos.`,
731
+ ];
732
+
733
+ if (repos.length > 5) {
734
+ lines.push(`⚠️ Max 5 per batch. ${repos.length - 5} skipped.`);
735
+ }
736
+
737
+ lines.push("", "---", "Powered by mcpskills.io");
738
+ return { content: [{ type: "text", text: lines.join("\n") }] };
739
+ }
740
+
741
+ case "auto_gate": {
742
+ const repo = args.repo;
743
+ if (!repo || !repo.includes("/")) {
744
+ return {
745
+ content: [{ type: "text", text: 'Invalid repo format. Use "owner/repo".' }],
746
+ isError: true,
747
+ };
748
+ }
749
+
750
+ const data = await fetchScore(repo, apiKey);
751
+
752
+ let proceed, reason;
753
+
754
+ if (data.safe !== undefined) {
755
+ // Agent compact response
756
+ proceed = data.safe || data.certified;
757
+ if (data.certified) {
758
+ reason = `Certified Safe — verified by MCP Skills (${data.score}/10)`;
759
+ } else if (data.safe) {
760
+ reason = `${data.tier} (${data.score}/10). ${data.reasoning || 'No disqualifiers.'}`;
761
+ } else {
762
+ reason = `${data.recommendation}: ${data.flags?.join(', ') || data.reasoning || 'Review needed.'}`;
763
+ }
764
+ } else {
765
+ // Full or human response — derive go/no-go
766
+ const tier = data.tier;
767
+ const disqualifiers = data.disqualifiers || [];
768
+ proceed = (tier === 'verified' || tier === 'established') && disqualifiers.length === 0;
769
+ const score = data.composite ?? data.score;
770
+ reason = proceed
771
+ ? `${tier} (${score}/10). Safe to install.`
772
+ : `${tier} (${score}/10). ${disqualifiers.length ? 'Disqualifiers: ' + disqualifiers.join(', ') : 'Review recommended.'}`;
773
+ }
774
+
775
+ const icon = proceed ? '✅' : '🚫';
776
+ const text = [
777
+ `${icon} ${proceed ? 'PROCEED' : 'DO NOT INSTALL'} — ${repo}`,
778
+ '',
779
+ reason,
780
+ ].join('\n');
781
+
782
+ return { content: [{ type: "text", text }] };
783
+ }
784
+
397
785
  default:
398
786
  return {
399
787
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@mcpskillsio/server",
3
- "version": "1.0.1",
4
- "description": "Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client. 12 signals across 4 dimensions with safety scanning.",
3
+ "version": "2.1.0",
4
+ "description": "Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client. 14 signals, safety scanning, recommendations, badges, monitoring, batch checking, and auto-gate decisions.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
8
  "mcpskills": "index.js"
9
9
  },
10
10
  "scripts": {
11
- "start": "node index.js"
11
+ "start": "node index.js",
12
+ "test": "node --test test/"
12
13
  },
13
14
  "keywords": [
14
15
  "mcp",
@@ -24,7 +25,8 @@
24
25
  "license": "MIT",
25
26
  "repository": {
26
27
  "type": "git",
27
- "url": "https://github.com/bebravebekind/mcpskills-server"
28
+ "url": "https://github.com/BeBraveBeKind/mcpskills.git",
29
+ "directory": "mcp-server"
28
30
  },
29
31
  "homepage": "https://mcpskills.io",
30
32
  "engines": {