skillmds 1.1.1 → 1.1.2

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to `skillmds` are documented here. Versions follow [semver](https://semver.org).
4
4
 
5
+ ## 1.1.2
6
+
7
+ - **MCP tool metadata.** Every tool now declares a `title`, `annotations` (all read-only except `skillmd_install`, none destructive), an `outputSchema`, and a description on every parameter; list-returning tools respond with `structuredContent: { items }`, `skillmd_lint` and `skillmd_install` return structured results, and refusals are flagged `isError`.
8
+ - **Server metadata.** `initialize` reports a title, description, website URL, icons and usage instructions.
9
+ - No behavior changes to what the tools do.
10
+
5
11
  ## 1.1.1
6
12
 
7
13
  - Releases are now published through npm Trusted Publishing (OIDC from GitHub Actions) with provenance; no long-lived npm tokens exist for this package.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://cdn.jsdelivr.net/npm/skillmds/assets/skillmd-banner.jpg" alt="SkillMD" width="560">
2
+ <img src="https://skillmd.com/brand/banner.png" alt="SkillMD" width="560">
3
3
  </p>
4
4
 
5
5
  # skillmds
Binary file
@@ -46,7 +46,17 @@ async function api(path, init = {}) {
46
46
  if (!res.ok) throw new Error(`SkillMD API ${res.status} on ${path}: ${await res.text().catch(() => "")}`);
47
47
  return res.json();
48
48
  }
49
- const text = (obj) => ({ content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }] });
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 }] });
50
60
 
51
61
  // The public API returns site-oriented fields (ratings, counters, avatars,
52
62
  // ranker internals) that are noise for an agent picking a skill. Strip them
@@ -67,76 +77,756 @@ const trimItems = (items) => (items ?? []).map((s) => Object.fromEntries(Object.
67
77
 
68
78
  const TOOLS = [
69
79
  {
70
- name: "skillmd_search",
71
- description: "Search SkillMD for Agent Skills by name, description, or task. Returns matching skills with their owner/name slug and install snippets.",
72
- inputSchema: {
73
- type: "object",
74
- properties: {
75
- query: { type: "string", description: "Natural-language query or keywords" },
76
- category: { type: "string", description: "Filter by category slug (e.g. devops, data, security)" },
77
- verified_only: { type: "boolean", description: "Only verified skills" },
78
- type: { type: "string", enum: ["single", "pack"], description: "single file or pack" },
79
- min_rating: { type: "number", description: "Minimum average rating 0-5" },
80
- limit: { type: "number", description: "Max results (default 20)" },
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
+ }
81
118
  },
82
- required: ["query"],
119
+ "required": [
120
+ "query"
121
+ ],
122
+ "additionalProperties": false
83
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
+ }
84
209
  },
85
210
  {
86
- name: "skillmd_get",
87
- description: "Fetch a single skill by slug (owner/name), including its SKILL.md body, provenance, license and security flags.",
88
- inputSchema: { type: "object", properties: { slug: { type: "string", description: "owner/name, e.g. anthropic/pdf" } }, required: ["slug"] },
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
+ }
89
362
  },
90
363
  {
91
- name: "skillmd_install",
92
- description:
93
- "Install a skill by slug: writes its SKILL.md into the agent's skills directory. Never executes any scripts. Any skill installs; the response reports its security flags (docs_only / network_calls / executes_scripts / reads_secrets) as information.",
94
- inputSchema: {
95
- type: "object",
96
- properties: {
97
- slug: { type: "string", description: "owner/name, e.g. anthropic/pdf" },
98
- dest: { type: "string", description: "Target skills dir. Default ~/.claude/skills" },
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
+ }
99
415
  },
100
- required: ["slug"],
416
+ "required": [
417
+ "installed",
418
+ "slug"
419
+ ]
101
420
  },
421
+ "annotations": {
422
+ "title": "Install skill",
423
+ "readOnlyHint": false,
424
+ "destructiveHint": false,
425
+ "idempotentHint": true,
426
+ "openWorldHint": false
427
+ }
102
428
  },
103
429
  {
104
- name: "skillmd_trending",
105
- description: "Top skills on the SkillMD leaderboard. range: '30d' or 'all'.",
106
- inputSchema: {
107
- type: "object",
108
- properties: {
109
- range: { type: "string", enum: ["30d", "all"], description: "Time window (default all)" },
110
- category: { type: "string", description: "Optional category slug" },
111
- limit: { type: "number", description: "Max results (default 20)" },
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
+ }
112
530
  },
531
+ "required": [
532
+ "items"
533
+ ]
113
534
  },
535
+ "annotations": {
536
+ "title": "Trending skills",
537
+ "readOnlyHint": true,
538
+ "destructiveHint": false,
539
+ "idempotentHint": true,
540
+ "openWorldHint": false
541
+ }
114
542
  },
115
543
  {
116
- name: "skillmd_recommend",
117
- description: "Recommend skills. If based_on is given, suggests skills similar to it (same category); otherwise returns trending.",
118
- inputSchema: {
119
- type: "object",
120
- properties: { based_on: { type: "string", description: "A slug to base recommendations on" }, limit: { type: "number" } },
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
+ ]
121
641
  },
642
+ "annotations": {
643
+ "title": "Recommend skills",
644
+ "readOnlyHint": true,
645
+ "destructiveHint": false,
646
+ "idempotentHint": true,
647
+ "openWorldHint": false
648
+ }
122
649
  },
123
650
  {
124
- name: "skillmd_list_saved",
125
- description: "List the signed-in user's saved skills. Requires SKILLMD_TOKEN to be set.",
126
- inputSchema: { type: "object", properties: {} },
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
+ }
127
744
  },
128
745
  {
129
- name: "skillmd_lint",
130
- description:
131
- "Validate a SKILL.md against the SkillMD spec and return diagnostics, security flags, and a quality score (0-100). Pass raw SKILL.md content, or a registry slug (owner/name) to fetch and lint.",
132
- inputSchema: {
133
- type: "object",
134
- properties: {
135
- content: { type: "string", description: "Raw SKILL.md content to validate" },
136
- slug: { type: "string", description: "Registry slug owner/name to fetch and validate" },
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
+ }
137
761
  },
762
+ "additionalProperties": false
138
763
  },
139
- },
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
+ }
140
830
  ];
141
831
 
142
832
  async function handleCall(name, args) {
@@ -174,12 +864,12 @@ async function handleCall(name, args) {
174
864
  return text(trimItems(r.items));
175
865
  }
176
866
  case "skillmd_list_saved": {
177
- if (!TOKEN) return text("Set SKILLMD_TOKEN (a SkillMD personal access token) to list your saved skills.");
867
+ if (!TOKEN) return notice("Set SKILLMD_TOKEN (a SkillMD personal access token) to list your saved skills.");
178
868
  const r = await api(`/api/favorites`);
179
869
  return text(r.items ?? []);
180
870
  }
181
871
  case "skillmd_lint": {
182
- if (!lint) return text("Validation engine unavailable — reinstall the skillmds package (npm i -g skillmds), or run `npm ci && npm run build` in a source checkout.");
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.");
183
873
  let raw = args.content;
184
874
  let slug;
185
875
  let verified = false;
@@ -190,18 +880,18 @@ async function handleCall(name, args) {
190
880
  slug = slugName;
191
881
  verified = !!skill.verified;
192
882
  }
193
- if (!raw) return text("Provide `content` (raw SKILL.md) or `slug` (owner/name).");
883
+ if (!raw) return notice("Provide `content` (raw SKILL.md) or `slug` (owner/name).");
194
884
  const result = lint(raw, { ...(slug ? { slug } : {}), verified });
195
885
  return text({ ok: result.ok, score: result.score, diagnostics: result.diagnostics, security: result.security });
196
886
  }
197
887
  case "skillmd_install": {
198
- if (!skillMdFor) return text("skillmds is not fully installed — reinstall it (npm i -g skillmds) and try again.");
888
+ if (!skillMdFor) return notice("skillmds is not fully installed — reinstall it (npm i -g skillmds) and try again.");
199
889
  const [owner, slugName] = String(args.slug).split("/");
200
890
  // Defense in depth: slugName becomes a directory name below — refuse
201
891
  // anything outside the registry's slugify alphabet (path separators,
202
892
  // "..", etc.) before touching the network or filesystem.
203
893
  if (!/^[a-z0-9][a-z0-9-]*$/.test(slugName ?? "")) {
204
- return text(`Refused: invalid skill name "${slugName}" — expected a lowercase slug.`);
894
+ return notice(`Refused: invalid skill name "${slugName}" — expected a lowercase slug.`);
205
895
  }
206
896
  const skill = await api(`/api/skills/${owner}/${slugName}`);
207
897
  const flags = skill.security_flags ?? [];
@@ -212,7 +902,7 @@ async function handleCall(name, args) {
212
902
  const result = lint(raw, { slug: slugName });
213
903
  if (!result.ok) {
214
904
  const errors = result.diagnostics.filter((d) => d.severity === "error");
215
- return text(`Refused: ${args.slug} failed validation (${errors.map((d) => `${d.id}: ${d.message}`).join("; ")}). Not installed.`);
905
+ return notice(`Refused: ${args.slug} failed validation (${errors.map((d) => `${d.id}: ${d.message}`).join("; ")}). Not installed.`);
216
906
  }
217
907
  }
218
908
  const baseDir = args.dest ? resolve(args.dest) : join(homedir(), ".claude", "skills");
@@ -265,7 +955,7 @@ async function handleCall(name, args) {
265
955
  written = resolved.length || 1;
266
956
  if (!resolved.some((f) => f.path === "SKILL.md")) await writeSafe("SKILL.md", raw);
267
957
  } catch (e) {
268
- if (e && e.integrity) return text(`Refused: ${args.slug} failed integrity verification — ${e.message}. Not installed.`);
958
+ if (e && e.integrity) return notice(`Refused: ${args.slug} failed integrity verification — ${e.message}. Not installed.`);
269
959
  await writeSafe("SKILL.md", raw);
270
960
  note = ` NOTE: pack assets could not be fetched — only SKILL.md was written. For the full pack run: npx skillmds add ${args.slug}`;
271
961
  }
@@ -273,7 +963,11 @@ async function handleCall(name, args) {
273
963
  await writeSafe("SKILL.md", raw);
274
964
  }
275
965
  api(`/api/skills/${owner}/${slugName}/install`, { method: "POST", body: JSON.stringify({ via: "mcp" }) }).catch(() => {});
276
- return text(`Installed ${args.slug} → ${dir} (${written} file${written === 1 ? "" : "s"}; security: ${flags.join(", ") || "unscanned"})${note}`);
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
+ };
277
971
  }
278
972
  default:
279
973
  throw new Error(`Unknown tool: ${name}`);
@@ -281,7 +975,25 @@ async function handleCall(name, args) {
281
975
  }
282
976
 
283
977
  async function startMcpServer() {
284
- const server = new Server({ name: "skillmd", version: VERSION }, { capabilities: { tools: {} } });
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
+ );
285
997
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
286
998
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
287
999
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillmds",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "SkillMD for agents: the `skillmds` MCP server + the `skillmd` CLI (lint, scan, search, install, publish Agent Skills) from skillmd.com.",
5
5
  "license": "MIT",
6
6
  "author": "SkillMD",