@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.
- package/README.md +37 -5
- package/index.js +433 -45
- 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,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
|
|
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
|
-
|
|
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.
|
|
86
|
-
lines.push(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
133
|
-
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
545
|
+
const itemName = item.name || `${item.owner}/${item.repo}`;
|
|
387
546
|
const desc = item.description || item.role || "";
|
|
388
|
-
lines.push(` - **${
|
|
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
|
+
`[](${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
|
|
4
|
-
"description": "Trust-score any AI skill or MCP server from inside Claude Code, Cursor, or any MCP client.
|
|
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/
|
|
28
|
+
"url": "https://github.com/BeBraveBeKind/mcpskills.git",
|
|
29
|
+
"directory": "mcp-server"
|
|
28
30
|
},
|
|
29
31
|
"homepage": "https://mcpskills.io",
|
|
30
32
|
"engines": {
|