@modootoday/pmcp 0.3.0 → 0.4.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
@@ -62,6 +62,27 @@ within the discovery depth. A skill needs a frontmatter `description` for search
62
62
  `skill_find` accepts a problem description and returns matching skills.
63
63
  `skill_call` opens the selected document. Local MCP reading requires no login.
64
64
 
65
+ ## Hosting over HTTP
66
+
67
+ `@modootoday/pmcp/http` serves the same five tools over streamable HTTP for a hosted catalog.
68
+ The host owns identity: `authorize(request)` returns the caller or `undefined` (answered 401),
69
+ and `tiersFor(caller)` decides which tiers that caller sees (default `open` and `free`; skills
70
+ without a tier count as `open`). The catalog is read once; call `reload()` after deploying new
71
+ skill releases.
72
+
73
+ ```ts
74
+ import { createSkillHttpHandler } from "@modootoday/pmcp/http";
75
+
76
+ const skills = createSkillHttpHandler({
77
+ roots: [],
78
+ marketplaces: ["/srv/marketplace", "/srv/marketplace-free"],
79
+ authorize: async (request) => verifyBearer(request.headers.get("authorization")),
80
+ });
81
+ export default { fetch: (request: Request) => skills.fetch(request) };
82
+ ```
83
+
84
+ Put Origin and Host validation in front of it when it is mounted bare.
85
+
65
86
  ## Runtime and licence
66
87
 
67
88
  Node.js 22 or later, or Bun 1.3 or later. Default ranking is lexical. The optional
@@ -768,7 +768,7 @@ function registerSkillTools(server, tools) {
768
768
  return server;
769
769
  }
770
770
  function createSkillServer(options) {
771
- const server = new McpServer({ name: SERVER_NAME, version: "0.3.0" });
771
+ const server = new McpServer({ name: SERVER_NAME, version: "0.4.0" });
772
772
  return registerSkillTools(server, createSkillTools(options));
773
773
  }
774
774
 
@@ -797,4 +797,4 @@ export {
797
797
  registerSkillTools,
798
798
  createSkillServer
799
799
  };
800
- //# sourceMappingURL=chunk-SPWHSHOC.js.map
800
+ //# sourceMappingURL=chunk-BFET6MFN.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSkillServer
3
- } from "./chunk-SPWHSHOC.js";
3
+ } from "./chunk-BFET6MFN.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-JL5BCIQM.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normaliseIntent
3
- } from "./chunk-SPWHSHOC.js";
3
+ } from "./chunk-BFET6MFN.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-MBAXK4IZ.js.map
134
+ //# sourceMappingURL=chunk-RBT3ISF4.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-MBAXK4IZ.js";
4
+ } from "./chunk-RBT3ISF4.js";
5
5
  import {
6
6
  serve
7
- } from "./chunk-QJS3NYLT.js";
7
+ } from "./chunk-JL5BCIQM.js";
8
8
  import {
9
9
  readCatalog
10
- } from "./chunk-SPWHSHOC.js";
10
+ } from "./chunk-BFET6MFN.js";
11
11
 
12
12
  // src/cli/command.ts
13
13
  var ArgumentError = class extends Error {
@@ -2085,7 +2085,7 @@ function dispatch(argv, options = {}) {
2085
2085
  }
2086
2086
  return run(command, rest, { ui, env, cwd });
2087
2087
  }
2088
- var VERSION = "0.3.0";
2088
+ var VERSION = "0.4.0";
2089
2089
  async function run(command, argv, context) {
2090
2090
  try {
2091
2091
  return await command.run({
package/dist/http.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { S as SkillEntry } from './catalog-BiPLcbLu.js';
2
+ import { S as SkillServerOptions } from './server-dNFrR_Zh.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-BFET6MFN.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-dNFrR_Zh.js';
2
+ export { C as CATALOG_PAGE_MAX, b as CATALOG_PAGE_SIZE, c as CallResponse, d as CatalogResponse, D as DEFAULT_FIND_LIMIT, e as DESCRIBE_FILE_LIMIT, E as Embedder, F as FindOptions, f as FindResult, g as IntentCacheStats, M as Match, R as READ_BUDGET_BYTES, h as Ranking, i as ReadResult, j as SkillFilters, k as cosine, l as createSkillTools, m as find, n as lexicalScore, o as normaliseIntent } from './server-dNFrR_Zh.js';
3
+ import { S as SkillEntry } from './catalog-BiPLcbLu.js';
4
+ export { C as CatalogOptions, 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';
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
  *
@@ -63,62 +36,6 @@ declare const DEFAULT_INTENT_CACHE_LIMIT = 4096;
63
36
  */
64
37
  declare function openIntentCache(options: IntentCacheOptions): Promise<IntentCache | null>;
65
38
 
66
- /**
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
39
  /**
123
40
  * Skills laid out as an agent plugin marketplace: a directory holding
124
41
  * `.claude-plugin/marketplace.json`, whose plugins keep skills at
@@ -145,112 +62,6 @@ declare function readFrontmatterText(source: string): string;
145
62
  */
146
63
  declare function readMarketplace(dir: string, onReject?: (rejection: Rejection) => void): SkillEntry[];
147
64
 
148
- /**
149
- * The five tools, and nothing else.
150
- *
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;
169
- }
170
- type ReadResult = {
171
- readonly ok: true;
172
- readonly name: string;
173
- 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[];
188
- }
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;
206
- }
207
- /**
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.
213
- */
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>;
253
-
254
65
  /**
255
66
  * The five tools, spoken over MCP.
256
67
  *
@@ -277,4 +88,4 @@ declare function registerSkillTools(server: McpServer, tools: SkillTools): McpSe
277
88
  */
278
89
  declare function createSkillServer(options: SkillServerOptions): McpServer;
279
90
 
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 };
91
+ export { DEFAULT_INTENT_CACHE_LIMIT, IntentCache, type IntentCacheOptions, type MetadataValue, type Rejection, SERVER_NAME, SkillEntry, SkillServerOptions, SkillTools, createSkillServer, 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-MBAXK4IZ.js";
4
+ } from "./chunk-RBT3ISF4.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-SPWHSHOC.js";
29
+ } from "./chunk-BFET6MFN.js";
30
30
  export {
31
31
  CATALOG_PAGE_MAX,
32
32
  CATALOG_PAGE_SIZE,
@@ -0,0 +1,194 @@
1
+ import { S as SkillEntry, C as CatalogOptions } from './catalog-BiPLcbLu.js';
2
+
3
+ /**
4
+ * The intent key and the cache contract.
5
+ *
6
+ * Separate from the sqlite implementation so that ranking, which needs the key
7
+ * and the interface, does not pull a bun-only module into a consumer running
8
+ * under node. Only `cache.ts` requires bun.
9
+ */
10
+ interface IntentCacheStats {
11
+ readonly hits: number;
12
+ readonly misses: number;
13
+ /** Rows found for the key but written by a different model. */
14
+ readonly mismatched: number;
15
+ readonly rows: number;
16
+ /** Which sqlite builtin answered, or absent when there is no cache. */
17
+ readonly provider?: string;
18
+ }
19
+ interface IntentCache {
20
+ /** The cached vector for an intent, or null when nothing usable is stored. */
21
+ get(intent: string): Float32Array | null;
22
+ put(intent: string, vector: Float32Array): void;
23
+ stats(): IntentCacheStats;
24
+ close(): void;
25
+ }
26
+ /**
27
+ * Same question, differently typed. Case, punctuation and spacing only — the
28
+ * key has to survive a caller writing "How do I wire X?" and "how do i wire x".
29
+ */
30
+ declare function normaliseIntent(intent: string): string;
31
+
32
+ /**
33
+ * Ranking an intent against skill descriptions.
34
+ *
35
+ * Semantic when an embedder is supplied, lexical otherwise, and the answer says
36
+ * which. A caller that cannot tell the two apart cannot tell a degraded server
37
+ * from a working one.
38
+ */
39
+
40
+ type Ranking = "semantic" | "lexical";
41
+ interface Match {
42
+ readonly name: string;
43
+ readonly package: string;
44
+ readonly description: string;
45
+ readonly score: number;
46
+ }
47
+ interface FindResult {
48
+ readonly ranking: Ranking;
49
+ readonly matches: readonly Match[];
50
+ }
51
+ /**
52
+ * The encoder, supplied by the caller. There is no default and no bundled
53
+ * model: which model runs decides what the scores mean, so it is the consuming
54
+ * repository's declaration rather than this package's.
55
+ */
56
+ interface Embedder {
57
+ readonly modelId: string;
58
+ readonly dims: number;
59
+ embed(text: string): Promise<Float32Array>;
60
+ }
61
+ interface FindOptions {
62
+ readonly entries: readonly SkillEntry[];
63
+ readonly intent: string;
64
+ readonly limit?: number;
65
+ readonly embedder?: Embedder;
66
+ readonly cache?: IntentCache;
67
+ /** Precomputed description vectors, keyed by skill name. */
68
+ readonly vectors?: ReadonlyMap<string, Float32Array>;
69
+ }
70
+ declare const DEFAULT_FIND_LIMIT = 5;
71
+ /**
72
+ * Token overlap and bigram Dice, averaged. Deterministic and dependency-free:
73
+ * this is the answer when there is no model, so it must never be the thing
74
+ * that fails.
75
+ */
76
+ declare function lexicalScore(intent: string, description: string): number;
77
+ /** Cosine over two equal-width vectors. Zero when either has no magnitude. */
78
+ declare function cosine(a: Float32Array, b: Float32Array): number;
79
+ /**
80
+ * Ranked matches, and which ranking produced them.
81
+ *
82
+ * Returns rather than loads: a search verb that also acts cannot be used to
83
+ * look, and the whole reason this server exists is that looking should be
84
+ * cheap.
85
+ */
86
+ declare function find(options: FindOptions): Promise<FindResult>;
87
+
88
+ /**
89
+ * The five tools, and nothing else.
90
+ *
91
+ * The point of the server is that a session's startup cost does not grow with
92
+ * the number of skills installed: the host lists five tools whatever the
93
+ * catalog holds, and a body is read only when one is asked for.
94
+ */
95
+
96
+ /** Largest file skill_read returns. A skill needing more should split it. */
97
+ declare const READ_BUDGET_BYTES: number;
98
+ /** How many files skill_describe lists before saying the rest were cut. */
99
+ declare const DESCRIBE_FILE_LIMIT = 200;
100
+ declare const CATALOG_PAGE_SIZE = 8;
101
+ declare const CATALOG_PAGE_MAX = 20;
102
+ /** Equality filters over catalog fields. Unset keys match everything. */
103
+ interface SkillFilters {
104
+ readonly tier?: string;
105
+ readonly package?: string;
106
+ readonly level?: string;
107
+ readonly domain?: string;
108
+ readonly medium?: string;
109
+ }
110
+ type ReadResult = {
111
+ readonly ok: true;
112
+ readonly name: string;
113
+ readonly path: string;
114
+ readonly bytes: number;
115
+ readonly sha256: string;
116
+ readonly text: string;
117
+ } | {
118
+ readonly ok: false;
119
+ readonly error: string;
120
+ readonly detail: string;
121
+ };
122
+ interface SkillServerOptions extends CatalogOptions {
123
+ readonly embedder?: Embedder;
124
+ readonly cache?: IntentCache;
125
+ readonly vectors?: ReadonlyMap<string, Float32Array>;
126
+ /** Injected for tests. Reads the catalog off disk otherwise. */
127
+ readonly loadCatalog?: (options: CatalogOptions) => SkillEntry[];
128
+ }
129
+ interface CatalogResponse {
130
+ readonly count: number;
131
+ readonly page: number;
132
+ readonly totalPages: number;
133
+ readonly hasNext: boolean;
134
+ readonly packages: readonly {
135
+ readonly package: string;
136
+ readonly skills: readonly {
137
+ readonly name: string;
138
+ readonly description: string;
139
+ readonly tier?: string;
140
+ }[];
141
+ }[];
142
+ }
143
+ interface CallResponse {
144
+ readonly name: string;
145
+ readonly body: string;
146
+ }
147
+ /**
148
+ * The catalog, the search and the read, as plain functions.
149
+ *
150
+ * Separate from the MCP wiring so they can be exercised without a transport,
151
+ * and so a consumer that wants them from a hook or a script is not made to
152
+ * speak a protocol to get them.
153
+ */
154
+ declare function createSkillTools(options: SkillServerOptions): {
155
+ /**
156
+ * Grouped, and without bodies. The cheap overview and the search's miss
157
+ * path. Paged by package so a large catalog never arrives in one answer.
158
+ */
159
+ catalog(request?: {
160
+ page?: number;
161
+ pageSize?: number;
162
+ tier?: string;
163
+ }): CatalogResponse;
164
+ /** Ranked names. Returns; never loads a body. */
165
+ find(intent: string, limit?: number, filters?: SkillFilters): Promise<FindResult>;
166
+ /** Every property of one skill and the files it carries, without bodies. */
167
+ describe(name: string): {
168
+ name: string;
169
+ package: string;
170
+ description: string;
171
+ tier: string | undefined;
172
+ metadata: Readonly<Record<string, string | readonly string[]>>;
173
+ frontmatter: string;
174
+ files: {
175
+ path: string;
176
+ bytes: number;
177
+ sha256: string;
178
+ }[];
179
+ filesTruncated: boolean;
180
+ } | null;
181
+ /**
182
+ * One file inside a skill's directory. Paths that leave the directory,
183
+ * directly or through a link, are refused; nothing is ever executed.
184
+ */
185
+ read(name: string, path?: string): ReadResult;
186
+ /** One body, at the moment it is needed. */
187
+ call(name: string): CallResponse | null;
188
+ /** Drops the memoised catalog, so the next call re-reads the tree. */
189
+ refresh(): void;
190
+ entries: () => Omit<SkillEntry, "path">[];
191
+ };
192
+ type SkillTools = ReturnType<typeof createSkillTools>;
193
+
194
+ export { CATALOG_PAGE_MAX as C, DEFAULT_FIND_LIMIT as D, type Embedder as E, type FindOptions as F, type IntentCache as I, type Match as M, READ_BUDGET_BYTES as R, type SkillServerOptions as S, type SkillTools as a, CATALOG_PAGE_SIZE as b, type CallResponse as c, type CatalogResponse as d, DESCRIBE_FILE_LIMIT as e, type FindResult as f, type IntentCacheStats as g, type Ranking as h, type ReadResult as i, type SkillFilters as j, cosine as k, createSkillTools as l, find as m, lexicalScore as n, normaliseIntent as o };
package/dist/stdio.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  main,
4
4
  resolveRoots,
5
5
  serve
6
- } from "./chunk-QJS3NYLT.js";
7
- import "./chunk-SPWHSHOC.js";
6
+ } from "./chunk-JL5BCIQM.js";
7
+ import "./chunk-BFET6MFN.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.3.0",
3
+ "version": "0.4.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.",
@@ -37,6 +37,10 @@
37
37
  "types": "./dist/stdio.d.ts",
38
38
  "import": "./dist/stdio.js"
39
39
  },
40
+ "./http": {
41
+ "types": "./dist/http.d.ts",
42
+ "import": "./dist/http.js"
43
+ },
40
44
  "./cli": {
41
45
  "types": "./dist/cli.d.ts",
42
46
  "import": "./dist/cli.js"