@modootoday/pmcp 0.7.1 → 0.8.1

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSkillServer
3
- } from "./chunk-C46YCY2I.js";
3
+ } from "./chunk-HB4VJ5VW.js";
4
4
 
5
5
  // src/stdio.ts
6
6
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -8,10 +8,13 @@ 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(catalog) {
11
+ function serve(catalog, serving = {}) {
12
12
  const options = {
13
13
  ...catalog,
14
+ ...serving,
14
15
  onReject: ({ path, reason }) => process.stderr.write(`pmcp: skipped ${path}: ${reason}
16
+ `),
17
+ onWarning: ({ path, reason }) => process.stderr.write(`pmcp: warning ${path}: ${reason}
15
18
  `)
16
19
  };
17
20
  serveStdio(() => createSkillServer(options), {
@@ -35,4 +38,4 @@ export {
35
38
  serve,
36
39
  main
37
40
  };
38
- //# sourceMappingURL=chunk-IJPOJ6PG.js.map
41
+ //# sourceMappingURL=chunk-K7BX33LH.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normaliseIntent
3
- } from "./chunk-C46YCY2I.js";
3
+ } from "./chunk-HB4VJ5VW.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-KP3TQ3QY.js.map
134
+ //# sourceMappingURL=chunk-MO3LLVVY.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  openDatabase
3
- } from "./chunk-KP3TQ3QY.js";
3
+ } from "./chunk-MO3LLVVY.js";
4
4
 
5
5
  // src/embed.ts
6
6
  var DEFAULT_MODEL_ID = "Xenova/multilingual-e5-small";
@@ -157,4 +157,4 @@ export {
157
157
  encodeEntries,
158
158
  openEntryVectors
159
159
  };
160
- //# sourceMappingURL=chunk-VBLAH6J2.js.map
160
+ //# sourceMappingURL=chunk-STBBSQ3R.js.map
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadEmbedder,
4
4
  openEntryVectors
5
- } from "./chunk-VBLAH6J2.js";
5
+ } from "./chunk-STBBSQ3R.js";
6
6
  import {
7
7
  ConfigError,
8
8
  ProjectError,
@@ -14,16 +14,18 @@ import {
14
14
  readSpec,
15
15
  renderFrontmatter,
16
16
  spawnRunner,
17
- splitFrontmatter
18
- } from "./chunk-GNJSZCRH.js";
19
- import "./chunk-KP3TQ3QY.js";
17
+ splitFrontmatter,
18
+ validateCatalog,
19
+ validateSkillDir
20
+ } from "./chunk-DTQAN33I.js";
21
+ import "./chunk-MO3LLVVY.js";
20
22
  import {
21
23
  serve
22
- } from "./chunk-IJPOJ6PG.js";
24
+ } from "./chunk-K7BX33LH.js";
23
25
  import {
24
26
  kindOf,
25
27
  readCatalog
26
- } from "./chunk-C46YCY2I.js";
28
+ } from "./chunk-HB4VJ5VW.js";
27
29
 
28
30
  // src/cli/command.ts
29
31
  var ArgumentError = class extends Error {
@@ -448,10 +450,19 @@ var listCommand = {
448
450
  var serveCommand = {
449
451
  name: "serve",
450
452
  describe: "Run the MCP server on stdio (the default with no command)",
451
- usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config]",
452
- options: [...CATALOG_OPTIONS],
453
+ usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config] [--prompts]",
454
+ options: [
455
+ ...CATALOG_OPTIONS,
456
+ {
457
+ name: "prompts",
458
+ describe: "Also offer one prompt per skill, for hosts that turn prompts into commands.",
459
+ boolean: true
460
+ }
461
+ ],
453
462
  run(context) {
454
- serve(catalogOptionsFrom(context));
463
+ serve(catalogOptionsFrom(context), {
464
+ prompts: context.args.flags.has("prompts")
465
+ });
455
466
  return 0;
456
467
  }
457
468
  };
@@ -2249,6 +2260,33 @@ var doctorCommand = {
2249
2260
  }
2250
2261
  };
2251
2262
 
2263
+ // src/commands/validate.ts
2264
+ var validateCommand = {
2265
+ name: "validate",
2266
+ describe: "Check skills against the Agent Skills specification: given directories, or every skill the catalog reaches",
2267
+ usage: "pmcp validate [<skill-dir>...] [--root <dir>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config] [--json]",
2268
+ options: [
2269
+ ...CATALOG_OPTIONS,
2270
+ { name: "json", describe: "Emit JSON rather than a table.", boolean: true }
2271
+ ],
2272
+ run(context) {
2273
+ const dirs = context.args.positional;
2274
+ const findings = dirs.length > 0 ? dirs.flatMap(validateSkillDir) : validateCatalog(catalogOptionsFrom(context));
2275
+ const errors = findings.filter((f) => f.level === "error").length;
2276
+ if (context.args.flags.has("json")) {
2277
+ context.ui.data(`${JSON.stringify({ findings }, null, 2)}
2278
+ `);
2279
+ return errors > 0 ? 1 : 0;
2280
+ }
2281
+ for (const finding of findings) {
2282
+ const report = finding.level === "error" ? context.ui.error : context.ui.warn;
2283
+ report.call(context.ui, finding.reason, finding.path);
2284
+ }
2285
+ if (findings.length === 0) context.ui.success("every skill is valid");
2286
+ return errors > 0 ? 1 : 0;
2287
+ }
2288
+ };
2289
+
2252
2290
  // src/commands/index.ts
2253
2291
  var COMMANDS = [
2254
2292
  serveCommand,
@@ -2262,7 +2300,8 @@ var COMMANDS = [
2262
2300
  installCommand,
2263
2301
  syncCommand,
2264
2302
  projectCommand,
2265
- doctorCommand
2303
+ doctorCommand,
2304
+ validateCommand
2266
2305
  ];
2267
2306
  var PLANNED = [];
2268
2307
  var GLOBAL = /* @__PURE__ */ new Set(["-q", "--quiet", "--no-color", "--color"]);
@@ -2308,7 +2347,7 @@ function dispatch(argv, options = {}) {
2308
2347
  }
2309
2348
  return run(command, rest, { ui, env, cwd });
2310
2349
  }
2311
- var VERSION = "0.7.1";
2350
+ var VERSION = "0.8.1";
2312
2351
  async function run(command, argv, context) {
2313
2352
  try {
2314
2353
  return await command.run({
package/dist/http.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { S as SkillEntry } from './catalog-5v7jrGwP.js';
2
- import { E as Embedder, S as SkillServerOptions } from './server-B9GDPRoE.js';
1
+ import { S as SkillEntry } from './catalog-CS0j6-0U.js';
2
+ import { E as Embedder, S as SkillServerOptions } from './server-CLyCpwZb.js';
3
+ import 'zod';
3
4
 
4
5
  /**
5
6
  * The optional encoder and the store for the vectors it produces.
@@ -36,6 +37,19 @@ interface LoadEmbedderOptions {
36
37
  */
37
38
  declare function loadEmbedder(modelId?: string, options?: LoadEmbedderOptions): Promise<Embedder | null>;
38
39
 
40
+ /**
41
+ * Checks a hosted skills server makes before a request reaches MCP: which browser origins
42
+ * and Host names may call it, how large a request may be, and how often one caller may call.
43
+ * Requests without an Origin header are not from a browser page and are not origin-checked.
44
+ */
45
+ declare const DEFAULT_MAX_REQUEST_BYTES: number;
46
+ interface RateLimitOptions {
47
+ /** Requests one caller may make in a minute. */
48
+ readonly perMinute: number;
49
+ /** Injected by tests. */
50
+ readonly now?: () => number;
51
+ }
52
+
39
53
  /**
40
54
  * The catalog over streamable HTTP, for a hosted skills server.
41
55
  *
@@ -67,6 +81,21 @@ interface SkillHttpOptions extends SkillServerOptions {
67
81
  readonly challenge?: string;
68
82
  /** Encode the catalog at start so find ranks by meaning across languages. */
69
83
  readonly semantic?: SkillHttpSemanticOptions;
84
+ /** One prompt per skill. Off by default: the list grows with the catalog. */
85
+ readonly prompts?: boolean;
86
+ /**
87
+ * Cache scope the Skills extension declares. The catalog is filtered per caller
88
+ * here, so "private" unless the host knows every caller sees the same list.
89
+ */
90
+ readonly cacheScope?: "public" | "private";
91
+ /** Browser origins, besides the server's own, whose pages may call; others get 403. */
92
+ readonly allowedOrigins?: readonly string[];
93
+ /** Host header values the server answers to; unset means any. */
94
+ readonly allowedHosts?: readonly string[];
95
+ /** Largest request body accepted; larger ones get 413. */
96
+ readonly maxRequestBytes?: number;
97
+ /** Per-caller request ceiling; unset means the host limits rate itself. */
98
+ readonly rateLimit?: RateLimitOptions;
70
99
  }
71
100
  declare const DEFAULT_TIERS: readonly string[];
72
101
  declare function visibleTo(entries: readonly SkillEntry[], tiers: readonly string[]): SkillEntry[];
@@ -79,4 +108,4 @@ interface SkillHttpHandler {
79
108
  }
80
109
  declare function createSkillHttpHandler(options: SkillHttpOptions): SkillHttpHandler;
81
110
 
82
- export { DEFAULT_MODEL_ID, DEFAULT_TIERS, Embedder, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, loadEmbedder, visibleTo };
111
+ export { DEFAULT_MAX_REQUEST_BYTES, DEFAULT_MODEL_ID, DEFAULT_TIERS, Embedder, type RateLimitOptions, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, loadEmbedder, visibleTo };
package/dist/http.js CHANGED
@@ -2,17 +2,120 @@ import {
2
2
  DEFAULT_MODEL_ID,
3
3
  encodeEntries,
4
4
  loadEmbedder
5
- } from "./chunk-VBLAH6J2.js";
6
- import "./chunk-KP3TQ3QY.js";
5
+ } from "./chunk-STBBSQ3R.js";
6
+ import "./chunk-MO3LLVVY.js";
7
7
  import {
8
8
  createSkillServer,
9
9
  readCatalog
10
- } from "./chunk-C46YCY2I.js";
10
+ } from "./chunk-HB4VJ5VW.js";
11
11
 
12
12
  // src/http.ts
13
13
  import {
14
14
  createMcpHandler
15
15
  } from "@modelcontextprotocol/server";
16
+
17
+ // src/http-guard.ts
18
+ var DEFAULT_MAX_REQUEST_BYTES = 1024 * 1024;
19
+ var RATE_WINDOW_MS = 6e4;
20
+ var CORS_ALLOW_HEADERS = "authorization, content-type, accept, mcp-session-id, mcp-protocol-version, last-event-id";
21
+ var json = (status, body, headers = {}) => new Response(JSON.stringify(body), {
22
+ status,
23
+ headers: { "content-type": "application/json", ...headers }
24
+ });
25
+ function checkOrigin(request, allowedOrigins = []) {
26
+ const origin = request.headers.get("origin");
27
+ if (origin === null) return { kind: "none" };
28
+ if (origin === new URL(request.url).origin) return { kind: "same" };
29
+ if (allowedOrigins.includes(origin)) return { kind: "allowed", origin };
30
+ return {
31
+ kind: "refused",
32
+ response: json(403, { error: "origin_not_allowed" })
33
+ };
34
+ }
35
+ function checkHost(request, allowedHosts) {
36
+ if (!allowedHosts) return void 0;
37
+ const host = request.headers.get("host") ?? new URL(request.url).host;
38
+ return allowedHosts.includes(host) ? void 0 : json(403, { error: "host_not_allowed" });
39
+ }
40
+ function corsHeaders(origin) {
41
+ return {
42
+ "access-control-allow-origin": origin,
43
+ "access-control-expose-headers": "mcp-session-id, www-authenticate",
44
+ vary: "Origin"
45
+ };
46
+ }
47
+ function preflight(origin) {
48
+ return new Response(null, {
49
+ status: 204,
50
+ headers: {
51
+ ...corsHeaders(origin),
52
+ "access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
53
+ "access-control-allow-headers": CORS_ALLOW_HEADERS,
54
+ "access-control-max-age": "600"
55
+ }
56
+ });
57
+ }
58
+ function withHeaders(response, headers) {
59
+ const merged = new Headers(response.headers);
60
+ for (const [name, value] of Object.entries(headers)) merged.set(name, value);
61
+ return new Response(response.body, {
62
+ status: response.status,
63
+ statusText: response.statusText,
64
+ headers: merged
65
+ });
66
+ }
67
+ var tooLarge = (max) => json(413, { error: "request_too_large", limit: max });
68
+ async function capBody(request, max) {
69
+ const declared = Number(request.headers.get("content-length") ?? NaN);
70
+ if (Number.isFinite(declared) && declared > max) return tooLarge(max);
71
+ if (!request.body) return request;
72
+ const reader = request.body.getReader();
73
+ const chunks = [];
74
+ let total = 0;
75
+ for (; ; ) {
76
+ const { done, value } = await reader.read();
77
+ if (done) break;
78
+ total += value.byteLength;
79
+ if (total > max) {
80
+ await reader.cancel();
81
+ return tooLarge(max);
82
+ }
83
+ chunks.push(value);
84
+ }
85
+ const body = new Uint8Array(total);
86
+ let offset = 0;
87
+ for (const chunk of chunks) {
88
+ body.set(chunk, offset);
89
+ offset += chunk.byteLength;
90
+ }
91
+ return new Request(request.url, {
92
+ method: request.method,
93
+ headers: request.headers,
94
+ body
95
+ });
96
+ }
97
+ function createRateLimiter(options) {
98
+ const now = options.now ?? Date.now;
99
+ const windows = /* @__PURE__ */ new Map();
100
+ return (callerId) => {
101
+ const t = now();
102
+ const window = windows.get(callerId);
103
+ if (!window || t - window.start >= RATE_WINDOW_MS) {
104
+ windows.set(callerId, { start: t, count: 1 });
105
+ return void 0;
106
+ }
107
+ window.count += 1;
108
+ if (window.count <= options.perMinute) return void 0;
109
+ const retry = Math.ceil((window.start + RATE_WINDOW_MS - t) / 1e3);
110
+ return json(
111
+ 429,
112
+ { error: "rate_limited" },
113
+ { "retry-after": String(Math.max(retry, 1)) }
114
+ );
115
+ };
116
+ }
117
+
118
+ // src/http.ts
16
119
  var DEFAULT_TIERS = ["open", "free"];
17
120
  function visibleTo(entries, tiers) {
18
121
  const allowed = new Set(tiers);
@@ -50,32 +153,50 @@ function createSkillHttpHandler(options) {
50
153
  () => createSkillServer({
51
154
  ...options,
52
155
  ...embedder && vectors ? { embedder, vectors } : {},
53
- loadCatalog: () => visibleTo(all, tiers)
156
+ loadCatalog: () => visibleTo(all, tiers),
157
+ skillsExtension: { cacheScope: options.cacheScope ?? "private" }
54
158
  })
55
159
  );
56
160
  handlers.set(key, handler);
57
161
  return handler;
58
162
  };
59
- return {
60
- async fetch(request) {
61
- const caller = await options.authorize(request);
62
- if (!caller) {
63
- return new Response(JSON.stringify({ error: "unauthorized" }), {
64
- status: 401,
65
- headers: {
66
- "content-type": "application/json",
67
- "www-authenticate": options.challenge ?? "Bearer"
68
- }
69
- });
70
- }
71
- return handlerFor(tiersFor(caller)).fetch(request, {
72
- authInfo: {
73
- token: "",
74
- clientId: caller.id,
75
- scopes: [],
76
- extra: { ...caller.claims }
163
+ const limit = options.rateLimit ? createRateLimiter(options.rateLimit) : void 0;
164
+ const serve = async (request) => {
165
+ const capped = await capBody(
166
+ request,
167
+ options.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES
168
+ );
169
+ if (capped instanceof Response) return capped;
170
+ const caller = await options.authorize(capped);
171
+ if (!caller) {
172
+ return new Response(JSON.stringify({ error: "unauthorized" }), {
173
+ status: 401,
174
+ headers: {
175
+ "content-type": "application/json",
176
+ "www-authenticate": options.challenge ?? "Bearer"
77
177
  }
78
178
  });
179
+ }
180
+ const limited = limit?.(caller.id);
181
+ if (limited) return limited;
182
+ return handlerFor(tiersFor(caller)).fetch(capped, {
183
+ authInfo: {
184
+ token: "",
185
+ clientId: caller.id,
186
+ scopes: [],
187
+ extra: { ...caller.claims }
188
+ }
189
+ });
190
+ };
191
+ return {
192
+ async fetch(request) {
193
+ const wrongHost = checkHost(request, options.allowedHosts);
194
+ if (wrongHost) return wrongHost;
195
+ const origin = checkOrigin(request, options.allowedOrigins);
196
+ if (origin.kind === "refused") return origin.response;
197
+ if (origin.kind !== "allowed") return serve(request);
198
+ if (request.method === "OPTIONS") return preflight(origin.origin);
199
+ return withHeaders(await serve(request), corsHeaders(origin.origin));
79
200
  },
80
201
  reload() {
81
202
  all = load(options);
@@ -87,6 +208,7 @@ function createSkillHttpHandler(options) {
87
208
  };
88
209
  }
89
210
  export {
211
+ DEFAULT_MAX_REQUEST_BYTES,
90
212
  DEFAULT_MODEL_ID,
91
213
  DEFAULT_TIERS,
92
214
  createSkillHttpHandler,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { I as IntentCache, S as SkillServerOptions, a as SkillTools } from './server-B9GDPRoE.js';
2
- export { C as CATALOG_PAGE_MAX, b as CATALOG_PAGE_SIZE, c as CallResponse, d as CatalogItem, e as CatalogResponse, D as DEFAULT_FIND_LIMIT, f as DESCRIBE_FILE_LIMIT, E as Embedder, F as FindOptions, g as FindResult, h as IntentCacheStats, K as KindFilter, M as Match, R as READ_BUDGET_BYTES, i as Ranking, j as ReadResult, k as SkillFilters, l as cosine, m as createSkillTools, n as find, o as lexicalScore, p as normaliseIntent } from './server-B9GDPRoE.js';
3
- import { S as SkillEntry } from './catalog-5v7jrGwP.js';
4
- export { a as ASSET_KINDS, A as AssetKind, C as CatalogOptions, M as MAX_SKILL_DEPTH, b as SKIPPED_DIRS, d as displayPath, k as kindOf, r as readBody, c as readCatalog, e as readFrontmatter, w as withoutBody } from './catalog-5v7jrGwP.js';
1
+ import { I as IntentCache, S as SkillServerOptions, a as SkillTools } from './server-CLyCpwZb.js';
2
+ export { B as BundleFile, C as CATALOG_PAGE_MAX, b as CATALOG_PAGE_SIZE, c as CallResponse, d as CatalogItem, e as CatalogResponse, f as Classified, D as DEFAULT_FIND_LIMIT, g as DESCRIBE_FILE_LIMIT, E as Embedder, F as FileKind, h as FindOptions, i as FindResult, j as IntentCacheStats, K as KindFilter, M as Match, R as READ_BUDGET_BYTES, k as Ranking, l as ReadResult, m as SkillBundle, n as SkillFile, o as SkillFileContent, p as SkillFileListing, q as SkillFilters, r as bundleResponseSchema, s as bundleSkill, t as catalogResponseSchema, u as classify, v as cosine, w as createSkillTools, x as describeResponseSchema, y as find, z as findResultSchema, A as isUtf8Text, G as lexicalScore, H as listSkillFiles, J as mimeOf, L as normaliseIntent, N as readSkillFile } from './server-CLyCpwZb.js';
3
+ import { S as SkillEntry, C as CatalogOptions } from './catalog-CS0j6-0U.js';
4
+ export { a as ASSET_KINDS, A as AssetKind, M as MAX_SKILL_DEPTH, b as SKIPPED_DIRS, d as displayPath, k as kindOf, r as readBody, c as readCatalog, e as readFrontmatter, w as withoutBody } from './catalog-CS0j6-0U.js';
5
5
  import { McpServer } from '@modelcontextprotocol/server';
6
+ import 'zod';
6
7
 
7
8
  /**
8
9
  * Query vectors, kept on disk between sessions.
@@ -36,6 +37,25 @@ declare const DEFAULT_INTENT_CACHE_LIMIT = 4096;
36
37
  */
37
38
  declare function openIntentCache(options: IntentCacheOptions): Promise<IntentCache | null>;
38
39
 
40
+ /**
41
+ * SKILL.md frontmatter, read as YAML the way a host reads it, and checked against
42
+ * the Agent Skills specification (https://agentskills.io/specification).
43
+ */
44
+ type MetadataValue = string | readonly string[];
45
+ /**
46
+ * Parsed frontmatter. A value with an unquoted ": " is a common authoring slip
47
+ * the client guide says to recover from by quoting it, so that is retried once.
48
+ */
49
+ declare function frontmatterObject(source: string): Record<string, unknown> | null;
50
+ declare const NAME_MAX = 64;
51
+ declare const DESCRIPTION_MAX = 1024;
52
+ declare const COMPATIBILITY_MAX = 500;
53
+ /**
54
+ * What the specification requires of one SKILL.md, as messages; empty when valid.
55
+ * directory is the name of the folder holding it.
56
+ */
57
+ declare function specProblems(front: Record<string, unknown> | null, directory: string): string[];
58
+
39
59
  /**
40
60
  * Assets laid out as an agent plugin marketplace: a directory holding
41
61
  * `.claude-plugin/marketplace.json`, whose plugins keep skills, commands,
@@ -43,16 +63,12 @@ declare function openIntentCache(options: IntentCacheOptions): Promise<IntentCac
43
63
  * package.json is involved, which is why the node_modules walk never saw them.
44
64
  */
45
65
 
46
- type MetadataValue = string | readonly string[];
47
66
  /** Why an asset was left out rather than served. */
48
67
  interface Rejection {
49
68
  readonly path: string;
50
69
  readonly reason: string;
51
70
  }
52
- /**
53
- * The `metadata:` block of a frontmatter, one level deep: scalars and inline
54
- * lists. Deeper nesting is skipped rather than half-read.
55
- */
71
+ /** The `metadata:` map of a frontmatter, one level deep: strings and string lists. */
56
72
  declare function readMetadata(source: string): Record<string, MetadataValue>;
57
73
  /** The raw frontmatter text between the fences, for skill_describe. */
58
74
  declare function readFrontmatterText(source: string): string;
@@ -200,7 +216,7 @@ interface RunResult {
200
216
  }
201
217
  type Runner = (command: string, args: readonly string[], cwd: string) => RunResult;
202
218
  type Status = "ok" | "fail" | "skip";
203
- interface Finding {
219
+ interface Finding$1 {
204
220
  readonly tool: Tool | "project";
205
221
  readonly check: string;
206
222
  readonly status: Status;
@@ -211,7 +227,7 @@ interface DoctorOptions {
211
227
  readonly home: string;
212
228
  readonly only?: ReadonlySet<Tool>;
213
229
  }
214
- declare function doctor(spec: ProjectSpec, options: DoctorOptions): Finding[];
230
+ declare function doctor(spec: ProjectSpec, options: DoctorOptions): Finding$1[];
215
231
 
216
232
  /**
217
233
  * Links each package's path-scoped rule file into .claude/rules/<dir>/. One level
@@ -225,60 +241,119 @@ declare class ProjectError extends Error {
225
241
  declare function deriveName(dir: string, strip: readonly string[]): string;
226
242
 
227
243
  /**
228
- * The five tools, spoken over MCP.
244
+ * One catalog skill as the Skills extension (SEP-2640) describes it: frontmatter
245
+ * plus a complete manifest of its files with sha256 digests and sizes.
246
+ */
247
+
248
+ interface SkillResource {
249
+ readonly uri: string;
250
+ readonly digest: string;
251
+ readonly size: number;
252
+ }
253
+ interface SkillExtensionEntry {
254
+ readonly uri: string;
255
+ readonly frontmatter: Readonly<Record<string, unknown>>;
256
+ readonly resources: readonly SkillResource[];
257
+ }
258
+ /**
259
+ * The extension's entry, or null when the skill cannot be served under SEP-2640:
260
+ * not a directory with SKILL.md, frontmatter that breaks the Agent Skills
261
+ * specification, a name unlike its path, or over the per-skill limits. Such a
262
+ * skill stays reachable through the skill_* tools.
263
+ */
264
+ declare function extensionEntry(entry: SkillEntry): SkillExtensionEntry | null;
265
+
266
+ /**
267
+ * The Skills extension's methods, skills/list and skills/get. Their files are
268
+ * served by the skill:// resources (resources.ts), which every surface shares.
269
+ */
270
+
271
+ declare const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
272
+ interface SkillsExtensionOptions {
273
+ /**
274
+ * "public" when every caller sees the same catalog; "private" when the list is
275
+ * filtered per caller, as it is behind authorization.
276
+ */
277
+ readonly cacheScope?: "public" | "private";
278
+ }
279
+ /** Declares the extension and its methods. Must run before the server connects. */
280
+ declare function registerSkillsExtension(server: McpServer, skills: () => readonly SkillEntry[], options?: SkillsExtensionOptions): void;
281
+
282
+ /**
283
+ * skill:// URIs for skill files. The authority names this server's namespace and
284
+ * resolves to nothing; the path is the catalog name, then the file inside it.
285
+ */
286
+
287
+ declare const skillFileUri: (entry: Pick<SkillEntry, "name">, file?: string) => string;
288
+ /**
289
+ * The catalog entry and relative file a URI names, matched against the entries
290
+ * the caller may see. Names contain slashes, so the longest matching name wins.
291
+ */
292
+ declare function resolveSkillFileUri(uri: string, entries: readonly SkillEntry[]): {
293
+ entry: SkillEntry;
294
+ file: string;
295
+ } | null;
296
+
297
+ /**
298
+ * The catalog, spoken over MCP: the skill_* tools, skill:// resources, the
299
+ * Skills extension and, when asked for, one prompt per skill.
229
300
  *
230
- * Thin on purpose: every decision is in `createSkillTools`, and this file only
231
- * names the tools and shapes their arguments. A consumer that wants the same
232
- * operations from a hook or a script uses the tools directly and never speaks
233
- * a protocol.
301
+ * Thin on purpose: every decision is in createSkillTools and the modules under
302
+ * tools/ and files/; this file only composes them on one server.
234
303
  */
235
304
 
236
305
  /** Reported to the host on connect. */
237
306
  declare const SERVER_NAME = "pmcp";
307
+ declare const SERVER_VERSION = "0.8.1";
238
308
  /**
239
- * Registers the five tools on a server.
309
+ * Registers the skill_* tools on a server.
240
310
  *
241
311
  * Exported separately from the stdio entry so a consumer already running an MCP
242
312
  * server can add these to it rather than starting a second process.
243
313
  */
244
314
  declare function registerSkillTools(server: McpServer, tools: SkillTools): McpServer;
315
+ interface SkillMcpOptions extends SkillServerOptions {
316
+ /** One prompt per skill, for hosts that offer prompts as commands. Off by default. */
317
+ readonly prompts?: boolean;
318
+ readonly skillsExtension?: SkillsExtensionOptions;
319
+ }
245
320
  /**
246
- * A server with the five tools and the Skills extension registered.
321
+ * A server with everything registered.
247
322
  *
248
323
  * The catalog is read on first use rather than here, so a host that connects
249
324
  * and never asks pays nothing for the walk.
250
325
  */
251
- declare function createSkillServer(options: SkillServerOptions): McpServer;
326
+ declare function createSkillServer(options: SkillMcpOptions): McpServer;
252
327
 
253
- /**
254
- * The MCP Skills extension (SEP-2640): `skills/list`, `skills/get` and the
255
- * skill files as `skill://pmcp/<name>/<file>` resources, so a host that speaks
256
- * the extension loads skills natively instead of through the skill_* tools.
257
- */
328
+ /** Checks skill directories against the Agent Skills specification. */
258
329
 
259
- declare const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
260
- interface SkillResource {
261
- readonly uri: string;
262
- readonly digest: string;
263
- readonly size: number;
264
- }
265
- interface SkillExtensionEntry {
266
- readonly uri: string;
267
- readonly frontmatter: Readonly<Record<string, unknown>>;
268
- readonly resources: readonly SkillResource[];
330
+ interface Finding {
331
+ readonly path: string;
332
+ readonly reason: string;
333
+ /** error: the skill is left out or breaks a MUST; warning: served anyway. */
334
+ readonly level: "error" | "warning";
269
335
  }
270
- declare const skillUri: (entry: Pick<SkillEntry, "name">, file?: string) => string;
336
+ /** One skill directory, or the SKILL.md inside it. */
337
+ declare function validateSkillDir(dir: string): Finding[];
338
+ /** Every skill the catalog options reach, with what loading reported. */
339
+ declare function validateCatalog(options: CatalogOptions): Finding[];
340
+
271
341
  /**
272
- * The extension's entry for one catalog skill, or null when the skill cannot
273
- * be served under SEP-2640: not a directory with SKILL.md, a frontmatter name
274
- * that is missing, invalid or unlike its path, or over the per-skill limits.
275
- * Such a skill stays reachable through the skill_* tools.
342
+ * skill:// resources: every file of every skill the caller may see. The tools,
343
+ * the Skills extension and resource links all resolve here, over the same
344
+ * caller-filtered catalog, so nothing a caller cannot list can be read.
276
345
  */
277
- declare function extensionEntry(entry: SkillEntry): SkillExtensionEntry | null;
346
+
347
+ declare function registerSkillResources(server: McpServer, skills: () => readonly SkillEntry[]): void;
348
+
278
349
  /**
279
- * Declares the extension and its methods on a server, over the skills the
280
- * catalog function returns. Must run before the server connects.
350
+ * One prompt per skill, for hosts that offer prompts as commands: the Agent
351
+ * Skills "user-explicit activation" path. Off unless the host asks for it,
352
+ * because the list grows with the catalog.
281
353
  */
282
- declare function registerSkillsExtension(server: McpServer, skills: () => readonly SkillEntry[]): void;
283
354
 
284
- export { CONFIG_FILE, type CatalogSection, ConfigError, DEFAULT_INTENT_CACHE_LIMIT, type Finding, IntentCache, type IntentCacheOptions, LOCK_FILE, type McpServerSpec, type MetadataValue, type Output, type PackageRulesSpec, type ProjectConfig, ProjectError, type ProjectResult, type ProjectSpec, type Rejection, type Runner, SERVER_NAME, SKILLS_EXTENSION, SkillEntry, type SkillExtensionEntry, type SkillResource, SkillServerOptions, SkillTools, TOOLS, type Tool, assetRoot, createSkillServer, deadGlobs, deriveName, doctor, extensionEntry, findConfig, openIntentCache, plan, project, readConfig, readFrontmatterText, readMarketplace, readMetadata, readSpec, registerSkillTools, registerSkillsExtension, skillUri };
355
+ /** Prompt names travel into host command names, so they stay to a plain set. */
356
+ declare const promptName: (entry: Pick<SkillEntry, "name">) => string;
357
+ declare function registerSkillPrompts(server: McpServer, tools: SkillTools): void;
358
+
359
+ export { COMPATIBILITY_MAX, CONFIG_FILE, CatalogOptions, type CatalogSection, ConfigError, DEFAULT_INTENT_CACHE_LIMIT, DESCRIPTION_MAX, type Finding$1 as Finding, IntentCache, type IntentCacheOptions, LOCK_FILE, type McpServerSpec, type MetadataValue, NAME_MAX, type Output, type PackageRulesSpec, type ProjectConfig, ProjectError, type ProjectResult, type ProjectSpec, type Rejection, type Runner, SERVER_NAME, SERVER_VERSION, SKILLS_EXTENSION, SkillEntry, type SkillExtensionEntry, type SkillMcpOptions, type SkillResource, SkillServerOptions, SkillTools, TOOLS, type Tool, type Finding as ValidationFinding, assetRoot, createSkillServer, deadGlobs, deriveName, doctor, extensionEntry, findConfig, openIntentCache, frontmatterObject as parseSkillFrontmatter, plan, project, promptName, readConfig, readFrontmatterText, readMarketplace, readMetadata, readSpec, registerSkillPrompts, registerSkillResources, registerSkillTools, registerSkillsExtension, resolveSkillFileUri, skillFileUri, skillFileUri as skillUri, specProblems, validateCatalog, validateSkillDir };