@modootoday/pmcp 0.7.0 → 0.8.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 +40 -9
- package/dist/{catalog-5v7jrGwP.d.ts → catalog-CS0j6-0U.d.ts} +11 -11
- package/dist/{chunk-POJOFL4L.js → chunk-LUBYOET7.js} +6 -3
- package/dist/{chunk-GNJSZCRH.js → chunk-M6LBYJDS.js} +37 -2
- package/dist/{chunk-ZLELYZHL.js → chunk-OPDVUHTK.js} +2 -2
- package/dist/{chunk-NI2OLZPA.js → chunk-WSTCBVXW.js} +2 -2
- package/dist/{chunk-QHFORYIN.js → chunk-ZDV75HZN.js} +823 -365
- package/dist/cli.js +50 -11
- package/dist/http.d.ts +10 -2
- package/dist/http.js +5 -4
- package/dist/index.d.ts +119 -44
- package/dist/index.js +51 -6
- package/dist/server-CLyCpwZb.d.ts +352 -0
- package/dist/stdio.d.ts +7 -3
- package/dist/stdio.js +2 -2
- package/package.json +1 -1
- package/dist/server-wpYiAJ2n.d.ts +0 -210
|
@@ -3,6 +3,109 @@ function normaliseIntent(intent) {
|
|
|
3
3
|
return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
// src/frontmatter.ts
|
|
7
|
+
import { parse } from "yaml";
|
|
8
|
+
function frontmatterText(source) {
|
|
9
|
+
const text = source.replace(/^/u, "").replace(/\r\n/gu, "\n");
|
|
10
|
+
if (!text.startsWith("---\n") && text !== "---") return null;
|
|
11
|
+
const end = text.indexOf("\n---", 3);
|
|
12
|
+
if (end === -1) return null;
|
|
13
|
+
return text.slice(4, end + 1);
|
|
14
|
+
}
|
|
15
|
+
function bodyOf(source) {
|
|
16
|
+
const text = source.replace(/^/u, "").replace(/\r\n/gu, "\n");
|
|
17
|
+
if (!text.startsWith("---\n")) return text;
|
|
18
|
+
const end = text.indexOf("\n---", 3);
|
|
19
|
+
if (end === -1) return text;
|
|
20
|
+
return text.slice(end + 4).replace(/^\n+/u, "");
|
|
21
|
+
}
|
|
22
|
+
var asObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
23
|
+
function frontmatterObject(source) {
|
|
24
|
+
const text = frontmatterText(source);
|
|
25
|
+
if (text === null) return null;
|
|
26
|
+
try {
|
|
27
|
+
return asObject(parse(text));
|
|
28
|
+
} catch {
|
|
29
|
+
const quoted = text.split("\n").map((line) => {
|
|
30
|
+
const match = /^([A-Za-z][\w-]*):\s+(.*:\s.*)$/u.exec(line);
|
|
31
|
+
if (!match || /^["'|>[{]/u.test(match[2] ?? "")) return line;
|
|
32
|
+
return `${match[1]}: ${JSON.stringify(match[2])}`;
|
|
33
|
+
}).join("\n");
|
|
34
|
+
try {
|
|
35
|
+
return asObject(parse(quoted));
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function stringFields(front) {
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [key, value] of Object.entries(front ?? {})) {
|
|
44
|
+
if (typeof value === "string") out[key] = value.trim();
|
|
45
|
+
else if (typeof value === "number" || typeof value === "boolean")
|
|
46
|
+
out[key] = String(value);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function metadataOf(front) {
|
|
51
|
+
const out = {};
|
|
52
|
+
const meta = asObject(front?.["metadata"]);
|
|
53
|
+
for (const [key, value] of Object.entries(meta ?? {})) {
|
|
54
|
+
if (typeof value === "string") out[key] = value;
|
|
55
|
+
else if (typeof value === "number" || typeof value === "boolean")
|
|
56
|
+
out[key] = String(value);
|
|
57
|
+
else if (Array.isArray(value))
|
|
58
|
+
out[key] = value.filter((v) => typeof v === "string");
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
var SKILL_NAME = /^[a-z0-9](?:[a-z0-9]|-(?!-))*[a-z0-9]$|^[a-z0-9]$/u;
|
|
63
|
+
var NAME_MAX = 64;
|
|
64
|
+
var DESCRIPTION_MAX = 1024;
|
|
65
|
+
var COMPATIBILITY_MAX = 500;
|
|
66
|
+
function specProblems(front, directory) {
|
|
67
|
+
if (front === null) return ["frontmatter is missing or is not YAML"];
|
|
68
|
+
const problems = [];
|
|
69
|
+
const name = front["name"];
|
|
70
|
+
if (typeof name !== "string" || name === "") {
|
|
71
|
+
problems.push("name is required");
|
|
72
|
+
} else {
|
|
73
|
+
if (name.length > NAME_MAX)
|
|
74
|
+
problems.push(`name is longer than ${NAME_MAX} characters`);
|
|
75
|
+
if (!SKILL_NAME.test(name))
|
|
76
|
+
problems.push(
|
|
77
|
+
"name must be lowercase letters, digits and single hyphens, not starting or ending with one"
|
|
78
|
+
);
|
|
79
|
+
if (name !== directory)
|
|
80
|
+
problems.push(`name ${name} does not match its directory ${directory}`);
|
|
81
|
+
}
|
|
82
|
+
const description = front["description"];
|
|
83
|
+
if (typeof description !== "string" || description.trim() === "") {
|
|
84
|
+
problems.push("description is required");
|
|
85
|
+
} else if (description.length > DESCRIPTION_MAX) {
|
|
86
|
+
problems.push(`description is longer than ${DESCRIPTION_MAX} characters`);
|
|
87
|
+
}
|
|
88
|
+
const compatibility = front["compatibility"];
|
|
89
|
+
if (compatibility !== void 0) {
|
|
90
|
+
if (typeof compatibility !== "string")
|
|
91
|
+
problems.push("compatibility must be a string");
|
|
92
|
+
else if (compatibility.length > COMPATIBILITY_MAX)
|
|
93
|
+
problems.push(
|
|
94
|
+
`compatibility is longer than ${COMPATIBILITY_MAX} characters`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const license = front["license"];
|
|
98
|
+
if (license !== void 0 && typeof license !== "string")
|
|
99
|
+
problems.push("license must be a string");
|
|
100
|
+
const allowed = front["allowed-tools"];
|
|
101
|
+
if (allowed !== void 0 && typeof allowed !== "string")
|
|
102
|
+
problems.push("allowed-tools must be a space-separated string");
|
|
103
|
+
const metadata = front["metadata"];
|
|
104
|
+
if (metadata !== void 0 && asObject(metadata) === null)
|
|
105
|
+
problems.push("metadata must be a map");
|
|
106
|
+
return problems;
|
|
107
|
+
}
|
|
108
|
+
|
|
6
109
|
// src/marketplace.ts
|
|
7
110
|
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
8
111
|
import { basename as basename2, dirname as dirname2, join as join2, resolve } from "path";
|
|
@@ -23,24 +126,10 @@ var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
|
23
126
|
]);
|
|
24
127
|
var MAX_SKILL_DEPTH = 3;
|
|
25
128
|
function readFrontmatter(source) {
|
|
26
|
-
|
|
27
|
-
const end = source.indexOf("\n---", 3);
|
|
28
|
-
if (end === -1) return {};
|
|
29
|
-
const fields = {};
|
|
30
|
-
for (const line of source.slice(3, end).split("\n")) {
|
|
31
|
-
const match = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/u.exec(line);
|
|
32
|
-
if (!match) continue;
|
|
33
|
-
const value = (match[2] ?? "").trim();
|
|
34
|
-
if (value === "" || value.startsWith("#")) continue;
|
|
35
|
-
fields[match[1]] = value.replace(/^["'](.*)["']$/u, "$1");
|
|
36
|
-
}
|
|
37
|
-
return fields;
|
|
129
|
+
return stringFields(frontmatterObject(source));
|
|
38
130
|
}
|
|
39
131
|
function readBody(source) {
|
|
40
|
-
|
|
41
|
-
const end = source.indexOf("\n---", 3);
|
|
42
|
-
if (end === -1) return source;
|
|
43
|
-
return source.slice(end + 4).replace(/^(?:\r?\n)+/u, "");
|
|
132
|
+
return bodyOf(source);
|
|
44
133
|
}
|
|
45
134
|
function skillFiles(packageDir) {
|
|
46
135
|
const found = [];
|
|
@@ -98,8 +187,8 @@ function readManifest(dir) {
|
|
|
98
187
|
return null;
|
|
99
188
|
}
|
|
100
189
|
}
|
|
101
|
-
function workspacePatterns(
|
|
102
|
-
const field =
|
|
190
|
+
function workspacePatterns(manifest2) {
|
|
191
|
+
const field = manifest2["workspaces"];
|
|
103
192
|
const list = Array.isArray(field) ? field : field?.packages;
|
|
104
193
|
if (!Array.isArray(list)) return [];
|
|
105
194
|
return list.filter((p) => typeof p === "string");
|
|
@@ -155,11 +244,11 @@ function workspacePackageDirs(root) {
|
|
|
155
244
|
const expand = (dir) => {
|
|
156
245
|
if (visited.has(dir)) return;
|
|
157
246
|
visited.add(dir);
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
247
|
+
const manifest2 = readManifest(dir);
|
|
248
|
+
if (manifest2 === null) return;
|
|
160
249
|
const included = /* @__PURE__ */ new Set();
|
|
161
250
|
const excluded = /* @__PURE__ */ new Set();
|
|
162
|
-
for (const raw of workspacePatterns(
|
|
251
|
+
for (const raw of workspacePatterns(manifest2)) {
|
|
163
252
|
const negated = raw.startsWith("!");
|
|
164
253
|
const pattern = negated ? raw.slice(1) : raw;
|
|
165
254
|
for (const alternative of expandBraces(pattern)) {
|
|
@@ -215,20 +304,32 @@ function readCatalog(options) {
|
|
|
215
304
|
} catch {
|
|
216
305
|
continue;
|
|
217
306
|
}
|
|
218
|
-
const
|
|
307
|
+
const parsed = frontmatterObject(source);
|
|
308
|
+
const front = stringFields(parsed);
|
|
219
309
|
const description = front["description"] ?? "";
|
|
220
310
|
if (description === "") continue;
|
|
221
311
|
const dirName = basename(dirname(path));
|
|
222
312
|
const slug = front["name"] ?? (dirName === basename(packageDir) ? packageName : dirName);
|
|
223
313
|
const name = `${packageName}/${slug}`;
|
|
224
|
-
if (seen.has(name))
|
|
314
|
+
if (seen.has(name)) {
|
|
315
|
+
if (!path.includes("/node_modules/"))
|
|
316
|
+
options.onWarning?.({
|
|
317
|
+
path,
|
|
318
|
+
reason: `duplicate name ${name}; the first one found is served`
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
225
322
|
seen.add(name);
|
|
323
|
+
for (const problem of specProblems(parsed, dirName))
|
|
324
|
+
options.onWarning?.({ path, reason: problem });
|
|
325
|
+
const metadata = metadataOf(parsed);
|
|
226
326
|
found.push({
|
|
227
327
|
name,
|
|
228
328
|
package: packageName,
|
|
229
329
|
slug,
|
|
230
330
|
description,
|
|
231
|
-
path
|
|
331
|
+
path,
|
|
332
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
232
333
|
});
|
|
233
334
|
}
|
|
234
335
|
}
|
|
@@ -242,6 +343,14 @@ function readCatalog(options) {
|
|
|
242
343
|
continue;
|
|
243
344
|
}
|
|
244
345
|
seen.add(entry.name);
|
|
346
|
+
if (kindOf(entry) === "skill" && basename(entry.path) === "SKILL.md") {
|
|
347
|
+
const parsed = frontmatterObject(readFileSync(entry.path, "utf8"));
|
|
348
|
+
for (const problem of specProblems(
|
|
349
|
+
parsed,
|
|
350
|
+
basename(dirname(entry.path))
|
|
351
|
+
))
|
|
352
|
+
options.onWarning?.({ path: entry.path, reason: problem });
|
|
353
|
+
}
|
|
245
354
|
found.push(entry);
|
|
246
355
|
}
|
|
247
356
|
}
|
|
@@ -259,38 +368,11 @@ function displayPath(root, entry) {
|
|
|
259
368
|
}
|
|
260
369
|
|
|
261
370
|
// src/marketplace.ts
|
|
262
|
-
function parseScalar(raw) {
|
|
263
|
-
const value = raw.trim();
|
|
264
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
265
|
-
return value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["'](.*)["']$/u, "$1")).filter(Boolean);
|
|
266
|
-
}
|
|
267
|
-
return value.replace(/^["'](.*)["']$/u, "$1");
|
|
268
|
-
}
|
|
269
371
|
function readMetadata(source) {
|
|
270
|
-
|
|
271
|
-
const end = source.indexOf("\n---", 3);
|
|
272
|
-
if (end === -1) return {};
|
|
273
|
-
const out = {};
|
|
274
|
-
let inside = false;
|
|
275
|
-
for (const line of source.slice(3, end).split("\n")) {
|
|
276
|
-
if (/^metadata:\s*$/u.test(line)) {
|
|
277
|
-
inside = true;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (!inside) continue;
|
|
281
|
-
if (!/^\s/u.test(line)) break;
|
|
282
|
-
const match = /^ {2}([A-Za-z][A-Za-z0-9_.-]*):\s*(.*)$/u.exec(line);
|
|
283
|
-
if (!match) continue;
|
|
284
|
-
const raw = match[2] ?? "";
|
|
285
|
-
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
286
|
-
out[match[1]] = parseScalar(raw);
|
|
287
|
-
}
|
|
288
|
-
return out;
|
|
372
|
+
return metadataOf(frontmatterObject(source));
|
|
289
373
|
}
|
|
290
374
|
function readFrontmatterText(source) {
|
|
291
|
-
|
|
292
|
-
const end = source.indexOf("\n---", 3);
|
|
293
|
-
return end === -1 ? "" : source.slice(3, end).replace(/^\r?\n/u, "");
|
|
375
|
+
return frontmatterText(source) ?? "";
|
|
294
376
|
}
|
|
295
377
|
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
296
378
|
var asList = (value) => {
|
|
@@ -392,8 +474,8 @@ function agentEntries(context) {
|
|
|
392
474
|
}
|
|
393
475
|
return found;
|
|
394
476
|
}
|
|
395
|
-
function hookEntries(context,
|
|
396
|
-
const declared = typeof
|
|
477
|
+
function hookEntries(context, manifest2) {
|
|
478
|
+
const declared = typeof manifest2.hooks === "string" ? manifest2.hooks : void 0;
|
|
397
479
|
const path = resolve(context.pluginDir, declared ?? "hooks/hooks.json");
|
|
398
480
|
if (!existsSync2(path)) return [];
|
|
399
481
|
const hooks = readJson(path);
|
|
@@ -451,9 +533,9 @@ function readMarketplace(dir, onReject) {
|
|
|
451
533
|
});
|
|
452
534
|
return [];
|
|
453
535
|
}
|
|
454
|
-
let
|
|
536
|
+
let manifest2;
|
|
455
537
|
try {
|
|
456
|
-
|
|
538
|
+
manifest2 = JSON.parse(
|
|
457
539
|
readFileSync2(manifestPath, "utf8")
|
|
458
540
|
);
|
|
459
541
|
} catch {
|
|
@@ -463,9 +545,9 @@ function readMarketplace(dir, onReject) {
|
|
|
463
545
|
});
|
|
464
546
|
return [];
|
|
465
547
|
}
|
|
466
|
-
const marketplace =
|
|
548
|
+
const marketplace = manifest2.name ?? "marketplace";
|
|
467
549
|
const found = [];
|
|
468
|
-
for (const plugin of
|
|
550
|
+
for (const plugin of manifest2.plugins ?? []) {
|
|
469
551
|
if (!plugin.name || !plugin.source) continue;
|
|
470
552
|
const pluginDir = resolve(dir, plugin.source);
|
|
471
553
|
if (!existsSync2(pluginDir)) continue;
|
|
@@ -477,7 +559,7 @@ function readMarketplace(dir, onReject) {
|
|
|
477
559
|
plugin: plugin.name,
|
|
478
560
|
pluginDir,
|
|
479
561
|
pluginDescription: typeof pluginManifest.description === "string" ? pluginManifest.description : void 0,
|
|
480
|
-
tier:
|
|
562
|
+
tier: manifest2.metadata?.tier,
|
|
481
563
|
onReject
|
|
482
564
|
};
|
|
483
565
|
found.push(
|
|
@@ -492,6 +574,316 @@ function readMarketplace(dir, onReject) {
|
|
|
492
574
|
}
|
|
493
575
|
var assetRoot = (entry) => entry.root ?? dirname2(entry.path);
|
|
494
576
|
|
|
577
|
+
// src/prompts.ts
|
|
578
|
+
import { z } from "zod";
|
|
579
|
+
var promptName = (entry) => entry.name.replace(/^@/u, "").replace(/[^A-Za-z0-9_-]+/gu, "-");
|
|
580
|
+
function messageText(tools, name, task) {
|
|
581
|
+
const found = tools.call(name);
|
|
582
|
+
if (!found) return `The skill ${name} is no longer in this catalog.`;
|
|
583
|
+
const files = found.files.map((file) => `- ${file.path}`);
|
|
584
|
+
const parts = [found.body];
|
|
585
|
+
if (files.length > 0)
|
|
586
|
+
parts.push(
|
|
587
|
+
`Files in skill ${found.name} (skill_read returns each by path):
|
|
588
|
+
${files.join("\n")}`
|
|
589
|
+
);
|
|
590
|
+
if (task) parts.push(`Task: ${task}`);
|
|
591
|
+
return parts.join("\n\n");
|
|
592
|
+
}
|
|
593
|
+
function registerSkillPrompts(server, tools) {
|
|
594
|
+
const taken = /* @__PURE__ */ new Set();
|
|
595
|
+
for (const entry of tools.skills()) {
|
|
596
|
+
if (kindOf(entry) !== "skill") continue;
|
|
597
|
+
const name = promptName(entry);
|
|
598
|
+
if (taken.has(name)) continue;
|
|
599
|
+
taken.add(name);
|
|
600
|
+
server.registerPrompt(
|
|
601
|
+
name,
|
|
602
|
+
{
|
|
603
|
+
title: entry.name,
|
|
604
|
+
description: entry.description,
|
|
605
|
+
argsSchema: z.object({
|
|
606
|
+
task: z.string().optional().describe("What to apply the skill to.")
|
|
607
|
+
})
|
|
608
|
+
},
|
|
609
|
+
({ task }) => ({
|
|
610
|
+
messages: [
|
|
611
|
+
{
|
|
612
|
+
role: "user",
|
|
613
|
+
content: {
|
|
614
|
+
type: "text",
|
|
615
|
+
text: messageText(tools, entry.name, task)
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/files/list.ts
|
|
625
|
+
import { createHash } from "crypto";
|
|
626
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
627
|
+
import { basename as basename3, join as join3, relative as relative2, sep } from "path";
|
|
628
|
+
var FILE_LIST_LIMIT = 200;
|
|
629
|
+
var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
630
|
+
function describe(path, full) {
|
|
631
|
+
const bytes = readFileSync3(full);
|
|
632
|
+
return { path, bytes: bytes.length, sha256: sha256(bytes) };
|
|
633
|
+
}
|
|
634
|
+
function listSkillFiles(root, limit = FILE_LIST_LIMIT) {
|
|
635
|
+
if (statSync(root).isFile()) {
|
|
636
|
+
return { files: [describe(basename3(root), root)], truncated: false };
|
|
637
|
+
}
|
|
638
|
+
const files = [];
|
|
639
|
+
const stack = [root];
|
|
640
|
+
while (stack.length > 0) {
|
|
641
|
+
const dir = stack.pop();
|
|
642
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
643
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
644
|
+
const full = join3(dir, entry.name);
|
|
645
|
+
if (entry.isDirectory()) {
|
|
646
|
+
stack.push(full);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!entry.isFile()) continue;
|
|
650
|
+
if (files.length >= limit) return { files, truncated: true };
|
|
651
|
+
files.push(describe(relative2(root, full).split(sep).join("/"), full));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
655
|
+
return { files, truncated: false };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/mime.ts
|
|
659
|
+
var TYPES = {
|
|
660
|
+
".md": "text/markdown",
|
|
661
|
+
".markdown": "text/markdown",
|
|
662
|
+
".txt": "text/plain",
|
|
663
|
+
".json": "application/json",
|
|
664
|
+
".jsonl": "application/jsonl",
|
|
665
|
+
".yaml": "text/yaml",
|
|
666
|
+
".yml": "text/yaml",
|
|
667
|
+
".toml": "application/toml",
|
|
668
|
+
".csv": "text/csv",
|
|
669
|
+
".tsv": "text/tab-separated-values",
|
|
670
|
+
".xml": "application/xml",
|
|
671
|
+
".html": "text/html",
|
|
672
|
+
".htm": "text/html",
|
|
673
|
+
".css": "text/css",
|
|
674
|
+
".js": "text/javascript",
|
|
675
|
+
".mjs": "text/javascript",
|
|
676
|
+
".cjs": "text/javascript",
|
|
677
|
+
".jsx": "text/javascript",
|
|
678
|
+
".ts": "text/typescript",
|
|
679
|
+
".tsx": "text/typescript",
|
|
680
|
+
".mts": "text/typescript",
|
|
681
|
+
".cts": "text/typescript",
|
|
682
|
+
".py": "text/x-python",
|
|
683
|
+
".rb": "text/x-ruby",
|
|
684
|
+
".go": "text/x-go",
|
|
685
|
+
".rs": "text/x-rust",
|
|
686
|
+
".java": "text/x-java",
|
|
687
|
+
".sh": "text/x-shellscript",
|
|
688
|
+
".bash": "text/x-shellscript",
|
|
689
|
+
".zsh": "text/x-shellscript",
|
|
690
|
+
".ps1": "text/x-powershell",
|
|
691
|
+
".sql": "application/sql",
|
|
692
|
+
".ejs": "text/x-ejs",
|
|
693
|
+
".hbs": "text/x-handlebars-template",
|
|
694
|
+
".mustache": "text/x-mustache",
|
|
695
|
+
".svg": "image/svg+xml",
|
|
696
|
+
".png": "image/png",
|
|
697
|
+
".jpg": "image/jpeg",
|
|
698
|
+
".jpeg": "image/jpeg",
|
|
699
|
+
".gif": "image/gif",
|
|
700
|
+
".webp": "image/webp",
|
|
701
|
+
".avif": "image/avif",
|
|
702
|
+
".ico": "image/x-icon",
|
|
703
|
+
".bmp": "image/bmp",
|
|
704
|
+
".mp3": "audio/mpeg",
|
|
705
|
+
".wav": "audio/wav",
|
|
706
|
+
".ogg": "audio/ogg",
|
|
707
|
+
".m4a": "audio/mp4",
|
|
708
|
+
".flac": "audio/flac",
|
|
709
|
+
".mp4": "video/mp4",
|
|
710
|
+
".webm": "video/webm",
|
|
711
|
+
".pdf": "application/pdf",
|
|
712
|
+
".zip": "application/zip",
|
|
713
|
+
".gz": "application/gzip",
|
|
714
|
+
".tar": "application/x-tar",
|
|
715
|
+
".wasm": "application/wasm",
|
|
716
|
+
".woff": "font/woff",
|
|
717
|
+
".woff2": "font/woff2",
|
|
718
|
+
".ttf": "font/ttf",
|
|
719
|
+
".otf": "font/otf",
|
|
720
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
721
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
722
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
723
|
+
};
|
|
724
|
+
var TEXT_APPLICATION = /* @__PURE__ */ new Set([
|
|
725
|
+
"application/json",
|
|
726
|
+
"application/jsonl",
|
|
727
|
+
"application/toml",
|
|
728
|
+
"application/xml",
|
|
729
|
+
"application/sql",
|
|
730
|
+
"image/svg+xml"
|
|
731
|
+
]);
|
|
732
|
+
function mimeOf(path) {
|
|
733
|
+
const slash = path.lastIndexOf("/");
|
|
734
|
+
const dot = path.lastIndexOf(".");
|
|
735
|
+
if (dot === -1 || dot < slash) return null;
|
|
736
|
+
return TYPES[path.slice(dot).toLowerCase()] ?? null;
|
|
737
|
+
}
|
|
738
|
+
function isUtf8Text(bytes) {
|
|
739
|
+
if (bytes.includes(0)) return false;
|
|
740
|
+
try {
|
|
741
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
742
|
+
return true;
|
|
743
|
+
} catch {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function classify(path, bytes) {
|
|
748
|
+
const declared = mimeOf(path);
|
|
749
|
+
const textual = declared === null ? isUtf8Text(bytes) : declared.startsWith("text/") || TEXT_APPLICATION.has(declared);
|
|
750
|
+
if (textual && isUtf8Text(bytes)) {
|
|
751
|
+
return { kind: "text", mimeType: declared ?? "text/plain" };
|
|
752
|
+
}
|
|
753
|
+
if (declared?.startsWith("image/") && declared !== "image/svg+xml") {
|
|
754
|
+
return { kind: "image", mimeType: declared };
|
|
755
|
+
}
|
|
756
|
+
if (declared?.startsWith("audio/"))
|
|
757
|
+
return { kind: "audio", mimeType: declared };
|
|
758
|
+
return { kind: "blob", mimeType: declared ?? "application/octet-stream" };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/files/read.ts
|
|
762
|
+
import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
|
|
763
|
+
|
|
764
|
+
// src/files/resolve.ts
|
|
765
|
+
import { realpathSync, statSync as statSync2 } from "fs";
|
|
766
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
|
|
767
|
+
var ownFile = (entry) => relative3(assetRoot(entry), entry.path).split(sep2).join("/");
|
|
768
|
+
function resolveSkillFile(entry, wanted = ownFile(entry)) {
|
|
769
|
+
if (isAbsolute2(wanted) || wanted.split(/[\\/]/u).includes("..")) {
|
|
770
|
+
return { ok: false, error: "path_outside_skill", detail: wanted };
|
|
771
|
+
}
|
|
772
|
+
const scope = realpathSync(assetRoot(entry));
|
|
773
|
+
const single = statSync2(scope).isFile();
|
|
774
|
+
const root = single ? dirname3(scope) : scope;
|
|
775
|
+
let target;
|
|
776
|
+
try {
|
|
777
|
+
target = realpathSync(
|
|
778
|
+
single && wanted === "" ? scope : resolve2(root, wanted)
|
|
779
|
+
);
|
|
780
|
+
} catch {
|
|
781
|
+
return { ok: false, error: "no_such_file", detail: wanted };
|
|
782
|
+
}
|
|
783
|
+
const inside = single ? target === scope : target === root || target.startsWith(root + sep2);
|
|
784
|
+
if (!inside)
|
|
785
|
+
return { ok: false, error: "path_outside_skill", detail: wanted };
|
|
786
|
+
return {
|
|
787
|
+
ok: true,
|
|
788
|
+
target,
|
|
789
|
+
path: relative3(root, target).split(sep2).join("/")
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/files/read.ts
|
|
794
|
+
var READ_BUDGET_BYTES = 256 * 1024;
|
|
795
|
+
function readSkillFile(entry, wanted, budget = READ_BUDGET_BYTES) {
|
|
796
|
+
const resolved = resolveSkillFile(entry, wanted);
|
|
797
|
+
if (!resolved.ok) return resolved;
|
|
798
|
+
const size = statSync3(resolved.target).size;
|
|
799
|
+
if (size > budget) {
|
|
800
|
+
return {
|
|
801
|
+
ok: false,
|
|
802
|
+
error: "over_budget",
|
|
803
|
+
detail: `${size} bytes > ${budget}`
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
const bytes = readFileSync4(resolved.target);
|
|
807
|
+
const { kind: kind2, mimeType } = classify(resolved.path, bytes);
|
|
808
|
+
return {
|
|
809
|
+
ok: true,
|
|
810
|
+
name: entry.name,
|
|
811
|
+
path: resolved.path,
|
|
812
|
+
bytes: bytes.length,
|
|
813
|
+
sha256: sha256(bytes),
|
|
814
|
+
mimeType,
|
|
815
|
+
kind: kind2,
|
|
816
|
+
...kind2 === "text" ? { text: bytes.toString("utf8") } : { blob: bytes.toString("base64") }
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/files/uri.ts
|
|
821
|
+
var SKILL_URI_AUTHORITY = "pmcp";
|
|
822
|
+
var PREFIX = `skill://${SKILL_URI_AUTHORITY}/`;
|
|
823
|
+
var skillFileUri = (entry, file = "SKILL.md") => `${PREFIX}${entry.name}/${file}`;
|
|
824
|
+
function resolveSkillFileUri(uri, entries) {
|
|
825
|
+
if (!uri.startsWith(PREFIX)) return null;
|
|
826
|
+
const rest = decodeURI(uri.slice(PREFIX.length));
|
|
827
|
+
let best = null;
|
|
828
|
+
for (const entry of entries) {
|
|
829
|
+
if (!rest.startsWith(`${entry.name}/`)) continue;
|
|
830
|
+
if (best === null || entry.name.length > best.name.length) best = entry;
|
|
831
|
+
}
|
|
832
|
+
if (best === null) return null;
|
|
833
|
+
const file = rest.slice(best.name.length + 1);
|
|
834
|
+
return file === "" ? null : { entry: best, file };
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/resources.ts
|
|
838
|
+
import { statSync as statSync4 } from "fs";
|
|
839
|
+
import {
|
|
840
|
+
ResourceNotFoundError,
|
|
841
|
+
ResourceTemplate
|
|
842
|
+
} from "@modelcontextprotocol/server";
|
|
843
|
+
var RESOURCE_READ_BUDGET = 16 * 1024 * 1024;
|
|
844
|
+
var COMPLETION_LIMIT = 100;
|
|
845
|
+
function listing(entries) {
|
|
846
|
+
return entries.filter((entry) => kindOf(entry) === "skill").map((entry) => ({
|
|
847
|
+
uri: skillFileUri(entry, ownFile(entry)),
|
|
848
|
+
name: entry.name,
|
|
849
|
+
description: entry.description,
|
|
850
|
+
mimeType: "text/markdown",
|
|
851
|
+
size: statSync4(entry.path).size
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
function complete(value, entries) {
|
|
855
|
+
const owner = entries.find((entry) => value.startsWith(`${entry.name}/`));
|
|
856
|
+
if (!owner) {
|
|
857
|
+
return entries.map((entry) => `${entry.name}/`).filter((name) => name.startsWith(value)).slice(0, COMPLETION_LIMIT);
|
|
858
|
+
}
|
|
859
|
+
return listSkillFiles(assetRoot(owner)).files.map((file) => `${owner.name}/${file.path}`).filter((path) => path.startsWith(value)).slice(0, COMPLETION_LIMIT);
|
|
860
|
+
}
|
|
861
|
+
function registerSkillResources(server, skills) {
|
|
862
|
+
server.registerResource(
|
|
863
|
+
"skill-files",
|
|
864
|
+
new ResourceTemplate(`skill://${SKILL_URI_AUTHORITY}/{+path}`, {
|
|
865
|
+
list: () => ({ resources: listing(skills()) }),
|
|
866
|
+
complete: { path: (value) => complete(value, skills()) }
|
|
867
|
+
}),
|
|
868
|
+
{
|
|
869
|
+
title: "Skill files",
|
|
870
|
+
description: "Files of the skills this catalog serves: SKILL.md, references, scripts and assets."
|
|
871
|
+
},
|
|
872
|
+
async (uri) => {
|
|
873
|
+
const href = uri.toString();
|
|
874
|
+
const found = resolveSkillFileUri(href, skills());
|
|
875
|
+
if (!found) throw new ResourceNotFoundError(href);
|
|
876
|
+
const file = readSkillFile(found.entry, found.file, RESOURCE_READ_BUDGET);
|
|
877
|
+
if (!file.ok) throw new ResourceNotFoundError(href);
|
|
878
|
+
return {
|
|
879
|
+
contents: [
|
|
880
|
+
file.kind === "text" ? { uri: href, mimeType: file.mimeType, text: file.text ?? "" } : { uri: href, mimeType: file.mimeType, blob: file.blob ?? "" }
|
|
881
|
+
]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
495
887
|
// src/find.ts
|
|
496
888
|
var DEFAULT_FIND_LIMIT = 5;
|
|
497
889
|
function tokens(text) {
|
|
@@ -575,25 +967,32 @@ function rank(matches2, limit) {
|
|
|
575
967
|
return matches2.filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, limit);
|
|
576
968
|
}
|
|
577
969
|
|
|
970
|
+
// src/files/bundle.ts
|
|
971
|
+
import { readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
972
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
973
|
+
function bundleSkill(entry) {
|
|
974
|
+
const root = assetRoot(entry);
|
|
975
|
+
const base = statSync5(root).isFile() ? dirname4(root) : root;
|
|
976
|
+
const listing2 = listSkillFiles(root);
|
|
977
|
+
return {
|
|
978
|
+
name: entry.name,
|
|
979
|
+
truncated: listing2.truncated,
|
|
980
|
+
files: listing2.files.map((file) => ({
|
|
981
|
+
...file,
|
|
982
|
+
uri: skillFileUri(entry, file.path),
|
|
983
|
+
mimeType: classify(file.path, readFileSync5(join4(base, file.path))).mimeType
|
|
984
|
+
}))
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
578
988
|
// src/server.ts
|
|
579
|
-
import {
|
|
580
|
-
|
|
581
|
-
import {
|
|
582
|
-
basename as basename3,
|
|
583
|
-
dirname as dirname3,
|
|
584
|
-
isAbsolute as isAbsolute2,
|
|
585
|
-
join as join3,
|
|
586
|
-
relative as relative2,
|
|
587
|
-
resolve as resolve2,
|
|
588
|
-
sep
|
|
589
|
-
} from "path";
|
|
590
|
-
var READ_BUDGET_BYTES = 256 * 1024;
|
|
591
|
-
var DESCRIBE_FILE_LIMIT = 200;
|
|
989
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
990
|
+
var DESCRIBE_FILE_LIMIT = FILE_LIST_LIMIT;
|
|
592
991
|
var CATALOG_PAGE_SIZE = 8;
|
|
593
992
|
var CATALOG_PAGE_MAX = 20;
|
|
594
993
|
function matches(entry, filters) {
|
|
595
|
-
const
|
|
596
|
-
if (
|
|
994
|
+
const kind2 = filters?.kind ?? "skill";
|
|
995
|
+
if (kind2 !== "any" && kindOf(entry) !== kind2) return false;
|
|
597
996
|
if (!filters) return true;
|
|
598
997
|
const meta = entry.metadata ?? {};
|
|
599
998
|
const field = (key) => {
|
|
@@ -611,42 +1010,6 @@ function matches(entry, filters) {
|
|
|
611
1010
|
return false;
|
|
612
1011
|
return true;
|
|
613
1012
|
}
|
|
614
|
-
var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
615
|
-
function listFiles(root) {
|
|
616
|
-
const files = [];
|
|
617
|
-
if (statSync(root).isFile()) {
|
|
618
|
-
const bytes = readFileSync3(root);
|
|
619
|
-
const only = {
|
|
620
|
-
path: basename3(root),
|
|
621
|
-
bytes: bytes.length,
|
|
622
|
-
sha256: sha256(bytes)
|
|
623
|
-
};
|
|
624
|
-
return { files: [only], truncated: false };
|
|
625
|
-
}
|
|
626
|
-
const stack = [root];
|
|
627
|
-
while (stack.length > 0) {
|
|
628
|
-
const dir = stack.pop();
|
|
629
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
630
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
631
|
-
const full = join3(dir, entry.name);
|
|
632
|
-
if (entry.isDirectory()) {
|
|
633
|
-
stack.push(full);
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
if (!entry.isFile()) continue;
|
|
637
|
-
if (files.length >= DESCRIBE_FILE_LIMIT)
|
|
638
|
-
return { files, truncated: true };
|
|
639
|
-
const bytes = readFileSync3(full);
|
|
640
|
-
files.push({
|
|
641
|
-
path: relative2(root, full).split(sep).join("/"),
|
|
642
|
-
bytes: bytes.length,
|
|
643
|
-
sha256: sha256(bytes)
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
648
|
-
return { files, truncated: false };
|
|
649
|
-
}
|
|
650
1013
|
function catalogItem(entry) {
|
|
651
1014
|
return {
|
|
652
1015
|
name: entry.name,
|
|
@@ -665,10 +1028,12 @@ function createSkillTools(options) {
|
|
|
665
1028
|
marketplaces: options.marketplaces,
|
|
666
1029
|
packages: options.packages,
|
|
667
1030
|
workspaces: options.workspaces,
|
|
668
|
-
onReject: options.onReject
|
|
1031
|
+
onReject: options.onReject,
|
|
1032
|
+
onWarning: options.onWarning
|
|
669
1033
|
});
|
|
670
1034
|
return entries;
|
|
671
1035
|
};
|
|
1036
|
+
const byName = (name) => catalog().find((candidate) => candidate.name === name) ?? null;
|
|
672
1037
|
return {
|
|
673
1038
|
/**
|
|
674
1039
|
* Grouped, and without bodies. The cheap overview and the search's miss
|
|
@@ -693,13 +1058,13 @@ function createSkillTools(options) {
|
|
|
693
1058
|
CATALOG_PAGE_MAX
|
|
694
1059
|
);
|
|
695
1060
|
const totalPages = Math.max(1, Math.ceil(groups.length / size));
|
|
696
|
-
const
|
|
1061
|
+
const page2 = Math.min(Math.max(request.page ?? 1, 1), totalPages);
|
|
697
1062
|
return {
|
|
698
1063
|
count,
|
|
699
|
-
page,
|
|
1064
|
+
page: page2,
|
|
700
1065
|
totalPages,
|
|
701
|
-
hasNext:
|
|
702
|
-
packages: groups.slice((
|
|
1066
|
+
hasNext: page2 < totalPages,
|
|
1067
|
+
packages: groups.slice((page2 - 1) * size, page2 * size).map(([pkg, skills]) => ({ package: pkg, skills }))
|
|
703
1068
|
};
|
|
704
1069
|
},
|
|
705
1070
|
/** Ranked names. Returns; never loads a body. */
|
|
@@ -727,20 +1092,20 @@ function createSkillTools(options) {
|
|
|
727
1092
|
},
|
|
728
1093
|
/** Every property of one skill and the files it carries, without bodies. */
|
|
729
1094
|
describe(name) {
|
|
730
|
-
const entry =
|
|
1095
|
+
const entry = byName(name);
|
|
731
1096
|
if (!entry) return null;
|
|
732
|
-
const source =
|
|
733
|
-
const
|
|
1097
|
+
const source = readFileSync6(entry.path, "utf8");
|
|
1098
|
+
const listing2 = listSkillFiles(assetRoot(entry));
|
|
734
1099
|
return {
|
|
735
1100
|
name: entry.name,
|
|
736
1101
|
kind: kindOf(entry),
|
|
737
1102
|
package: entry.package,
|
|
738
1103
|
description: entry.description,
|
|
739
|
-
tier: entry.tier,
|
|
1104
|
+
...entry.tier === void 0 ? {} : { tier: entry.tier },
|
|
740
1105
|
metadata: entry.metadata ?? {},
|
|
741
1106
|
frontmatter: readFrontmatterText(source),
|
|
742
|
-
files:
|
|
743
|
-
filesTruncated:
|
|
1107
|
+
files: [...listing2.files],
|
|
1108
|
+
filesTruncated: listing2.truncated
|
|
744
1109
|
};
|
|
745
1110
|
},
|
|
746
1111
|
/**
|
|
@@ -748,54 +1113,30 @@ function createSkillTools(options) {
|
|
|
748
1113
|
* directly or through a link, are refused; nothing is ever executed.
|
|
749
1114
|
*/
|
|
750
1115
|
read(name, path) {
|
|
751
|
-
const entry =
|
|
1116
|
+
const entry = byName(name);
|
|
752
1117
|
if (!entry) return { ok: false, error: "unknown_skill", detail: name };
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
const root = single ? dirname3(scope) : scope;
|
|
760
|
-
let target;
|
|
761
|
-
try {
|
|
762
|
-
target = realpathSync(
|
|
763
|
-
single && wanted === "" ? scope : resolve2(root, wanted)
|
|
764
|
-
);
|
|
765
|
-
} catch {
|
|
766
|
-
return { ok: false, error: "no_such_file", detail: wanted };
|
|
767
|
-
}
|
|
768
|
-
const inside = single ? target === scope : target === root || target.startsWith(root + sep);
|
|
769
|
-
if (!inside) {
|
|
770
|
-
return { ok: false, error: "path_outside_skill", detail: wanted };
|
|
771
|
-
}
|
|
772
|
-
const size = statSync(target).size;
|
|
773
|
-
if (size > READ_BUDGET_BYTES) {
|
|
774
|
-
return {
|
|
775
|
-
ok: false,
|
|
776
|
-
error: "over_budget",
|
|
777
|
-
detail: `${size} bytes > ${READ_BUDGET_BYTES}`
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
const bytes = readFileSync3(target);
|
|
781
|
-
return {
|
|
782
|
-
ok: true,
|
|
783
|
-
name: entry.name,
|
|
784
|
-
path: relative2(root, target).split(sep).join("/"),
|
|
785
|
-
bytes: bytes.length,
|
|
786
|
-
sha256: sha256(bytes),
|
|
787
|
-
text: bytes.toString("utf8")
|
|
788
|
-
};
|
|
1118
|
+
return readSkillFile(entry, path);
|
|
1119
|
+
},
|
|
1120
|
+
/** Every file of one skill as links; the bytes stay out of the answer. */
|
|
1121
|
+
bundle(name) {
|
|
1122
|
+
const entry = byName(name);
|
|
1123
|
+
return entry ? bundleSkill(entry) : null;
|
|
789
1124
|
},
|
|
790
|
-
/** One body, at the moment it is needed. */
|
|
1125
|
+
/** One body, at the moment it is needed, and the other files it carries. */
|
|
791
1126
|
call(name) {
|
|
792
|
-
const entry =
|
|
1127
|
+
const entry = byName(name);
|
|
793
1128
|
if (!entry) return null;
|
|
1129
|
+
const bundle = bundleSkill(entry);
|
|
1130
|
+
const own = ownFile(entry);
|
|
794
1131
|
return {
|
|
795
1132
|
name: entry.name,
|
|
796
|
-
body: readBody(
|
|
1133
|
+
body: readBody(readFileSync6(entry.path, "utf8")),
|
|
1134
|
+
files: bundle.files.filter((file) => file.path !== own),
|
|
1135
|
+
filesTruncated: bundle.truncated
|
|
797
1136
|
};
|
|
798
1137
|
},
|
|
1138
|
+
/** The catalog entry for a name, among those this caller may see. */
|
|
1139
|
+
entry: (name) => byName(name),
|
|
799
1140
|
/** Drops the memoised catalog, so the next call re-reads the tree. */
|
|
800
1141
|
refresh() {
|
|
801
1142
|
entries = null;
|
|
@@ -806,32 +1147,19 @@ function createSkillTools(options) {
|
|
|
806
1147
|
};
|
|
807
1148
|
}
|
|
808
1149
|
|
|
809
|
-
// src/
|
|
1150
|
+
// src/extension/entry.ts
|
|
810
1151
|
import { createHash as createHash2 } from "crypto";
|
|
811
|
-
import { readFileSync as
|
|
812
|
-
import { basename as basename4, dirname as
|
|
813
|
-
import {
|
|
814
|
-
ProtocolError,
|
|
815
|
-
ProtocolErrorCode,
|
|
816
|
-
ResourceNotFoundError,
|
|
817
|
-
ResourceTemplate
|
|
818
|
-
} from "@modelcontextprotocol/server";
|
|
819
|
-
import { parse as parseYaml } from "yaml";
|
|
820
|
-
import { z } from "zod";
|
|
821
|
-
var SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
|
|
822
|
-
var AUTHORITY = "pmcp";
|
|
823
|
-
var SKILL_NAME = /^[a-z0-9](?:[a-z0-9]|-(?!-))*[a-z0-9]$|^[a-z0-9]$/u;
|
|
1152
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
|
|
1153
|
+
import { basename as basename4, dirname as dirname5, join as join5, relative as relative4, sep as sep3 } from "path";
|
|
824
1154
|
var MAX_RESOURCES = 512;
|
|
825
1155
|
var MAX_SKILL_BYTES = 16 * 1024 * 1024;
|
|
826
|
-
var PAGE_SIZE = 50;
|
|
827
|
-
var LIST_TTL_MS = 6e4;
|
|
828
1156
|
var digests = /* @__PURE__ */ new Map();
|
|
829
1157
|
function facts(path) {
|
|
830
|
-
const stat =
|
|
1158
|
+
const stat = statSync6(path);
|
|
831
1159
|
const key = `${stat.mtimeMs}:${stat.size}`;
|
|
832
1160
|
const cached = digests.get(path);
|
|
833
1161
|
if (cached?.key === key) return cached;
|
|
834
|
-
const digest = `sha256:${createHash2("sha256").update(
|
|
1162
|
+
const digest = `sha256:${createHash2("sha256").update(readFileSync7(path)).digest("hex")}`;
|
|
835
1163
|
const fresh = { key, digest, size: stat.size };
|
|
836
1164
|
digests.set(path, fresh);
|
|
837
1165
|
return fresh;
|
|
@@ -843,7 +1171,7 @@ function filesUnder(root) {
|
|
|
843
1171
|
const dir = stack.pop();
|
|
844
1172
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
845
1173
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
846
|
-
const full =
|
|
1174
|
+
const full = join5(dir, entry.name);
|
|
847
1175
|
if (entry.isDirectory()) stack.push(full);
|
|
848
1176
|
else if (entry.isFile()) found.push(full);
|
|
849
1177
|
if (found.length > MAX_RESOURCES) return null;
|
|
@@ -851,117 +1179,85 @@ function filesUnder(root) {
|
|
|
851
1179
|
}
|
|
852
1180
|
return found.sort();
|
|
853
1181
|
}
|
|
854
|
-
function frontmatterObject(source) {
|
|
855
|
-
if (!source.startsWith("---")) return null;
|
|
856
|
-
const end = source.indexOf("\n---", 3);
|
|
857
|
-
if (end === -1) return null;
|
|
858
|
-
try {
|
|
859
|
-
const parsed = parseYaml(source.slice(3, end));
|
|
860
|
-
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
861
|
-
} catch {
|
|
862
|
-
return null;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
var skillUri = (entry, file = "SKILL.md") => `skill://${AUTHORITY}/${entry.name}/${file}`;
|
|
866
1182
|
function extensionEntry(entry) {
|
|
867
1183
|
if (kindOf(entry) !== "skill" || basename4(entry.path) !== "SKILL.md")
|
|
868
1184
|
return null;
|
|
869
|
-
const frontmatter = frontmatterObject(
|
|
870
|
-
const
|
|
871
|
-
if (
|
|
872
|
-
|
|
873
|
-
if (basename4(dirname4(entry.path)) !== name) return null;
|
|
874
|
-
if (typeof frontmatter?.["description"] !== "string") return null;
|
|
875
|
-
const root = dirname4(entry.path);
|
|
1185
|
+
const frontmatter = frontmatterObject(readFileSync7(entry.path, "utf8"));
|
|
1186
|
+
const root = dirname5(entry.path);
|
|
1187
|
+
if (specProblems(frontmatter, basename4(root)).length > 0) return null;
|
|
1188
|
+
if (frontmatter?.["name"] !== entry.slug) return null;
|
|
876
1189
|
const files = filesUnder(root);
|
|
877
1190
|
if (files === null) return null;
|
|
878
1191
|
let total = 0;
|
|
879
1192
|
const resources = files.map((path) => {
|
|
880
1193
|
const fact = facts(path);
|
|
881
1194
|
total += fact.size;
|
|
882
|
-
const file =
|
|
883
|
-
return {
|
|
1195
|
+
const file = relative4(root, path).split(sep3).join("/");
|
|
1196
|
+
return {
|
|
1197
|
+
uri: skillFileUri(entry, file),
|
|
1198
|
+
digest: fact.digest,
|
|
1199
|
+
size: fact.size
|
|
1200
|
+
};
|
|
884
1201
|
});
|
|
885
1202
|
if (total > MAX_SKILL_BYTES) return null;
|
|
886
|
-
return { uri:
|
|
1203
|
+
return { uri: skillFileUri(entry), frontmatter, resources };
|
|
887
1204
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
".toml": "text/plain"
|
|
900
|
-
};
|
|
901
|
-
var mimeOf = (path) => {
|
|
902
|
-
const dot = path.lastIndexOf(".");
|
|
903
|
-
return dot === -1 ? void 0 : MIME[path.slice(dot).toLowerCase()];
|
|
904
|
-
};
|
|
905
|
-
function registerSkillsExtension(server, skills) {
|
|
1205
|
+
|
|
1206
|
+
// src/extension/register.ts
|
|
1207
|
+
import {
|
|
1208
|
+
ProtocolError,
|
|
1209
|
+
ProtocolErrorCode
|
|
1210
|
+
} from "@modelcontextprotocol/server";
|
|
1211
|
+
import { z as z2 } from "zod";
|
|
1212
|
+
var SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
|
|
1213
|
+
var PAGE_SIZE = 50;
|
|
1214
|
+
var TTL_MS = 6e4;
|
|
1215
|
+
function entriesOf(skills) {
|
|
906
1216
|
let memo = null;
|
|
907
|
-
|
|
1217
|
+
return () => {
|
|
908
1218
|
const source = skills();
|
|
909
1219
|
if (memo?.source === source) return memo.entries;
|
|
910
1220
|
const map = /* @__PURE__ */ new Map();
|
|
911
1221
|
for (const entry of source) {
|
|
912
|
-
let served = null;
|
|
913
1222
|
try {
|
|
914
|
-
served = extensionEntry(entry);
|
|
1223
|
+
const served = extensionEntry(entry);
|
|
1224
|
+
if (served) map.set(served.uri, served);
|
|
915
1225
|
} catch {
|
|
916
1226
|
}
|
|
917
|
-
if (served) map.set(served.uri, served);
|
|
918
1227
|
}
|
|
919
1228
|
memo = { source, entries: map };
|
|
920
1229
|
return map;
|
|
921
1230
|
};
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const prefix = skill.slice(0, -"SKILL.md".length);
|
|
933
|
-
if (uri.startsWith(prefix)) return join4(root, uri.slice(prefix.length));
|
|
934
|
-
}
|
|
935
|
-
return void 0;
|
|
1231
|
+
}
|
|
1232
|
+
function page(all, cursor) {
|
|
1233
|
+
const start = cursor ? Number.parseInt(cursor, 10) : 0;
|
|
1234
|
+
if (!Number.isInteger(start) || start < 0 || start > all.length) {
|
|
1235
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "invalid cursor");
|
|
1236
|
+
}
|
|
1237
|
+
const end = start + PAGE_SIZE;
|
|
1238
|
+
return {
|
|
1239
|
+
skills: all.slice(start, end),
|
|
1240
|
+
...end < all.length ? { nextCursor: String(end) } : {}
|
|
936
1241
|
};
|
|
1242
|
+
}
|
|
1243
|
+
function registerSkillsExtension(server, skills, options = {}) {
|
|
1244
|
+
const entries = entriesOf(skills);
|
|
1245
|
+
const cache = { ttlMs: TTL_MS, cacheScope: options.cacheScope ?? "public" };
|
|
937
1246
|
server.server.registerCapabilities({
|
|
938
1247
|
extensions: { [SKILLS_EXTENSION]: {} }
|
|
939
1248
|
});
|
|
940
1249
|
server.server.setRequestHandler(
|
|
941
1250
|
"skills/list",
|
|
942
|
-
{ params:
|
|
943
|
-
async (params) => {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
ProtocolErrorCode.InvalidParams,
|
|
949
|
-
"invalid cursor"
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
const end = start + PAGE_SIZE;
|
|
953
|
-
return {
|
|
954
|
-
resultType: "complete",
|
|
955
|
-
skills: all.slice(start, end),
|
|
956
|
-
...end < all.length ? { nextCursor: String(end) } : {},
|
|
957
|
-
ttlMs: LIST_TTL_MS,
|
|
958
|
-
cacheScope: "public"
|
|
959
|
-
};
|
|
960
|
-
}
|
|
1251
|
+
{ params: z2.object({ cursor: z2.string().optional() }).passthrough() },
|
|
1252
|
+
async (params) => ({
|
|
1253
|
+
resultType: "complete",
|
|
1254
|
+
...page([...entries().values()], params.cursor),
|
|
1255
|
+
...cache
|
|
1256
|
+
})
|
|
961
1257
|
);
|
|
962
1258
|
server.server.setRequestHandler(
|
|
963
1259
|
"skills/get",
|
|
964
|
-
{ params:
|
|
1260
|
+
{ params: z2.object({ uri: z2.string() }).passthrough() },
|
|
965
1261
|
async (params) => {
|
|
966
1262
|
const skill = entries().get(params.uri);
|
|
967
1263
|
if (!skill) {
|
|
@@ -970,39 +1266,104 @@ function registerSkillsExtension(server, skills) {
|
|
|
970
1266
|
`not a skill this server serves: ${params.uri}`
|
|
971
1267
|
);
|
|
972
1268
|
}
|
|
973
|
-
return { resultType: "complete", skill };
|
|
974
|
-
}
|
|
975
|
-
);
|
|
976
|
-
server.registerResource(
|
|
977
|
-
"skill-files",
|
|
978
|
-
new ResourceTemplate(`skill://${AUTHORITY}/{+path}`, { list: void 0 }),
|
|
979
|
-
{
|
|
980
|
-
title: "Skill files",
|
|
981
|
-
description: "Files of the skills listed by skills/list."
|
|
982
|
-
},
|
|
983
|
-
async (uri) => {
|
|
984
|
-
const href = uri.toString();
|
|
985
|
-
const listed = [...entries().values()].some(
|
|
986
|
-
(skill) => skill.resources.some((resource) => resource.uri === href)
|
|
987
|
-
);
|
|
988
|
-
const path = listed ? locate(href) : void 0;
|
|
989
|
-
if (path === void 0) throw new ResourceNotFoundError(href);
|
|
990
|
-
const bytes = readFileSync4(path);
|
|
991
|
-
const mimeType = mimeOf(path);
|
|
992
|
-
const text = mimeType !== void 0;
|
|
993
|
-
return {
|
|
994
|
-
contents: [
|
|
995
|
-
text ? { uri: href, mimeType, text: bytes.toString("utf8") } : { uri: href, blob: bytes.toString("base64") }
|
|
996
|
-
]
|
|
997
|
-
};
|
|
1269
|
+
return { resultType: "complete", skill, ...cache };
|
|
998
1270
|
}
|
|
999
1271
|
);
|
|
1000
1272
|
}
|
|
1001
1273
|
|
|
1274
|
+
// src/tools/schemas.ts
|
|
1275
|
+
import { z as z3 } from "zod";
|
|
1276
|
+
var kind = z3.enum(ASSET_KINDS);
|
|
1277
|
+
var catalogItemSchema = z3.object({
|
|
1278
|
+
name: z3.string(),
|
|
1279
|
+
description: z3.string(),
|
|
1280
|
+
tier: z3.string().optional(),
|
|
1281
|
+
kind: kind.optional()
|
|
1282
|
+
});
|
|
1283
|
+
var catalogResponseSchema = z3.object({
|
|
1284
|
+
count: z3.number().int(),
|
|
1285
|
+
page: z3.number().int(),
|
|
1286
|
+
totalPages: z3.number().int(),
|
|
1287
|
+
hasNext: z3.boolean(),
|
|
1288
|
+
packages: z3.array(
|
|
1289
|
+
z3.object({ package: z3.string(), skills: z3.array(catalogItemSchema) })
|
|
1290
|
+
)
|
|
1291
|
+
});
|
|
1292
|
+
var findResultSchema = z3.object({
|
|
1293
|
+
ranking: z3.enum(["semantic", "lexical"]),
|
|
1294
|
+
matches: z3.array(
|
|
1295
|
+
z3.object({
|
|
1296
|
+
name: z3.string(),
|
|
1297
|
+
package: z3.string(),
|
|
1298
|
+
description: z3.string(),
|
|
1299
|
+
score: z3.number()
|
|
1300
|
+
})
|
|
1301
|
+
)
|
|
1302
|
+
});
|
|
1303
|
+
var skillFileSchema = z3.object({
|
|
1304
|
+
path: z3.string(),
|
|
1305
|
+
bytes: z3.number().int(),
|
|
1306
|
+
sha256: z3.string()
|
|
1307
|
+
});
|
|
1308
|
+
var describeResponseSchema = z3.object({
|
|
1309
|
+
name: z3.string(),
|
|
1310
|
+
kind,
|
|
1311
|
+
package: z3.string(),
|
|
1312
|
+
description: z3.string(),
|
|
1313
|
+
tier: z3.string().optional(),
|
|
1314
|
+
metadata: z3.record(
|
|
1315
|
+
z3.string(),
|
|
1316
|
+
z3.union([z3.string(), z3.array(z3.string()).readonly()])
|
|
1317
|
+
),
|
|
1318
|
+
frontmatter: z3.string(),
|
|
1319
|
+
files: z3.array(skillFileSchema),
|
|
1320
|
+
filesTruncated: z3.boolean()
|
|
1321
|
+
});
|
|
1322
|
+
var bundleResponseSchema = z3.object({
|
|
1323
|
+
name: z3.string(),
|
|
1324
|
+
truncated: z3.boolean(),
|
|
1325
|
+
files: z3.array(
|
|
1326
|
+
skillFileSchema.extend({ uri: z3.string(), mimeType: z3.string() })
|
|
1327
|
+
)
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1002
1330
|
// src/mcp.ts
|
|
1003
1331
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
1004
|
-
|
|
1005
|
-
|
|
1332
|
+
|
|
1333
|
+
// src/tools/bundle.ts
|
|
1334
|
+
import { z as z5 } from "zod";
|
|
1335
|
+
|
|
1336
|
+
// src/tools/content.ts
|
|
1337
|
+
function fileBlock(file) {
|
|
1338
|
+
if (file.kind === "text") {
|
|
1339
|
+
return { type: "text", text: file.text ?? "" };
|
|
1340
|
+
}
|
|
1341
|
+
if (file.kind === "image" || file.kind === "audio") {
|
|
1342
|
+
return {
|
|
1343
|
+
type: file.kind,
|
|
1344
|
+
data: file.blob ?? "",
|
|
1345
|
+
mimeType: file.mimeType
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
return {
|
|
1349
|
+
type: "resource",
|
|
1350
|
+
resource: {
|
|
1351
|
+
uri: skillFileUri({ name: file.name }, file.path),
|
|
1352
|
+
mimeType: file.mimeType,
|
|
1353
|
+
blob: file.blob ?? ""
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
var resourceLink = (file) => ({
|
|
1358
|
+
type: "resource_link",
|
|
1359
|
+
uri: file.uri,
|
|
1360
|
+
name: file.path,
|
|
1361
|
+
mimeType: file.mimeType,
|
|
1362
|
+
size: file.bytes
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
// src/tools/shared.ts
|
|
1366
|
+
import { z as z4 } from "zod";
|
|
1006
1367
|
var readOnly = {
|
|
1007
1368
|
readOnlyHint: true,
|
|
1008
1369
|
destructiveHint: false,
|
|
@@ -1013,130 +1374,212 @@ var readOnlyTool = (title) => ({
|
|
|
1013
1374
|
title,
|
|
1014
1375
|
annotations: { ...readOnly, title }
|
|
1015
1376
|
});
|
|
1016
|
-
var kindInput =
|
|
1377
|
+
var kindInput = z4.enum(["skill", "agent", "hook", "mcp", "any"]).optional().describe(
|
|
1017
1378
|
"skill (default), agent, hook, mcp, or any. Commands are skills; agents, hooks and MCP servers are installed with pmcp add rather than read."
|
|
1018
1379
|
);
|
|
1380
|
+
var tierInput = z4.enum(["open", "free", "paid", "internal"]).optional();
|
|
1381
|
+
var skillNameInput = z4.string().min(1).describe("A skill name as skill_find or skill_catalog returns it.");
|
|
1019
1382
|
var asText = (value) => ({
|
|
1020
1383
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
1021
1384
|
});
|
|
1022
|
-
|
|
1385
|
+
var structured = (value) => ({
|
|
1386
|
+
...asText(value),
|
|
1387
|
+
structuredContent: { ...value }
|
|
1388
|
+
});
|
|
1389
|
+
var unknownSkill = (name) => ({
|
|
1390
|
+
...asText({
|
|
1391
|
+
error: "unknown_skill",
|
|
1392
|
+
name,
|
|
1393
|
+
hint: "skill_catalog lists the names that exist."
|
|
1394
|
+
}),
|
|
1395
|
+
isError: true
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
// src/tools/bundle.ts
|
|
1399
|
+
function registerBundleTool(server, tools) {
|
|
1023
1400
|
server.registerTool(
|
|
1024
|
-
"
|
|
1401
|
+
"skill_bundle",
|
|
1025
1402
|
{
|
|
1026
|
-
...readOnlyTool("List
|
|
1027
|
-
description: "Every
|
|
1028
|
-
inputSchema:
|
|
1029
|
-
|
|
1030
|
-
pageSize: z2.number().int().min(1).max(20).optional(),
|
|
1031
|
-
tier: z2.enum(["open", "free", "paid", "internal"]).optional(),
|
|
1032
|
-
kind: kindInput
|
|
1033
|
-
})
|
|
1403
|
+
...readOnlyTool("List a skill's files"),
|
|
1404
|
+
description: "Every file of one skill with its path relative to the skill directory, size, media type and sha256, as resource links that resources/read returns. The bytes are not included in this result. Used to write a skill to a local skills directory.",
|
|
1405
|
+
inputSchema: z5.object({ name: skillNameInput }),
|
|
1406
|
+
outputSchema: bundleResponseSchema
|
|
1034
1407
|
},
|
|
1035
|
-
async (
|
|
1408
|
+
async ({ name }) => {
|
|
1409
|
+
const bundle = tools.bundle(name);
|
|
1410
|
+
if (!bundle) return unknownSkill(name);
|
|
1411
|
+
const value = { ...bundle, files: [...bundle.files] };
|
|
1412
|
+
return {
|
|
1413
|
+
content: [
|
|
1414
|
+
{ type: "text", text: JSON.stringify(value, null, 2) },
|
|
1415
|
+
...bundle.files.map(resourceLink)
|
|
1416
|
+
],
|
|
1417
|
+
structuredContent: value
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/tools/call.ts
|
|
1424
|
+
import { z as z6 } from "zod";
|
|
1425
|
+
function manifest(found) {
|
|
1426
|
+
const lines = found.files.map(
|
|
1427
|
+
(file) => `- ${file.path} (${file.bytes} bytes, ${file.mimeType}, sha256 ${file.sha256.slice(0, 12)})`
|
|
1036
1428
|
);
|
|
1429
|
+
if (found.filesTruncated)
|
|
1430
|
+
lines.push("- (list cut; skill_bundle has the rest)");
|
|
1431
|
+
return [
|
|
1432
|
+
`Files in skill ${found.name}. skill_read returns one by this name and its path; skill_bundle returns every file with its path:`,
|
|
1433
|
+
...lines
|
|
1434
|
+
].join("\n");
|
|
1435
|
+
}
|
|
1436
|
+
function registerCallTool(server, tools) {
|
|
1037
1437
|
server.registerTool(
|
|
1038
|
-
"
|
|
1438
|
+
"skill_call",
|
|
1039
1439
|
{
|
|
1040
|
-
...readOnlyTool("
|
|
1041
|
-
description: "
|
|
1042
|
-
inputSchema:
|
|
1043
|
-
|
|
1044
|
-
limit: z2.number().int().min(1).max(25).optional(),
|
|
1045
|
-
filters: z2.object({
|
|
1046
|
-
kind: kindInput,
|
|
1047
|
-
tier: z2.enum(["open", "free", "paid", "internal"]).optional(),
|
|
1048
|
-
package: z2.string().optional(),
|
|
1049
|
-
level: z2.string().optional(),
|
|
1050
|
-
domain: z2.string().optional(),
|
|
1051
|
-
medium: z2.string().optional()
|
|
1052
|
-
}).optional().describe("Exact-match filters over the catalog before ranking.")
|
|
1440
|
+
...readOnlyTool("Read a skill"),
|
|
1441
|
+
description: "The body of a single skill, by the name skill_find or skill_catalog returned, followed by the list of reference, script and asset files the skill carries, if any, as text and as resource links. This is the only call that costs a document.",
|
|
1442
|
+
inputSchema: z6.object({
|
|
1443
|
+
name: z6.string().min(1).describe("The skill name, as <package>/<slug>.")
|
|
1053
1444
|
})
|
|
1054
1445
|
},
|
|
1055
|
-
async ({
|
|
1446
|
+
async ({ name }) => {
|
|
1447
|
+
const found = tools.call(name);
|
|
1448
|
+
if (!found) return unknownSkill(name);
|
|
1449
|
+
const body = { type: "text", text: found.body };
|
|
1450
|
+
if (found.files.length === 0) return { content: [body] };
|
|
1451
|
+
return {
|
|
1452
|
+
content: [
|
|
1453
|
+
body,
|
|
1454
|
+
{ type: "text", text: manifest(found) },
|
|
1455
|
+
...found.files.map(resourceLink)
|
|
1456
|
+
]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/tools/catalog.ts
|
|
1463
|
+
import { z as z7 } from "zod";
|
|
1464
|
+
function registerCatalogTool(server, tools) {
|
|
1465
|
+
server.registerTool(
|
|
1466
|
+
"skill_catalog",
|
|
1467
|
+
{
|
|
1468
|
+
...readOnlyTool("List skills"),
|
|
1469
|
+
description: "Every skill the installed packages and marketplaces ship, grouped by package or plugin, with names, one-line descriptions and tier, and no bodies. Paged by group. With kind it lists agents, hooks or MCP servers instead.",
|
|
1470
|
+
inputSchema: z7.object({
|
|
1471
|
+
page: z7.number().int().min(1).optional(),
|
|
1472
|
+
pageSize: z7.number().int().min(1).max(20).optional(),
|
|
1473
|
+
tier: tierInput,
|
|
1474
|
+
kind: kindInput
|
|
1475
|
+
}),
|
|
1476
|
+
outputSchema: catalogResponseSchema
|
|
1477
|
+
},
|
|
1478
|
+
async (request) => structured(tools.catalog(request))
|
|
1056
1479
|
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/tools/describe.ts
|
|
1483
|
+
import { z as z8 } from "zod";
|
|
1484
|
+
function registerDescribeTool(server, tools) {
|
|
1057
1485
|
server.registerTool(
|
|
1058
1486
|
"skill_describe",
|
|
1059
1487
|
{
|
|
1060
1488
|
...readOnlyTool("Describe a skill"),
|
|
1061
|
-
description: "
|
|
1062
|
-
inputSchema:
|
|
1063
|
-
|
|
1064
|
-
})
|
|
1489
|
+
description: "One skill's frontmatter, metadata (tier, level, requirements, licence, approval), and the files it carries with sizes and sha256, without any body.",
|
|
1490
|
+
inputSchema: z8.object({ name: skillNameInput }),
|
|
1491
|
+
outputSchema: describeResponseSchema
|
|
1065
1492
|
},
|
|
1066
1493
|
async ({ name }) => {
|
|
1067
1494
|
const described = tools.describe(name);
|
|
1068
|
-
|
|
1069
|
-
return {
|
|
1070
|
-
...asText({
|
|
1071
|
-
error: "unknown_skill",
|
|
1072
|
-
name,
|
|
1073
|
-
hint: "Call skill_catalog for the names that exist."
|
|
1074
|
-
}),
|
|
1075
|
-
isError: true
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
return asText(described);
|
|
1495
|
+
return described ? structured(described) : unknownSkill(name);
|
|
1079
1496
|
}
|
|
1080
1497
|
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/tools/find.ts
|
|
1501
|
+
import { z as z9 } from "zod";
|
|
1502
|
+
function registerFindTool(server, tools) {
|
|
1503
|
+
server.registerTool(
|
|
1504
|
+
"skill_find",
|
|
1505
|
+
{
|
|
1506
|
+
...readOnlyTool("Find skills"),
|
|
1507
|
+
description: "Skill names ranked against an intent, with descriptions and scores, and no bodies. The ranking compares the intent with skill descriptions, so a full sentence matches better than a single keyword.",
|
|
1508
|
+
inputSchema: z9.object({
|
|
1509
|
+
intent: z9.string().min(1).max(256).describe("What the task is, as a sentence."),
|
|
1510
|
+
limit: z9.number().int().min(1).max(25).optional(),
|
|
1511
|
+
filters: z9.object({
|
|
1512
|
+
kind: kindInput,
|
|
1513
|
+
tier: tierInput,
|
|
1514
|
+
package: z9.string().optional(),
|
|
1515
|
+
level: z9.string().optional(),
|
|
1516
|
+
domain: z9.string().optional(),
|
|
1517
|
+
medium: z9.string().optional()
|
|
1518
|
+
}).optional().describe("Exact-match filters over the catalog before ranking.")
|
|
1519
|
+
}),
|
|
1520
|
+
outputSchema: findResultSchema
|
|
1521
|
+
},
|
|
1522
|
+
async ({ intent, limit, filters }) => structured(await tools.find(intent, limit, filters))
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/tools/read.ts
|
|
1527
|
+
import { z as z10 } from "zod";
|
|
1528
|
+
function registerReadTool(server, tools) {
|
|
1081
1529
|
server.registerTool(
|
|
1082
1530
|
"skill_read",
|
|
1083
1531
|
{
|
|
1084
1532
|
...readOnlyTool("Read a skill file"),
|
|
1085
|
-
description: "
|
|
1086
|
-
inputSchema:
|
|
1087
|
-
name:
|
|
1088
|
-
path:
|
|
1089
|
-
"Path relative to the skill directory
|
|
1533
|
+
description: "One file inside a skill's directory in this catalog, with its sha256 and media type: text as text, images and audio as image and audio content, other binary files as an embedded resource. path is relative to the skill, as skill_call, skill_bundle and skill_describe list it (layout: https://pmcp.build). Scripts are returned as content, never run, and no external API is called. Files over 256 KiB and paths outside the skill are refused.",
|
|
1534
|
+
inputSchema: z10.object({
|
|
1535
|
+
name: skillNameInput,
|
|
1536
|
+
path: z10.string().min(1).optional().describe(
|
|
1537
|
+
"Path relative to the skill directory. Defaults to SKILL.md."
|
|
1090
1538
|
)
|
|
1091
1539
|
})
|
|
1092
1540
|
},
|
|
1093
1541
|
async ({ name, path }) => {
|
|
1094
1542
|
const result = tools.read(name, path);
|
|
1095
1543
|
if (!result.ok) return { ...asText(result), isError: true };
|
|
1096
|
-
const { text, ...header } = result;
|
|
1544
|
+
const { text: _text, blob: _blob, ...header } = result;
|
|
1097
1545
|
return {
|
|
1098
1546
|
content: [
|
|
1099
1547
|
{ type: "text", text: JSON.stringify(header) },
|
|
1100
|
-
|
|
1548
|
+
fileBlock(result)
|
|
1101
1549
|
]
|
|
1102
1550
|
};
|
|
1103
1551
|
}
|
|
1104
1552
|
);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
return {
|
|
1118
|
-
...asText({
|
|
1119
|
-
error: "unknown_skill",
|
|
1120
|
-
name,
|
|
1121
|
-
hint: "Call skill_catalog for the names that exist."
|
|
1122
|
-
}),
|
|
1123
|
-
isError: true
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
return { content: [{ type: "text", text: found.body }] };
|
|
1127
|
-
}
|
|
1128
|
-
);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/mcp.ts
|
|
1556
|
+
var SERVER_NAME = "pmcp";
|
|
1557
|
+
var SERVER_VERSION = "0.8.0";
|
|
1558
|
+
function registerSkillTools(server, tools) {
|
|
1559
|
+
registerCatalogTool(server, tools);
|
|
1560
|
+
registerFindTool(server, tools);
|
|
1561
|
+
registerDescribeTool(server, tools);
|
|
1562
|
+
registerReadTool(server, tools);
|
|
1563
|
+
registerCallTool(server, tools);
|
|
1564
|
+
registerBundleTool(server, tools);
|
|
1129
1565
|
return server;
|
|
1130
1566
|
}
|
|
1131
1567
|
function createSkillServer(options) {
|
|
1132
|
-
const server = new McpServer({ name: SERVER_NAME, version:
|
|
1568
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
1133
1569
|
const tools = createSkillTools(options);
|
|
1134
|
-
registerSkillsExtension(server, tools.skills);
|
|
1570
|
+
registerSkillsExtension(server, tools.skills, options.skillsExtension);
|
|
1571
|
+
registerSkillResources(server, tools.skills);
|
|
1572
|
+
if (options.prompts) registerSkillPrompts(server, tools);
|
|
1135
1573
|
return registerSkillTools(server, tools);
|
|
1136
1574
|
}
|
|
1137
1575
|
|
|
1138
1576
|
export {
|
|
1139
1577
|
normaliseIntent,
|
|
1578
|
+
frontmatterObject,
|
|
1579
|
+
NAME_MAX,
|
|
1580
|
+
DESCRIPTION_MAX,
|
|
1581
|
+
COMPATIBILITY_MAX,
|
|
1582
|
+
specProblems,
|
|
1140
1583
|
readMetadata,
|
|
1141
1584
|
readFrontmatterText,
|
|
1142
1585
|
readMarketplace,
|
|
@@ -1150,21 +1593,36 @@ export {
|
|
|
1150
1593
|
readCatalog,
|
|
1151
1594
|
withoutBody,
|
|
1152
1595
|
displayPath,
|
|
1596
|
+
promptName,
|
|
1597
|
+
registerSkillPrompts,
|
|
1598
|
+
listSkillFiles,
|
|
1599
|
+
mimeOf,
|
|
1600
|
+
isUtf8Text,
|
|
1601
|
+
classify,
|
|
1602
|
+
READ_BUDGET_BYTES,
|
|
1603
|
+
readSkillFile,
|
|
1604
|
+
skillFileUri,
|
|
1605
|
+
resolveSkillFileUri,
|
|
1606
|
+
registerSkillResources,
|
|
1153
1607
|
DEFAULT_FIND_LIMIT,
|
|
1154
1608
|
lexicalScore,
|
|
1155
1609
|
cosine,
|
|
1156
1610
|
find,
|
|
1157
|
-
|
|
1611
|
+
bundleSkill,
|
|
1158
1612
|
DESCRIBE_FILE_LIMIT,
|
|
1159
1613
|
CATALOG_PAGE_SIZE,
|
|
1160
1614
|
CATALOG_PAGE_MAX,
|
|
1161
1615
|
createSkillTools,
|
|
1162
|
-
SKILLS_EXTENSION,
|
|
1163
|
-
skillUri,
|
|
1164
1616
|
extensionEntry,
|
|
1617
|
+
SKILLS_EXTENSION,
|
|
1165
1618
|
registerSkillsExtension,
|
|
1619
|
+
catalogResponseSchema,
|
|
1620
|
+
findResultSchema,
|
|
1621
|
+
describeResponseSchema,
|
|
1622
|
+
bundleResponseSchema,
|
|
1166
1623
|
SERVER_NAME,
|
|
1624
|
+
SERVER_VERSION,
|
|
1167
1625
|
registerSkillTools,
|
|
1168
1626
|
createSkillServer
|
|
1169
1627
|
};
|
|
1170
|
-
//# sourceMappingURL=chunk-
|
|
1628
|
+
//# sourceMappingURL=chunk-ZDV75HZN.js.map
|