@frontmcp/adapters 1.2.1 → 1.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.
@@ -702,8 +702,7 @@ var NpmSource = class {
702
702
  };
703
703
 
704
704
  // libs/adapters/src/skills/sources/saas-pull.source.ts
705
- import { promises as fs } from "node:fs";
706
- import * as path from "node:path";
705
+ import { dirname, ensureDir, pathResolve, readFile, writeFile } from "@frontmcp/utils";
707
706
  var DEFAULT_CACHE_DIR = ".frontmcp/skilled-openapi";
708
707
  var DEFAULT_PULL_TIMEOUT_MS = 3e4;
709
708
  var SaasPullSource = class {
@@ -805,13 +804,13 @@ var SaasPullSource = class {
805
804
  }
806
805
  cachePath() {
807
806
  const dir = this.cacheDir ?? DEFAULT_CACHE_DIR;
808
- return path.resolve(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);
807
+ return pathResolve(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);
809
808
  }
810
809
  async persistCache(bundle) {
811
810
  try {
812
811
  const filePath = this.cachePath();
813
- await fs.mkdir(path.dirname(filePath), { recursive: true });
814
- await fs.writeFile(filePath, JSON.stringify(bundle), "utf8");
812
+ await ensureDir(dirname(filePath));
813
+ await writeFile(filePath, JSON.stringify(bundle));
815
814
  } catch (e) {
816
815
  this.logger.warn(`[saas-source] cache persist failed: ${e.message}`);
817
816
  }
@@ -819,7 +818,7 @@ var SaasPullSource = class {
819
818
  async loadCache() {
820
819
  try {
821
820
  const filePath = this.cachePath();
822
- const raw = await fs.readFile(filePath, "utf8");
821
+ const raw = await readFile(filePath, "utf8");
823
822
  return parseOverlay({ kind: "json", content: raw });
824
823
  } catch {
825
824
  return void 0;
@@ -837,9 +836,7 @@ var SaasPullSource = class {
837
836
  };
838
837
 
839
838
  // libs/adapters/src/skills/sources/static.source.ts
840
- import { watch as fsWatch } from "node:fs";
841
- import * as path2 from "node:path";
842
- import { readFile } from "@frontmcp/utils";
839
+ import { extname, pathResolve as pathResolve2, readFile as readFile2, watchFile } from "@frontmcp/utils";
843
840
  var StaticSource = class {
844
841
  constructor(options, logger) {
845
842
  this.options = options;
@@ -877,9 +874,9 @@ var StaticSource = class {
877
874
  this.listeners.clear();
878
875
  }
879
876
  async fetchOnce() {
880
- const absPath = path2.resolve(this.options.path);
881
- const raw = await readFile(absPath, "utf8");
882
- const ext = path2.extname(absPath).toLowerCase();
877
+ const absPath = pathResolve2(this.options.path);
878
+ const raw = await readFile2(absPath, "utf8");
879
+ const ext = extname(absPath).toLowerCase();
883
880
  let input;
884
881
  if (ext === ".json") {
885
882
  input = { kind: "json", content: raw };
@@ -901,9 +898,9 @@ var StaticSource = class {
901
898
  }
902
899
  }
903
900
  beginWatch() {
904
- const absPath = path2.resolve(this.options.path);
901
+ const absPath = pathResolve2(this.options.path);
905
902
  try {
906
- this.watcher = fsWatch(absPath, { persistent: false }, () => this.scheduleRefresh());
903
+ this.watcher = watchFile(absPath, () => this.scheduleRefresh());
907
904
  } catch (e) {
908
905
  this.logger.warn(`[static-source] fs.watch failed (${e.message}); polling at 2s instead`);
909
906
  const tick = () => {
@@ -942,9 +939,17 @@ function createBundleSource(source, cacheDir, logger) {
942
939
  }
943
940
 
944
941
  // libs/adapters/src/skills/sources/filesystem-skills.source.ts
945
- import { watch as fsWatch2 } from "node:fs";
946
- import { sep as pathSep } from "node:path";
947
- import { basename, joinPath, pathResolve, readdir, readFile as readFile2, realpath, stat } from "@frontmcp/utils";
942
+ import {
943
+ basename,
944
+ joinPath,
945
+ pathResolve as pathResolve3,
946
+ readdir,
947
+ readFile as readFile3,
948
+ realpath,
949
+ sep,
950
+ stat,
951
+ watchFile as watchFile2
952
+ } from "@frontmcp/utils";
948
953
  var DEFAULT_OPTIONS = {
949
954
  skillsDir: "./skills",
950
955
  watch: true,
@@ -974,7 +979,7 @@ var FilesystemSkillsSource = class {
974
979
  };
975
980
  }
976
981
  async start() {
977
- const absRoot = pathResolve(this.options.skillsDir);
982
+ const absRoot = pathResolve3(this.options.skillsDir);
978
983
  try {
979
984
  this.rootResolved = await realpath(absRoot);
980
985
  } catch {
@@ -1027,7 +1032,7 @@ var FilesystemSkillsSource = class {
1027
1032
  } catch {
1028
1033
  continue;
1029
1034
  }
1030
- if (!resolved.startsWith(this.rootResolved + pathSep) && resolved !== this.rootResolved) {
1035
+ if (!resolved.startsWith(this.rootResolved + sep) && resolved !== this.rootResolved) {
1031
1036
  this.logger.warn(`[fs-skills] skipping ${name}: realpath escapes skillsDir`);
1032
1037
  continue;
1033
1038
  }
@@ -1082,18 +1087,22 @@ var FilesystemSkillsSource = class {
1082
1087
  }
1083
1088
  beginWatch() {
1084
1089
  try {
1085
- this.watcher = fsWatch2(this.rootResolved, { recursive: true, persistent: false }, (_eventType, filename) => {
1086
- if (this.stopped) return;
1087
- if (!filename) {
1088
- this.scheduleRescan();
1089
- return;
1090
- }
1091
- const first = filename.split(pathSep)[0];
1092
- if (!first || first.startsWith(".")) return;
1093
- const absDir = joinPath(this.rootResolved, first);
1094
- this.scheduleRefresh(absDir);
1095
- });
1096
- this.watcher.on("error", (e) => {
1090
+ this.watcher = watchFile2(
1091
+ this.rootResolved,
1092
+ (_eventType, filename) => {
1093
+ if (this.stopped) return;
1094
+ if (!filename) {
1095
+ this.scheduleRescan();
1096
+ return;
1097
+ }
1098
+ const first = filename.split(sep)[0];
1099
+ if (!first || first.startsWith(".")) return;
1100
+ const absDir = joinPath(this.rootResolved, first);
1101
+ this.scheduleRefresh(absDir);
1102
+ },
1103
+ { recursive: true }
1104
+ );
1105
+ this.watcher.onError((e) => {
1097
1106
  this.logger.warn(`[fs-skills] fs.watch error: ${e.message}; falling back to polling`);
1098
1107
  this.watcher?.close();
1099
1108
  this.watcher = void 0;
@@ -1162,7 +1171,7 @@ var FilesystemSkillsSource = class {
1162
1171
  };
1163
1172
  async function loadFilesystemSkill(absDir) {
1164
1173
  const skillMdPath = joinPath(absDir, "SKILL.md");
1165
- const raw = await readFile2(skillMdPath, "utf8");
1174
+ const raw = await readFile3(skillMdPath, "utf8");
1166
1175
  const { frontmatter, body } = parseSkillFrontmatter(raw);
1167
1176
  const dirName = basename(absDir);
1168
1177
  const name = frontmatter["name"] ?? dirName;
@@ -2066,10 +2075,699 @@ var SkillAuditWriterToken = /* @__PURE__ */ Symbol.for(
2066
2075
 
2067
2076
  // libs/adapters/src/skills/index.ts
2068
2077
  import { hasSkillAuditFactory, setSkillAuditFactory } from "@frontmcp/sdk";
2078
+
2079
+ // libs/adapters/src/skills/harvester/op-reference.ts
2080
+ var SPEC_ID_RE = "[A-Za-z0-9][A-Za-z0-9_.-]*";
2081
+ var OPERATION_ID_RE = "[A-Za-z_][A-Za-z0-9_]*";
2082
+ var TRAILING_BOUNDARY = String.raw`(?![A-Za-z0-9_\-])(?!\.[A-Za-z0-9_])`;
2083
+ var REFERENCE_RE = new RegExp(
2084
+ String.raw`(?<![A-Za-z0-9])op:\/\/(${SPEC_ID_RE})\/(${OPERATION_ID_RE})${TRAILING_BOUNDARY}` + `|` + String.raw`\[\[op:\s?(${SPEC_ID_RE})\/(${OPERATION_ID_RE})\]\]`,
2085
+ "g"
2086
+ );
2087
+ function extractOpReferences(markdown) {
2088
+ if (typeof markdown !== "string" || markdown.length === 0) return [];
2089
+ const refs = [];
2090
+ const lineStarts = computeLineStarts(markdown);
2091
+ REFERENCE_RE.lastIndex = 0;
2092
+ let match;
2093
+ while ((match = REFERENCE_RE.exec(markdown)) !== null) {
2094
+ const offset = match.index;
2095
+ const [raw, uriSpec, uriOp, wikiSpec, wikiOp] = match;
2096
+ const isWikilink = wikiSpec !== void 0 && wikiOp !== void 0;
2097
+ const spec = isWikilink ? wikiSpec : uriSpec;
2098
+ const operationId = isWikilink ? wikiOp : uriOp;
2099
+ refs.push({
2100
+ spec,
2101
+ operationId,
2102
+ syntax: isWikilink ? "wikilink" : "uri",
2103
+ raw,
2104
+ location: offsetToLocation(offset, lineStarts)
2105
+ });
2106
+ }
2107
+ return refs;
2108
+ }
2109
+ function buildKnownOps(entries) {
2110
+ const map = /* @__PURE__ */ new Map();
2111
+ for (const entry of entries) {
2112
+ const slash = entry.indexOf("/");
2113
+ if (slash <= 0 || slash === entry.length - 1) continue;
2114
+ const spec = entry.slice(0, slash);
2115
+ const op = entry.slice(slash + 1);
2116
+ let set = map.get(spec);
2117
+ if (!set) {
2118
+ set = /* @__PURE__ */ new Set();
2119
+ map.set(spec, set);
2120
+ }
2121
+ set.add(op);
2122
+ }
2123
+ return map;
2124
+ }
2125
+ function validateOpReferences(refs, knownOps) {
2126
+ const diagnostics = [];
2127
+ for (const ref of refs) {
2128
+ const specOps = knownOps.get(ref.spec);
2129
+ if (!specOps) {
2130
+ diagnostics.push({
2131
+ ref,
2132
+ kind: "unknown-spec",
2133
+ message: `Unknown OpenAPI spec "${ref.spec}" referenced as ${ref.raw}`,
2134
+ suggestions: Array.from(knownOps.keys()).sort()
2135
+ });
2136
+ continue;
2137
+ }
2138
+ if (!specOps.has(ref.operationId)) {
2139
+ diagnostics.push({
2140
+ ref,
2141
+ kind: "unknown-operation",
2142
+ message: `Unknown operationId "${ref.operationId}" in spec "${ref.spec}" (${ref.raw})`,
2143
+ suggestions: suggestSimilarOps(ref.operationId, specOps)
2144
+ });
2145
+ }
2146
+ }
2147
+ return diagnostics;
2148
+ }
2149
+ function dedupeOpReferences(refs) {
2150
+ const seen = /* @__PURE__ */ new Set();
2151
+ const result = [];
2152
+ for (const ref of refs) {
2153
+ const key = `${ref.spec}/${ref.operationId}`;
2154
+ if (seen.has(key)) continue;
2155
+ seen.add(key);
2156
+ result.push({ spec: ref.spec, operationId: ref.operationId });
2157
+ }
2158
+ return result;
2159
+ }
2160
+ function computeLineStarts(source) {
2161
+ const starts = [0];
2162
+ for (let i = 0; i < source.length; i++) {
2163
+ if (source.charCodeAt(i) === 10) starts.push(i + 1);
2164
+ }
2165
+ return starts;
2166
+ }
2167
+ function offsetToLocation(offset, lineStarts) {
2168
+ let lo = 0;
2169
+ let hi = lineStarts.length - 1;
2170
+ while (lo < hi) {
2171
+ const mid = lo + hi + 1 >>> 1;
2172
+ if (lineStarts[mid] <= offset) lo = mid;
2173
+ else hi = mid - 1;
2174
+ }
2175
+ const line = lo + 1;
2176
+ const column = offset - lineStarts[lo] + 1;
2177
+ return { line, column, offset };
2178
+ }
2179
+ function suggestSimilarOps(query, known) {
2180
+ if (known.size === 0) return [];
2181
+ const q = query.toLowerCase();
2182
+ const candidates = Array.from(known);
2183
+ const prefix = candidates.filter((c) => c.toLowerCase().startsWith(q));
2184
+ const substring = candidates.filter((c) => !prefix.includes(c) && c.toLowerCase().includes(q));
2185
+ const close = candidates.filter((c) => !prefix.includes(c) && !substring.includes(c)).filter((c) => levenshtein(q, c.toLowerCase()) <= Math.max(2, Math.floor(q.length / 4)));
2186
+ return [...prefix, ...substring, ...close].slice(0, 3);
2187
+ }
2188
+ function levenshtein(a, b) {
2189
+ if (a === b) return 0;
2190
+ if (a.length === 0) return b.length;
2191
+ if (b.length === 0) return a.length;
2192
+ let prev = new Array(b.length + 1);
2193
+ let curr = new Array(b.length + 1);
2194
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
2195
+ for (let i = 1; i <= a.length; i++) {
2196
+ curr[0] = i;
2197
+ for (let j = 1; j <= b.length; j++) {
2198
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
2199
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
2200
+ }
2201
+ [prev, curr] = [curr, prev];
2202
+ }
2203
+ return prev[b.length];
2204
+ }
2205
+
2206
+ // libs/adapters/src/skills/deploy/deploy-manifest.schema.ts
2207
+ import { z as z3 } from "@frontmcp/lazy-zod";
2208
+ var IDENT_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
2209
+ var SPEC_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
2210
+ var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
2211
+ var BINDING_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
2212
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2213
+ var serverInfoSchema = z3.object({
2214
+ name: z3.string().min(1).max(128),
2215
+ version: z3.string().min(1).max(64),
2216
+ title: z3.string().min(1).max(256).optional()
2217
+ }).strict();
2218
+ var serverSchema = z3.object({
2219
+ info: serverInfoSchema,
2220
+ /**
2221
+ * Top-level server instructions injected at `initialize` time. Picked up
2222
+ * by the existing `skillsConfig.injectInstructions` machinery (defaults
2223
+ * to `append` so the skill catalog summary follows).
2224
+ */
2225
+ instructions: z3.string().max(16 * 1024).optional()
2226
+ }).strict();
2227
+ var runtimeSchema = z3.object({
2228
+ /** Only `cloudflare-worker` in v1. Future: `vercel-edge`, `deno-deploy`. */
2229
+ target: z3.literal("cloudflare-worker"),
2230
+ compatibilityDate: z3.string().regex(ISO_DATE_RE, "compatibilityDate must be ISO-8601 (YYYY-MM-DD)"),
2231
+ /** Worker compatibility flags (e.g. `nodejs_compat`). */
2232
+ compatibilityFlags: z3.array(z3.string().min(1).max(128)).max(32).optional()
2233
+ }).strict();
2234
+ var specSourceSchema = z3.union([
2235
+ /** Local file path or HTTPS URL to a single spec. Spec id = filename stem. */
2236
+ z3.string().min(1).max(2048),
2237
+ z3.object({
2238
+ id: z3.string().min(1).max(128).regex(SPEC_ID_RE2, "spec id must be a URI-safe identifier"),
2239
+ spec: z3.string().min(1).max(2048),
2240
+ baseUrl: z3.string().url("baseUrl must be a valid URL").optional(),
2241
+ /** Optional namespace override for AgentScript bindings (default = `id`). */
2242
+ bindingName: z3.string().min(1).max(64).regex(IDENT_RE).optional()
2243
+ }).strict()
2244
+ ]);
2245
+ var specsSchema = z3.union([
2246
+ /** Single directory path; the loader auto-discovers `*.yaml` / `*.json`. */
2247
+ z3.string().min(1).max(2048),
2248
+ /** Explicit list (mixed strings and detail objects allowed). */
2249
+ z3.array(specSourceSchema).max(64)
2250
+ ]);
2251
+ var skillsSchema = z3.object({
2252
+ /** Directory under the manifest. Defaults to `./skills/`. */
2253
+ source: z3.string().min(1).max(2048).default("./skills/"),
2254
+ /**
2255
+ * IDs of skills the worker pre-loads into every `execute()` regardless
2256
+ * of which skills the agent passed. Mirrors the per-skill
2257
+ * `alwaysLoad: true` frontmatter, but lets the server force it.
2258
+ */
2259
+ alwaysLoad: z3.array(z3.string().min(1).max(128)).max(32).optional(),
2260
+ /** Optional tag filter applied at deploy time. */
2261
+ tags: z3.object({
2262
+ include: z3.array(z3.string().min(1).max(64)).max(64).optional(),
2263
+ exclude: z3.array(z3.string().min(1).max(64)).max(64).optional()
2264
+ }).strict().optional()
2265
+ }).strict();
2266
+ var tagSchema = z3.object({
2267
+ name: z3.string().min(1).max(64),
2268
+ description: z3.string().min(1).max(512).optional()
2269
+ }).strict();
2270
+ var classificationRuleSchema = z3.object({
2271
+ /**
2272
+ * Match pattern: METHOD followed by a path-glob, e.g.
2273
+ * `POST <star>/reset-<star>` (where <star> is the literal `*` glob).
2274
+ * Written this way to avoid prematurely closing the JSDoc with star-slash.
2275
+ */
2276
+ match: z3.string().min(1).max(256),
2277
+ /**
2278
+ * Override the MCP surface for matching operations: tool (default for
2279
+ * mutations), resource (default for GET list / GET path-param), or both
2280
+ * (default for GET path-param). Omit to leave the classifier's default.
2281
+ */
2282
+ expose: z3.enum(["tool", "resource", "both"]).optional(),
2283
+ /**
2284
+ * Resource-update emit target. `self` (mutation on its own resource),
2285
+ * `parent` (mutation that affects the parent collection / parent path),
2286
+ * or `none` (suppress). Defaults from the HTTP-semantics classifier.
2287
+ */
2288
+ emits: z3.enum(["self", "parent", "none"]).optional()
2289
+ }).strict();
2290
+ var classificationSchema = z3.object({
2291
+ rules: z3.array(classificationRuleSchema).max(64).optional()
2292
+ }).strict();
2293
+ var durableObjectBindingSchema = z3.object({
2294
+ binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
2295
+ className: z3.string().min(1).max(128).regex(IDENT_RE),
2296
+ scriptName: z3.string().min(1).max(128).optional()
2297
+ }).strict();
2298
+ var d1BindingSchema = z3.object({
2299
+ binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
2300
+ databaseName: z3.string().min(1).max(256),
2301
+ databaseId: z3.string().min(1).max(128)
2302
+ }).strict();
2303
+ var kvBindingSchema = z3.object({
2304
+ binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
2305
+ id: z3.string().min(1).max(128)
2306
+ }).strict();
2307
+ var r2BindingSchema = z3.object({
2308
+ binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
2309
+ bucketName: z3.string().min(1).max(256)
2310
+ }).strict();
2311
+ var bindingsSchema = z3.object({
2312
+ durableObjects: z3.array(durableObjectBindingSchema).max(32).optional(),
2313
+ d1Databases: z3.array(d1BindingSchema).max(16).optional(),
2314
+ kvNamespaces: z3.array(kvBindingSchema).max(32).optional(),
2315
+ r2Buckets: z3.array(r2BindingSchema).max(16).optional(),
2316
+ /** Plaintext vars surfaced as `env.<name>` on the Worker. */
2317
+ vars: z3.record(z3.string().min(1).max(128), z3.string().max(4096)).optional()
2318
+ }).strict();
2319
+ var trustRootSchema = z3.object({
2320
+ /** Key id — matches the JWS `kid` header on the signed bundle. */
2321
+ kid: z3.string().min(1).max(128),
2322
+ /** Name of the secret binding holding the PEM-encoded public key. */
2323
+ publicKeySecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
2324
+ }).strict();
2325
+ var replayGuardSchema = z3.object({
2326
+ /** Acceptable clock skew; rejects bundles older than this. */
2327
+ windowSeconds: z3.number().int().min(10).max(3600).default(300),
2328
+ /** KV binding name that stores nonces for replay protection. */
2329
+ nonceKv: z3.string().min(1).max(128).regex(BINDING_NAME_RE)
2330
+ }).strict();
2331
+ var signingSchema = z3.object({
2332
+ algorithm: z3.enum(["ed25519", "rs256"]),
2333
+ trustRoots: z3.array(trustRootSchema).min(1).max(8),
2334
+ replay: replayGuardSchema
2335
+ }).strict();
2336
+ var fronteggAuthSchema = z3.object({
2337
+ tenantResolver: z3.enum(["subdomain", "header", "jwt-claim"]).default("subdomain"),
2338
+ audience: z3.string().min(1).max(256),
2339
+ /** Secret name holding the issuer URL. */
2340
+ issuerSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
2341
+ }).strict();
2342
+ var oauthAuthSchema = z3.object({
2343
+ issuer: z3.string().url(),
2344
+ audience: z3.string().min(1).max(256),
2345
+ /** Optional secret name for client credentials (machine-to-machine). */
2346
+ credentialsSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE).optional()
2347
+ }).strict();
2348
+ var apiKeyAuthSchema = z3.object({
2349
+ /** Header name carrying the key (e.g. `X-API-Key`). */
2350
+ header: z3.string().min(1).max(64).default("X-API-Key"),
2351
+ /** Secret name holding the allowlist (newline-separated keys). */
2352
+ allowlistSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
2353
+ }).strict();
2354
+ var authSchema = z3.discriminatedUnion("provider", [
2355
+ z3.object({ provider: z3.literal("none") }).strict(),
2356
+ z3.object({ provider: z3.literal("frontegg"), frontegg: fronteggAuthSchema }).strict(),
2357
+ z3.object({ provider: z3.literal("oauth"), oauth: oauthAuthSchema }).strict(),
2358
+ z3.object({ provider: z3.literal("apiKey"), apiKey: apiKeyAuthSchema }).strict()
2359
+ ]);
2360
+ var secretSchema = z3.object({
2361
+ name: z3.string().min(1).max(128).regex(SECRET_NAME_RE),
2362
+ required: z3.boolean().default(true),
2363
+ description: z3.string().min(1).max(512).optional()
2364
+ }).strict();
2365
+ var environmentOverlaySchema = z3.object({
2366
+ server: serverSchema.partial().optional(),
2367
+ specs: specsSchema.optional(),
2368
+ skills: skillsSchema.partial().optional(),
2369
+ classification: classificationSchema.optional(),
2370
+ bindings: bindingsSchema.optional(),
2371
+ auth: authSchema.optional(),
2372
+ vars: z3.record(z3.string().min(1).max(128), z3.string().max(4096)).optional()
2373
+ }).strict();
2374
+ var deployManifestSchema = z3.object({
2375
+ /** Optional JSON Schema URL — accepted for editor autocompletion. */
2376
+ $schema: z3.string().url().optional(),
2377
+ /** Schema version. Only `1` accepted in v1.3. */
2378
+ version: z3.literal(1),
2379
+ /** Project name (for logs + the Worker's `worker.name`). */
2380
+ name: z3.string().min(1).max(128).regex(IDENT_RE),
2381
+ runtime: runtimeSchema,
2382
+ server: serverSchema,
2383
+ specs: specsSchema,
2384
+ skills: skillsSchema.default({ source: "./skills/" }),
2385
+ tags: z3.array(tagSchema).max(64).optional(),
2386
+ classification: classificationSchema.optional(),
2387
+ bindings: bindingsSchema,
2388
+ signing: signingSchema,
2389
+ auth: authSchema,
2390
+ secrets: z3.array(secretSchema).max(64).optional(),
2391
+ environments: z3.record(z3.string().min(1).max(64).regex(IDENT_RE), environmentOverlaySchema).optional()
2392
+ }).strict();
2393
+ function applyEnvironmentOverlay(base, overlay) {
2394
+ const effectiveBindings = overlay.bindings ?? base.bindings;
2395
+ const mergedBindings = overlay.vars !== void 0 ? { ...effectiveBindings, vars: { ...effectiveBindings.vars ?? {}, ...overlay.vars } } : effectiveBindings;
2396
+ return {
2397
+ ...base,
2398
+ server: overlay.server ? { ...base.server, ...overlay.server } : base.server,
2399
+ specs: overlay.specs ?? base.specs,
2400
+ skills: overlay.skills ? { ...base.skills, ...overlay.skills } : base.skills,
2401
+ classification: overlay.classification ?? base.classification,
2402
+ bindings: mergedBindings,
2403
+ auth: overlay.auth ?? base.auth
2404
+ };
2405
+ }
2406
+ function validateEffectiveManifest(manifest, label, errors) {
2407
+ const declaredSecrets = new Set((manifest.secrets ?? []).map((s) => s.name));
2408
+ const refs = [];
2409
+ if (manifest.auth.provider === "frontegg") {
2410
+ refs.push({ where: "auth.frontegg.issuerSecret", name: manifest.auth.frontegg.issuerSecret });
2411
+ } else if (manifest.auth.provider === "oauth" && manifest.auth.oauth.credentialsSecret) {
2412
+ refs.push({ where: "auth.oauth.credentialsSecret", name: manifest.auth.oauth.credentialsSecret });
2413
+ } else if (manifest.auth.provider === "apiKey") {
2414
+ refs.push({ where: "auth.apiKey.allowlistSecret", name: manifest.auth.apiKey.allowlistSecret });
2415
+ }
2416
+ for (const root of manifest.signing.trustRoots) {
2417
+ refs.push({ where: `signing.trustRoots[kid=${root.kid}].publicKeySecret`, name: root.publicKeySecret });
2418
+ }
2419
+ for (const ref of refs) {
2420
+ if (!declaredSecrets.has(ref.name)) {
2421
+ errors.push(`${label}Secret "${ref.name}" referenced at ${ref.where} is not declared in secrets[]`);
2422
+ }
2423
+ }
2424
+ const declaredKv = new Set((manifest.bindings.kvNamespaces ?? []).map((kv) => kv.binding));
2425
+ if (!declaredKv.has(manifest.signing.replay.nonceKv)) {
2426
+ errors.push(
2427
+ `${label}signing.replay.nonceKv "${manifest.signing.replay.nonceKv}" does not match any bindings.kvNamespaces[].binding`
2428
+ );
2429
+ }
2430
+ for (const id of manifest.skills.alwaysLoad ?? []) {
2431
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
2432
+ errors.push(`${label}skills.alwaysLoad entry "${id}" is not a valid kebab-case skill id`);
2433
+ }
2434
+ }
2435
+ if (manifest.tags) {
2436
+ const declaredTags = new Set(manifest.tags.map((t) => t.name));
2437
+ const tagFilter = manifest.skills.tags;
2438
+ for (const t of [...tagFilter?.include ?? [], ...tagFilter?.exclude ?? []]) {
2439
+ if (!declaredTags.has(t)) {
2440
+ errors.push(`${label}skills.tags references unknown tag "${t}" (not in tags[])`);
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+ function crossValidateManifest(manifest) {
2446
+ const errors = [];
2447
+ validateEffectiveManifest(manifest, "", errors);
2448
+ for (const [envName, overlay] of Object.entries(manifest.environments ?? {})) {
2449
+ const effective = applyEnvironmentOverlay(manifest, overlay);
2450
+ validateEffectiveManifest(effective, `environments.${envName}: `, errors);
2451
+ }
2452
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
2453
+ }
2454
+
2455
+ // libs/adapters/src/skills/classifier/openapi-classify.ts
2456
+ function classifyOperations(specId, ops) {
2457
+ if (!specId || specId.length === 0) {
2458
+ throw new Error("classifyOperations: specId is required");
2459
+ }
2460
+ if (!Array.isArray(ops)) return [];
2461
+ const pathsWithGet = /* @__PURE__ */ new Set();
2462
+ for (const op of ops) {
2463
+ if (op && typeof op.path === "string" && typeof op.method === "string" && op.method.toUpperCase() === "GET") {
2464
+ pathsWithGet.add(op.path);
2465
+ }
2466
+ }
2467
+ return ops.map((op) => classifyOne(specId, op, pathsWithGet));
2468
+ }
2469
+ function classifyOne(specId, op, pathsWithGet) {
2470
+ const method = normaliseMethod(op.method);
2471
+ const path = op.path;
2472
+ const hasTerminalParam = pathHasTerminalParam(path);
2473
+ const parent = parentPath(path);
2474
+ const thisIsResource = pathsWithGet.has(path);
2475
+ const parentIsResource = parent !== null && pathsWithGet.has(parent);
2476
+ const selfUri = pathToResourceUri(specId, path);
2477
+ const parentUri = parent !== null ? pathToResourceUri(specId, parent) : null;
2478
+ if (method === "GET") {
2479
+ if (hasTerminalParam) {
2480
+ return {
2481
+ operationId: op.operationId,
2482
+ method,
2483
+ path,
2484
+ specId,
2485
+ expose: "both",
2486
+ resourceUriTemplate: selfUri
2487
+ };
2488
+ }
2489
+ return {
2490
+ operationId: op.operationId,
2491
+ method,
2492
+ path,
2493
+ specId,
2494
+ expose: "resource",
2495
+ resourceUriTemplate: selfUri
2496
+ };
2497
+ }
2498
+ const base = {
2499
+ operationId: op.operationId,
2500
+ method,
2501
+ path,
2502
+ specId,
2503
+ expose: "tool"
2504
+ // `resourceUriTemplate` left undefined for mutations — the emit target
2505
+ // is the authoritative thing to look at.
2506
+ };
2507
+ if (method === "POST") {
2508
+ if (thisIsResource && !hasTerminalParam) {
2509
+ return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
2510
+ }
2511
+ if (thisIsResource && hasTerminalParam) {
2512
+ return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
2513
+ }
2514
+ if (parentIsResource && parentUri !== null && parent !== null) {
2515
+ return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
2516
+ }
2517
+ return base;
2518
+ }
2519
+ if (method === "PUT" || method === "PATCH") {
2520
+ if (thisIsResource) {
2521
+ return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
2522
+ }
2523
+ if (parentIsResource && parentUri !== null && parent !== null) {
2524
+ return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
2525
+ }
2526
+ return base;
2527
+ }
2528
+ if (method === "DELETE") {
2529
+ if (hasTerminalParam && parentIsResource && parentUri !== null && parent !== null) {
2530
+ return { ...base, emit: { kind: "listChanged", pathTemplate: parent, resourceUriTemplate: parentUri } };
2531
+ }
2532
+ if (!hasTerminalParam && thisIsResource) {
2533
+ return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
2534
+ }
2535
+ return base;
2536
+ }
2537
+ return base;
2538
+ }
2539
+ function applyClassificationOverrides(classified, rules) {
2540
+ if (!Array.isArray(rules) || rules.length === 0) return classified.slice();
2541
+ const compiled = rules.map((rule) => ({ rule, matcher: compileMatcher(rule.match) }));
2542
+ return classified.map((op) => {
2543
+ for (const { rule, matcher } of compiled) {
2544
+ if (!matcher(op)) continue;
2545
+ return applyOne(op, rule);
2546
+ }
2547
+ return op;
2548
+ });
2549
+ }
2550
+ var KNOWN_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"]);
2551
+ function normaliseMethod(m) {
2552
+ const up = (m ?? "").toUpperCase();
2553
+ return KNOWN_METHODS.has(up) ? up : up;
2554
+ }
2555
+ function pathHasTerminalParam(path) {
2556
+ if (typeof path !== "string" || path.length === 0) return false;
2557
+ const segments = path.split("/").filter((s) => s.length > 0);
2558
+ if (segments.length === 0) return false;
2559
+ const last = segments[segments.length - 1];
2560
+ return last.startsWith("{") && last.endsWith("}") && last.length >= 3;
2561
+ }
2562
+ function parentPath(path) {
2563
+ if (typeof path !== "string" || path.length === 0) return null;
2564
+ const segments = path.split("/").filter((s) => s.length > 0);
2565
+ if (segments.length <= 1) return null;
2566
+ return "/" + segments.slice(0, -1).join("/");
2567
+ }
2568
+ function pathToResourceUri(specId, path) {
2569
+ const cleaned = path.startsWith("/") ? path.slice(1) : path;
2570
+ return `mcp+op://${specId}/${cleaned}`;
2571
+ }
2572
+ function compileMatcher(match) {
2573
+ const trimmed = (match ?? "").trim();
2574
+ const sep2 = trimmed.indexOf(" ");
2575
+ if (sep2 <= 0 || sep2 === trimmed.length - 1) {
2576
+ return () => false;
2577
+ }
2578
+ const methodPattern = trimmed.slice(0, sep2).toUpperCase();
2579
+ const pathPattern = trimmed.slice(sep2 + 1);
2580
+ const pathRe = globToRegExp(pathPattern);
2581
+ return (op) => {
2582
+ if (methodPattern !== "*" && methodPattern !== op.method) return false;
2583
+ return pathRe.test(op.path);
2584
+ };
2585
+ }
2586
+ function globToRegExp(glob) {
2587
+ const STAR_STAR = "\0";
2588
+ const STAR = "";
2589
+ const replaced = glob.replace(/\*\*/g, STAR_STAR).replace(/\*/g, STAR);
2590
+ const escaped = replaced.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2591
+ const expanded = escaped.split(STAR_STAR).join(".*").split(STAR).join("[^/]*");
2592
+ return new RegExp(`^${expanded}$`);
2593
+ }
2594
+ function defaultEmitKindForMethod(method) {
2595
+ return method === "DELETE" ? "listChanged" : "updated";
2596
+ }
2597
+ function applyOne(op, rule) {
2598
+ let next = { ...op };
2599
+ if (rule.expose !== void 0) {
2600
+ next = { ...next, expose: rule.expose };
2601
+ }
2602
+ if (rule.emits !== void 0) {
2603
+ if (rule.emits === "none") {
2604
+ const { emit: _drop, ...rest } = next;
2605
+ next = rest;
2606
+ } else if (rule.emits === "self") {
2607
+ const target = pathToResourceUri(op.specId, op.path);
2608
+ next = {
2609
+ ...next,
2610
+ emit: {
2611
+ // Preserve the classifier's original kind if it set one; otherwise
2612
+ // derive from the HTTP method so a DELETE override doesn't end up
2613
+ // emitting `updated` on its own (now-gone) URI.
2614
+ kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
2615
+ pathTemplate: op.path,
2616
+ resourceUriTemplate: target
2617
+ }
2618
+ };
2619
+ } else if (rule.emits === "parent") {
2620
+ const parent = parentPath(op.path);
2621
+ if (parent !== null) {
2622
+ next = {
2623
+ ...next,
2624
+ emit: {
2625
+ kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
2626
+ pathTemplate: parent,
2627
+ resourceUriTemplate: pathToResourceUri(op.specId, parent)
2628
+ }
2629
+ };
2630
+ } else {
2631
+ const { emit: _drop, ...rest } = next;
2632
+ next = rest;
2633
+ }
2634
+ }
2635
+ }
2636
+ return next;
2637
+ }
2638
+
2639
+ // libs/adapters/src/skills/classifier/render-resource-uri.ts
2640
+ var TEMPLATE_PLACEHOLDER_RE = /\{([^{}]+)\}/g;
2641
+ function renderResourceUri(template, args) {
2642
+ if (typeof template !== "string" || template.length === 0) {
2643
+ return { ok: false, missing: [] };
2644
+ }
2645
+ const argObj = args !== null && typeof args === "object" ? args : {};
2646
+ const missing = [];
2647
+ const uri = template.replace(TEMPLATE_PLACEHOLDER_RE, (_match, name) => {
2648
+ const value = argObj[name];
2649
+ if (value === void 0 || value === null) {
2650
+ missing.push(name);
2651
+ return "";
2652
+ }
2653
+ if (typeof value === "object") {
2654
+ missing.push(name);
2655
+ return "";
2656
+ }
2657
+ if (typeof value === "number" && !Number.isFinite(value)) {
2658
+ missing.push(name);
2659
+ return "";
2660
+ }
2661
+ return encodeURIComponent(String(value));
2662
+ });
2663
+ if (missing.length > 0) return { ok: false, missing };
2664
+ return { ok: true, uri };
2665
+ }
2666
+
2667
+ // libs/adapters/src/skills/classifier/resource-change-notification.ts
2668
+ function buildResourceChangeNotification(classification, args) {
2669
+ const emit = classification.emit;
2670
+ if (!emit) return { notification: null, reason: "no-emit" };
2671
+ if (emit.kind === "listChanged") {
2672
+ const rendered2 = renderResourceUri(emit.resourceUriTemplate, args);
2673
+ if (!rendered2.ok) {
2674
+ return { notification: null, reason: "unresolved-template", missing: rendered2.missing };
2675
+ }
2676
+ return { notification: { method: "notifications/resources/list_changed" } };
2677
+ }
2678
+ const rendered = renderResourceUri(emit.resourceUriTemplate, args);
2679
+ if (!rendered.ok) {
2680
+ return { notification: null, reason: "unresolved-template", missing: rendered.missing };
2681
+ }
2682
+ return {
2683
+ notification: {
2684
+ method: "notifications/resources/updated",
2685
+ params: { uri: rendered.uri }
2686
+ }
2687
+ };
2688
+ }
2689
+
2690
+ // libs/adapters/src/skills/classifier/classification-registry.ts
2691
+ var ClassificationRegistry = class {
2692
+ byToolName = /* @__PURE__ */ new Map();
2693
+ /**
2694
+ * Register (or replace) a classification for a tool name. Returns the
2695
+ * previous classification if one existed.
2696
+ *
2697
+ * Useful for hot-reload: the deploy pipeline re-builds classifications
2698
+ * from the new bundle and overwrites in place.
2699
+ */
2700
+ register(toolName, classification) {
2701
+ if (typeof toolName !== "string" || toolName.length === 0) {
2702
+ throw new Error("ClassificationRegistry.register: toolName must be a non-empty string");
2703
+ }
2704
+ const prev = this.byToolName.get(toolName);
2705
+ this.byToolName.set(toolName, classification);
2706
+ return prev;
2707
+ }
2708
+ /**
2709
+ * Bulk-register an array of classifications keyed by `${specId}.${operationId}`.
2710
+ * Returns the count of newly-added entries (existing entries are overwritten
2711
+ * but not counted toward `added`).
2712
+ */
2713
+ registerAll(classifications) {
2714
+ let added = 0;
2715
+ let replaced = 0;
2716
+ for (const c of classifications) {
2717
+ const toolName = `${c.specId}.${c.operationId}`;
2718
+ const prev = this.byToolName.get(toolName);
2719
+ this.byToolName.set(toolName, c);
2720
+ if (prev) replaced++;
2721
+ else added++;
2722
+ }
2723
+ return { added, replaced };
2724
+ }
2725
+ /** Look up the classification for a tool. */
2726
+ lookup(toolName) {
2727
+ return this.byToolName.get(toolName);
2728
+ }
2729
+ /** Remove a single classification. Returns true if it was present. */
2730
+ unregister(toolName) {
2731
+ return this.byToolName.delete(toolName);
2732
+ }
2733
+ /** Drop every registration; used by the loader on full re-deploy. */
2734
+ clear() {
2735
+ this.byToolName.clear();
2736
+ }
2737
+ /** Current size — handy for tests and metrics. */
2738
+ size() {
2739
+ return this.byToolName.size;
2740
+ }
2741
+ /** Snapshot of every (toolName, classification) pair in insertion order. */
2742
+ getAll() {
2743
+ const out = [];
2744
+ for (const [toolName, classification] of this.byToolName.entries()) {
2745
+ out.push({ toolName, classification });
2746
+ }
2747
+ return out;
2748
+ }
2749
+ /**
2750
+ * Convenience: in one step, look up the classification for `toolName`
2751
+ * and build the resource-change notification it should produce after a
2752
+ * successful call with `args`. Returns `{ notification: null }` when the
2753
+ * tool has no classification (e.g. it's not an openapi-derived tool) or
2754
+ * when its classification has no `emit`.
2755
+ *
2756
+ * The dispatcher is expected to call this from a successful-call hook
2757
+ * and forward the resulting notification through whichever notification
2758
+ * channel the host provides.
2759
+ */
2760
+ buildNotificationForCall(toolName, args) {
2761
+ const classification = this.byToolName.get(toolName);
2762
+ if (!classification) return { notification: null, reason: "no-emit" };
2763
+ return buildResourceChangeNotification(classification, args);
2764
+ }
2765
+ };
2069
2766
  export {
2070
2767
  BundlePinnedError,
2071
2768
  BundlePushJwtVerifier,
2072
2769
  BundleStore,
2770
+ ClassificationRegistry,
2073
2771
  FilesystemSkillsSource,
2074
2772
  Hs256AuditSigner,
2075
2773
  MemoryAuditStore,
@@ -2089,15 +2787,25 @@ export {
2089
2787
  StaticSource,
2090
2788
  StorageAdapterAuditStore,
2091
2789
  WebhookReplayGuard,
2790
+ applyClassificationOverrides,
2791
+ applyEnvironmentOverlay,
2792
+ buildKnownOps,
2793
+ buildResourceChangeNotification,
2092
2794
  bundleDigest,
2093
2795
  bundleSkillToActions,
2094
2796
  bundleSourceSchema,
2095
2797
  canonicalize,
2096
2798
  canonicalizeRecordForSigning,
2799
+ classifyOne,
2800
+ classifyOperations,
2097
2801
  createBundleSource,
2098
2802
  crossValidate,
2803
+ crossValidateManifest,
2804
+ dedupeOpReferences,
2099
2805
  defaultAuditSignatureVerifier,
2806
+ deployManifestSchema,
2100
2807
  diffBundles,
2808
+ extractOpReferences,
2101
2809
  formatDiffSummary,
2102
2810
  hasSkillAuditFactory,
2103
2811
  linkRecord,
@@ -2106,12 +2814,14 @@ export {
2106
2814
  npmSourceSchema,
2107
2815
  parseOverlay,
2108
2816
  parseSkillFrontmatter,
2817
+ renderResourceUri,
2109
2818
  resolveSkillLoadOrder,
2110
2819
  resolvedBundleSchema,
2111
2820
  saasSourceSchema,
2112
2821
  setSkillAuditFactory,
2113
2822
  signatureKeySchema,
2114
2823
  staticSourceSchema,
2824
+ validateOpReferences,
2115
2825
  verifyBundleSignature,
2116
2826
  verifyChain
2117
2827
  };