@mcpskillsio/server 2.0.0 → 2.1.1
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/README.md +37 -5
- package/index.js +235 -52
- 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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
15
|
-
* Full reports
|
|
16
|
-
* Get your key at https://mcpskills.io
|
|
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 = {
|
|
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,11 +78,12 @@ 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
|
|
|
@@ -94,39 +113,33 @@ function formatRecommendations(recs) {
|
|
|
94
113
|
return lines;
|
|
95
114
|
}
|
|
96
115
|
|
|
97
|
-
|
|
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
|
+
|
|
98
123
|
const lines = [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`**Score:** ${data.composite}/10`,
|
|
102
|
-
`**Tier:** ${formatTier(data.tier)}`,
|
|
103
|
-
`**Mode:** ${data.mode === "skills" ? "Skills Mode (AI skill detected)" : "Standard Mode"}`,
|
|
104
|
-
"",
|
|
105
|
-
`## Dimensions`,
|
|
106
|
-
formatDimensions(data.dimensions),
|
|
107
|
-
"",
|
|
108
|
-
`⭐ ${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}`,
|
|
109
126
|
];
|
|
110
127
|
|
|
111
|
-
if (data.
|
|
112
|
-
lines.push(
|
|
113
|
-
lines.push(` Type: ${data.skill.type}`);
|
|
114
|
-
lines.push(` Safety: ${data.skill.safety?.summary || "Not scanned"}`);
|
|
128
|
+
if (data.flags?.length > 0) {
|
|
129
|
+
lines.push(`Flags: ${data.flags.join(', ')}`);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
lines.push(...formatRecommendations(data.recommendations));
|
|
132
|
+
if (data.reasoning) {
|
|
133
|
+
lines.push(`Analysis: ${data.reasoning}`);
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"🔒 Full report (all signals + safety findings) → https://mcpskills.io",
|
|
126
|
-
"Set MCPSKILLS_API_KEY for full reports in-context."
|
|
127
|
-
);
|
|
136
|
+
if (data.certified) {
|
|
137
|
+
lines.push('🏅 Certified Safe by MCP Skills');
|
|
138
|
+
}
|
|
128
139
|
|
|
129
|
-
|
|
140
|
+
lines.push('', 'Set MCPSKILLS_API_KEY for full 14-signal breakdown.');
|
|
141
|
+
|
|
142
|
+
return lines.join('\n');
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
function formatFullResult(data) {
|
|
@@ -161,21 +174,38 @@ function formatFullResult(data) {
|
|
|
161
174
|
supply_chain_safety: "Supply Chain Safety",
|
|
162
175
|
};
|
|
163
176
|
|
|
164
|
-
for (const [key, val] of Object.entries(data.signals)) {
|
|
177
|
+
for (const [key, val] of Object.entries(data.signals || {})) {
|
|
165
178
|
const label = signalLabels[key] || key;
|
|
166
|
-
const
|
|
167
|
-
|
|
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}`);
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
lines.push(
|
|
171
185
|
"",
|
|
172
|
-
`⭐ ${data.meta
|
|
186
|
+
`⭐ ${(data.meta?.stars || 0).toLocaleString()} stars | 🍴 ${(data.meta?.forks || 0).toLocaleString()} forks | 📄 ${data.meta?.license || 'Unknown'}`
|
|
173
187
|
);
|
|
174
188
|
|
|
175
189
|
if (data.mode === "skills" && data.skill) {
|
|
176
190
|
lines.push("", `## Skill Details`);
|
|
177
191
|
lines.push(` Type: ${data.skill.type}`);
|
|
178
|
-
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
|
+
}
|
|
179
209
|
|
|
180
210
|
if (data.skill.specChecks) {
|
|
181
211
|
lines.push("", ` ### Spec Compliance`);
|
|
@@ -201,7 +231,6 @@ function formatFullResult(data) {
|
|
|
201
231
|
lines.push("", `## ⚠️ Disqualifiers: ${data.disqualifiers.join(", ")}`);
|
|
202
232
|
}
|
|
203
233
|
|
|
204
|
-
// Recommendations
|
|
205
234
|
if (data.recommendations) {
|
|
206
235
|
lines.push(...formatRecommendations(data.recommendations));
|
|
207
236
|
}
|
|
@@ -214,14 +243,15 @@ function formatFullResult(data) {
|
|
|
214
243
|
|
|
215
244
|
function formatSafetyResult(data) {
|
|
216
245
|
if (data.mode !== "skills" || !data.skill) {
|
|
246
|
+
const score = data.composite ?? data.score ?? '?';
|
|
217
247
|
return [
|
|
218
248
|
`# Safety Scan: ${data.repo}`,
|
|
219
249
|
"",
|
|
220
250
|
"This repo was not detected as an AI skill or MCP server.",
|
|
221
251
|
"Safety scanning only runs on repos identified as skills.",
|
|
222
252
|
"",
|
|
223
|
-
`Mode: ${data.mode}`,
|
|
224
|
-
`Score: ${
|
|
253
|
+
`Mode: ${data.mode || 'standard'}`,
|
|
254
|
+
`Score: ${score}/10 | Tier: ${formatTier(data.tier)}`,
|
|
225
255
|
].join("\n");
|
|
226
256
|
}
|
|
227
257
|
|
|
@@ -229,7 +259,7 @@ function formatSafetyResult(data) {
|
|
|
229
259
|
`# Safety Scan: ${data.repo}`,
|
|
230
260
|
"",
|
|
231
261
|
`**Skill Type:** ${data.skill.type}`,
|
|
232
|
-
`**Safety Score:** ${data.signals
|
|
262
|
+
`**Safety Score:** ${data.signals?.tool_safety ?? '?'}/10`,
|
|
233
263
|
`**Status:** ${data.skill.safety?.summary || "Unknown"}`,
|
|
234
264
|
"",
|
|
235
265
|
];
|
|
@@ -278,7 +308,7 @@ function formatSafetyResult(data) {
|
|
|
278
308
|
const server = new Server(
|
|
279
309
|
{
|
|
280
310
|
name: "mcpskills",
|
|
281
|
-
version: "2.
|
|
311
|
+
version: "2.1.0",
|
|
282
312
|
},
|
|
283
313
|
{
|
|
284
314
|
capabilities: {
|
|
@@ -294,7 +324,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
294
324
|
{
|
|
295
325
|
name: "check_trust_score",
|
|
296
326
|
description:
|
|
297
|
-
"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
|
|
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.",
|
|
298
328
|
inputSchema: {
|
|
299
329
|
type: "object",
|
|
300
330
|
properties: {
|
|
@@ -356,7 +386,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
356
386
|
{
|
|
357
387
|
name: "watch_repo",
|
|
358
388
|
description:
|
|
359
|
-
"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).
|
|
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.",
|
|
360
390
|
inputSchema: {
|
|
361
391
|
type: "object",
|
|
362
392
|
properties: {
|
|
@@ -387,6 +417,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
387
417
|
required: ["email"],
|
|
388
418
|
},
|
|
389
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
|
+
},
|
|
390
451
|
],
|
|
391
452
|
};
|
|
392
453
|
});
|
|
@@ -413,9 +474,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
413
474
|
}
|
|
414
475
|
|
|
415
476
|
const data = await fetchScore(repo, apiKey);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
}
|
|
419
490
|
|
|
420
491
|
return { content: [{ type: "text", text: formatted }] };
|
|
421
492
|
}
|
|
@@ -471,9 +542,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
471
542
|
lines.push("");
|
|
472
543
|
const items = pkg.skills || pkg.repos || [];
|
|
473
544
|
for (const item of items) {
|
|
474
|
-
const
|
|
545
|
+
const itemName = item.name || `${item.owner}/${item.repo}`;
|
|
475
546
|
const desc = item.description || item.role || "";
|
|
476
|
-
lines.push(` - **${
|
|
547
|
+
lines.push(` - **${itemName}** — ${desc}`);
|
|
477
548
|
}
|
|
478
549
|
lines.push("");
|
|
479
550
|
}
|
|
@@ -535,13 +606,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
535
606
|
};
|
|
536
607
|
}
|
|
537
608
|
|
|
609
|
+
const headers = { "Content-Type": "application/json" };
|
|
610
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
611
|
+
|
|
538
612
|
const res = await fetch(`${API_BASE}/monitor`, {
|
|
539
613
|
method: "POST",
|
|
540
|
-
headers
|
|
614
|
+
headers,
|
|
541
615
|
body: JSON.stringify({ action: "watch", repo, email }),
|
|
542
616
|
});
|
|
543
617
|
const data = await res.json();
|
|
544
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
|
+
|
|
545
626
|
const lines = [
|
|
546
627
|
`# Watching ${repo}`,
|
|
547
628
|
"",
|
|
@@ -566,9 +647,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
566
647
|
};
|
|
567
648
|
}
|
|
568
649
|
|
|
650
|
+
const headers = { "Content-Type": "application/json" };
|
|
651
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
652
|
+
|
|
569
653
|
const res = await fetch(`${API_BASE}/monitor`, {
|
|
570
654
|
method: "POST",
|
|
571
|
-
headers
|
|
655
|
+
headers,
|
|
572
656
|
body: JSON.stringify({ action: "check", email }),
|
|
573
657
|
});
|
|
574
658
|
const data = await res.json();
|
|
@@ -599,6 +683,105 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
599
683
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
600
684
|
}
|
|
601
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
|
+
|
|
602
785
|
default:
|
|
603
786
|
return {
|
|
604
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": "2.
|
|
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, and
|
|
3
|
+
"version": "2.1.1",
|
|
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/
|
|
28
|
+
"url": "https://github.com/BeBraveBeKind/mcpskills.git",
|
|
29
|
+
"directory": "mcp-server"
|
|
28
30
|
},
|
|
29
31
|
"homepage": "https://mcpskills.io",
|
|
30
32
|
"engines": {
|