skillmds 1.1.2 → 1.2.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/CHANGELOG.md +84 -0
- package/README.md +89 -14
- package/bin/skillmd-mcp.mjs +2 -1020
- package/dist/chunk-A7BKAUUO.js +850 -0
- package/dist/cli.js +1073 -666
- package/dist/mcp.js +579 -0
- package/package.json +2 -3
package/bin/skillmd-mcp.mjs
CHANGED
|
@@ -1,1021 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// SkillMD MCP server (stdio)
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// claude mcp add skillmd -- npx -y skillmds
|
|
6
|
-
//
|
|
7
|
-
// Env:
|
|
8
|
-
// SKILLMD_API override API base (default https://api.skillmd.com)
|
|
9
|
-
// SKILLMD_TOKEN personal access token (enables skillmd_list_saved / personalized recommend)
|
|
10
|
-
|
|
11
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
16
|
-
import { createHash } from "node:crypto";
|
|
17
|
-
import { homedir } from "node:os";
|
|
18
|
-
import { join, resolve, dirname, sep } from "node:path";
|
|
19
|
-
|
|
20
|
-
const VERSION = (() => {
|
|
21
|
-
try {
|
|
22
|
-
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
23
|
-
} catch {
|
|
24
|
-
return "0.0.0";
|
|
25
|
-
}
|
|
26
|
-
})();
|
|
27
|
-
// Shared validation engine + registry helpers from @skillmds/core (a runtime
|
|
28
|
-
// dependency of this package). Falls back gracefully when running from an
|
|
29
|
-
// unbuilt source checkout.
|
|
30
|
-
let lint = null;
|
|
31
|
-
let skillMdFor = null;
|
|
32
|
-
let isAllowedSourceUrl = () => false;
|
|
33
|
-
try {
|
|
34
|
-
({ lint, skillMdFor, isAllowedSourceUrl } = await import("@skillmds/core"));
|
|
35
|
-
} catch {
|
|
36
|
-
/* skillmd_lint / skillmd_install will report that the engine is unavailable */
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const API = process.env.SKILLMD_API || "https://api.skillmd.com";
|
|
40
|
-
const TOKEN = process.env.SKILLMD_TOKEN || "";
|
|
41
|
-
|
|
42
|
-
async function api(path, init = {}) {
|
|
43
|
-
const headers = { accept: "application/json", ...(init.headers || {}) };
|
|
44
|
-
if (TOKEN) headers.authorization = `Bearer ${TOKEN}`;
|
|
45
|
-
const res = await fetch(API + path, { ...init, headers });
|
|
46
|
-
if (!res.ok) throw new Error(`SkillMD API ${res.status} on ${path}: ${await res.text().catch(() => "")}`);
|
|
47
|
-
return res.json();
|
|
48
|
-
}
|
|
49
|
-
// Tool results carry both a text rendering and structuredContent matching the
|
|
50
|
-
// tool's outputSchema. Arrays are wrapped as { items } so the structured payload
|
|
51
|
-
// is always an object, as the spec requires. Plain strings are messages only.
|
|
52
|
-
const text = (obj) => {
|
|
53
|
-
if (typeof obj === "string") return { content: [{ type: "text", text: obj }] };
|
|
54
|
-
const structured = Array.isArray(obj) ? { items: obj } : obj;
|
|
55
|
-
return { content: [{ type: "text", text: JSON.stringify(structured, null, 2) }], structuredContent: structured };
|
|
56
|
-
};
|
|
57
|
-
// A human-readable refusal/notice: text only, flagged as an error so clients do
|
|
58
|
-
// not expect structuredContent for it.
|
|
59
|
-
const notice = (msg) => ({ isError: true, content: [{ type: "text", text: msg }] });
|
|
60
|
-
|
|
61
|
-
// The public API returns site-oriented fields (ratings, counters, avatars,
|
|
62
|
-
// ranker internals) that are noise for an agent picking a skill. Strip them
|
|
63
|
-
// from every list-returning tool so results stay compact and rankable only
|
|
64
|
-
// on what matters to the caller.
|
|
65
|
-
const OMIT_FIELDS = new Set([
|
|
66
|
-
"type",
|
|
67
|
-
"verified",
|
|
68
|
-
"avg_rating",
|
|
69
|
-
"rating_count",
|
|
70
|
-
"install_count",
|
|
71
|
-
"save_count",
|
|
72
|
-
"owner_avatar",
|
|
73
|
-
"confident",
|
|
74
|
-
"category_slug",
|
|
75
|
-
]);
|
|
76
|
-
const trimItems = (items) => (items ?? []).map((s) => Object.fromEntries(Object.entries(s).filter(([k]) => !OMIT_FIELDS.has(k))));
|
|
77
|
-
|
|
78
|
-
const TOOLS = [
|
|
79
|
-
{
|
|
80
|
-
"name": "skillmd_search",
|
|
81
|
-
"title": "Search skills",
|
|
82
|
-
"description": "Search the SkillMD registry of Agent Skills (SKILL.md files) by task, name or keywords. Returns matching skills with their owner/name slug, description, category, safety verdict and install snippet, best match first.",
|
|
83
|
-
"inputSchema": {
|
|
84
|
-
"type": "object",
|
|
85
|
-
"properties": {
|
|
86
|
-
"query": {
|
|
87
|
-
"type": "string",
|
|
88
|
-
"description": "Natural-language query or keywords describing the task or skill"
|
|
89
|
-
},
|
|
90
|
-
"category": {
|
|
91
|
-
"type": "string",
|
|
92
|
-
"description": "Restrict to one category slug, e.g. coding, devops, data, security"
|
|
93
|
-
},
|
|
94
|
-
"verified_only": {
|
|
95
|
-
"type": "boolean",
|
|
96
|
-
"description": "Return only skills from verified publishers (default false)"
|
|
97
|
-
},
|
|
98
|
-
"type": {
|
|
99
|
-
"type": "string",
|
|
100
|
-
"enum": [
|
|
101
|
-
"single",
|
|
102
|
-
"pack"
|
|
103
|
-
],
|
|
104
|
-
"description": "Restrict to single-file skills or multi-file packs"
|
|
105
|
-
},
|
|
106
|
-
"min_rating": {
|
|
107
|
-
"type": "number",
|
|
108
|
-
"minimum": 0,
|
|
109
|
-
"maximum": 5,
|
|
110
|
-
"description": "Minimum average rating, 0 to 5"
|
|
111
|
-
},
|
|
112
|
-
"limit": {
|
|
113
|
-
"type": "integer",
|
|
114
|
-
"minimum": 1,
|
|
115
|
-
"maximum": 100,
|
|
116
|
-
"description": "Maximum number of results (default 20)"
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
"required": [
|
|
120
|
-
"query"
|
|
121
|
-
],
|
|
122
|
-
"additionalProperties": false
|
|
123
|
-
},
|
|
124
|
-
"outputSchema": {
|
|
125
|
-
"type": "object",
|
|
126
|
-
"properties": {
|
|
127
|
-
"items": {
|
|
128
|
-
"type": "array",
|
|
129
|
-
"items": {
|
|
130
|
-
"type": "object",
|
|
131
|
-
"description": "A skill as listed in search and leaderboard results.",
|
|
132
|
-
"properties": {
|
|
133
|
-
"slug": {
|
|
134
|
-
"type": "string",
|
|
135
|
-
"description": "owner/name identifier, e.g. anthropic/pdf"
|
|
136
|
-
},
|
|
137
|
-
"title": {
|
|
138
|
-
"type": "string",
|
|
139
|
-
"description": "Human-readable skill title"
|
|
140
|
-
},
|
|
141
|
-
"description": {
|
|
142
|
-
"type": "string",
|
|
143
|
-
"description": "What the skill does and when an agent should use it"
|
|
144
|
-
},
|
|
145
|
-
"category": {
|
|
146
|
-
"type": "string",
|
|
147
|
-
"description": "Category name"
|
|
148
|
-
},
|
|
149
|
-
"owner_handle": {
|
|
150
|
-
"type": "string",
|
|
151
|
-
"description": "Publisher handle"
|
|
152
|
-
},
|
|
153
|
-
"owner_name": {
|
|
154
|
-
"type": "string",
|
|
155
|
-
"description": "Publisher display name"
|
|
156
|
-
},
|
|
157
|
-
"license": {
|
|
158
|
-
"type": [
|
|
159
|
-
"string",
|
|
160
|
-
"null"
|
|
161
|
-
],
|
|
162
|
-
"description": "SPDX license id when declared"
|
|
163
|
-
},
|
|
164
|
-
"security_flags": {
|
|
165
|
-
"type": "array",
|
|
166
|
-
"items": {
|
|
167
|
-
"type": "string"
|
|
168
|
-
},
|
|
169
|
-
"description": "Capability flags: docs_only, network_calls, executes_scripts, reads_secrets, untrusted_install"
|
|
170
|
-
},
|
|
171
|
-
"ai_audit_verdict": {
|
|
172
|
-
"type": [
|
|
173
|
-
"string",
|
|
174
|
-
"null"
|
|
175
|
-
],
|
|
176
|
-
"description": "Safety review verdict: pass, caution, warning, fail or inconclusive"
|
|
177
|
-
},
|
|
178
|
-
"repo_stars": {
|
|
179
|
-
"type": [
|
|
180
|
-
"number",
|
|
181
|
-
"null"
|
|
182
|
-
],
|
|
183
|
-
"description": "GitHub stars of the source repository"
|
|
184
|
-
},
|
|
185
|
-
"install_snippet": {
|
|
186
|
-
"type": "string",
|
|
187
|
-
"description": "One-line install command"
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
"required": [
|
|
191
|
-
"slug"
|
|
192
|
-
],
|
|
193
|
-
"additionalProperties": true
|
|
194
|
-
},
|
|
195
|
-
"description": "Matching skills, best first"
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
"required": [
|
|
199
|
-
"items"
|
|
200
|
-
]
|
|
201
|
-
},
|
|
202
|
-
"annotations": {
|
|
203
|
-
"title": "Search skills",
|
|
204
|
-
"readOnlyHint": true,
|
|
205
|
-
"destructiveHint": false,
|
|
206
|
-
"idempotentHint": true,
|
|
207
|
-
"openWorldHint": false
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
"name": "skillmd_get",
|
|
212
|
-
"title": "Get skill details",
|
|
213
|
-
"description": "Fetch one skill by owner/name slug, including its SKILL.md body, provenance (source repo and pinned commit), license, safety verdict and capability flags.",
|
|
214
|
-
"inputSchema": {
|
|
215
|
-
"type": "object",
|
|
216
|
-
"properties": {
|
|
217
|
-
"slug": {
|
|
218
|
-
"type": "string",
|
|
219
|
-
"pattern": "^[^/]+/[^/]+$",
|
|
220
|
-
"description": "Skill identifier as owner/name, e.g. anthropic/pdf"
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
"required": [
|
|
224
|
-
"slug"
|
|
225
|
-
],
|
|
226
|
-
"additionalProperties": false
|
|
227
|
-
},
|
|
228
|
-
"outputSchema": {
|
|
229
|
-
"type": "object",
|
|
230
|
-
"description": "Full skill record.",
|
|
231
|
-
"properties": {
|
|
232
|
-
"slug": {
|
|
233
|
-
"type": "string",
|
|
234
|
-
"description": "owner/name identifier, e.g. anthropic/pdf"
|
|
235
|
-
},
|
|
236
|
-
"title": {
|
|
237
|
-
"type": "string",
|
|
238
|
-
"description": "Human-readable skill title"
|
|
239
|
-
},
|
|
240
|
-
"description": {
|
|
241
|
-
"type": "string",
|
|
242
|
-
"description": "What the skill does and when an agent should use it"
|
|
243
|
-
},
|
|
244
|
-
"category": {
|
|
245
|
-
"type": "string",
|
|
246
|
-
"description": "Category name"
|
|
247
|
-
},
|
|
248
|
-
"owner_handle": {
|
|
249
|
-
"type": "string",
|
|
250
|
-
"description": "Publisher handle"
|
|
251
|
-
},
|
|
252
|
-
"owner_name": {
|
|
253
|
-
"type": "string",
|
|
254
|
-
"description": "Publisher display name"
|
|
255
|
-
},
|
|
256
|
-
"license": {
|
|
257
|
-
"type": [
|
|
258
|
-
"string",
|
|
259
|
-
"null"
|
|
260
|
-
],
|
|
261
|
-
"description": "SPDX license id when declared"
|
|
262
|
-
},
|
|
263
|
-
"security_flags": {
|
|
264
|
-
"type": "array",
|
|
265
|
-
"items": {
|
|
266
|
-
"type": "string"
|
|
267
|
-
},
|
|
268
|
-
"description": "Capability flags: docs_only, network_calls, executes_scripts, reads_secrets, untrusted_install"
|
|
269
|
-
},
|
|
270
|
-
"ai_audit_verdict": {
|
|
271
|
-
"type": [
|
|
272
|
-
"string",
|
|
273
|
-
"null"
|
|
274
|
-
],
|
|
275
|
-
"description": "Safety review verdict: pass, caution, warning, fail or inconclusive"
|
|
276
|
-
},
|
|
277
|
-
"repo_stars": {
|
|
278
|
-
"type": [
|
|
279
|
-
"number",
|
|
280
|
-
"null"
|
|
281
|
-
],
|
|
282
|
-
"description": "GitHub stars of the source repository"
|
|
283
|
-
},
|
|
284
|
-
"install_snippet": {
|
|
285
|
-
"type": "string",
|
|
286
|
-
"description": "One-line install command"
|
|
287
|
-
},
|
|
288
|
-
"type": {
|
|
289
|
-
"type": "string",
|
|
290
|
-
"enum": [
|
|
291
|
-
"single",
|
|
292
|
-
"pack"
|
|
293
|
-
],
|
|
294
|
-
"description": "single SKILL.md or a multi-file pack"
|
|
295
|
-
},
|
|
296
|
-
"body_md": {
|
|
297
|
-
"type": "string",
|
|
298
|
-
"description": "Markdown body of SKILL.md"
|
|
299
|
-
},
|
|
300
|
-
"raw_md": {
|
|
301
|
-
"type": [
|
|
302
|
-
"string",
|
|
303
|
-
"null"
|
|
304
|
-
],
|
|
305
|
-
"description": "Verbatim SKILL.md including frontmatter when stored"
|
|
306
|
-
},
|
|
307
|
-
"verified": {
|
|
308
|
-
"type": "boolean",
|
|
309
|
-
"description": "Published by a verified publisher"
|
|
310
|
-
},
|
|
311
|
-
"source_repo": {
|
|
312
|
-
"type": [
|
|
313
|
-
"string",
|
|
314
|
-
"null"
|
|
315
|
-
],
|
|
316
|
-
"description": "Upstream repository"
|
|
317
|
-
},
|
|
318
|
-
"commit_sha": {
|
|
319
|
-
"type": [
|
|
320
|
-
"string",
|
|
321
|
-
"null"
|
|
322
|
-
],
|
|
323
|
-
"description": "Pinned upstream commit"
|
|
324
|
-
},
|
|
325
|
-
"files": {
|
|
326
|
-
"type": "array",
|
|
327
|
-
"description": "Companion files for packs",
|
|
328
|
-
"items": {
|
|
329
|
-
"type": "object",
|
|
330
|
-
"additionalProperties": true
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
"tags": {
|
|
334
|
-
"type": "array",
|
|
335
|
-
"items": {
|
|
336
|
-
"type": "object",
|
|
337
|
-
"additionalProperties": true
|
|
338
|
-
}
|
|
339
|
-
},
|
|
340
|
-
"categories": {
|
|
341
|
-
"type": "array",
|
|
342
|
-
"items": {
|
|
343
|
-
"type": "object",
|
|
344
|
-
"additionalProperties": true
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
},
|
|
348
|
-
"required": [
|
|
349
|
-
"slug",
|
|
350
|
-
"title",
|
|
351
|
-
"description"
|
|
352
|
-
],
|
|
353
|
-
"additionalProperties": true
|
|
354
|
-
},
|
|
355
|
-
"annotations": {
|
|
356
|
-
"title": "Get skill details",
|
|
357
|
-
"readOnlyHint": true,
|
|
358
|
-
"destructiveHint": false,
|
|
359
|
-
"idempotentHint": true,
|
|
360
|
-
"openWorldHint": false
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
"name": "skillmd_install",
|
|
365
|
-
"title": "Install skill",
|
|
366
|
-
"description": "Install a skill by owner/name slug: validates it, then writes SKILL.md (and companion files for packs, integrity-checked) into the agent's skills directory. Never executes scripts. The response reports the skill's capability flags (docs_only, network_calls, executes_scripts, reads_secrets) as information.",
|
|
367
|
-
"inputSchema": {
|
|
368
|
-
"type": "object",
|
|
369
|
-
"properties": {
|
|
370
|
-
"slug": {
|
|
371
|
-
"type": "string",
|
|
372
|
-
"pattern": "^[^/]+/[^/]+$",
|
|
373
|
-
"description": "Skill identifier as owner/name, e.g. anthropic/pdf"
|
|
374
|
-
},
|
|
375
|
-
"dest": {
|
|
376
|
-
"type": "string",
|
|
377
|
-
"description": "Skills directory to write into (default ~/.claude/skills)"
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
"required": [
|
|
381
|
-
"slug"
|
|
382
|
-
],
|
|
383
|
-
"additionalProperties": false
|
|
384
|
-
},
|
|
385
|
-
"outputSchema": {
|
|
386
|
-
"type": "object",
|
|
387
|
-
"properties": {
|
|
388
|
-
"installed": {
|
|
389
|
-
"type": "boolean",
|
|
390
|
-
"description": "Whether files were written"
|
|
391
|
-
},
|
|
392
|
-
"slug": {
|
|
393
|
-
"type": "string",
|
|
394
|
-
"description": "owner/name of the skill"
|
|
395
|
-
},
|
|
396
|
-
"dir": {
|
|
397
|
-
"type": "string",
|
|
398
|
-
"description": "Absolute directory the skill was written to"
|
|
399
|
-
},
|
|
400
|
-
"files_written": {
|
|
401
|
-
"type": "integer",
|
|
402
|
-
"description": "Number of files written"
|
|
403
|
-
},
|
|
404
|
-
"security_flags": {
|
|
405
|
-
"type": "array",
|
|
406
|
-
"items": {
|
|
407
|
-
"type": "string"
|
|
408
|
-
},
|
|
409
|
-
"description": "Capability flags reported for the skill"
|
|
410
|
-
},
|
|
411
|
-
"note": {
|
|
412
|
-
"type": "string",
|
|
413
|
-
"description": "Caveats, e.g. when only SKILL.md could be written"
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
"required": [
|
|
417
|
-
"installed",
|
|
418
|
-
"slug"
|
|
419
|
-
]
|
|
420
|
-
},
|
|
421
|
-
"annotations": {
|
|
422
|
-
"title": "Install skill",
|
|
423
|
-
"readOnlyHint": false,
|
|
424
|
-
"destructiveHint": false,
|
|
425
|
-
"idempotentHint": true,
|
|
426
|
-
"openWorldHint": false
|
|
427
|
-
}
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
"name": "skillmd_trending",
|
|
431
|
-
"title": "Trending skills",
|
|
432
|
-
"description": "Top skills on the SkillMD leaderboard, ranked by installs, saves and ratings, optionally within one category and time window.",
|
|
433
|
-
"inputSchema": {
|
|
434
|
-
"type": "object",
|
|
435
|
-
"properties": {
|
|
436
|
-
"range": {
|
|
437
|
-
"type": "string",
|
|
438
|
-
"enum": [
|
|
439
|
-
"30d",
|
|
440
|
-
"all"
|
|
441
|
-
],
|
|
442
|
-
"description": "Ranking window: last 30 days or all time (default all)"
|
|
443
|
-
},
|
|
444
|
-
"category": {
|
|
445
|
-
"type": "string",
|
|
446
|
-
"description": "Restrict to one category slug, e.g. coding, devops, data, security"
|
|
447
|
-
},
|
|
448
|
-
"limit": {
|
|
449
|
-
"type": "integer",
|
|
450
|
-
"minimum": 1,
|
|
451
|
-
"maximum": 100,
|
|
452
|
-
"description": "Maximum number of results (default 20)"
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
"additionalProperties": false
|
|
456
|
-
},
|
|
457
|
-
"outputSchema": {
|
|
458
|
-
"type": "object",
|
|
459
|
-
"properties": {
|
|
460
|
-
"items": {
|
|
461
|
-
"type": "array",
|
|
462
|
-
"items": {
|
|
463
|
-
"type": "object",
|
|
464
|
-
"description": "A skill as listed in search and leaderboard results.",
|
|
465
|
-
"properties": {
|
|
466
|
-
"slug": {
|
|
467
|
-
"type": "string",
|
|
468
|
-
"description": "owner/name identifier, e.g. anthropic/pdf"
|
|
469
|
-
},
|
|
470
|
-
"title": {
|
|
471
|
-
"type": "string",
|
|
472
|
-
"description": "Human-readable skill title"
|
|
473
|
-
},
|
|
474
|
-
"description": {
|
|
475
|
-
"type": "string",
|
|
476
|
-
"description": "What the skill does and when an agent should use it"
|
|
477
|
-
},
|
|
478
|
-
"category": {
|
|
479
|
-
"type": "string",
|
|
480
|
-
"description": "Category name"
|
|
481
|
-
},
|
|
482
|
-
"owner_handle": {
|
|
483
|
-
"type": "string",
|
|
484
|
-
"description": "Publisher handle"
|
|
485
|
-
},
|
|
486
|
-
"owner_name": {
|
|
487
|
-
"type": "string",
|
|
488
|
-
"description": "Publisher display name"
|
|
489
|
-
},
|
|
490
|
-
"license": {
|
|
491
|
-
"type": [
|
|
492
|
-
"string",
|
|
493
|
-
"null"
|
|
494
|
-
],
|
|
495
|
-
"description": "SPDX license id when declared"
|
|
496
|
-
},
|
|
497
|
-
"security_flags": {
|
|
498
|
-
"type": "array",
|
|
499
|
-
"items": {
|
|
500
|
-
"type": "string"
|
|
501
|
-
},
|
|
502
|
-
"description": "Capability flags: docs_only, network_calls, executes_scripts, reads_secrets, untrusted_install"
|
|
503
|
-
},
|
|
504
|
-
"ai_audit_verdict": {
|
|
505
|
-
"type": [
|
|
506
|
-
"string",
|
|
507
|
-
"null"
|
|
508
|
-
],
|
|
509
|
-
"description": "Safety review verdict: pass, caution, warning, fail or inconclusive"
|
|
510
|
-
},
|
|
511
|
-
"repo_stars": {
|
|
512
|
-
"type": [
|
|
513
|
-
"number",
|
|
514
|
-
"null"
|
|
515
|
-
],
|
|
516
|
-
"description": "GitHub stars of the source repository"
|
|
517
|
-
},
|
|
518
|
-
"install_snippet": {
|
|
519
|
-
"type": "string",
|
|
520
|
-
"description": "One-line install command"
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
"required": [
|
|
524
|
-
"slug"
|
|
525
|
-
],
|
|
526
|
-
"additionalProperties": true
|
|
527
|
-
},
|
|
528
|
-
"description": "Matching skills, best first"
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
"required": [
|
|
532
|
-
"items"
|
|
533
|
-
]
|
|
534
|
-
},
|
|
535
|
-
"annotations": {
|
|
536
|
-
"title": "Trending skills",
|
|
537
|
-
"readOnlyHint": true,
|
|
538
|
-
"destructiveHint": false,
|
|
539
|
-
"idempotentHint": true,
|
|
540
|
-
"openWorldHint": false
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
"name": "skillmd_recommend",
|
|
545
|
-
"title": "Recommend skills",
|
|
546
|
-
"description": "Recommend skills. With based_on set to a skill's owner/name slug, returns the top skills in that skill's category (excluding itself); without it, returns what is trending over the last 30 days.",
|
|
547
|
-
"inputSchema": {
|
|
548
|
-
"type": "object",
|
|
549
|
-
"properties": {
|
|
550
|
-
"based_on": {
|
|
551
|
-
"type": "string",
|
|
552
|
-
"pattern": "^[^/]+/[^/]+$",
|
|
553
|
-
"description": "Skill slug (owner/name) to base recommendations on"
|
|
554
|
-
},
|
|
555
|
-
"limit": {
|
|
556
|
-
"type": "integer",
|
|
557
|
-
"minimum": 1,
|
|
558
|
-
"maximum": 100,
|
|
559
|
-
"description": "Maximum number of results (default 10)"
|
|
560
|
-
}
|
|
561
|
-
},
|
|
562
|
-
"additionalProperties": false
|
|
563
|
-
},
|
|
564
|
-
"outputSchema": {
|
|
565
|
-
"type": "object",
|
|
566
|
-
"properties": {
|
|
567
|
-
"items": {
|
|
568
|
-
"type": "array",
|
|
569
|
-
"items": {
|
|
570
|
-
"type": "object",
|
|
571
|
-
"description": "A skill as listed in search and leaderboard results.",
|
|
572
|
-
"properties": {
|
|
573
|
-
"slug": {
|
|
574
|
-
"type": "string",
|
|
575
|
-
"description": "owner/name identifier, e.g. anthropic/pdf"
|
|
576
|
-
},
|
|
577
|
-
"title": {
|
|
578
|
-
"type": "string",
|
|
579
|
-
"description": "Human-readable skill title"
|
|
580
|
-
},
|
|
581
|
-
"description": {
|
|
582
|
-
"type": "string",
|
|
583
|
-
"description": "What the skill does and when an agent should use it"
|
|
584
|
-
},
|
|
585
|
-
"category": {
|
|
586
|
-
"type": "string",
|
|
587
|
-
"description": "Category name"
|
|
588
|
-
},
|
|
589
|
-
"owner_handle": {
|
|
590
|
-
"type": "string",
|
|
591
|
-
"description": "Publisher handle"
|
|
592
|
-
},
|
|
593
|
-
"owner_name": {
|
|
594
|
-
"type": "string",
|
|
595
|
-
"description": "Publisher display name"
|
|
596
|
-
},
|
|
597
|
-
"license": {
|
|
598
|
-
"type": [
|
|
599
|
-
"string",
|
|
600
|
-
"null"
|
|
601
|
-
],
|
|
602
|
-
"description": "SPDX license id when declared"
|
|
603
|
-
},
|
|
604
|
-
"security_flags": {
|
|
605
|
-
"type": "array",
|
|
606
|
-
"items": {
|
|
607
|
-
"type": "string"
|
|
608
|
-
},
|
|
609
|
-
"description": "Capability flags: docs_only, network_calls, executes_scripts, reads_secrets, untrusted_install"
|
|
610
|
-
},
|
|
611
|
-
"ai_audit_verdict": {
|
|
612
|
-
"type": [
|
|
613
|
-
"string",
|
|
614
|
-
"null"
|
|
615
|
-
],
|
|
616
|
-
"description": "Safety review verdict: pass, caution, warning, fail or inconclusive"
|
|
617
|
-
},
|
|
618
|
-
"repo_stars": {
|
|
619
|
-
"type": [
|
|
620
|
-
"number",
|
|
621
|
-
"null"
|
|
622
|
-
],
|
|
623
|
-
"description": "GitHub stars of the source repository"
|
|
624
|
-
},
|
|
625
|
-
"install_snippet": {
|
|
626
|
-
"type": "string",
|
|
627
|
-
"description": "One-line install command"
|
|
628
|
-
}
|
|
629
|
-
},
|
|
630
|
-
"required": [
|
|
631
|
-
"slug"
|
|
632
|
-
],
|
|
633
|
-
"additionalProperties": true
|
|
634
|
-
},
|
|
635
|
-
"description": "Matching skills, best first"
|
|
636
|
-
}
|
|
637
|
-
},
|
|
638
|
-
"required": [
|
|
639
|
-
"items"
|
|
640
|
-
]
|
|
641
|
-
},
|
|
642
|
-
"annotations": {
|
|
643
|
-
"title": "Recommend skills",
|
|
644
|
-
"readOnlyHint": true,
|
|
645
|
-
"destructiveHint": false,
|
|
646
|
-
"idempotentHint": true,
|
|
647
|
-
"openWorldHint": false
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
"name": "skillmd_list_saved",
|
|
652
|
-
"title": "List saved skills",
|
|
653
|
-
"description": "List the signed-in user's saved skills. Requires the SKILLMD_TOKEN environment variable (a SkillMD personal access token).",
|
|
654
|
-
"inputSchema": {
|
|
655
|
-
"type": "object",
|
|
656
|
-
"properties": {},
|
|
657
|
-
"additionalProperties": false
|
|
658
|
-
},
|
|
659
|
-
"outputSchema": {
|
|
660
|
-
"type": "object",
|
|
661
|
-
"properties": {
|
|
662
|
-
"items": {
|
|
663
|
-
"type": "array",
|
|
664
|
-
"items": {
|
|
665
|
-
"type": "object",
|
|
666
|
-
"description": "A skill as listed in search and leaderboard results.",
|
|
667
|
-
"properties": {
|
|
668
|
-
"slug": {
|
|
669
|
-
"type": "string",
|
|
670
|
-
"description": "owner/name identifier, e.g. anthropic/pdf"
|
|
671
|
-
},
|
|
672
|
-
"title": {
|
|
673
|
-
"type": "string",
|
|
674
|
-
"description": "Human-readable skill title"
|
|
675
|
-
},
|
|
676
|
-
"description": {
|
|
677
|
-
"type": "string",
|
|
678
|
-
"description": "What the skill does and when an agent should use it"
|
|
679
|
-
},
|
|
680
|
-
"category": {
|
|
681
|
-
"type": "string",
|
|
682
|
-
"description": "Category name"
|
|
683
|
-
},
|
|
684
|
-
"owner_handle": {
|
|
685
|
-
"type": "string",
|
|
686
|
-
"description": "Publisher handle"
|
|
687
|
-
},
|
|
688
|
-
"owner_name": {
|
|
689
|
-
"type": "string",
|
|
690
|
-
"description": "Publisher display name"
|
|
691
|
-
},
|
|
692
|
-
"license": {
|
|
693
|
-
"type": [
|
|
694
|
-
"string",
|
|
695
|
-
"null"
|
|
696
|
-
],
|
|
697
|
-
"description": "SPDX license id when declared"
|
|
698
|
-
},
|
|
699
|
-
"security_flags": {
|
|
700
|
-
"type": "array",
|
|
701
|
-
"items": {
|
|
702
|
-
"type": "string"
|
|
703
|
-
},
|
|
704
|
-
"description": "Capability flags: docs_only, network_calls, executes_scripts, reads_secrets, untrusted_install"
|
|
705
|
-
},
|
|
706
|
-
"ai_audit_verdict": {
|
|
707
|
-
"type": [
|
|
708
|
-
"string",
|
|
709
|
-
"null"
|
|
710
|
-
],
|
|
711
|
-
"description": "Safety review verdict: pass, caution, warning, fail or inconclusive"
|
|
712
|
-
},
|
|
713
|
-
"repo_stars": {
|
|
714
|
-
"type": [
|
|
715
|
-
"number",
|
|
716
|
-
"null"
|
|
717
|
-
],
|
|
718
|
-
"description": "GitHub stars of the source repository"
|
|
719
|
-
},
|
|
720
|
-
"install_snippet": {
|
|
721
|
-
"type": "string",
|
|
722
|
-
"description": "One-line install command"
|
|
723
|
-
}
|
|
724
|
-
},
|
|
725
|
-
"required": [
|
|
726
|
-
"slug"
|
|
727
|
-
],
|
|
728
|
-
"additionalProperties": true
|
|
729
|
-
},
|
|
730
|
-
"description": "Matching skills, best first"
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
|
-
"required": [
|
|
734
|
-
"items"
|
|
735
|
-
]
|
|
736
|
-
},
|
|
737
|
-
"annotations": {
|
|
738
|
-
"title": "List saved skills",
|
|
739
|
-
"readOnlyHint": true,
|
|
740
|
-
"destructiveHint": false,
|
|
741
|
-
"idempotentHint": true,
|
|
742
|
-
"openWorldHint": false
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
{
|
|
746
|
-
"name": "skillmd_lint",
|
|
747
|
-
"title": "Lint a SKILL.md",
|
|
748
|
-
"description": "Validate a SKILL.md against the SkillMD format and return diagnostics, capability flags and a 0-100 quality score. Pass raw SKILL.md content, or a registry slug (owner/name) to fetch and lint that skill.",
|
|
749
|
-
"inputSchema": {
|
|
750
|
-
"type": "object",
|
|
751
|
-
"properties": {
|
|
752
|
-
"content": {
|
|
753
|
-
"type": "string",
|
|
754
|
-
"description": "Raw SKILL.md text to validate (frontmatter plus body)"
|
|
755
|
-
},
|
|
756
|
-
"slug": {
|
|
757
|
-
"type": "string",
|
|
758
|
-
"pattern": "^[^/]+/[^/]+$",
|
|
759
|
-
"description": "Registry skill (owner/name) to fetch and validate instead of content"
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
"additionalProperties": false
|
|
763
|
-
},
|
|
764
|
-
"outputSchema": {
|
|
765
|
-
"type": "object",
|
|
766
|
-
"properties": {
|
|
767
|
-
"ok": {
|
|
768
|
-
"type": "boolean",
|
|
769
|
-
"description": "True when there are no error-level diagnostics"
|
|
770
|
-
},
|
|
771
|
-
"score": {
|
|
772
|
-
"type": "number",
|
|
773
|
-
"description": "Quality score 0-100"
|
|
774
|
-
},
|
|
775
|
-
"diagnostics": {
|
|
776
|
-
"type": "array",
|
|
777
|
-
"items": {
|
|
778
|
-
"type": "object",
|
|
779
|
-
"properties": {
|
|
780
|
-
"id": {
|
|
781
|
-
"type": "string",
|
|
782
|
-
"description": "Rule id, e.g. SK011"
|
|
783
|
-
},
|
|
784
|
-
"severity": {
|
|
785
|
-
"type": "string",
|
|
786
|
-
"enum": [
|
|
787
|
-
"error",
|
|
788
|
-
"warning",
|
|
789
|
-
"info"
|
|
790
|
-
]
|
|
791
|
-
},
|
|
792
|
-
"message": {
|
|
793
|
-
"type": "string"
|
|
794
|
-
},
|
|
795
|
-
"line": {
|
|
796
|
-
"type": [
|
|
797
|
-
"integer",
|
|
798
|
-
"null"
|
|
799
|
-
]
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
"required": [
|
|
803
|
-
"id",
|
|
804
|
-
"severity",
|
|
805
|
-
"message"
|
|
806
|
-
],
|
|
807
|
-
"additionalProperties": true
|
|
808
|
-
}
|
|
809
|
-
},
|
|
810
|
-
"security": {
|
|
811
|
-
"type": "object",
|
|
812
|
-
"description": "Scanner findings and capability flags",
|
|
813
|
-
"additionalProperties": true
|
|
814
|
-
}
|
|
815
|
-
},
|
|
816
|
-
"required": [
|
|
817
|
-
"ok",
|
|
818
|
-
"score",
|
|
819
|
-
"diagnostics"
|
|
820
|
-
]
|
|
821
|
-
},
|
|
822
|
-
"annotations": {
|
|
823
|
-
"title": "Lint a SKILL.md",
|
|
824
|
-
"readOnlyHint": true,
|
|
825
|
-
"destructiveHint": false,
|
|
826
|
-
"idempotentHint": true,
|
|
827
|
-
"openWorldHint": false
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
];
|
|
831
|
-
|
|
832
|
-
async function handleCall(name, args) {
|
|
833
|
-
switch (name) {
|
|
834
|
-
case "skillmd_search": {
|
|
835
|
-
const p = new URLSearchParams({ q: args.query, limit: String(args.limit ?? 20) });
|
|
836
|
-
if (args.category) p.set("category", args.category);
|
|
837
|
-
if (args.verified_only) p.set("verified", "true");
|
|
838
|
-
if (args.type) p.set("type", args.type);
|
|
839
|
-
if (args.min_rating != null) p.set("min_rating", String(args.min_rating));
|
|
840
|
-
const r = await api(`/api/search?${p}`);
|
|
841
|
-
return text(trimItems(r.items));
|
|
842
|
-
}
|
|
843
|
-
case "skillmd_get": {
|
|
844
|
-
const [owner, slugName] = String(args.slug).split("/");
|
|
845
|
-
const r = await api(`/api/skills/${owner}/${slugName}`);
|
|
846
|
-
return text(r);
|
|
847
|
-
}
|
|
848
|
-
case "skillmd_trending": {
|
|
849
|
-
const p = new URLSearchParams({ range: args.range ?? "all", limit: String(args.limit ?? 20) });
|
|
850
|
-
if (args.category) p.set("category", args.category);
|
|
851
|
-
const r = await api(`/api/leaderboard?${p}`);
|
|
852
|
-
return text(trimItems(r.items));
|
|
853
|
-
}
|
|
854
|
-
case "skillmd_recommend": {
|
|
855
|
-
if (args.based_on) {
|
|
856
|
-
const [owner, slugName] = String(args.based_on).split("/");
|
|
857
|
-
const base = await api(`/api/skills/${owner}/${slugName}`).catch(() => null);
|
|
858
|
-
if (base?.category_slug) {
|
|
859
|
-
const r = await api(`/api/leaderboard?range=all&limit=${args.limit ?? 10}&category=${base.category_slug}`);
|
|
860
|
-
return text(trimItems(r.items).filter((s) => s.slug !== args.based_on));
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
const r = await api(`/api/leaderboard?range=30d&limit=${args.limit ?? 10}`);
|
|
864
|
-
return text(trimItems(r.items));
|
|
865
|
-
}
|
|
866
|
-
case "skillmd_list_saved": {
|
|
867
|
-
if (!TOKEN) return notice("Set SKILLMD_TOKEN (a SkillMD personal access token) to list your saved skills.");
|
|
868
|
-
const r = await api(`/api/favorites`);
|
|
869
|
-
return text(r.items ?? []);
|
|
870
|
-
}
|
|
871
|
-
case "skillmd_lint": {
|
|
872
|
-
if (!lint) return notice("Validation engine unavailable — reinstall the skillmds package (npm i -g skillmds), or run `npm ci && npm run build` in a source checkout.");
|
|
873
|
-
let raw = args.content;
|
|
874
|
-
let slug;
|
|
875
|
-
let verified = false;
|
|
876
|
-
if (!raw && args.slug) {
|
|
877
|
-
const [owner, slugName] = String(args.slug).split("/");
|
|
878
|
-
const skill = await api(`/api/skills/${owner}/${slugName}`);
|
|
879
|
-
raw = skillMdFor(skill);
|
|
880
|
-
slug = slugName;
|
|
881
|
-
verified = !!skill.verified;
|
|
882
|
-
}
|
|
883
|
-
if (!raw) return notice("Provide `content` (raw SKILL.md) or `slug` (owner/name).");
|
|
884
|
-
const result = lint(raw, { ...(slug ? { slug } : {}), verified });
|
|
885
|
-
return text({ ok: result.ok, score: result.score, diagnostics: result.diagnostics, security: result.security });
|
|
886
|
-
}
|
|
887
|
-
case "skillmd_install": {
|
|
888
|
-
if (!skillMdFor) return notice("skillmds is not fully installed — reinstall it (npm i -g skillmds) and try again.");
|
|
889
|
-
const [owner, slugName] = String(args.slug).split("/");
|
|
890
|
-
// Defense in depth: slugName becomes a directory name below — refuse
|
|
891
|
-
// anything outside the registry's slugify alphabet (path separators,
|
|
892
|
-
// "..", etc.) before touching the network or filesystem.
|
|
893
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(slugName ?? "")) {
|
|
894
|
-
return notice(`Refused: invalid skill name "${slugName}" — expected a lowercase slug.`);
|
|
895
|
-
}
|
|
896
|
-
const skill = await api(`/api/skills/${owner}/${slugName}`);
|
|
897
|
-
const flags = skill.security_flags ?? [];
|
|
898
|
-
// Verification never gates installs — any skill installs; flags are
|
|
899
|
-
// reported in the response as information, not a wall.
|
|
900
|
-
const raw = skillMdFor(skill); // returns raw_md verbatim when the registry has it
|
|
901
|
-
if (lint) {
|
|
902
|
-
const result = lint(raw, { slug: slugName });
|
|
903
|
-
if (!result.ok) {
|
|
904
|
-
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
905
|
-
return notice(`Refused: ${args.slug} failed validation (${errors.map((d) => `${d.id}: ${d.message}`).join("; ")}). Not installed.`);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
const baseDir = args.dest ? resolve(args.dest) : join(homedir(), ".claude", "skills");
|
|
909
|
-
const dir = join(baseDir, slugName);
|
|
910
|
-
await mkdir(dir, { recursive: true });
|
|
911
|
-
const root = resolve(dir);
|
|
912
|
-
|
|
913
|
-
// Zip-slip guard: refuse any path that resolves outside the skill's dir.
|
|
914
|
-
const writeSafe = async (relPath, contents) => {
|
|
915
|
-
const fp = resolve(dir, relPath);
|
|
916
|
-
if (fp !== root && !fp.startsWith(root + sep)) throw new Error(`unsafe path: ${relPath}`);
|
|
917
|
-
await mkdir(dirname(fp), { recursive: true });
|
|
918
|
-
await writeFile(fp, contents);
|
|
919
|
-
};
|
|
920
|
-
|
|
921
|
-
let written = 1;
|
|
922
|
-
let note = "";
|
|
923
|
-
if (skill.type === "pack") {
|
|
924
|
-
// Pull the full pack (SKILL.md + companion files) in one call. The JSON
|
|
925
|
-
// bundle avoids adding an unzip dependency to this standalone binary.
|
|
926
|
-
try {
|
|
927
|
-
const bundle = await api(`/api/skills/${owner}/${slugName}/bundle?format=json`);
|
|
928
|
-
// Resolve + integrity-check every file BEFORE writing any, so a
|
|
929
|
-
// tampered pack refuses cleanly instead of leaving a partial install.
|
|
930
|
-
const resolved = [];
|
|
931
|
-
for (const f of bundle.files ?? []) {
|
|
932
|
-
if (f.path.includes("..") || f.path.startsWith("/")) continue;
|
|
933
|
-
let buf;
|
|
934
|
-
if (f.content_base64 != null) {
|
|
935
|
-
buf = Buffer.from(f.content_base64, "base64");
|
|
936
|
-
// Registry-stored files are content-addressed; verify the bytes
|
|
937
|
-
// match the registry's sha256. Files without one (the reconstructed
|
|
938
|
-
// inline SKILL.md) carry no hash and are skipped.
|
|
939
|
-
if (f.sha256) {
|
|
940
|
-
const got = createHash("sha256").update(buf).digest("hex");
|
|
941
|
-
if (got !== f.sha256) {
|
|
942
|
-
const err = new Error(`integrity check failed for "${f.path}"`);
|
|
943
|
-
err.integrity = true;
|
|
944
|
-
throw err;
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
} else if (f.source_url && isAllowedSourceUrl(f.source_url)) {
|
|
948
|
-
const r = await fetch(f.source_url).catch(() => null);
|
|
949
|
-
if (!r || !r.ok) continue;
|
|
950
|
-
buf = Buffer.from(await r.arrayBuffer());
|
|
951
|
-
} else continue;
|
|
952
|
-
resolved.push({ path: f.path, contents: f.path === "SKILL.md" ? raw : buf });
|
|
953
|
-
}
|
|
954
|
-
for (const f of resolved) await writeSafe(f.path, f.contents);
|
|
955
|
-
written = resolved.length || 1;
|
|
956
|
-
if (!resolved.some((f) => f.path === "SKILL.md")) await writeSafe("SKILL.md", raw);
|
|
957
|
-
} catch (e) {
|
|
958
|
-
if (e && e.integrity) return notice(`Refused: ${args.slug} failed integrity verification — ${e.message}. Not installed.`);
|
|
959
|
-
await writeSafe("SKILL.md", raw);
|
|
960
|
-
note = ` NOTE: pack assets could not be fetched — only SKILL.md was written. For the full pack run: npx skillmds add ${args.slug}`;
|
|
961
|
-
}
|
|
962
|
-
} else {
|
|
963
|
-
await writeSafe("SKILL.md", raw);
|
|
964
|
-
}
|
|
965
|
-
api(`/api/skills/${owner}/${slugName}/install`, { method: "POST", body: JSON.stringify({ via: "mcp" }) }).catch(() => {});
|
|
966
|
-
const summary = { installed: true, slug: args.slug, dir, files_written: written, security_flags: flags, ...(note ? { note: note.trim() } : {}) };
|
|
967
|
-
return {
|
|
968
|
-
content: [{ type: "text", text: `Installed ${args.slug} → ${dir} (${written} file${written === 1 ? "" : "s"}; security: ${flags.join(", ") || "unscanned"})${note}` }],
|
|
969
|
-
structuredContent: summary,
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
default:
|
|
973
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
async function startMcpServer() {
|
|
978
|
-
const server = new Server(
|
|
979
|
-
{
|
|
980
|
-
name: "skillmd",
|
|
981
|
-
version: VERSION,
|
|
982
|
-
title: "SkillMD",
|
|
983
|
-
description:
|
|
984
|
-
"The open registry of Agent Skills: search, inspect, lint and install 25,000+ linted, security-reviewed SKILL.md files for Claude Code, Cursor, Codex and 60+ other agents.",
|
|
985
|
-
websiteUrl: "https://skillmd.com",
|
|
986
|
-
icons: [
|
|
987
|
-
{ src: "https://skillmd.com/favicon.png", mimeType: "image/png", sizes: ["512x512"] },
|
|
988
|
-
{ src: "https://skillmd.com/favicon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
|
|
989
|
-
],
|
|
990
|
-
},
|
|
991
|
-
{
|
|
992
|
-
capabilities: { tools: {} },
|
|
993
|
-
instructions:
|
|
994
|
-
"SkillMD is a registry of Agent Skills (SKILL.md files). Use skillmd_search to find skills for a task, skillmd_get to inspect one (safety verdict, capability flags, provenance), skillmd_install to write it into the agent's skills directory, and skillmd_lint to validate your own SKILL.md. skillmd_trending and skillmd_recommend surface popular and related skills. Only skillmd_install writes files; it never executes scripts.",
|
|
995
|
-
},
|
|
996
|
-
);
|
|
997
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
998
|
-
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
999
|
-
try {
|
|
1000
|
-
return await handleCall(req.params.name, req.params.arguments ?? {});
|
|
1001
|
-
} catch (e) {
|
|
1002
|
-
return { isError: true, content: [{ type: "text", text: String(e?.message || e) }] };
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1005
|
-
const transport = new StdioServerTransport();
|
|
1006
|
-
await server.connect(transport);
|
|
1007
|
-
console.error("skillmd MCP server running (stdio)");
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// This binary is invoked three ways:
|
|
1011
|
-
// - `skillmds <anything>` → run the CLI. commander owns argv, so known
|
|
1012
|
-
// commands run and unknown commands get a proper "unknown command" error
|
|
1013
|
-
// instead of silently starting an MCP server.
|
|
1014
|
-
// - `skillmds` in a terminal → run the CLI (opens the interactive menu)
|
|
1015
|
-
// - `skillmds` over piped stdio → start the MCP server (how MCP clients launch it;
|
|
1016
|
-
// they always connect stdin as a pipe, never a TTY, and pass no args)
|
|
1017
|
-
if (process.argv.length > 2 || process.stdin.isTTY) {
|
|
1018
|
-
await import("../dist/cli.js"); // the CLI parses process.argv itself
|
|
1019
|
-
} else {
|
|
1020
|
-
await startMcpServer();
|
|
1021
|
-
}
|
|
2
|
+
// SkillMD MCP server (stdio) + CLI dispatch. Source: src/mcp/index.ts.
|
|
3
|
+
import("../dist/mcp.js");
|