@modootoday/pmcp 0.4.0 → 0.5.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.
@@ -5,11 +5,13 @@ function normaliseIntent(intent) {
5
5
 
6
6
  // src/marketplace.ts
7
7
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
8
- import { join as join2, resolve } from "path";
8
+ import { basename as basename2, dirname as dirname2, join as join2, resolve } from "path";
9
9
 
10
10
  // src/catalog.ts
11
11
  import { existsSync, readFileSync, readdirSync } from "fs";
12
12
  import { basename, dirname, isAbsolute, join, relative } from "path";
13
+ var ASSET_KINDS = ["skill", "agent", "hook", "mcp"];
14
+ var kindOf = (entry) => entry.kind ?? "skill";
13
15
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([
14
16
  "node_modules",
15
17
  ".git",
@@ -295,6 +297,151 @@ var asList = (value) => {
295
297
  if (value === void 0) return [];
296
298
  return typeof value === "string" ? [value] : value;
297
299
  };
300
+ function readJson(path) {
301
+ try {
302
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
303
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+ function markdownEntry(context, path, fallbackSlug, shape) {
309
+ const source = readFileSync2(path, "utf8");
310
+ const front = readFrontmatter(source);
311
+ const description = front["description"] ?? "";
312
+ if (description === "") {
313
+ context.onReject?.({ path, reason: "no description" });
314
+ return null;
315
+ }
316
+ const metadata = readMetadata(source);
317
+ const ownTier = asString(metadata["tier"]);
318
+ if (context.tier !== void 0 && ownTier !== void 0 && ownTier !== context.tier) {
319
+ context.onReject?.({
320
+ path,
321
+ reason: `metadata.tier ${ownTier} in a ${context.tier} marketplace`
322
+ });
323
+ return null;
324
+ }
325
+ const slug = front["name"] ?? fallbackSlug;
326
+ const prefix = `${context.marketplace}/${context.plugin}`;
327
+ return {
328
+ name: shape.namespace ? `${prefix}/${shape.namespace}/${slug}` : `${prefix}/${slug}`,
329
+ package: prefix,
330
+ slug,
331
+ description,
332
+ path,
333
+ tier: ownTier ?? context.tier,
334
+ metadata,
335
+ keywords: asList(metadata["keywords"]),
336
+ ...shape.kind ? { kind: shape.kind } : {},
337
+ ...shape.root ? { root: shape.root } : {}
338
+ };
339
+ }
340
+ function skillEntries(context) {
341
+ const dir = join2(context.pluginDir, "skills");
342
+ if (!existsSync2(dir)) return [];
343
+ const found = [];
344
+ for (const child of readdirSync2(dir, { withFileTypes: true })) {
345
+ if (!child.isDirectory()) continue;
346
+ const path = join2(dir, child.name, "SKILL.md");
347
+ if (!existsSync2(path)) continue;
348
+ const entry = markdownEntry(context, path, child.name, {});
349
+ if (entry) found.push(entry);
350
+ }
351
+ return found;
352
+ }
353
+ function commandEntries(context) {
354
+ const dir = join2(context.pluginDir, "commands");
355
+ if (!existsSync2(dir)) return [];
356
+ const found = [];
357
+ for (const child of readdirSync2(dir, { withFileTypes: true })) {
358
+ if (!child.isFile() || !child.name.endsWith(".md")) continue;
359
+ const path = join2(dir, child.name);
360
+ const entry = markdownEntry(context, path, basename2(child.name, ".md"), {
361
+ root: path
362
+ });
363
+ if (entry) found.push(entry);
364
+ }
365
+ return found;
366
+ }
367
+ function agentEntries(context) {
368
+ const dir = join2(context.pluginDir, "agents");
369
+ if (!existsSync2(dir)) return [];
370
+ const found = [];
371
+ for (const child of readdirSync2(dir, { withFileTypes: true })) {
372
+ let entry = null;
373
+ if (child.isFile() && child.name.endsWith(".md")) {
374
+ const path = join2(dir, child.name);
375
+ entry = markdownEntry(context, path, basename2(child.name, ".md"), {
376
+ kind: "agent",
377
+ root: path,
378
+ namespace: "agents"
379
+ });
380
+ } else if (child.isDirectory() && existsSync2(join2(dir, child.name, "agent.md"))) {
381
+ entry = markdownEntry(
382
+ context,
383
+ join2(dir, child.name, "agent.md"),
384
+ child.name,
385
+ {
386
+ kind: "agent",
387
+ namespace: "agents"
388
+ }
389
+ );
390
+ }
391
+ if (entry) found.push(entry);
392
+ }
393
+ return found;
394
+ }
395
+ function hookEntries(context, manifest) {
396
+ const declared = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
397
+ const path = resolve(context.pluginDir, declared ?? "hooks/hooks.json");
398
+ if (!existsSync2(path)) return [];
399
+ const hooks = readJson(path);
400
+ if (hooks === null) {
401
+ context.onReject?.({ path, reason: "hooks.json is not a JSON object" });
402
+ return [];
403
+ }
404
+ const own = typeof hooks["description"] === "string" ? hooks["description"] : void 0;
405
+ const description = own ?? context.pluginDescription ?? "";
406
+ if (description === "") {
407
+ context.onReject?.({ path, reason: "no description" });
408
+ return [];
409
+ }
410
+ const prefix = `${context.marketplace}/${context.plugin}`;
411
+ return [
412
+ {
413
+ name: `${prefix}/hooks`,
414
+ package: prefix,
415
+ slug: "hooks",
416
+ description,
417
+ path,
418
+ tier: context.tier,
419
+ kind: "hook",
420
+ root: context.pluginDir
421
+ }
422
+ ];
423
+ }
424
+ function mcpEntries(context) {
425
+ const path = join2(context.pluginDir, ".mcp.json");
426
+ if (!existsSync2(path)) return [];
427
+ const config = readJson(path);
428
+ const servers = config?.["mcpServers"];
429
+ if (servers === null || typeof servers !== "object" || Array.isArray(servers)) {
430
+ context.onReject?.({ path, reason: ".mcp.json has no mcpServers object" });
431
+ return [];
432
+ }
433
+ const prefix = `${context.marketplace}/${context.plugin}`;
434
+ return Object.keys(servers).map((server) => ({
435
+ name: `${prefix}/mcp/${server}`,
436
+ package: prefix,
437
+ slug: server,
438
+ description: context.pluginDescription ? `MCP server ${server}. ${context.pluginDescription}` : `MCP server ${server} from plugin ${context.plugin}.`,
439
+ path,
440
+ tier: context.tier,
441
+ kind: "mcp",
442
+ root: path
443
+ }));
444
+ }
298
445
  function readMarketplace(dir, onReject) {
299
446
  const manifestPath = join2(dir, ".claude-plugin", "marketplace.json");
300
447
  if (!existsSync2(manifestPath)) {
@@ -317,47 +464,33 @@ function readMarketplace(dir, onReject) {
317
464
  return [];
318
465
  }
319
466
  const marketplace = manifest.name ?? "marketplace";
320
- const tier = manifest.metadata?.tier;
321
467
  const found = [];
322
468
  for (const plugin of manifest.plugins ?? []) {
323
469
  if (!plugin.name || !plugin.source) continue;
324
- const skillsDir = join2(resolve(dir, plugin.source), "skills");
325
- if (!existsSync2(skillsDir)) continue;
326
- for (const child of readdirSync2(skillsDir, { withFileTypes: true })) {
327
- if (!child.isDirectory()) continue;
328
- const path = join2(skillsDir, child.name, "SKILL.md");
329
- if (!existsSync2(path)) continue;
330
- const source = readFileSync2(path, "utf8");
331
- const front = readFrontmatter(source);
332
- const description = front["description"] ?? "";
333
- if (description === "") {
334
- onReject?.({ path, reason: "no description" });
335
- continue;
336
- }
337
- const metadata = readMetadata(source);
338
- const ownTier = asString(metadata["tier"]);
339
- if (tier !== void 0 && ownTier !== void 0 && ownTier !== tier) {
340
- onReject?.({
341
- path,
342
- reason: `metadata.tier ${ownTier} in a ${tier} marketplace`
343
- });
344
- continue;
345
- }
346
- const slug = front["name"] ?? child.name;
347
- found.push({
348
- name: `${marketplace}/${plugin.name}/${slug}`,
349
- package: `${marketplace}/${plugin.name}`,
350
- slug,
351
- description,
352
- path,
353
- tier: ownTier ?? tier,
354
- metadata,
355
- keywords: asList(metadata["keywords"])
356
- });
357
- }
470
+ const pluginDir = resolve(dir, plugin.source);
471
+ if (!existsSync2(pluginDir)) continue;
472
+ const pluginManifest = readJson(
473
+ join2(pluginDir, ".claude-plugin", "plugin.json")
474
+ ) ?? {};
475
+ const context = {
476
+ marketplace,
477
+ plugin: plugin.name,
478
+ pluginDir,
479
+ pluginDescription: typeof pluginManifest.description === "string" ? pluginManifest.description : void 0,
480
+ tier: manifest.metadata?.tier,
481
+ onReject
482
+ };
483
+ found.push(
484
+ ...skillEntries(context),
485
+ ...commandEntries(context),
486
+ ...agentEntries(context),
487
+ ...hookEntries(context, pluginManifest),
488
+ ...mcpEntries(context)
489
+ );
358
490
  }
359
491
  return found;
360
492
  }
493
+ var assetRoot = (entry) => entry.root ?? dirname2(entry.path);
361
494
 
362
495
  // src/find.ts
363
496
  var DEFAULT_FIND_LIMIT = 5;
@@ -445,12 +578,22 @@ function rank(matches2, limit) {
445
578
  // src/server.ts
446
579
  import { createHash } from "crypto";
447
580
  import { readFileSync as readFileSync3, readdirSync as readdirSync3, realpathSync, statSync } from "fs";
448
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2, sep } from "path";
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";
449
590
  var READ_BUDGET_BYTES = 256 * 1024;
450
591
  var DESCRIBE_FILE_LIMIT = 200;
451
592
  var CATALOG_PAGE_SIZE = 8;
452
593
  var CATALOG_PAGE_MAX = 20;
453
594
  function matches(entry, filters) {
595
+ const kind = filters?.kind ?? "skill";
596
+ if (kind !== "any" && kindOf(entry) !== kind) return false;
454
597
  if (!filters) return true;
455
598
  const meta = entry.metadata ?? {};
456
599
  const field = (key) => {
@@ -471,6 +614,15 @@ function matches(entry, filters) {
471
614
  var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
472
615
  function listFiles(root) {
473
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
+ }
474
626
  const stack = [root];
475
627
  while (stack.length > 0) {
476
628
  const dir = stack.pop();
@@ -495,6 +647,14 @@ function listFiles(root) {
495
647
  files.sort((a, b) => a.path.localeCompare(b.path));
496
648
  return { files, truncated: false };
497
649
  }
650
+ function catalogItem(entry) {
651
+ return {
652
+ name: entry.name,
653
+ description: entry.description,
654
+ ...entry.tier === void 0 ? {} : { tier: entry.tier },
655
+ ...kindOf(entry) === "skill" ? {} : { kind: kindOf(entry) }
656
+ };
657
+ }
498
658
  function createSkillTools(options) {
499
659
  const load = options.loadCatalog ?? readCatalog;
500
660
  let entries = null;
@@ -518,16 +678,11 @@ function createSkillTools(options) {
518
678
  const byPackage = /* @__PURE__ */ new Map();
519
679
  let count = 0;
520
680
  for (const entry of catalog()) {
521
- if (!matches(entry, { tier: request.tier })) continue;
681
+ if (!matches(entry, { tier: request.tier, kind: request.kind }))
682
+ continue;
522
683
  count += 1;
523
684
  const list = byPackage.get(entry.package) ?? [];
524
- list.push(
525
- entry.tier === void 0 ? { name: entry.name, description: entry.description } : {
526
- name: entry.name,
527
- description: entry.description,
528
- tier: entry.tier
529
- }
530
- );
685
+ list.push(catalogItem(entry));
531
686
  byPackage.set(entry.package, list);
532
687
  }
533
688
  const groups = [...byPackage.entries()].sort(
@@ -575,9 +730,10 @@ function createSkillTools(options) {
575
730
  const entry = catalog().find((candidate) => candidate.name === name);
576
731
  if (!entry) return null;
577
732
  const source = readFileSync3(entry.path, "utf8");
578
- const listing = listFiles(dirname2(entry.path));
733
+ const listing = listFiles(assetRoot(entry));
579
734
  return {
580
735
  name: entry.name,
736
+ kind: kindOf(entry),
581
737
  package: entry.package,
582
738
  description: entry.description,
583
739
  tier: entry.tier,
@@ -591,21 +747,27 @@ function createSkillTools(options) {
591
747
  * One file inside a skill's directory. Paths that leave the directory,
592
748
  * directly or through a link, are refused; nothing is ever executed.
593
749
  */
594
- read(name, path = "SKILL.md") {
750
+ read(name, path) {
595
751
  const entry = catalog().find((candidate) => candidate.name === name);
596
752
  if (!entry) return { ok: false, error: "unknown_skill", detail: name };
597
- if (isAbsolute2(path) || path.split(/[\\/]/u).includes("..")) {
598
- return { ok: false, error: "path_outside_skill", detail: path };
753
+ const wanted = path ?? relative2(assetRoot(entry), entry.path);
754
+ if (isAbsolute2(wanted) || wanted.split(/[\\/]/u).includes("..")) {
755
+ return { ok: false, error: "path_outside_skill", detail: wanted };
599
756
  }
600
- const root = realpathSync(dirname2(entry.path));
757
+ const scope = realpathSync(assetRoot(entry));
758
+ const single = statSync(scope).isFile();
759
+ const root = single ? dirname3(scope) : scope;
601
760
  let target;
602
761
  try {
603
- target = realpathSync(resolve2(root, path));
762
+ target = realpathSync(
763
+ single && wanted === "" ? scope : resolve2(root, wanted)
764
+ );
604
765
  } catch {
605
- return { ok: false, error: "no_such_file", detail: path };
766
+ return { ok: false, error: "no_such_file", detail: wanted };
606
767
  }
607
- if (target !== root && !target.startsWith(root + sep)) {
608
- return { ok: false, error: "path_outside_skill", detail: path };
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 };
609
771
  }
610
772
  const size = statSync(target).size;
611
773
  if (size > READ_BUDGET_BYTES) {
@@ -638,13 +800,208 @@ function createSkillTools(options) {
638
800
  refresh() {
639
801
  entries = null;
640
802
  },
641
- entries: () => catalog().map(withoutBody)
803
+ entries: () => catalog().map(withoutBody),
804
+ /** The catalog with paths, for the Skills extension. The same array until refresh. */
805
+ skills: () => catalog()
806
+ };
807
+ }
808
+
809
+ // src/skills-extension.ts
810
+ import { createHash as createHash2 } from "crypto";
811
+ import { readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
812
+ import { basename as basename4, dirname as dirname4, join as join4, relative as relative3, sep as sep2 } from "path";
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;
824
+ var MAX_RESOURCES = 512;
825
+ var MAX_SKILL_BYTES = 16 * 1024 * 1024;
826
+ var PAGE_SIZE = 50;
827
+ var LIST_TTL_MS = 6e4;
828
+ var digests = /* @__PURE__ */ new Map();
829
+ function facts(path) {
830
+ const stat = statSync2(path);
831
+ const key = `${stat.mtimeMs}:${stat.size}`;
832
+ const cached = digests.get(path);
833
+ if (cached?.key === key) return cached;
834
+ const digest = `sha256:${createHash2("sha256").update(readFileSync4(path)).digest("hex")}`;
835
+ const fresh = { key, digest, size: stat.size };
836
+ digests.set(path, fresh);
837
+ return fresh;
838
+ }
839
+ function filesUnder(root) {
840
+ const found = [];
841
+ const stack = [root];
842
+ while (stack.length > 0) {
843
+ const dir = stack.pop();
844
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
845
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
846
+ const full = join4(dir, entry.name);
847
+ if (entry.isDirectory()) stack.push(full);
848
+ else if (entry.isFile()) found.push(full);
849
+ if (found.length > MAX_RESOURCES) return null;
850
+ }
851
+ }
852
+ return found.sort();
853
+ }
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
+ function extensionEntry(entry) {
867
+ if (kindOf(entry) !== "skill" || basename4(entry.path) !== "SKILL.md")
868
+ return null;
869
+ const frontmatter = frontmatterObject(readFileSync4(entry.path, "utf8"));
870
+ const name = frontmatter?.["name"];
871
+ if (typeof name !== "string" || name !== entry.slug || !SKILL_NAME.test(name))
872
+ return null;
873
+ if (basename4(dirname4(entry.path)) !== name) return null;
874
+ if (typeof frontmatter?.["description"] !== "string") return null;
875
+ const root = dirname4(entry.path);
876
+ const files = filesUnder(root);
877
+ if (files === null) return null;
878
+ let total = 0;
879
+ const resources = files.map((path) => {
880
+ const fact = facts(path);
881
+ total += fact.size;
882
+ const file = relative3(root, path).split(sep2).join("/");
883
+ return { uri: skillUri(entry, file), digest: fact.digest, size: fact.size };
884
+ });
885
+ if (total > MAX_SKILL_BYTES) return null;
886
+ return { uri: skillUri(entry), frontmatter, resources };
887
+ }
888
+ var MIME = {
889
+ ".md": "text/markdown",
890
+ ".json": "application/json",
891
+ ".txt": "text/plain",
892
+ ".py": "text/x-python",
893
+ ".js": "text/javascript",
894
+ ".mjs": "text/javascript",
895
+ ".ts": "text/typescript",
896
+ ".sh": "text/x-shellscript",
897
+ ".yaml": "text/yaml",
898
+ ".yml": "text/yaml",
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) {
906
+ let memo = null;
907
+ const entries = () => {
908
+ const source = skills();
909
+ if (memo?.source === source) return memo.entries;
910
+ const map = /* @__PURE__ */ new Map();
911
+ for (const entry of source) {
912
+ let served = null;
913
+ try {
914
+ served = extensionEntry(entry);
915
+ } catch {
916
+ }
917
+ if (served) map.set(served.uri, served);
918
+ }
919
+ memo = { source, entries: map };
920
+ return map;
921
+ };
922
+ const pathOf = /* @__PURE__ */ new Map();
923
+ const locate = (uri) => {
924
+ if (pathOf.size === 0) {
925
+ for (const entry of skills()) {
926
+ if (kindOf(entry) === "skill" && basename4(entry.path) === "SKILL.md") {
927
+ pathOf.set(skillUri(entry), dirname4(entry.path));
928
+ }
929
+ }
930
+ }
931
+ for (const [skill, root] of pathOf) {
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;
642
936
  };
937
+ server.server.registerCapabilities({
938
+ extensions: { [SKILLS_EXTENSION]: {} }
939
+ });
940
+ server.server.setRequestHandler(
941
+ "skills/list",
942
+ { params: z.object({ cursor: z.string().optional() }).passthrough() },
943
+ async (params) => {
944
+ const all = [...entries().values()];
945
+ const start = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
946
+ if (!Number.isInteger(start) || start < 0 || start > all.length) {
947
+ throw new ProtocolError(
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
+ }
961
+ );
962
+ server.server.setRequestHandler(
963
+ "skills/get",
964
+ { params: z.object({ uri: z.string() }).passthrough() },
965
+ async (params) => {
966
+ const skill = entries().get(params.uri);
967
+ if (!skill) {
968
+ throw new ProtocolError(
969
+ ProtocolErrorCode.InvalidParams,
970
+ `not a skill this server serves: ${params.uri}`
971
+ );
972
+ }
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
+ };
998
+ }
999
+ );
643
1000
  }
644
1001
 
645
1002
  // src/mcp.ts
646
1003
  import { McpServer } from "@modelcontextprotocol/server";
647
- import { z } from "zod";
1004
+ import { z as z2 } from "zod";
648
1005
  var SERVER_NAME = "pmcp";
649
1006
  var readOnly = {
650
1007
  readOnlyHint: true,
@@ -652,6 +1009,9 @@ var readOnly = {
652
1009
  idempotentHint: true,
653
1010
  openWorldHint: false
654
1011
  };
1012
+ var kindInput = z2.enum(["skill", "agent", "hook", "mcp", "any"]).optional().describe(
1013
+ "skill (default), agent, hook, mcp, or any. Commands are skills; agents, hooks and MCP servers are installed with pmcp add rather than read."
1014
+ );
655
1015
  var asText = (value) => ({
656
1016
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
657
1017
  });
@@ -660,11 +1020,12 @@ function registerSkillTools(server, tools) {
660
1020
  "skill_catalog",
661
1021
  {
662
1022
  title: "List installed skills without their bodies",
663
- 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. Use it to see what exists, or when skill_find returns nothing.",
664
- inputSchema: z.object({
665
- page: z.number().int().min(1).optional(),
666
- pageSize: z.number().int().min(1).max(20).optional(),
667
- tier: z.enum(["open", "free", "paid", "internal"]).optional()
1023
+ 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. Use it to see what exists, or when skill_find returns nothing. Pass kind to list agents, hooks or MCP servers instead.",
1024
+ inputSchema: z2.object({
1025
+ page: z2.number().int().min(1).optional(),
1026
+ pageSize: z2.number().int().min(1).max(20).optional(),
1027
+ tier: z2.enum(["open", "free", "paid", "internal"]).optional(),
1028
+ kind: kindInput
668
1029
  }),
669
1030
  annotations: readOnly
670
1031
  },
@@ -675,15 +1036,16 @@ function registerSkillTools(server, tools) {
675
1036
  {
676
1037
  title: "Find skills by describing the problem",
677
1038
  description: "Ranked skill names for an intent. Describe the problem in a full sentence in English rather than a keyword: a single word matches far worse, and the ranking is over descriptions. Returns names only -- read one with skill_call.",
678
- inputSchema: z.object({
679
- intent: z.string().min(1).max(256).describe("What you are trying to do, as an English sentence."),
680
- limit: z.number().int().min(1).max(25).optional(),
681
- filters: z.object({
682
- tier: z.enum(["open", "free", "paid", "internal"]).optional(),
683
- package: z.string().optional(),
684
- level: z.string().optional(),
685
- domain: z.string().optional(),
686
- medium: z.string().optional()
1039
+ inputSchema: z2.object({
1040
+ intent: z2.string().min(1).max(256).describe("What you are trying to do, as an English sentence."),
1041
+ limit: z2.number().int().min(1).max(25).optional(),
1042
+ filters: z2.object({
1043
+ kind: kindInput,
1044
+ tier: z2.enum(["open", "free", "paid", "internal"]).optional(),
1045
+ package: z2.string().optional(),
1046
+ level: z2.string().optional(),
1047
+ domain: z2.string().optional(),
1048
+ medium: z2.string().optional()
687
1049
  }).optional().describe("Exact-match filters over the catalog before ranking.")
688
1050
  }),
689
1051
  annotations: readOnly
@@ -695,8 +1057,8 @@ function registerSkillTools(server, tools) {
695
1057
  {
696
1058
  title: "Check one skill's properties before loading it",
697
1059
  description: "Frontmatter, metadata (tier, level, requirements, licence, approval), and the files the skill carries with sizes and sha256, without any body. Use it to decide between candidates before skill_call.",
698
- inputSchema: z.object({
699
- name: z.string().min(1).describe("The skill name skill_find or skill_catalog returned.")
1060
+ inputSchema: z2.object({
1061
+ name: z2.string().min(1).describe("The skill name skill_find or skill_catalog returned.")
700
1062
  }),
701
1063
  annotations: readOnly
702
1064
  },
@@ -720,9 +1082,9 @@ function registerSkillTools(server, tools) {
720
1082
  {
721
1083
  title: "Read one file a skill carries",
722
1084
  description: "A reference or script file inside one skill's directory, as text, with its sha256. Read only when the skill body points at the file. Scripts are returned, never run. Files over 256 KiB and paths outside the skill are refused.",
723
- inputSchema: z.object({
724
- name: z.string().min(1),
725
- path: z.string().min(1).optional().describe(
1085
+ inputSchema: z2.object({
1086
+ name: z2.string().min(1),
1087
+ path: z2.string().min(1).optional().describe(
726
1088
  "Path relative to the skill directory, as skill_describe lists it. Defaults to SKILL.md."
727
1089
  )
728
1090
  }),
@@ -745,8 +1107,8 @@ function registerSkillTools(server, tools) {
745
1107
  {
746
1108
  title: "Read one skill",
747
1109
  description: "The body of a single skill, by the name skill_find or skill_catalog returned. This is the only call that costs a document.",
748
- inputSchema: z.object({
749
- name: z.string().min(1).describe("The skill name, as <package>/<slug>.")
1110
+ inputSchema: z2.object({
1111
+ name: z2.string().min(1).describe("The skill name, as <package>/<slug>.")
750
1112
  }),
751
1113
  annotations: readOnly
752
1114
  },
@@ -768,8 +1130,10 @@ function registerSkillTools(server, tools) {
768
1130
  return server;
769
1131
  }
770
1132
  function createSkillServer(options) {
771
- const server = new McpServer({ name: SERVER_NAME, version: "0.4.0" });
772
- return registerSkillTools(server, createSkillTools(options));
1133
+ const server = new McpServer({ name: SERVER_NAME, version: "0.5.0" });
1134
+ const tools = createSkillTools(options);
1135
+ registerSkillsExtension(server, tools.skills);
1136
+ return registerSkillTools(server, tools);
773
1137
  }
774
1138
 
775
1139
  export {
@@ -777,6 +1141,9 @@ export {
777
1141
  readMetadata,
778
1142
  readFrontmatterText,
779
1143
  readMarketplace,
1144
+ assetRoot,
1145
+ ASSET_KINDS,
1146
+ kindOf,
780
1147
  SKIPPED_DIRS,
781
1148
  MAX_SKILL_DEPTH,
782
1149
  readFrontmatter,
@@ -793,8 +1160,12 @@ export {
793
1160
  CATALOG_PAGE_SIZE,
794
1161
  CATALOG_PAGE_MAX,
795
1162
  createSkillTools,
1163
+ SKILLS_EXTENSION,
1164
+ skillUri,
1165
+ extensionEntry,
1166
+ registerSkillsExtension,
796
1167
  SERVER_NAME,
797
1168
  registerSkillTools,
798
1169
  createSkillServer
799
1170
  };
800
- //# sourceMappingURL=chunk-BFET6MFN.js.map
1171
+ //# sourceMappingURL=chunk-JOQTOVTJ.js.map