@modootoday/pmcp 0.2.0 → 0.3.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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # pmcp
2
2
 
3
3
  Find and use package skills without loading every document into your agent's context.
4
- `pmcp` reads `SKILL.md` files from installed packages and from plugin marketplaces
5
- (`--marketplace <dir>`), and exposes five MCP tools: `skill_catalog`, `skill_find`,
4
+ `pmcp` reads `SKILL.md` files from installed packages, from monorepo workspaces
5
+ (`--workspace <dir>`: the root and every member its `workspaces` globs name, nested
6
+ workspace roots included), from single package directories (`--package <dir>`), and
7
+ from plugin marketplaces (`--marketplace <dir>`), and exposes five MCP tools: `skill_catalog`, `skill_find`,
6
8
  `skill_describe`, `skill_call`, and `skill_read`.
7
9
 
8
10
  ## Install
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The skills the installed packages brought with them.
3
+ *
4
+ * Derived from `node_modules` at read time rather than from a list somebody
5
+ * maintains: installing a package is how its skill arrives and uninstalling it
6
+ * is how the skill leaves, so the catalog is always true of the project.
7
+ */
8
+ interface SkillEntry {
9
+ /** `<package>/<slug>`. Unique, and stable while the package is installed. */
10
+ readonly name: string;
11
+ readonly package: string;
12
+ readonly slug: string;
13
+ /** Frontmatter description. The only text `find` ranks against. */
14
+ readonly description: string;
15
+ /** Absolute path to the SKILL.md. `call` reads the body from here. */
16
+ readonly path: string;
17
+ /** open, free or paid. Set for marketplace skills, absent for packages. */
18
+ readonly tier?: string;
19
+ /** The frontmatter `metadata:` block, one level deep. */
20
+ readonly metadata?: Readonly<Record<string, string | readonly string[]>>;
21
+ /** Extra search terms. Ranked with the description, never used as triggers. */
22
+ readonly keywords?: readonly string[];
23
+ }
24
+ interface CatalogOptions {
25
+ /** Directories to walk. Each is a `node_modules` root, not a package root. */
26
+ readonly roots: readonly string[];
27
+ /** Package name prefixes to consider. Empty means every package. */
28
+ readonly scopes?: readonly string[];
29
+ /** Marketplace directories, each holding .claude-plugin/marketplace.json. */
30
+ readonly marketplaces?: readonly string[];
31
+ /** Package directories read directly, for a package no node_modules links. */
32
+ readonly packages?: readonly string[];
33
+ /** Workspace roots: the root and every member its workspaces globs name. */
34
+ readonly workspaces?: readonly string[];
35
+ /** Told about every marketplace skill left out, and why. */
36
+ readonly onReject?: (rejection: {
37
+ path: string;
38
+ reason: string;
39
+ }) => void;
40
+ }
41
+ /**
42
+ * Directories a walk does not enter. Kept as short as the evidence allows: a
43
+ * name here is a package whose skill will never be found, and the cost of one
44
+ * extra readdir is far below the cost of missing a skill. Measured 20260906 --
45
+ * `lib` was on this list and hid playwright's `lib/skill/SKILL.md`, which is
46
+ * the most widely installed skill in the real world.
47
+ */
48
+ declare const SKIPPED_DIRS: ReadonlySet<string>;
49
+ /**
50
+ * How deep below a package root a SKILL.md is looked for. Three reaches every
51
+ * convention in use (skills, .agent/skills, .claude/skills, .gemini/skills,
52
+ * .codex/skills); four was measured to find nothing more.
53
+ */
54
+ declare const MAX_SKILL_DEPTH = 3;
55
+ /**
56
+ * Reads `name` and `description` out of YAML frontmatter without a parser.
57
+ *
58
+ * A parser is a dependency this server does not otherwise need, and the two
59
+ * fields it wants are flat scalars on their own line. Anything more nested is
60
+ * not read, which is the honest limit rather than a partial YAML.
61
+ */
62
+ declare function readFrontmatter(source: string): Record<string, string>;
63
+ /**
64
+ * The body after the frontmatter, or the whole file when there is none.
65
+ *
66
+ * The slice starts at the newline terminating the closing `---`, and a blank
67
+ * line after it is separator rather than content, so both are dropped.
68
+ */
69
+ declare function readBody(source: string): string;
70
+ /**
71
+ * Every skill reachable from the given roots, sorted by name.
72
+ *
73
+ * A package with no skills directory contributes nothing and costs one
74
+ * `existsSync` per convention. A skill with no description is skipped rather
75
+ * than listed:
76
+ * `find` ranks descriptions, so one without a description can only ever be
77
+ * found by already knowing its name, and listing it would spend a session's
78
+ * tokens on an entry that cannot answer.
79
+ */
80
+ declare function readCatalog(options: CatalogOptions): SkillEntry[];
81
+ /** A catalog entry without its body, which is what `catalog` returns. */
82
+ declare function withoutBody({ path, ...rest }: SkillEntry): Omit<SkillEntry, "path">;
83
+ /** Path relative to a root, for a message a reader can act on. */
84
+ declare function displayPath(root: string, entry: SkillEntry): string;
85
+
86
+ export { type CatalogOptions as C, MAX_SKILL_DEPTH as M, type SkillEntry as S, SKIPPED_DIRS as a, readCatalog as b, readFrontmatter as c, displayPath as d, readBody as r, withoutBody as w };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normaliseIntent
3
- } from "./chunk-3NNRQ5A4.js";
3
+ } from "./chunk-SPWHSHOC.js";
4
4
 
5
5
  // src/cache.ts
6
6
  async function openDatabase(path) {
@@ -131,4 +131,4 @@ export {
131
131
  DEFAULT_INTENT_CACHE_LIMIT,
132
132
  openIntentCache
133
133
  };
134
- //# sourceMappingURL=chunk-2NOO5RSP.js.map
134
+ //# sourceMappingURL=chunk-MBAXK4IZ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSkillServer
3
- } from "./chunk-3NNRQ5A4.js";
3
+ } from "./chunk-SPWHSHOC.js";
4
4
 
5
5
  // src/stdio.ts
6
6
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -8,11 +8,9 @@ function resolveRoots(argv, cwd) {
8
8
  const given = argv.filter((arg) => !arg.startsWith("-"));
9
9
  return given.length > 0 ? given : [`${cwd}/node_modules`];
10
10
  }
11
- function serve(roots, scopes = [], marketplaces = []) {
11
+ function serve(catalog) {
12
12
  const options = {
13
- roots,
14
- ...scopes.length > 0 ? { scopes } : {},
15
- ...marketplaces.length > 0 ? { marketplaces } : {},
13
+ ...catalog,
16
14
  onReject: ({ path, reason }) => process.stderr.write(`pmcp: skipped ${path}: ${reason}
17
15
  `)
18
16
  };
@@ -25,10 +23,11 @@ function serve(roots, scopes = [], marketplaces = []) {
25
23
  }
26
24
  function main(argv = process.argv.slice(2)) {
27
25
  const scopeArg = argv.find((arg) => arg.startsWith("--scope="));
28
- serve(
29
- resolveRoots(argv, process.cwd()),
30
- scopeArg ? scopeArg.slice("--scope=".length).split(",") : []
31
- );
26
+ const scopes = scopeArg ? scopeArg.slice("--scope=".length).split(",") : [];
27
+ serve({
28
+ roots: resolveRoots(argv, process.cwd()),
29
+ ...scopes.length > 0 ? { scopes } : {}
30
+ });
32
31
  }
33
32
 
34
33
  export {
@@ -36,4 +35,4 @@ export {
36
35
  serve,
37
36
  main
38
37
  };
39
- //# sourceMappingURL=chunk-Y32X35YV.js.map
38
+ //# sourceMappingURL=chunk-QJS3NYLT.js.map
@@ -9,7 +9,7 @@ import { join as join2, resolve } from "path";
9
9
 
10
10
  // src/catalog.ts
11
11
  import { existsSync, readFileSync, readdirSync } from "fs";
12
- import { basename, dirname, join, relative } from "path";
12
+ import { basename, dirname, isAbsolute, join, relative } from "path";
13
13
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([
14
14
  "node_modules",
15
15
  ".git",
@@ -86,48 +86,148 @@ function packageDirs(root) {
86
86
  }
87
87
  return found;
88
88
  }
89
+ function readManifest(dir) {
90
+ try {
91
+ const parsed = JSON.parse(
92
+ readFileSync(join(dir, "package.json"), "utf8")
93
+ );
94
+ return parsed !== null && typeof parsed === "object" ? parsed : null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function workspacePatterns(manifest) {
100
+ const field = manifest["workspaces"];
101
+ const list = Array.isArray(field) ? field : field?.packages;
102
+ if (!Array.isArray(list)) return [];
103
+ return list.filter((p) => typeof p === "string");
104
+ }
105
+ function expandBraces(pattern) {
106
+ const match = /\{([^{}]*)\}/u.exec(pattern);
107
+ if (!match) return [pattern];
108
+ const head = pattern.slice(0, match.index);
109
+ const tail = pattern.slice(match.index + match[0].length);
110
+ return match[1].split(",").flatMap((option) => expandBraces(`${head}${option}${tail}`));
111
+ }
112
+ function segmentMatcher(segment) {
113
+ const body = segment.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/gu, "\\$&")).join("[^/]*");
114
+ return new RegExp(`^${body}$`, "u");
115
+ }
116
+ function globDirs(base, pattern) {
117
+ const segments = pattern.split("/").filter((s) => s !== "" && s !== ".");
118
+ const found = /* @__PURE__ */ new Set();
119
+ const walk = (dir, index) => {
120
+ if (index === segments.length) {
121
+ found.add(dir);
122
+ return;
123
+ }
124
+ const segment = segments[index];
125
+ if (!segment.includes("*")) {
126
+ const next = join(dir, segment);
127
+ if (existsSync(next)) walk(next, index + 1);
128
+ return;
129
+ }
130
+ let entries;
131
+ try {
132
+ entries = readdirSync(dir, { withFileTypes: true });
133
+ } catch {
134
+ return;
135
+ }
136
+ const children = entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).filter((e) => !SKIPPED_DIRS.has(e.name));
137
+ if (segment === "**") {
138
+ walk(dir, index + 1);
139
+ for (const child of children) walk(join(dir, child.name), index);
140
+ return;
141
+ }
142
+ const matcher = segmentMatcher(segment);
143
+ for (const child of children) {
144
+ if (matcher.test(child.name)) walk(join(dir, child.name), index + 1);
145
+ }
146
+ };
147
+ walk(base, 0);
148
+ return [...found];
149
+ }
150
+ function workspacePackageDirs(root) {
151
+ const ordered = [];
152
+ const visited = /* @__PURE__ */ new Set();
153
+ const expand = (dir) => {
154
+ if (visited.has(dir)) return;
155
+ visited.add(dir);
156
+ const manifest = readManifest(dir);
157
+ if (manifest === null) return;
158
+ const included = /* @__PURE__ */ new Set();
159
+ const excluded = /* @__PURE__ */ new Set();
160
+ for (const raw of workspacePatterns(manifest)) {
161
+ const negated = raw.startsWith("!");
162
+ const pattern = negated ? raw.slice(1) : raw;
163
+ for (const alternative of expandBraces(pattern)) {
164
+ for (const match of globDirs(dir, alternative)) {
165
+ (negated ? excluded : included).add(match);
166
+ }
167
+ }
168
+ }
169
+ const members = [...included].filter((m) => !excluded.has(m) && existsSync(join(m, "package.json"))).sort();
170
+ for (const member of members) expand(member);
171
+ ordered.push(dir);
172
+ };
173
+ expand(root);
174
+ return ordered;
175
+ }
176
+ function nameOf(dir, named) {
177
+ const manifestName = readManifest(dir)?.["name"];
178
+ if (typeof manifestName === "string" && manifestName !== "") {
179
+ return manifestName;
180
+ }
181
+ if (named) return basename(dir);
182
+ return "";
183
+ }
184
+ function expandRootGlob(dir) {
185
+ if (!dir.includes("*")) return [dir];
186
+ const absolute = isAbsolute(dir);
187
+ const base = absolute ? "/" : ".";
188
+ return globDirs(base, dir).filter((d) => existsSync(join(d, "package.json"))).sort();
189
+ }
89
190
  function readCatalog(options) {
90
191
  const scopes = options.scopes ?? [];
91
192
  const found = [];
92
193
  const seen = /* @__PURE__ */ new Set();
93
- for (const root of options.roots) {
94
- for (const packageDir of packageDirs(root)) {
95
- const manifestPath = join(packageDir, "package.json");
96
- if (!existsSync(manifestPath)) continue;
97
- let packageName;
194
+ const seenPaths = /* @__PURE__ */ new Set();
195
+ const direct = new Set(options.packages ?? []);
196
+ const packageDirsToRead = [
197
+ ...options.roots.flatMap(packageDirs),
198
+ ...direct,
199
+ ...(options.workspaces ?? []).flatMap(expandRootGlob).flatMap(workspacePackageDirs)
200
+ ];
201
+ for (const packageDir of packageDirsToRead) {
202
+ const packageName = nameOf(packageDir, direct.has(packageDir));
203
+ if (packageName === "") continue;
204
+ if (scopes.length > 0 && !scopes.some((s) => packageName.startsWith(s))) {
205
+ continue;
206
+ }
207
+ for (const path of skillFiles(packageDir)) {
208
+ if (seenPaths.has(path)) continue;
209
+ seenPaths.add(path);
210
+ let source;
98
211
  try {
99
- const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
100
- if (!manifest.name) continue;
101
- packageName = manifest.name;
212
+ source = readFileSync(path, "utf8");
102
213
  } catch {
103
214
  continue;
104
215
  }
105
- if (scopes.length > 0 && !scopes.some((s) => packageName.startsWith(s))) {
106
- continue;
107
- }
108
- for (const path of skillFiles(packageDir)) {
109
- let source;
110
- try {
111
- source = readFileSync(path, "utf8");
112
- } catch {
113
- continue;
114
- }
115
- const front = readFrontmatter(source);
116
- const description = front["description"] ?? "";
117
- if (description === "") continue;
118
- const dirName = basename(dirname(path));
119
- const slug = front["name"] ?? (dirName === basename(packageDir) ? packageName : dirName);
120
- const name = `${packageName}/${slug}`;
121
- if (seen.has(name)) continue;
122
- seen.add(name);
123
- found.push({
124
- name,
125
- package: packageName,
126
- slug,
127
- description,
128
- path
129
- });
130
- }
216
+ const front = readFrontmatter(source);
217
+ const description = front["description"] ?? "";
218
+ if (description === "") continue;
219
+ const dirName = basename(dirname(path));
220
+ const slug = front["name"] ?? (dirName === basename(packageDir) ? packageName : dirName);
221
+ const name = `${packageName}/${slug}`;
222
+ if (seen.has(name)) continue;
223
+ seen.add(name);
224
+ found.push({
225
+ name,
226
+ package: packageName,
227
+ slug,
228
+ description,
229
+ path
230
+ });
131
231
  }
132
232
  }
133
233
  for (const dir of options.marketplaces ?? []) {
@@ -345,7 +445,7 @@ function rank(matches2, limit) {
345
445
  // src/server.ts
346
446
  import { createHash } from "crypto";
347
447
  import { readFileSync as readFileSync3, readdirSync as readdirSync3, realpathSync, statSync } from "fs";
348
- import { dirname as dirname2, isAbsolute, join as join3, relative as relative2, resolve as resolve2, sep } from "path";
448
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2, sep } from "path";
349
449
  var READ_BUDGET_BYTES = 256 * 1024;
350
450
  var DESCRIBE_FILE_LIMIT = 200;
351
451
  var CATALOG_PAGE_SIZE = 8;
@@ -403,6 +503,8 @@ function createSkillTools(options) {
403
503
  roots: options.roots,
404
504
  scopes: options.scopes,
405
505
  marketplaces: options.marketplaces,
506
+ packages: options.packages,
507
+ workspaces: options.workspaces,
406
508
  onReject: options.onReject
407
509
  });
408
510
  return entries;
@@ -492,7 +594,7 @@ function createSkillTools(options) {
492
594
  read(name, path = "SKILL.md") {
493
595
  const entry = catalog().find((candidate) => candidate.name === name);
494
596
  if (!entry) return { ok: false, error: "unknown_skill", detail: name };
495
- if (isAbsolute(path) || path.split(/[\\/]/u).includes("..")) {
597
+ if (isAbsolute2(path) || path.split(/[\\/]/u).includes("..")) {
496
598
  return { ok: false, error: "path_outside_skill", detail: path };
497
599
  }
498
600
  const root = realpathSync(dirname2(entry.path));
@@ -562,7 +664,7 @@ function registerSkillTools(server, tools) {
562
664
  inputSchema: z.object({
563
665
  page: z.number().int().min(1).optional(),
564
666
  pageSize: z.number().int().min(1).max(20).optional(),
565
- tier: z.enum(["open", "free", "paid"]).optional()
667
+ tier: z.enum(["open", "free", "paid", "internal"]).optional()
566
668
  }),
567
669
  annotations: readOnly
568
670
  },
@@ -577,7 +679,7 @@ function registerSkillTools(server, tools) {
577
679
  intent: z.string().min(1).max(256).describe("What you are trying to do, as an English sentence."),
578
680
  limit: z.number().int().min(1).max(25).optional(),
579
681
  filters: z.object({
580
- tier: z.enum(["open", "free", "paid"]).optional(),
682
+ tier: z.enum(["open", "free", "paid", "internal"]).optional(),
581
683
  package: z.string().optional(),
582
684
  level: z.string().optional(),
583
685
  domain: z.string().optional(),
@@ -666,7 +768,7 @@ function registerSkillTools(server, tools) {
666
768
  return server;
667
769
  }
668
770
  function createSkillServer(options) {
669
- const server = new McpServer({ name: SERVER_NAME, version: "0.2.0" });
771
+ const server = new McpServer({ name: SERVER_NAME, version: "0.3.0" });
670
772
  return registerSkillTools(server, createSkillTools(options));
671
773
  }
672
774
 
@@ -695,4 +797,4 @@ export {
695
797
  registerSkillTools,
696
798
  createSkillServer
697
799
  };
698
- //# sourceMappingURL=chunk-3NNRQ5A4.js.map
800
+ //# sourceMappingURL=chunk-SPWHSHOC.js.map
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  openDatabase
4
- } from "./chunk-2NOO5RSP.js";
4
+ } from "./chunk-MBAXK4IZ.js";
5
5
  import {
6
6
  serve
7
- } from "./chunk-Y32X35YV.js";
7
+ } from "./chunk-QJS3NYLT.js";
8
8
  import {
9
9
  readCatalog
10
- } from "./chunk-3NNRQ5A4.js";
10
+ } from "./chunk-SPWHSHOC.js";
11
11
 
12
12
  // src/cli/command.ts
13
13
  var ArgumentError = class extends Error {
@@ -308,6 +308,18 @@ var CATALOG_OPTIONS = [
308
308
  describe: "A plugin marketplace directory (holds .claude-plugin/marketplace.json). Repeatable.",
309
309
  repeat: true,
310
310
  placeholder: "<dir>"
311
+ },
312
+ {
313
+ name: "package",
314
+ describe: "A package directory read directly, for one no node_modules links; without a package.json its folder name is the package name. Repeatable.",
315
+ repeat: true,
316
+ placeholder: "<dir>"
317
+ },
318
+ {
319
+ name: "workspace",
320
+ describe: "A workspace root, or a glob of them: each root and every member its workspaces globs name. Repeatable.",
321
+ repeat: true,
322
+ placeholder: "<dir>"
311
323
  }
312
324
  ];
313
325
  function rootsFrom(context) {
@@ -317,10 +329,14 @@ function rootsFrom(context) {
317
329
  function catalogOptionsFrom(context, onReject) {
318
330
  const scopes = many(context.args, "scope");
319
331
  const marketplaces = many(context.args, "marketplace");
332
+ const packages = many(context.args, "package");
333
+ const workspaces = many(context.args, "workspace");
320
334
  return {
321
335
  roots: rootsFrom(context),
322
336
  ...scopes.length > 0 ? { scopes } : {},
323
337
  ...marketplaces.length > 0 ? { marketplaces } : {},
338
+ ...packages.length > 0 ? { packages } : {},
339
+ ...workspaces.length > 0 ? { workspaces } : {},
324
340
  ...onReject ? { onReject } : {}
325
341
  };
326
342
  }
@@ -433,7 +449,7 @@ var indexCommand = {
433
449
  var listCommand = {
434
450
  name: "list",
435
451
  describe: "List the skills discovered in node_modules and marketplaces",
436
- usage: "pmcp list [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--json]",
452
+ usage: "pmcp list [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir|glob>] [--json]",
437
453
  options: [
438
454
  ...CATALOG_OPTIONS,
439
455
  { name: "json", describe: "Emit JSON rather than a table.", boolean: true }
@@ -472,14 +488,10 @@ var listCommand = {
472
488
  var serveCommand = {
473
489
  name: "serve",
474
490
  describe: "Run the MCP server on stdio (the default with no command)",
475
- usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>]",
491
+ usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>]",
476
492
  options: [...CATALOG_OPTIONS],
477
493
  run(context) {
478
- serve(
479
- rootsFrom(context),
480
- many(context.args, "scope"),
481
- many(context.args, "marketplace")
482
- );
494
+ serve(catalogOptionsFrom(context));
483
495
  return 0;
484
496
  }
485
497
  };
@@ -2073,7 +2085,7 @@ function dispatch(argv, options = {}) {
2073
2085
  }
2074
2086
  return run(command, rest, { ui, env, cwd });
2075
2087
  }
2076
- var VERSION = "0.2.0";
2088
+ var VERSION = "0.3.0";
2077
2089
  async function run(command, argv, context) {
2078
2090
  try {
2079
2091
  return await command.run({
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { S as SkillEntry, C as CatalogOptions } from './catalog-BiPLcbLu.js';
2
+ export { M as MAX_SKILL_DEPTH, a as SKIPPED_DIRS, d as displayPath, r as readBody, b as readCatalog, c as readFrontmatter, w as withoutBody } from './catalog-BiPLcbLu.js';
1
3
  import { McpServer } from '@modelcontextprotocol/server';
2
4
 
3
5
  /**
@@ -61,87 +63,6 @@ declare const DEFAULT_INTENT_CACHE_LIMIT = 4096;
61
63
  */
62
64
  declare function openIntentCache(options: IntentCacheOptions): Promise<IntentCache | null>;
63
65
 
64
- /**
65
- * The skills the installed packages brought with them.
66
- *
67
- * Derived from `node_modules` at read time rather than from a list somebody
68
- * maintains: installing a package is how its skill arrives and uninstalling it
69
- * is how the skill leaves, so the catalog is always true of the project.
70
- */
71
- interface SkillEntry {
72
- /** `<package>/<slug>`. Unique, and stable while the package is installed. */
73
- readonly name: string;
74
- readonly package: string;
75
- readonly slug: string;
76
- /** Frontmatter description. The only text `find` ranks against. */
77
- readonly description: string;
78
- /** Absolute path to the SKILL.md. `call` reads the body from here. */
79
- readonly path: string;
80
- /** open, free or paid. Set for marketplace skills, absent for packages. */
81
- readonly tier?: string;
82
- /** The frontmatter `metadata:` block, one level deep. */
83
- readonly metadata?: Readonly<Record<string, string | readonly string[]>>;
84
- /** Extra search terms. Ranked with the description, never used as triggers. */
85
- readonly keywords?: readonly string[];
86
- }
87
- interface CatalogOptions {
88
- /** Directories to walk. Each is a `node_modules` root, not a package root. */
89
- readonly roots: readonly string[];
90
- /** Package name prefixes to consider. Empty means every package. */
91
- readonly scopes?: readonly string[];
92
- /** Marketplace directories, each holding .claude-plugin/marketplace.json. */
93
- readonly marketplaces?: readonly string[];
94
- /** Told about every marketplace skill left out, and why. */
95
- readonly onReject?: (rejection: {
96
- path: string;
97
- reason: string;
98
- }) => void;
99
- }
100
- /**
101
- * Directories a walk does not enter. Kept as short as the evidence allows: a
102
- * name here is a package whose skill will never be found, and the cost of one
103
- * extra readdir is far below the cost of missing a skill. Measured 20260906 --
104
- * `lib` was on this list and hid playwright's `lib/skill/SKILL.md`, which is
105
- * the most widely installed skill in the real world.
106
- */
107
- declare const SKIPPED_DIRS: ReadonlySet<string>;
108
- /**
109
- * How deep below a package root a SKILL.md is looked for. Three reaches every
110
- * convention in use (skills, .agent/skills, .claude/skills, .gemini/skills,
111
- * .codex/skills); four was measured to find nothing more.
112
- */
113
- declare const MAX_SKILL_DEPTH = 3;
114
- /**
115
- * Reads `name` and `description` out of YAML frontmatter without a parser.
116
- *
117
- * A parser is a dependency this server does not otherwise need, and the two
118
- * fields it wants are flat scalars on their own line. Anything more nested is
119
- * not read, which is the honest limit rather than a partial YAML.
120
- */
121
- declare function readFrontmatter(source: string): Record<string, string>;
122
- /**
123
- * The body after the frontmatter, or the whole file when there is none.
124
- *
125
- * The slice starts at the newline terminating the closing `---`, and a blank
126
- * line after it is separator rather than content, so both are dropped.
127
- */
128
- declare function readBody(source: string): string;
129
- /**
130
- * Every skill reachable from the given roots, sorted by name.
131
- *
132
- * A package with no skills directory contributes nothing and costs one
133
- * `existsSync` per convention. A skill with no description is skipped rather
134
- * than listed:
135
- * `find` ranks descriptions, so one without a description can only ever be
136
- * found by already knowing its name, and listing it would spend a session's
137
- * tokens on an entry that cannot answer.
138
- */
139
- declare function readCatalog(options: CatalogOptions): SkillEntry[];
140
- /** A catalog entry without its body, which is what `catalog` returns. */
141
- declare function withoutBody({ path, ...rest }: SkillEntry): Omit<SkillEntry, "path">;
142
- /** Path relative to a root, for a message a reader can act on. */
143
- declare function displayPath(root: string, entry: SkillEntry): string;
144
-
145
66
  /**
146
67
  * Ranking an intent against skill descriptions.
147
68
  *
@@ -356,4 +277,4 @@ declare function registerSkillTools(server: McpServer, tools: SkillTools): McpSe
356
277
  */
357
278
  declare function createSkillServer(options: SkillServerOptions): McpServer;
358
279
 
359
- export { CATALOG_PAGE_MAX, CATALOG_PAGE_SIZE, type CallResponse, type CatalogOptions, type CatalogResponse, DEFAULT_FIND_LIMIT, DEFAULT_INTENT_CACHE_LIMIT, DESCRIBE_FILE_LIMIT, type Embedder, type FindOptions, type FindResult, type IntentCache, type IntentCacheOptions, type IntentCacheStats, MAX_SKILL_DEPTH, type Match, type MetadataValue, READ_BUDGET_BYTES, type Ranking, type ReadResult, type Rejection, SERVER_NAME, SKIPPED_DIRS, type SkillEntry, type SkillFilters, type SkillServerOptions, type SkillTools, cosine, createSkillServer, createSkillTools, displayPath, find, lexicalScore, normaliseIntent, openIntentCache, readBody, readCatalog, readFrontmatter, readFrontmatterText, readMarketplace, readMetadata, registerSkillTools, withoutBody };
280
+ export { CATALOG_PAGE_MAX, CATALOG_PAGE_SIZE, type CallResponse, CatalogOptions, type CatalogResponse, DEFAULT_FIND_LIMIT, DEFAULT_INTENT_CACHE_LIMIT, DESCRIBE_FILE_LIMIT, type Embedder, type FindOptions, type FindResult, type IntentCache, type IntentCacheOptions, type IntentCacheStats, type Match, type MetadataValue, READ_BUDGET_BYTES, type Ranking, type ReadResult, type Rejection, SERVER_NAME, SkillEntry, type SkillFilters, type SkillServerOptions, type SkillTools, cosine, createSkillServer, createSkillTools, find, lexicalScore, normaliseIntent, openIntentCache, readFrontmatterText, readMarketplace, readMetadata, registerSkillTools };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_INTENT_CACHE_LIMIT,
3
3
  openIntentCache
4
- } from "./chunk-2NOO5RSP.js";
4
+ } from "./chunk-MBAXK4IZ.js";
5
5
  import {
6
6
  CATALOG_PAGE_MAX,
7
7
  CATALOG_PAGE_SIZE,
@@ -26,7 +26,7 @@ import {
26
26
  readMetadata,
27
27
  registerSkillTools,
28
28
  withoutBody
29
- } from "./chunk-3NNRQ5A4.js";
29
+ } from "./chunk-SPWHSHOC.js";
30
30
  export {
31
31
  CATALOG_PAGE_MAX,
32
32
  CATALOG_PAGE_SIZE,
package/dist/stdio.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { C as CatalogOptions } from './catalog-BiPLcbLu.js';
3
+
2
4
  /**
3
5
  * The stdio entry a host launches.
4
6
  *
@@ -6,9 +8,10 @@
6
8
  * the dependency tree is its data source, and from an npx cache it is not a
7
9
  * sibling of the consumer's packages and would find no skills at all.
8
10
  */
11
+
9
12
  /** `node_modules` roots to walk, from argv or from the working directory. */
10
13
  declare function resolveRoots(argv: readonly string[], cwd: string): string[];
11
- declare function serve(roots: readonly string[], scopes?: readonly string[], marketplaces?: readonly string[]): void;
14
+ declare function serve(catalog: CatalogOptions): void;
12
15
  declare function main(argv?: readonly string[]): void;
13
16
 
14
17
  export { main, resolveRoots, serve };
package/dist/stdio.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  main,
4
4
  resolveRoots,
5
5
  serve
6
- } from "./chunk-Y32X35YV.js";
7
- import "./chunk-3NNRQ5A4.js";
6
+ } from "./chunk-QJS3NYLT.js";
7
+ import "./chunk-SPWHSHOC.js";
8
8
  export {
9
9
  main,
10
10
  resolveRoots,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/pmcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "Elastic-2.0",
5
5
  "author": "modootoday",
6
6
  "description": "An MCP server that finds the skills your installed packages already ship. Reads SKILL.md out of node_modules by shape rather than by path, so it sees the skills, .agent, .claude, .gemini and .codex conventions alike, and keeps session startup flat as the number of installed skills grows.",