@modootoday/pmcp 0.3.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normaliseIntent
3
- } from "./chunk-SPWHSHOC.js";
3
+ } from "./chunk-JOQTOVTJ.js";
4
4
 
5
5
  // src/cache.ts
6
6
  async function openDatabase(path) {
@@ -126,9 +126,68 @@ async function openIntentCache(options) {
126
126
  };
127
127
  }
128
128
 
129
+ // src/config.ts
130
+ import { existsSync, readFileSync } from "fs";
131
+ import { dirname, join, resolve } from "path";
132
+ import { parse } from "smol-toml";
133
+ var CONFIG_FILE = "pmcp.toml";
134
+ var ConfigError = class extends Error {
135
+ constructor(path, message) {
136
+ super(`${path}: ${message}`);
137
+ this.name = "ConfigError";
138
+ }
139
+ };
140
+ var PATH_KEYS = ["roots", "workspaces", "packages", "marketplaces"];
141
+ var CATALOG_KEYS = /* @__PURE__ */ new Set([...PATH_KEYS, "scopes"]);
142
+ function findConfig(start) {
143
+ let dir = resolve(start);
144
+ for (; ; ) {
145
+ const candidate = join(dir, CONFIG_FILE);
146
+ if (existsSync(candidate)) return candidate;
147
+ const parent = dirname(dir);
148
+ if (parent === dir) return null;
149
+ dir = parent;
150
+ }
151
+ }
152
+ function stringList(path, key, value) {
153
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
154
+ throw new ConfigError(path, `[catalog] ${key} must be a list of strings`);
155
+ }
156
+ return value;
157
+ }
158
+ function readConfig(path) {
159
+ const absolute = resolve(path);
160
+ const dir = dirname(absolute);
161
+ let raw;
162
+ try {
163
+ raw = parse(readFileSync(absolute, "utf8"));
164
+ } catch (error) {
165
+ const reason = error instanceof Error ? error.message : "unreadable";
166
+ throw new ConfigError(absolute, reason.split("\n")[0] ?? reason);
167
+ }
168
+ const section = raw["catalog"];
169
+ if (section === void 0) return { path: absolute, dir, catalog: {}, raw };
170
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
171
+ throw new ConfigError(absolute, "[catalog] must be a table");
172
+ }
173
+ const catalog = {};
174
+ for (const [key, value] of Object.entries(section)) {
175
+ if (!CATALOG_KEYS.has(key)) {
176
+ throw new ConfigError(absolute, `unknown key [catalog] ${key}`);
177
+ }
178
+ const list = stringList(absolute, key, value);
179
+ catalog[key] = key === "scopes" ? list : list.map((p) => resolve(dir, p));
180
+ }
181
+ return { path: absolute, dir, catalog, raw };
182
+ }
183
+
129
184
  export {
130
185
  openDatabase,
131
186
  DEFAULT_INTENT_CACHE_LIMIT,
132
- openIntentCache
187
+ openIntentCache,
188
+ CONFIG_FILE,
189
+ ConfigError,
190
+ findConfig,
191
+ readConfig
133
192
  };
134
- //# sourceMappingURL=chunk-MBAXK4IZ.js.map
193
+ //# sourceMappingURL=chunk-PUGLIJYM.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSkillServer
3
- } from "./chunk-SPWHSHOC.js";
3
+ } from "./chunk-JOQTOVTJ.js";
4
4
 
5
5
  // src/stdio.ts
6
6
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -35,4 +35,4 @@ export {
35
35
  serve,
36
36
  main
37
37
  };
38
- //# sourceMappingURL=chunk-QJS3NYLT.js.map
38
+ //# sourceMappingURL=chunk-X4LYYULW.js.map
package/dist/cli.js CHANGED
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- openDatabase
4
- } from "./chunk-MBAXK4IZ.js";
3
+ ConfigError,
4
+ findConfig,
5
+ openDatabase,
6
+ readConfig
7
+ } from "./chunk-PUGLIJYM.js";
5
8
  import {
6
9
  serve
7
- } from "./chunk-QJS3NYLT.js";
10
+ } from "./chunk-X4LYYULW.js";
8
11
  import {
9
12
  readCatalog
10
- } from "./chunk-SPWHSHOC.js";
13
+ } from "./chunk-JOQTOVTJ.js";
11
14
 
12
15
  // src/cli/command.ts
13
16
  var ArgumentError = class extends Error {
@@ -320,17 +323,48 @@ var CATALOG_OPTIONS = [
320
323
  describe: "A workspace root, or a glob of them: each root and every member its workspaces globs name. Repeatable.",
321
324
  repeat: true,
322
325
  placeholder: "<dir>"
326
+ },
327
+ {
328
+ name: "config",
329
+ describe: "A pmcp.toml to read instead of the nearest one at or above the working directory.",
330
+ placeholder: "<file>"
331
+ },
332
+ {
333
+ name: "no-config",
334
+ describe: "Ignore pmcp.toml; only the flags given count.",
335
+ boolean: true
323
336
  }
324
337
  ];
338
+ function configFrom(context) {
339
+ if (context.args.flags.has("no-config")) return null;
340
+ const named = one(context.args, "config");
341
+ const path = named ?? findConfig(context.cwd);
342
+ if (path === null) return null;
343
+ try {
344
+ return readConfig(path);
345
+ } catch (error) {
346
+ if (error instanceof ConfigError) throw new ArgumentError(error.message);
347
+ if (named !== void 0) throw new ArgumentError(`cannot read ${named}`);
348
+ throw error;
349
+ }
350
+ }
325
351
  function rootsFrom(context) {
326
352
  const given = many(context.args, "root");
327
- return given.length > 0 ? given : [`${context.cwd}/node_modules`];
353
+ if (given.length > 0) return given;
354
+ const config = configFrom(context);
355
+ if (config?.catalog.roots !== void 0) return config.catalog.roots;
356
+ return [`${config?.dir ?? context.cwd}/node_modules`];
328
357
  }
329
358
  function catalogOptionsFrom(context, onReject) {
330
- const scopes = many(context.args, "scope");
331
- const marketplaces = many(context.args, "marketplace");
332
- const packages = many(context.args, "package");
333
- const workspaces = many(context.args, "workspace");
359
+ const config = configFrom(context)?.catalog ?? {};
360
+ const pick = (flag, fromFile) => {
361
+ const given = many(context.args, flag);
362
+ return given.length > 0 ? given : fromFile ?? [];
363
+ };
364
+ const scopes = pick("scope", config.scopes);
365
+ const marketplaces = pick("marketplace", config.marketplaces);
366
+ const packages = pick("package", config.packages);
367
+ const workspaces = pick("workspace", config.workspaces);
334
368
  return {
335
369
  roots: rootsFrom(context),
336
370
  ...scopes.length > 0 ? { scopes } : {},
@@ -449,7 +483,7 @@ var indexCommand = {
449
483
  var listCommand = {
450
484
  name: "list",
451
485
  describe: "List the skills discovered in node_modules and marketplaces",
452
- usage: "pmcp list [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir|glob>] [--json]",
486
+ usage: "pmcp list [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir|glob>] [--config <file> | --no-config] [--json]",
453
487
  options: [
454
488
  ...CATALOG_OPTIONS,
455
489
  { name: "json", describe: "Emit JSON rather than a table.", boolean: true }
@@ -474,12 +508,28 @@ var listCommand = {
474
508
  return 1;
475
509
  }
476
510
  context.ui.table(
477
- entries.map((entry2) => [
478
- entry2.tier === void 0 ? entry2.name : `${entry2.name} [${entry2.tier}]`,
479
- entry2.description
480
- ])
511
+ entries.map((entry2) => {
512
+ const tags = [entry2.kind, entry2.tier].filter(Boolean).join(", ");
513
+ return [
514
+ tags === "" ? entry2.name : `${entry2.name} [${tags}]`,
515
+ entry2.description
516
+ ];
517
+ })
518
+ );
519
+ const counts = /* @__PURE__ */ new Map();
520
+ for (const entry2 of entries) {
521
+ const kind = entry2.kind ?? "skill";
522
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
523
+ }
524
+ const plural = {
525
+ skill: "skills",
526
+ agent: "agents",
527
+ hook: "hooks",
528
+ mcp: "MCP servers"
529
+ };
530
+ context.ui.success(
531
+ [...counts].map(([kind, n]) => `${n} ${plural[kind] ?? kind}`).join(", ")
481
532
  );
482
- context.ui.success(`${entries.length} skills`);
483
533
  return rejected.length === 0 ? 0 : 1;
484
534
  }
485
535
  };
@@ -488,7 +538,7 @@ var listCommand = {
488
538
  var serveCommand = {
489
539
  name: "serve",
490
540
  describe: "Run the MCP server on stdio (the default with no command)",
491
- usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>]",
541
+ usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config]",
492
542
  options: [...CATALOG_OPTIONS],
493
543
  run(context) {
494
544
  serve(catalogOptionsFrom(context));
@@ -2085,7 +2135,7 @@ function dispatch(argv, options = {}) {
2085
2135
  }
2086
2136
  return run(command, rest, { ui, env, cwd });
2087
2137
  }
2088
- var VERSION = "0.3.0";
2138
+ var VERSION = "0.5.0";
2089
2139
  async function run(command, argv, context) {
2090
2140
  try {
2091
2141
  return await command.run({
package/dist/http.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { S as SkillEntry } from './catalog-5v7jrGwP.js';
2
+ import { S as SkillServerOptions } from './server-Dm71qZgM.js';
3
+
4
+ /**
5
+ * The catalog over streamable HTTP, for a hosted skills server.
6
+ *
7
+ * The host owns identity: it passes authorize(), which turns a request into a
8
+ * caller or undefined. Anonymous requests are refused; each caller sees only the
9
+ * tiers tiersFor() grants. The catalog is read once and filtered per request.
10
+ */
11
+
12
+ interface SkillCaller {
13
+ /** Stable id of the signed-in user, recorded by the host, never by pmcp. */
14
+ readonly id: string;
15
+ /** Whatever the host's token carried; passed to tiersFor unchanged. */
16
+ readonly claims?: Readonly<Record<string, unknown>>;
17
+ }
18
+ interface SkillHttpOptions extends SkillServerOptions {
19
+ readonly authorize: (request: Request) => SkillCaller | undefined | Promise<SkillCaller | undefined>;
20
+ /** Tiers a caller may list and load. Package skills without a tier count as open. */
21
+ readonly tiersFor?: (caller: SkillCaller) => readonly string[];
22
+ /** Sent in WWW-Authenticate on a refused request, e.g. a resource metadata URL. */
23
+ readonly challenge?: string;
24
+ }
25
+ declare const DEFAULT_TIERS: readonly string[];
26
+ declare function visibleTo(entries: readonly SkillEntry[], tiers: readonly string[]): SkillEntry[];
27
+ interface SkillHttpHandler {
28
+ fetch(request: Request): Promise<Response>;
29
+ /** Re-read the catalog, for a host that deploys new skill releases in place. */
30
+ reload(): void;
31
+ }
32
+ declare function createSkillHttpHandler(options: SkillHttpOptions): SkillHttpHandler;
33
+
34
+ export { DEFAULT_TIERS, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, createSkillHttpHandler, visibleTo };
package/dist/http.js ADDED
@@ -0,0 +1,65 @@
1
+ import {
2
+ createSkillServer,
3
+ readCatalog
4
+ } from "./chunk-JOQTOVTJ.js";
5
+
6
+ // src/http.ts
7
+ import {
8
+ createMcpHandler
9
+ } from "@modelcontextprotocol/server";
10
+ var DEFAULT_TIERS = ["open", "free"];
11
+ function visibleTo(entries, tiers) {
12
+ const allowed = new Set(tiers);
13
+ return entries.filter((entry) => allowed.has(entry.tier ?? "open"));
14
+ }
15
+ function createSkillHttpHandler(options) {
16
+ const load = options.loadCatalog ?? readCatalog;
17
+ let all = load(options);
18
+ const tiersFor = options.tiersFor ?? (() => DEFAULT_TIERS);
19
+ const handlers = /* @__PURE__ */ new Map();
20
+ const handlerFor = (tiers) => {
21
+ const key = [...tiers].sort().join(",");
22
+ const cached = handlers.get(key);
23
+ if (cached) return cached;
24
+ const handler = createMcpHandler(
25
+ () => createSkillServer({
26
+ ...options,
27
+ loadCatalog: () => visibleTo(all, tiers)
28
+ })
29
+ );
30
+ handlers.set(key, handler);
31
+ return handler;
32
+ };
33
+ return {
34
+ async fetch(request) {
35
+ const caller = await options.authorize(request);
36
+ if (!caller) {
37
+ return new Response(JSON.stringify({ error: "unauthorized" }), {
38
+ status: 401,
39
+ headers: {
40
+ "content-type": "application/json",
41
+ "www-authenticate": options.challenge ?? "Bearer"
42
+ }
43
+ });
44
+ }
45
+ return handlerFor(tiersFor(caller)).fetch(request, {
46
+ authInfo: {
47
+ token: "",
48
+ clientId: caller.id,
49
+ scopes: [],
50
+ extra: { ...caller.claims }
51
+ }
52
+ });
53
+ },
54
+ reload() {
55
+ all = load(options);
56
+ handlers.clear();
57
+ }
58
+ };
59
+ }
60
+ export {
61
+ DEFAULT_TIERS,
62
+ createSkillHttpHandler,
63
+ visibleTo
64
+ };
65
+ //# sourceMappingURL=http.js.map
package/dist/index.d.ts CHANGED
@@ -1,36 +1,9 @@
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
+ import { I as IntentCache, S as SkillServerOptions, a as SkillTools } from './server-Dm71qZgM.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-Dm71qZgM.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';
3
5
  import { McpServer } from '@modelcontextprotocol/server';
4
6
 
5
- /**
6
- * The intent key and the cache contract.
7
- *
8
- * Separate from the sqlite implementation so that ranking, which needs the key
9
- * and the interface, does not pull a bun-only module into a consumer running
10
- * under node. Only `cache.ts` requires bun.
11
- */
12
- interface IntentCacheStats {
13
- readonly hits: number;
14
- readonly misses: number;
15
- /** Rows found for the key but written by a different model. */
16
- readonly mismatched: number;
17
- readonly rows: number;
18
- /** Which sqlite builtin answered, or absent when there is no cache. */
19
- readonly provider?: string;
20
- }
21
- interface IntentCache {
22
- /** The cached vector for an intent, or null when nothing usable is stored. */
23
- get(intent: string): Float32Array | null;
24
- put(intent: string, vector: Float32Array): void;
25
- stats(): IntentCacheStats;
26
- close(): void;
27
- }
28
- /**
29
- * Same question, differently typed. Case, punctuation and spacing only — the
30
- * key has to survive a caller writing "How do I wire X?" and "how do i wire x".
31
- */
32
- declare function normaliseIntent(intent: string): string;
33
-
34
7
  /**
35
8
  * Query vectors, kept on disk between sessions.
36
9
  *
@@ -64,70 +37,14 @@ declare const DEFAULT_INTENT_CACHE_LIMIT = 4096;
64
37
  declare function openIntentCache(options: IntentCacheOptions): Promise<IntentCache | null>;
65
38
 
66
39
  /**
67
- * Ranking an intent against skill descriptions.
68
- *
69
- * Semantic when an embedder is supplied, lexical otherwise, and the answer says
70
- * which. A caller that cannot tell the two apart cannot tell a degraded server
71
- * from a working one.
72
- */
73
-
74
- type Ranking = "semantic" | "lexical";
75
- interface Match {
76
- readonly name: string;
77
- readonly package: string;
78
- readonly description: string;
79
- readonly score: number;
80
- }
81
- interface FindResult {
82
- readonly ranking: Ranking;
83
- readonly matches: readonly Match[];
84
- }
85
- /**
86
- * The encoder, supplied by the caller. There is no default and no bundled
87
- * model: which model runs decides what the scores mean, so it is the consuming
88
- * repository's declaration rather than this package's.
89
- */
90
- interface Embedder {
91
- readonly modelId: string;
92
- readonly dims: number;
93
- embed(text: string): Promise<Float32Array>;
94
- }
95
- interface FindOptions {
96
- readonly entries: readonly SkillEntry[];
97
- readonly intent: string;
98
- readonly limit?: number;
99
- readonly embedder?: Embedder;
100
- readonly cache?: IntentCache;
101
- /** Precomputed description vectors, keyed by skill name. */
102
- readonly vectors?: ReadonlyMap<string, Float32Array>;
103
- }
104
- declare const DEFAULT_FIND_LIMIT = 5;
105
- /**
106
- * Token overlap and bigram Dice, averaged. Deterministic and dependency-free:
107
- * this is the answer when there is no model, so it must never be the thing
108
- * that fails.
109
- */
110
- declare function lexicalScore(intent: string, description: string): number;
111
- /** Cosine over two equal-width vectors. Zero when either has no magnitude. */
112
- declare function cosine(a: Float32Array, b: Float32Array): number;
113
- /**
114
- * Ranked matches, and which ranking produced them.
115
- *
116
- * Returns rather than loads: a search verb that also acts cannot be used to
117
- * look, and the whole reason this server exists is that looking should be
118
- * cheap.
119
- */
120
- declare function find(options: FindOptions): Promise<FindResult>;
121
-
122
- /**
123
- * Skills laid out as an agent plugin marketplace: a directory holding
124
- * `.claude-plugin/marketplace.json`, whose plugins keep skills at
125
- * `skills/<skill>/SKILL.md`. No package.json is involved, which is why the
126
- * node_modules walk never saw them.
40
+ * Assets laid out as an agent plugin marketplace: a directory holding
41
+ * `.claude-plugin/marketplace.json`, whose plugins keep skills, commands,
42
+ * agents, hooks and MCP servers in the plugin format's own places. No
43
+ * package.json is involved, which is why the node_modules walk never saw them.
127
44
  */
128
45
 
129
46
  type MetadataValue = string | readonly string[];
130
- /** Why a skill was left out rather than served. */
47
+ /** Why an asset was left out rather than served. */
131
48
  interface Rejection {
132
49
  readonly path: string;
133
50
  readonly reason: string;
@@ -139,117 +56,48 @@ interface Rejection {
139
56
  declare function readMetadata(source: string): Record<string, MetadataValue>;
140
57
  /** The raw frontmatter text between the fences, for skill_describe. */
141
58
  declare function readFrontmatterText(source: string): string;
142
- /**
143
- * Every skill one marketplace lists. A skill whose own tier disagrees with the
144
- * marketplace's is rejected: a mislabelled tier is how a private skill leaks.
145
- */
59
+ /** Every asset one marketplace lists. */
146
60
  declare function readMarketplace(dir: string, onReject?: (rejection: Rejection) => void): SkillEntry[];
61
+ /** The directory or single file an entry's describe and read may open. */
62
+ declare const assetRoot: (entry: Pick<SkillEntry, "root" | "path">) => string;
147
63
 
148
64
  /**
149
- * The five tools, and nothing else.
65
+ * `pmcp.toml`, the project's one record of where its catalog lives.
150
66
  *
151
- * The point of the server is that a session's startup cost does not grow with
152
- * the number of skills installed: the host lists five tools whatever the
153
- * catalog holds, and a body is read only when one is asked for.
154
- */
155
-
156
- /** Largest file skill_read returns. A skill needing more should split it. */
157
- declare const READ_BUDGET_BYTES: number;
158
- /** How many files skill_describe lists before saying the rest were cut. */
159
- declare const DESCRIBE_FILE_LIMIT = 200;
160
- declare const CATALOG_PAGE_SIZE = 8;
161
- declare const CATALOG_PAGE_MAX = 20;
162
- /** Equality filters over catalog fields. Unset keys match everything. */
163
- interface SkillFilters {
164
- readonly tier?: string;
165
- readonly package?: string;
166
- readonly level?: string;
167
- readonly domain?: string;
168
- readonly medium?: string;
67
+ * Paths in it are relative to the file, so the same file works from any
68
+ * directory a host starts pmcp in. Only `[catalog]` is read here; the other
69
+ * sections belong to the projector and are kept as given.
70
+ */
71
+ declare const CONFIG_FILE = "pmcp.toml";
72
+ /** What `[catalog]` may say. Each mirrors the repeatable CLI flag of the same meaning. */
73
+ interface CatalogSection {
74
+ readonly roots?: readonly string[];
75
+ readonly scopes?: readonly string[];
76
+ readonly workspaces?: readonly string[];
77
+ readonly packages?: readonly string[];
78
+ readonly marketplaces?: readonly string[];
169
79
  }
170
- type ReadResult = {
171
- readonly ok: true;
172
- readonly name: string;
80
+ interface ProjectConfig {
81
+ /** Absolute path of the file read. */
173
82
  readonly path: string;
174
- readonly bytes: number;
175
- readonly sha256: string;
176
- readonly text: string;
177
- } | {
178
- readonly ok: false;
179
- readonly error: string;
180
- readonly detail: string;
181
- };
182
- interface SkillServerOptions extends CatalogOptions {
183
- readonly embedder?: Embedder;
184
- readonly cache?: IntentCache;
185
- readonly vectors?: ReadonlyMap<string, Float32Array>;
186
- /** Injected for tests. Reads the catalog off disk otherwise. */
187
- readonly loadCatalog?: (options: CatalogOptions) => SkillEntry[];
83
+ /** Its directory; every relative path in the file resolves against it. */
84
+ readonly dir: string;
85
+ /** `[catalog]` with paths already absolute. */
86
+ readonly catalog: CatalogSection;
87
+ /** The whole document, for sections this module does not interpret. */
88
+ readonly raw: Readonly<Record<string, unknown>>;
188
89
  }
189
- interface CatalogResponse {
190
- readonly count: number;
191
- readonly page: number;
192
- readonly totalPages: number;
193
- readonly hasNext: boolean;
194
- readonly packages: readonly {
195
- readonly package: string;
196
- readonly skills: readonly {
197
- readonly name: string;
198
- readonly description: string;
199
- readonly tier?: string;
200
- }[];
201
- }[];
202
- }
203
- interface CallResponse {
204
- readonly name: string;
205
- readonly body: string;
90
+ declare class ConfigError extends Error {
91
+ constructor(path: string, message: string);
206
92
  }
93
+ /** The nearest pmcp.toml at or above start, or null when there is none. */
94
+ declare function findConfig(start: string): string | null;
207
95
  /**
208
- * The catalog, the search and the read, as plain functions.
209
- *
210
- * Separate from the MCP wiring so they can be exercised without a transport,
211
- * and so a consumer that wants them from a hook or a script is not made to
212
- * speak a protocol to get them.
96
+ * Reads and checks one file. An unknown key under `[catalog]` is an error
97
+ * rather than ignored, for the same reason an unknown flag is: a misspelt
98
+ * `workspace = [...]` must not look like it worked.
213
99
  */
214
- declare function createSkillTools(options: SkillServerOptions): {
215
- /**
216
- * Grouped, and without bodies. The cheap overview and the search's miss
217
- * path. Paged by package so a large catalog never arrives in one answer.
218
- */
219
- catalog(request?: {
220
- page?: number;
221
- pageSize?: number;
222
- tier?: string;
223
- }): CatalogResponse;
224
- /** Ranked names. Returns; never loads a body. */
225
- find(intent: string, limit?: number, filters?: SkillFilters): Promise<FindResult>;
226
- /** Every property of one skill and the files it carries, without bodies. */
227
- describe(name: string): {
228
- name: string;
229
- package: string;
230
- description: string;
231
- tier: string | undefined;
232
- metadata: Readonly<Record<string, string | readonly string[]>>;
233
- frontmatter: string;
234
- files: {
235
- path: string;
236
- bytes: number;
237
- sha256: string;
238
- }[];
239
- filesTruncated: boolean;
240
- } | null;
241
- /**
242
- * One file inside a skill's directory. Paths that leave the directory,
243
- * directly or through a link, are refused; nothing is ever executed.
244
- */
245
- read(name: string, path?: string): ReadResult;
246
- /** One body, at the moment it is needed. */
247
- call(name: string): CallResponse | null;
248
- /** Drops the memoised catalog, so the next call re-reads the tree. */
249
- refresh(): void;
250
- entries: () => Omit<SkillEntry, "path">[];
251
- };
252
- type SkillTools = ReturnType<typeof createSkillTools>;
100
+ declare function readConfig(path: string): ProjectConfig;
253
101
 
254
102
  /**
255
103
  * The five tools, spoken over MCP.
@@ -270,11 +118,42 @@ declare const SERVER_NAME = "pmcp";
270
118
  */
271
119
  declare function registerSkillTools(server: McpServer, tools: SkillTools): McpServer;
272
120
  /**
273
- * A server with the five tools registered.
121
+ * A server with the five tools and the Skills extension registered.
274
122
  *
275
123
  * The catalog is read on first use rather than here, so a host that connects
276
124
  * and never asks pays nothing for the walk.
277
125
  */
278
126
  declare function createSkillServer(options: SkillServerOptions): McpServer;
279
127
 
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 };
128
+ /**
129
+ * The MCP Skills extension (SEP-2640): `skills/list`, `skills/get` and the
130
+ * skill files as `skill://pmcp/<name>/<file>` resources, so a host that speaks
131
+ * the extension loads skills natively instead of through the skill_* tools.
132
+ */
133
+
134
+ declare const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
135
+ interface SkillResource {
136
+ readonly uri: string;
137
+ readonly digest: string;
138
+ readonly size: number;
139
+ }
140
+ interface SkillExtensionEntry {
141
+ readonly uri: string;
142
+ readonly frontmatter: Readonly<Record<string, unknown>>;
143
+ readonly resources: readonly SkillResource[];
144
+ }
145
+ declare const skillUri: (entry: Pick<SkillEntry, "name">, file?: string) => string;
146
+ /**
147
+ * The extension's entry for one catalog skill, or null when the skill cannot
148
+ * be served under SEP-2640: not a directory with SKILL.md, a frontmatter name
149
+ * that is missing, invalid or unlike its path, or over the per-skill limits.
150
+ * Such a skill stays reachable through the skill_* tools.
151
+ */
152
+ declare function extensionEntry(entry: SkillEntry): SkillExtensionEntry | null;
153
+ /**
154
+ * Declares the extension and its methods on a server, over the skills the
155
+ * catalog function returns. Must run before the server connects.
156
+ */
157
+ declare function registerSkillsExtension(server: McpServer, skills: () => readonly SkillEntry[]): void;
158
+
159
+ export { CONFIG_FILE, type CatalogSection, ConfigError, DEFAULT_INTENT_CACHE_LIMIT, IntentCache, type IntentCacheOptions, type MetadataValue, type ProjectConfig, type Rejection, SERVER_NAME, SKILLS_EXTENSION, SkillEntry, type SkillExtensionEntry, type SkillResource, SkillServerOptions, SkillTools, assetRoot, createSkillServer, extensionEntry, findConfig, openIntentCache, readConfig, readFrontmatterText, readMarketplace, readMetadata, registerSkillTools, registerSkillsExtension, skillUri };