@frontmcp/adapters 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/skills/index.js CHANGED
@@ -33,6 +33,7 @@ __export(skills_exports, {
33
33
  BundlePinnedError: () => BundlePinnedError,
34
34
  BundlePushJwtVerifier: () => BundlePushJwtVerifier,
35
35
  BundleStore: () => BundleStore,
36
+ ClassificationRegistry: () => ClassificationRegistry,
36
37
  FilesystemSkillsSource: () => FilesystemSkillsSource,
37
38
  Hs256AuditSigner: () => Hs256AuditSigner,
38
39
  MemoryAuditStore: () => MemoryAuditStore,
@@ -52,15 +53,25 @@ __export(skills_exports, {
52
53
  StaticSource: () => StaticSource,
53
54
  StorageAdapterAuditStore: () => StorageAdapterAuditStore,
54
55
  WebhookReplayGuard: () => WebhookReplayGuard,
56
+ applyClassificationOverrides: () => applyClassificationOverrides,
57
+ applyEnvironmentOverlay: () => applyEnvironmentOverlay,
58
+ buildKnownOps: () => buildKnownOps,
59
+ buildResourceChangeNotification: () => buildResourceChangeNotification,
55
60
  bundleDigest: () => bundleDigest,
56
61
  bundleSkillToActions: () => bundleSkillToActions,
57
62
  bundleSourceSchema: () => bundleSourceSchema,
58
63
  canonicalize: () => canonicalize,
59
64
  canonicalizeRecordForSigning: () => canonicalizeRecordForSigning,
65
+ classifyOne: () => classifyOne,
66
+ classifyOperations: () => classifyOperations,
60
67
  createBundleSource: () => createBundleSource,
61
68
  crossValidate: () => crossValidate,
69
+ crossValidateManifest: () => crossValidateManifest,
70
+ dedupeOpReferences: () => dedupeOpReferences,
62
71
  defaultAuditSignatureVerifier: () => defaultAuditSignatureVerifier,
72
+ deployManifestSchema: () => deployManifestSchema,
63
73
  diffBundles: () => diffBundles,
74
+ extractOpReferences: () => extractOpReferences,
64
75
  formatDiffSummary: () => formatDiffSummary,
65
76
  hasSkillAuditFactory: () => import_sdk.hasSkillAuditFactory,
66
77
  linkRecord: () => linkRecord,
@@ -69,12 +80,14 @@ __export(skills_exports, {
69
80
  npmSourceSchema: () => npmSourceSchema,
70
81
  parseOverlay: () => parseOverlay,
71
82
  parseSkillFrontmatter: () => parseSkillFrontmatter,
83
+ renderResourceUri: () => renderResourceUri,
72
84
  resolveSkillLoadOrder: () => resolveSkillLoadOrder,
73
85
  resolvedBundleSchema: () => resolvedBundleSchema,
74
86
  saasSourceSchema: () => saasSourceSchema,
75
87
  setSkillAuditFactory: () => import_sdk.setSkillAuditFactory,
76
88
  signatureKeySchema: () => signatureKeySchema,
77
89
  staticSourceSchema: () => staticSourceSchema,
90
+ validateOpReferences: () => validateOpReferences,
78
91
  verifyBundleSignature: () => verifyBundleSignature,
79
92
  verifyChain: () => verifyChain
80
93
  });
@@ -784,8 +797,7 @@ var NpmSource = class {
784
797
  };
785
798
 
786
799
  // libs/adapters/src/skills/sources/saas-pull.source.ts
787
- var import_node_fs = require("node:fs");
788
- var path = __toESM(require("node:path"));
800
+ var import_utils = require("@frontmcp/utils");
789
801
  var DEFAULT_CACHE_DIR = ".frontmcp/skilled-openapi";
790
802
  var DEFAULT_PULL_TIMEOUT_MS = 3e4;
791
803
  var SaasPullSource = class {
@@ -887,13 +899,13 @@ var SaasPullSource = class {
887
899
  }
888
900
  cachePath() {
889
901
  const dir = this.cacheDir ?? DEFAULT_CACHE_DIR;
890
- return path.resolve(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);
902
+ return (0, import_utils.pathResolve)(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);
891
903
  }
892
904
  async persistCache(bundle) {
893
905
  try {
894
906
  const filePath = this.cachePath();
895
- await import_node_fs.promises.mkdir(path.dirname(filePath), { recursive: true });
896
- await import_node_fs.promises.writeFile(filePath, JSON.stringify(bundle), "utf8");
907
+ await (0, import_utils.ensureDir)((0, import_utils.dirname)(filePath));
908
+ await (0, import_utils.writeFile)(filePath, JSON.stringify(bundle));
897
909
  } catch (e) {
898
910
  this.logger.warn(`[saas-source] cache persist failed: ${e.message}`);
899
911
  }
@@ -901,7 +913,7 @@ var SaasPullSource = class {
901
913
  async loadCache() {
902
914
  try {
903
915
  const filePath = this.cachePath();
904
- const raw = await import_node_fs.promises.readFile(filePath, "utf8");
916
+ const raw = await (0, import_utils.readFile)(filePath, "utf8");
905
917
  return parseOverlay({ kind: "json", content: raw });
906
918
  } catch {
907
919
  return void 0;
@@ -919,9 +931,7 @@ var SaasPullSource = class {
919
931
  };
920
932
 
921
933
  // libs/adapters/src/skills/sources/static.source.ts
922
- var import_node_fs2 = require("node:fs");
923
- var path2 = __toESM(require("node:path"));
924
- var import_utils = require("@frontmcp/utils");
934
+ var import_utils2 = require("@frontmcp/utils");
925
935
  var StaticSource = class {
926
936
  constructor(options, logger) {
927
937
  this.options = options;
@@ -959,9 +969,9 @@ var StaticSource = class {
959
969
  this.listeners.clear();
960
970
  }
961
971
  async fetchOnce() {
962
- const absPath = path2.resolve(this.options.path);
963
- const raw = await (0, import_utils.readFile)(absPath, "utf8");
964
- const ext = path2.extname(absPath).toLowerCase();
972
+ const absPath = (0, import_utils2.pathResolve)(this.options.path);
973
+ const raw = await (0, import_utils2.readFile)(absPath, "utf8");
974
+ const ext = (0, import_utils2.extname)(absPath).toLowerCase();
965
975
  let input;
966
976
  if (ext === ".json") {
967
977
  input = { kind: "json", content: raw };
@@ -983,9 +993,9 @@ var StaticSource = class {
983
993
  }
984
994
  }
985
995
  beginWatch() {
986
- const absPath = path2.resolve(this.options.path);
996
+ const absPath = (0, import_utils2.pathResolve)(this.options.path);
987
997
  try {
988
- this.watcher = (0, import_node_fs2.watch)(absPath, { persistent: false }, () => this.scheduleRefresh());
998
+ this.watcher = (0, import_utils2.watchFile)(absPath, () => this.scheduleRefresh());
989
999
  } catch (e) {
990
1000
  this.logger.warn(`[static-source] fs.watch failed (${e.message}); polling at 2s instead`);
991
1001
  const tick = () => {
@@ -1024,9 +1034,7 @@ function createBundleSource(source, cacheDir, logger) {
1024
1034
  }
1025
1035
 
1026
1036
  // libs/adapters/src/skills/sources/filesystem-skills.source.ts
1027
- var import_node_fs3 = require("node:fs");
1028
- var import_node_path = require("node:path");
1029
- var import_utils2 = require("@frontmcp/utils");
1037
+ var import_utils3 = require("@frontmcp/utils");
1030
1038
  var DEFAULT_OPTIONS = {
1031
1039
  skillsDir: "./skills",
1032
1040
  watch: true,
@@ -1056,9 +1064,9 @@ var FilesystemSkillsSource = class {
1056
1064
  };
1057
1065
  }
1058
1066
  async start() {
1059
- const absRoot = (0, import_utils2.pathResolve)(this.options.skillsDir);
1067
+ const absRoot = (0, import_utils3.pathResolve)(this.options.skillsDir);
1060
1068
  try {
1061
- this.rootResolved = await (0, import_utils2.realpath)(absRoot);
1069
+ this.rootResolved = await (0, import_utils3.realpath)(absRoot);
1062
1070
  } catch {
1063
1071
  this.rootResolved = absRoot;
1064
1072
  }
@@ -1094,7 +1102,7 @@ var FilesystemSkillsSource = class {
1094
1102
  async listSkillSubdirs() {
1095
1103
  let entries;
1096
1104
  try {
1097
- entries = await (0, import_utils2.readdir)(this.rootResolved);
1105
+ entries = await (0, import_utils3.readdir)(this.rootResolved);
1098
1106
  } catch (e) {
1099
1107
  this.logger.warn(`[fs-skills] cannot read ${this.rootResolved}: ${e.message}`);
1100
1108
  return [];
@@ -1102,19 +1110,19 @@ var FilesystemSkillsSource = class {
1102
1110
  const out = [];
1103
1111
  for (const name of entries) {
1104
1112
  if (name.startsWith(".")) continue;
1105
- const abs = (0, import_utils2.joinPath)(this.rootResolved, name);
1113
+ const abs = (0, import_utils3.joinPath)(this.rootResolved, name);
1106
1114
  let resolved;
1107
1115
  try {
1108
- resolved = await (0, import_utils2.realpath)(abs);
1116
+ resolved = await (0, import_utils3.realpath)(abs);
1109
1117
  } catch {
1110
1118
  continue;
1111
1119
  }
1112
- if (!resolved.startsWith(this.rootResolved + import_node_path.sep) && resolved !== this.rootResolved) {
1120
+ if (!resolved.startsWith(this.rootResolved + import_utils3.sep) && resolved !== this.rootResolved) {
1113
1121
  this.logger.warn(`[fs-skills] skipping ${name}: realpath escapes skillsDir`);
1114
1122
  continue;
1115
1123
  }
1116
1124
  try {
1117
- const st = await (0, import_utils2.stat)(resolved);
1125
+ const st = await (0, import_utils3.stat)(resolved);
1118
1126
  if (st.isDirectory()) out.push(resolved);
1119
1127
  } catch {
1120
1128
  }
@@ -1126,7 +1134,7 @@ var FilesystemSkillsSource = class {
1126
1134
  if (this.stopped) return;
1127
1135
  let exists = false;
1128
1136
  try {
1129
- const st = await (0, import_utils2.stat)(absDir);
1137
+ const st = await (0, import_utils3.stat)(absDir);
1130
1138
  exists = st.isDirectory();
1131
1139
  } catch {
1132
1140
  exists = false;
@@ -1164,18 +1172,22 @@ var FilesystemSkillsSource = class {
1164
1172
  }
1165
1173
  beginWatch() {
1166
1174
  try {
1167
- this.watcher = (0, import_node_fs3.watch)(this.rootResolved, { recursive: true, persistent: false }, (_eventType, filename) => {
1168
- if (this.stopped) return;
1169
- if (!filename) {
1170
- this.scheduleRescan();
1171
- return;
1172
- }
1173
- const first = filename.split(import_node_path.sep)[0];
1174
- if (!first || first.startsWith(".")) return;
1175
- const absDir = (0, import_utils2.joinPath)(this.rootResolved, first);
1176
- this.scheduleRefresh(absDir);
1177
- });
1178
- this.watcher.on("error", (e) => {
1175
+ this.watcher = (0, import_utils3.watchFile)(
1176
+ this.rootResolved,
1177
+ (_eventType, filename) => {
1178
+ if (this.stopped) return;
1179
+ if (!filename) {
1180
+ this.scheduleRescan();
1181
+ return;
1182
+ }
1183
+ const first = filename.split(import_utils3.sep)[0];
1184
+ if (!first || first.startsWith(".")) return;
1185
+ const absDir = (0, import_utils3.joinPath)(this.rootResolved, first);
1186
+ this.scheduleRefresh(absDir);
1187
+ },
1188
+ { recursive: true }
1189
+ );
1190
+ this.watcher.onError((e) => {
1179
1191
  this.logger.warn(`[fs-skills] fs.watch error: ${e.message}; falling back to polling`);
1180
1192
  this.watcher?.close();
1181
1193
  this.watcher = void 0;
@@ -1243,10 +1255,10 @@ var FilesystemSkillsSource = class {
1243
1255
  }
1244
1256
  };
1245
1257
  async function loadFilesystemSkill(absDir) {
1246
- const skillMdPath = (0, import_utils2.joinPath)(absDir, "SKILL.md");
1247
- const raw = await (0, import_utils2.readFile)(skillMdPath, "utf8");
1258
+ const skillMdPath = (0, import_utils3.joinPath)(absDir, "SKILL.md");
1259
+ const raw = await (0, import_utils3.readFile)(skillMdPath, "utf8");
1248
1260
  const { frontmatter, body } = parseSkillFrontmatter(raw);
1249
- const dirName = (0, import_utils2.basename)(absDir);
1261
+ const dirName = (0, import_utils3.basename)(absDir);
1250
1262
  const name = frontmatter["name"] ?? dirName;
1251
1263
  const description = frontmatter["description"] ?? "";
1252
1264
  if (!description) {
@@ -1259,9 +1271,9 @@ async function loadFilesystemSkill(absDir) {
1259
1271
  const tools = parseToolList(frontmatter["tools"]);
1260
1272
  const resources = {};
1261
1273
  for (const sub of ["scripts", "references", "examples", "assets"]) {
1262
- const subAbs = (0, import_utils2.joinPath)(absDir, sub);
1274
+ const subAbs = (0, import_utils3.joinPath)(absDir, sub);
1263
1275
  try {
1264
- const st = await (0, import_utils2.stat)(subAbs);
1276
+ const st = await (0, import_utils3.stat)(subAbs);
1265
1277
  if (st.isDirectory()) resources[sub] = subAbs;
1266
1278
  } catch {
1267
1279
  }
@@ -1334,7 +1346,7 @@ function skillContentEqual(a, b) {
1334
1346
  }
1335
1347
 
1336
1348
  // libs/adapters/src/skills/security/bundle-signature.ts
1337
- var import_utils3 = require("@frontmcp/utils");
1349
+ var import_utils4 = require("@frontmcp/utils");
1338
1350
  function classifyReason(reason) {
1339
1351
  if (reason.includes("missing integrity")) return "missing_integrity";
1340
1352
  if (reason.includes("digest mismatch")) return "digest_mismatch";
@@ -1360,11 +1372,11 @@ function canonicalize(value) {
1360
1372
  function bundleDigest(bundle) {
1361
1373
  const { integrity: _drop, ...rest } = bundle;
1362
1374
  void _drop;
1363
- return (0, import_utils3.sha256Hex)(canonicalize(rest));
1375
+ return (0, import_utils4.sha256Hex)(canonicalize(rest));
1364
1376
  }
1365
1377
  function decodeSignatureBytes(signatureBase64Url) {
1366
1378
  try {
1367
- return (0, import_utils3.base64urlDecode)(signatureBase64Url);
1379
+ return (0, import_utils4.base64urlDecode)(signatureBase64Url);
1368
1380
  } catch {
1369
1381
  return void 0;
1370
1382
  }
@@ -1400,7 +1412,7 @@ function verifyBundleSignature(bundle, trustedKeys, telemetry) {
1400
1412
  }
1401
1413
  let publicJwk;
1402
1414
  try {
1403
- publicJwk = (0, import_utils3.pemToPublicJwk)(trusted.publicKeyPem);
1415
+ publicJwk = (0, import_utils4.pemToPublicJwk)(trusted.publicKeyPem);
1404
1416
  } catch (e) {
1405
1417
  return fail(`failed to parse public key for "${keyId}": ${e.message}`);
1406
1418
  }
@@ -1411,7 +1423,7 @@ function verifyBundleSignature(bundle, trustedKeys, telemetry) {
1411
1423
  const { integrity: _drop, ...rest } = bundle;
1412
1424
  void _drop;
1413
1425
  const canonicalBytes = new TextEncoder().encode(canonicalize(rest));
1414
- const verified = (0, import_utils3.rsaVerifySync)(alg, canonicalBytes, publicJwk, sigBytes);
1426
+ const verified = (0, import_utils4.rsaVerifySync)(alg, canonicalBytes, publicJwk, sigBytes);
1415
1427
  if (!verified) {
1416
1428
  return fail("signature verification failed");
1417
1429
  }
@@ -1488,7 +1500,7 @@ var BundlePushJwtVerifier = class {
1488
1500
  };
1489
1501
 
1490
1502
  // libs/adapters/src/skills/security/webhook-replay-guard.ts
1491
- var import_utils4 = require("@frontmcp/utils");
1503
+ var import_utils5 = require("@frontmcp/utils");
1492
1504
  function classifyReplayReason(reason) {
1493
1505
  if (reason.includes("not finite")) return "invalid_timestamp";
1494
1506
  if (reason.includes("too short")) return "invalid_nonce";
@@ -1551,7 +1563,7 @@ var WebhookReplayGuard = class {
1551
1563
  if (Math.abs(now - timestampMs) > this.windowMs) {
1552
1564
  return reject(`timestamp outside ${this.windowMs}ms window`);
1553
1565
  }
1554
- const key = (0, import_utils4.sha256Hex)(nonce);
1566
+ const key = (0, import_utils5.sha256Hex)(nonce);
1555
1567
  const expiresAt = this.seen.get(key);
1556
1568
  if (expiresAt !== void 0 && expiresAt >= now) {
1557
1569
  return reject("nonce replay detected within freshness window");
@@ -1602,7 +1614,7 @@ var SKILL_AUDIT_KEYS = {
1602
1614
  };
1603
1615
 
1604
1616
  // libs/adapters/src/skills/audit/audit-chain.ts
1605
- var import_utils5 = require("@frontmcp/utils");
1617
+ var import_utils6 = require("@frontmcp/utils");
1606
1618
  function canonicalizeRecordForSigning(record) {
1607
1619
  const { signature: _drop, ...rest } = record;
1608
1620
  void _drop;
@@ -1610,7 +1622,7 @@ function canonicalizeRecordForSigning(record) {
1610
1622
  }
1611
1623
  function nextPrevHash(prev) {
1612
1624
  if (!prev) return SKILL_AUDIT_GENESIS_PREV_HASH;
1613
- return (0, import_utils5.sha256Hex)(canonicalizeRecordForSigning(prev));
1625
+ return (0, import_utils6.sha256Hex)(canonicalizeRecordForSigning(prev));
1614
1626
  }
1615
1627
  function linkRecord(prev, partial) {
1616
1628
  return {
@@ -1664,7 +1676,7 @@ function verifyChain(records, trustedKeys, verifier) {
1664
1676
  }
1665
1677
 
1666
1678
  // libs/adapters/src/skills/audit/audit-signer.ts
1667
- var import_utils6 = require("@frontmcp/utils");
1679
+ var import_utils7 = require("@frontmcp/utils");
1668
1680
  var Hs256AuditSigner = class {
1669
1681
  key;
1670
1682
  keyId;
@@ -1699,9 +1711,9 @@ var Hs256AuditSigner = class {
1699
1711
  signatureAlg: "HS256"
1700
1712
  })
1701
1713
  );
1702
- const mac = (0, import_utils6.hmacSha256)(this.key, canonicalBytes);
1714
+ const mac = (0, import_utils7.hmacSha256)(this.key, canonicalBytes);
1703
1715
  return {
1704
- signature: (0, import_utils6.base64urlEncode)(mac),
1716
+ signature: (0, import_utils7.base64urlEncode)(mac),
1705
1717
  keyId: this.keyId,
1706
1718
  alg: "HS256"
1707
1719
  };
@@ -1741,7 +1753,7 @@ var Rs256AuditSigner = class {
1741
1753
  signatureAlg: "RS256"
1742
1754
  })
1743
1755
  );
1744
- const signature = (0, import_utils6.rsaSignBase64Url)("RS256", canonicalBytes, this.privateJwk);
1756
+ const signature = (0, import_utils7.rsaSignBase64Url)("RS256", canonicalBytes, this.privateJwk);
1745
1757
  return {
1746
1758
  signature,
1747
1759
  keyId: this.keyId,
@@ -1766,31 +1778,31 @@ var defaultAuditSignatureVerifier = ({
1766
1778
  if (!trusted) return false;
1767
1779
  if (alg === "HS256") {
1768
1780
  if (!trusted.secret) return false;
1769
- const expected = (0, import_utils6.hmacSha256)(trusted.secret, data);
1781
+ const expected = (0, import_utils7.hmacSha256)(trusted.secret, data);
1770
1782
  let actual;
1771
1783
  try {
1772
- actual = (0, import_utils6.base64urlDecode)(signatureBase64Url);
1784
+ actual = (0, import_utils7.base64urlDecode)(signatureBase64Url);
1773
1785
  } catch {
1774
1786
  return false;
1775
1787
  }
1776
1788
  if (actual.length !== expected.length) return false;
1777
- return (0, import_utils6.timingSafeEqual)(actual, expected);
1789
+ return (0, import_utils7.timingSafeEqual)(actual, expected);
1778
1790
  }
1779
1791
  if (alg === "RS256") {
1780
1792
  if (!trusted.publicKeyPem && !trusted.publicJwk) return false;
1781
1793
  let publicJwk;
1782
1794
  try {
1783
- publicJwk = trusted.publicJwk ?? (0, import_utils6.pemToPublicJwk)(trusted.publicKeyPem);
1795
+ publicJwk = trusted.publicJwk ?? (0, import_utils7.pemToPublicJwk)(trusted.publicKeyPem);
1784
1796
  } catch {
1785
1797
  return false;
1786
1798
  }
1787
1799
  let sigBytes;
1788
1800
  try {
1789
- sigBytes = (0, import_utils6.base64urlDecode)(signatureBase64Url);
1801
+ sigBytes = (0, import_utils7.base64urlDecode)(signatureBase64Url);
1790
1802
  } catch {
1791
1803
  return false;
1792
1804
  }
1793
- return (0, import_utils6.rsaVerifySync)("RS256", data, publicJwk, sigBytes);
1805
+ return (0, import_utils7.rsaVerifySync)("RS256", data, publicJwk, sigBytes);
1794
1806
  }
1795
1807
  const _exhaustive = alg;
1796
1808
  void _exhaustive;
@@ -1890,7 +1902,7 @@ var StorageAdapterAuditStore = class {
1890
1902
  };
1891
1903
 
1892
1904
  // libs/adapters/src/skills/audit/audit-writer.ts
1893
- var import_utils7 = require("@frontmcp/utils");
1905
+ var import_utils8 = require("@frontmcp/utils");
1894
1906
  var SKILL_AUDIT_ERROR_MESSAGE_MAX = 500;
1895
1907
  var SKILL_AUDIT_QUEUE_MAX = 1e3;
1896
1908
  var SkillAuditWriter = class {
@@ -1997,7 +2009,7 @@ var SkillAuditWriter = class {
1997
2009
  }
1998
2010
  let inputHash;
1999
2011
  try {
2000
- inputHash = (0, import_utils7.sha256Hex)(canonicalize(ctx.input ?? null));
2012
+ inputHash = (0, import_utils8.sha256Hex)(canonicalize(ctx.input ?? null));
2001
2013
  } catch (e) {
2002
2014
  this.logger.warn(`[skill-audit] failed to canonicalize input: ${e.message}`);
2003
2015
  this.metrics?.incrementWriteFailure("unexpected");
@@ -2012,7 +2024,7 @@ var SkillAuditWriter = class {
2012
2024
  return;
2013
2025
  }
2014
2026
  const partial = {
2015
- id: (0, import_utils7.randomUUID)(),
2027
+ id: (0, import_utils8.randomUUID)(),
2016
2028
  sequence,
2017
2029
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2018
2030
  subject: this.applySubjectMode(ctx.subject || "anonymous"),
@@ -2077,8 +2089,8 @@ var SkillAuditWriter = class {
2077
2089
  applySubjectMode(subject) {
2078
2090
  if (this.subjectMode === "plain") return subject;
2079
2091
  if (this.subjectMode === "omit") return "redacted";
2080
- const mac = (0, import_utils7.hmacSha256)(this.subjectHashSecret, new TextEncoder().encode(subject));
2081
- const hex = (0, import_utils7.bytesToHex)(mac);
2092
+ const mac = (0, import_utils8.hmacSha256)(this.subjectHashSecret, new TextEncoder().encode(subject));
2093
+ const hex = (0, import_utils8.bytesToHex)(mac);
2082
2094
  return `hashed:${hex.slice(0, 32)}`;
2083
2095
  }
2084
2096
  /**
@@ -2105,9 +2117,9 @@ var SkillAuditWriter = class {
2105
2117
  */
2106
2118
  safeHash(value) {
2107
2119
  try {
2108
- return (0, import_utils7.sha256Hex)(canonicalize(value ?? null));
2120
+ return (0, import_utils8.sha256Hex)(canonicalize(value ?? null));
2109
2121
  } catch {
2110
- return (0, import_utils7.sha256Hex)("audit:non-canonicalizable-output");
2122
+ return (0, import_utils8.sha256Hex)("audit:non-canonicalizable-output");
2111
2123
  }
2112
2124
  }
2113
2125
  /** Truncate + sanitize an error to fit in the record. */
@@ -2140,11 +2152,700 @@ var SkillAuditWriterToken = /* @__PURE__ */ Symbol.for(
2140
2152
 
2141
2153
  // libs/adapters/src/skills/index.ts
2142
2154
  var import_sdk = require("@frontmcp/sdk");
2155
+
2156
+ // libs/adapters/src/skills/harvester/op-reference.ts
2157
+ var SPEC_ID_RE = "[A-Za-z0-9][A-Za-z0-9_.-]*";
2158
+ var OPERATION_ID_RE = "[A-Za-z_][A-Za-z0-9_]*";
2159
+ var TRAILING_BOUNDARY = String.raw`(?![A-Za-z0-9_\-])(?!\.[A-Za-z0-9_])`;
2160
+ var REFERENCE_RE = new RegExp(
2161
+ 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})\]\]`,
2162
+ "g"
2163
+ );
2164
+ function extractOpReferences(markdown) {
2165
+ if (typeof markdown !== "string" || markdown.length === 0) return [];
2166
+ const refs = [];
2167
+ const lineStarts = computeLineStarts(markdown);
2168
+ REFERENCE_RE.lastIndex = 0;
2169
+ let match;
2170
+ while ((match = REFERENCE_RE.exec(markdown)) !== null) {
2171
+ const offset = match.index;
2172
+ const [raw, uriSpec, uriOp, wikiSpec, wikiOp] = match;
2173
+ const isWikilink = wikiSpec !== void 0 && wikiOp !== void 0;
2174
+ const spec = isWikilink ? wikiSpec : uriSpec;
2175
+ const operationId = isWikilink ? wikiOp : uriOp;
2176
+ refs.push({
2177
+ spec,
2178
+ operationId,
2179
+ syntax: isWikilink ? "wikilink" : "uri",
2180
+ raw,
2181
+ location: offsetToLocation(offset, lineStarts)
2182
+ });
2183
+ }
2184
+ return refs;
2185
+ }
2186
+ function buildKnownOps(entries) {
2187
+ const map = /* @__PURE__ */ new Map();
2188
+ for (const entry of entries) {
2189
+ const slash = entry.indexOf("/");
2190
+ if (slash <= 0 || slash === entry.length - 1) continue;
2191
+ const spec = entry.slice(0, slash);
2192
+ const op = entry.slice(slash + 1);
2193
+ let set = map.get(spec);
2194
+ if (!set) {
2195
+ set = /* @__PURE__ */ new Set();
2196
+ map.set(spec, set);
2197
+ }
2198
+ set.add(op);
2199
+ }
2200
+ return map;
2201
+ }
2202
+ function validateOpReferences(refs, knownOps) {
2203
+ const diagnostics = [];
2204
+ for (const ref of refs) {
2205
+ const specOps = knownOps.get(ref.spec);
2206
+ if (!specOps) {
2207
+ diagnostics.push({
2208
+ ref,
2209
+ kind: "unknown-spec",
2210
+ message: `Unknown OpenAPI spec "${ref.spec}" referenced as ${ref.raw}`,
2211
+ suggestions: Array.from(knownOps.keys()).sort()
2212
+ });
2213
+ continue;
2214
+ }
2215
+ if (!specOps.has(ref.operationId)) {
2216
+ diagnostics.push({
2217
+ ref,
2218
+ kind: "unknown-operation",
2219
+ message: `Unknown operationId "${ref.operationId}" in spec "${ref.spec}" (${ref.raw})`,
2220
+ suggestions: suggestSimilarOps(ref.operationId, specOps)
2221
+ });
2222
+ }
2223
+ }
2224
+ return diagnostics;
2225
+ }
2226
+ function dedupeOpReferences(refs) {
2227
+ const seen = /* @__PURE__ */ new Set();
2228
+ const result = [];
2229
+ for (const ref of refs) {
2230
+ const key = `${ref.spec}/${ref.operationId}`;
2231
+ if (seen.has(key)) continue;
2232
+ seen.add(key);
2233
+ result.push({ spec: ref.spec, operationId: ref.operationId });
2234
+ }
2235
+ return result;
2236
+ }
2237
+ function computeLineStarts(source) {
2238
+ const starts = [0];
2239
+ for (let i = 0; i < source.length; i++) {
2240
+ if (source.charCodeAt(i) === 10) starts.push(i + 1);
2241
+ }
2242
+ return starts;
2243
+ }
2244
+ function offsetToLocation(offset, lineStarts) {
2245
+ let lo = 0;
2246
+ let hi = lineStarts.length - 1;
2247
+ while (lo < hi) {
2248
+ const mid = lo + hi + 1 >>> 1;
2249
+ if (lineStarts[mid] <= offset) lo = mid;
2250
+ else hi = mid - 1;
2251
+ }
2252
+ const line = lo + 1;
2253
+ const column = offset - lineStarts[lo] + 1;
2254
+ return { line, column, offset };
2255
+ }
2256
+ function suggestSimilarOps(query, known) {
2257
+ if (known.size === 0) return [];
2258
+ const q = query.toLowerCase();
2259
+ const candidates = Array.from(known);
2260
+ const prefix = candidates.filter((c) => c.toLowerCase().startsWith(q));
2261
+ const substring = candidates.filter((c) => !prefix.includes(c) && c.toLowerCase().includes(q));
2262
+ 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)));
2263
+ return [...prefix, ...substring, ...close].slice(0, 3);
2264
+ }
2265
+ function levenshtein(a, b) {
2266
+ if (a === b) return 0;
2267
+ if (a.length === 0) return b.length;
2268
+ if (b.length === 0) return a.length;
2269
+ let prev = new Array(b.length + 1);
2270
+ let curr = new Array(b.length + 1);
2271
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
2272
+ for (let i = 1; i <= a.length; i++) {
2273
+ curr[0] = i;
2274
+ for (let j = 1; j <= b.length; j++) {
2275
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
2276
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
2277
+ }
2278
+ [prev, curr] = [curr, prev];
2279
+ }
2280
+ return prev[b.length];
2281
+ }
2282
+
2283
+ // libs/adapters/src/skills/deploy/deploy-manifest.schema.ts
2284
+ var import_lazy_zod3 = require("@frontmcp/lazy-zod");
2285
+ var IDENT_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
2286
+ var SPEC_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
2287
+ var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
2288
+ var BINDING_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
2289
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2290
+ var serverInfoSchema = import_lazy_zod3.z.object({
2291
+ name: import_lazy_zod3.z.string().min(1).max(128),
2292
+ version: import_lazy_zod3.z.string().min(1).max(64),
2293
+ title: import_lazy_zod3.z.string().min(1).max(256).optional()
2294
+ }).strict();
2295
+ var serverSchema = import_lazy_zod3.z.object({
2296
+ info: serverInfoSchema,
2297
+ /**
2298
+ * Top-level server instructions injected at `initialize` time. Picked up
2299
+ * by the existing `skillsConfig.injectInstructions` machinery (defaults
2300
+ * to `append` so the skill catalog summary follows).
2301
+ */
2302
+ instructions: import_lazy_zod3.z.string().max(16 * 1024).optional()
2303
+ }).strict();
2304
+ var runtimeSchema = import_lazy_zod3.z.object({
2305
+ /** Only `cloudflare-worker` in v1. Future: `vercel-edge`, `deno-deploy`. */
2306
+ target: import_lazy_zod3.z.literal("cloudflare-worker"),
2307
+ compatibilityDate: import_lazy_zod3.z.string().regex(ISO_DATE_RE, "compatibilityDate must be ISO-8601 (YYYY-MM-DD)"),
2308
+ /** Worker compatibility flags (e.g. `nodejs_compat`). */
2309
+ compatibilityFlags: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1).max(128)).max(32).optional()
2310
+ }).strict();
2311
+ var specSourceSchema = import_lazy_zod3.z.union([
2312
+ /** Local file path or HTTPS URL to a single spec. Spec id = filename stem. */
2313
+ import_lazy_zod3.z.string().min(1).max(2048),
2314
+ import_lazy_zod3.z.object({
2315
+ id: import_lazy_zod3.z.string().min(1).max(128).regex(SPEC_ID_RE2, "spec id must be a URI-safe identifier"),
2316
+ spec: import_lazy_zod3.z.string().min(1).max(2048),
2317
+ baseUrl: import_lazy_zod3.z.string().url("baseUrl must be a valid URL").optional(),
2318
+ /** Optional namespace override for AgentScript bindings (default = `id`). */
2319
+ bindingName: import_lazy_zod3.z.string().min(1).max(64).regex(IDENT_RE).optional()
2320
+ }).strict()
2321
+ ]);
2322
+ var specsSchema = import_lazy_zod3.z.union([
2323
+ /** Single directory path; the loader auto-discovers `*.yaml` / `*.json`. */
2324
+ import_lazy_zod3.z.string().min(1).max(2048),
2325
+ /** Explicit list (mixed strings and detail objects allowed). */
2326
+ import_lazy_zod3.z.array(specSourceSchema).max(64)
2327
+ ]);
2328
+ var skillsSchema = import_lazy_zod3.z.object({
2329
+ /** Directory under the manifest. Defaults to `./skills/`. */
2330
+ source: import_lazy_zod3.z.string().min(1).max(2048).default("./skills/"),
2331
+ /**
2332
+ * IDs of skills the worker pre-loads into every `execute()` regardless
2333
+ * of which skills the agent passed. Mirrors the per-skill
2334
+ * `alwaysLoad: true` frontmatter, but lets the server force it.
2335
+ */
2336
+ alwaysLoad: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1).max(128)).max(32).optional(),
2337
+ /** Optional tag filter applied at deploy time. */
2338
+ tags: import_lazy_zod3.z.object({
2339
+ include: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1).max(64)).max(64).optional(),
2340
+ exclude: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1).max(64)).max(64).optional()
2341
+ }).strict().optional()
2342
+ }).strict();
2343
+ var tagSchema = import_lazy_zod3.z.object({
2344
+ name: import_lazy_zod3.z.string().min(1).max(64),
2345
+ description: import_lazy_zod3.z.string().min(1).max(512).optional()
2346
+ }).strict();
2347
+ var classificationRuleSchema = import_lazy_zod3.z.object({
2348
+ /**
2349
+ * Match pattern: METHOD followed by a path-glob, e.g.
2350
+ * `POST <star>/reset-<star>` (where <star> is the literal `*` glob).
2351
+ * Written this way to avoid prematurely closing the JSDoc with star-slash.
2352
+ */
2353
+ match: import_lazy_zod3.z.string().min(1).max(256),
2354
+ /**
2355
+ * Override the MCP surface for matching operations: tool (default for
2356
+ * mutations), resource (default for GET list / GET path-param), or both
2357
+ * (default for GET path-param). Omit to leave the classifier's default.
2358
+ */
2359
+ expose: import_lazy_zod3.z.enum(["tool", "resource", "both"]).optional(),
2360
+ /**
2361
+ * Resource-update emit target. `self` (mutation on its own resource),
2362
+ * `parent` (mutation that affects the parent collection / parent path),
2363
+ * or `none` (suppress). Defaults from the HTTP-semantics classifier.
2364
+ */
2365
+ emits: import_lazy_zod3.z.enum(["self", "parent", "none"]).optional()
2366
+ }).strict();
2367
+ var classificationSchema = import_lazy_zod3.z.object({
2368
+ rules: import_lazy_zod3.z.array(classificationRuleSchema).max(64).optional()
2369
+ }).strict();
2370
+ var durableObjectBindingSchema = import_lazy_zod3.z.object({
2371
+ binding: import_lazy_zod3.z.string().min(1).max(128).regex(BINDING_NAME_RE),
2372
+ className: import_lazy_zod3.z.string().min(1).max(128).regex(IDENT_RE),
2373
+ scriptName: import_lazy_zod3.z.string().min(1).max(128).optional()
2374
+ }).strict();
2375
+ var d1BindingSchema = import_lazy_zod3.z.object({
2376
+ binding: import_lazy_zod3.z.string().min(1).max(128).regex(BINDING_NAME_RE),
2377
+ databaseName: import_lazy_zod3.z.string().min(1).max(256),
2378
+ databaseId: import_lazy_zod3.z.string().min(1).max(128)
2379
+ }).strict();
2380
+ var kvBindingSchema = import_lazy_zod3.z.object({
2381
+ binding: import_lazy_zod3.z.string().min(1).max(128).regex(BINDING_NAME_RE),
2382
+ id: import_lazy_zod3.z.string().min(1).max(128)
2383
+ }).strict();
2384
+ var r2BindingSchema = import_lazy_zod3.z.object({
2385
+ binding: import_lazy_zod3.z.string().min(1).max(128).regex(BINDING_NAME_RE),
2386
+ bucketName: import_lazy_zod3.z.string().min(1).max(256)
2387
+ }).strict();
2388
+ var bindingsSchema = import_lazy_zod3.z.object({
2389
+ durableObjects: import_lazy_zod3.z.array(durableObjectBindingSchema).max(32).optional(),
2390
+ d1Databases: import_lazy_zod3.z.array(d1BindingSchema).max(16).optional(),
2391
+ kvNamespaces: import_lazy_zod3.z.array(kvBindingSchema).max(32).optional(),
2392
+ r2Buckets: import_lazy_zod3.z.array(r2BindingSchema).max(16).optional(),
2393
+ /** Plaintext vars surfaced as `env.<name>` on the Worker. */
2394
+ vars: import_lazy_zod3.z.record(import_lazy_zod3.z.string().min(1).max(128), import_lazy_zod3.z.string().max(4096)).optional()
2395
+ }).strict();
2396
+ var trustRootSchema = import_lazy_zod3.z.object({
2397
+ /** Key id — matches the JWS `kid` header on the signed bundle. */
2398
+ kid: import_lazy_zod3.z.string().min(1).max(128),
2399
+ /** Name of the secret binding holding the PEM-encoded public key. */
2400
+ publicKeySecret: import_lazy_zod3.z.string().min(1).max(128).regex(SECRET_NAME_RE)
2401
+ }).strict();
2402
+ var replayGuardSchema = import_lazy_zod3.z.object({
2403
+ /** Acceptable clock skew; rejects bundles older than this. */
2404
+ windowSeconds: import_lazy_zod3.z.number().int().min(10).max(3600).default(300),
2405
+ /** KV binding name that stores nonces for replay protection. */
2406
+ nonceKv: import_lazy_zod3.z.string().min(1).max(128).regex(BINDING_NAME_RE)
2407
+ }).strict();
2408
+ var signingSchema = import_lazy_zod3.z.object({
2409
+ algorithm: import_lazy_zod3.z.enum(["ed25519", "rs256"]),
2410
+ trustRoots: import_lazy_zod3.z.array(trustRootSchema).min(1).max(8),
2411
+ replay: replayGuardSchema
2412
+ }).strict();
2413
+ var fronteggAuthSchema = import_lazy_zod3.z.object({
2414
+ tenantResolver: import_lazy_zod3.z.enum(["subdomain", "header", "jwt-claim"]).default("subdomain"),
2415
+ audience: import_lazy_zod3.z.string().min(1).max(256),
2416
+ /** Secret name holding the issuer URL. */
2417
+ issuerSecret: import_lazy_zod3.z.string().min(1).max(128).regex(SECRET_NAME_RE)
2418
+ }).strict();
2419
+ var oauthAuthSchema = import_lazy_zod3.z.object({
2420
+ issuer: import_lazy_zod3.z.string().url(),
2421
+ audience: import_lazy_zod3.z.string().min(1).max(256),
2422
+ /** Optional secret name for client credentials (machine-to-machine). */
2423
+ credentialsSecret: import_lazy_zod3.z.string().min(1).max(128).regex(SECRET_NAME_RE).optional()
2424
+ }).strict();
2425
+ var apiKeyAuthSchema = import_lazy_zod3.z.object({
2426
+ /** Header name carrying the key (e.g. `X-API-Key`). */
2427
+ header: import_lazy_zod3.z.string().min(1).max(64).default("X-API-Key"),
2428
+ /** Secret name holding the allowlist (newline-separated keys). */
2429
+ allowlistSecret: import_lazy_zod3.z.string().min(1).max(128).regex(SECRET_NAME_RE)
2430
+ }).strict();
2431
+ var authSchema = import_lazy_zod3.z.discriminatedUnion("provider", [
2432
+ import_lazy_zod3.z.object({ provider: import_lazy_zod3.z.literal("none") }).strict(),
2433
+ import_lazy_zod3.z.object({ provider: import_lazy_zod3.z.literal("frontegg"), frontegg: fronteggAuthSchema }).strict(),
2434
+ import_lazy_zod3.z.object({ provider: import_lazy_zod3.z.literal("oauth"), oauth: oauthAuthSchema }).strict(),
2435
+ import_lazy_zod3.z.object({ provider: import_lazy_zod3.z.literal("apiKey"), apiKey: apiKeyAuthSchema }).strict()
2436
+ ]);
2437
+ var secretSchema = import_lazy_zod3.z.object({
2438
+ name: import_lazy_zod3.z.string().min(1).max(128).regex(SECRET_NAME_RE),
2439
+ required: import_lazy_zod3.z.boolean().default(true),
2440
+ description: import_lazy_zod3.z.string().min(1).max(512).optional()
2441
+ }).strict();
2442
+ var environmentOverlaySchema = import_lazy_zod3.z.object({
2443
+ server: serverSchema.partial().optional(),
2444
+ specs: specsSchema.optional(),
2445
+ skills: skillsSchema.partial().optional(),
2446
+ classification: classificationSchema.optional(),
2447
+ bindings: bindingsSchema.optional(),
2448
+ auth: authSchema.optional(),
2449
+ vars: import_lazy_zod3.z.record(import_lazy_zod3.z.string().min(1).max(128), import_lazy_zod3.z.string().max(4096)).optional()
2450
+ }).strict();
2451
+ var deployManifestSchema = import_lazy_zod3.z.object({
2452
+ /** Optional JSON Schema URL — accepted for editor autocompletion. */
2453
+ $schema: import_lazy_zod3.z.string().url().optional(),
2454
+ /** Schema version. Only `1` accepted in v1.3. */
2455
+ version: import_lazy_zod3.z.literal(1),
2456
+ /** Project name (for logs + the Worker's `worker.name`). */
2457
+ name: import_lazy_zod3.z.string().min(1).max(128).regex(IDENT_RE),
2458
+ runtime: runtimeSchema,
2459
+ server: serverSchema,
2460
+ specs: specsSchema,
2461
+ skills: skillsSchema.default({ source: "./skills/" }),
2462
+ tags: import_lazy_zod3.z.array(tagSchema).max(64).optional(),
2463
+ classification: classificationSchema.optional(),
2464
+ bindings: bindingsSchema,
2465
+ signing: signingSchema,
2466
+ auth: authSchema,
2467
+ secrets: import_lazy_zod3.z.array(secretSchema).max(64).optional(),
2468
+ environments: import_lazy_zod3.z.record(import_lazy_zod3.z.string().min(1).max(64).regex(IDENT_RE), environmentOverlaySchema).optional()
2469
+ }).strict();
2470
+ function applyEnvironmentOverlay(base, overlay) {
2471
+ const effectiveBindings = overlay.bindings ?? base.bindings;
2472
+ const mergedBindings = overlay.vars !== void 0 ? { ...effectiveBindings, vars: { ...effectiveBindings.vars ?? {}, ...overlay.vars } } : effectiveBindings;
2473
+ return {
2474
+ ...base,
2475
+ server: overlay.server ? { ...base.server, ...overlay.server } : base.server,
2476
+ specs: overlay.specs ?? base.specs,
2477
+ skills: overlay.skills ? { ...base.skills, ...overlay.skills } : base.skills,
2478
+ classification: overlay.classification ?? base.classification,
2479
+ bindings: mergedBindings,
2480
+ auth: overlay.auth ?? base.auth
2481
+ };
2482
+ }
2483
+ function validateEffectiveManifest(manifest, label, errors) {
2484
+ const declaredSecrets = new Set((manifest.secrets ?? []).map((s) => s.name));
2485
+ const refs = [];
2486
+ if (manifest.auth.provider === "frontegg") {
2487
+ refs.push({ where: "auth.frontegg.issuerSecret", name: manifest.auth.frontegg.issuerSecret });
2488
+ } else if (manifest.auth.provider === "oauth" && manifest.auth.oauth.credentialsSecret) {
2489
+ refs.push({ where: "auth.oauth.credentialsSecret", name: manifest.auth.oauth.credentialsSecret });
2490
+ } else if (manifest.auth.provider === "apiKey") {
2491
+ refs.push({ where: "auth.apiKey.allowlistSecret", name: manifest.auth.apiKey.allowlistSecret });
2492
+ }
2493
+ for (const root of manifest.signing.trustRoots) {
2494
+ refs.push({ where: `signing.trustRoots[kid=${root.kid}].publicKeySecret`, name: root.publicKeySecret });
2495
+ }
2496
+ for (const ref of refs) {
2497
+ if (!declaredSecrets.has(ref.name)) {
2498
+ errors.push(`${label}Secret "${ref.name}" referenced at ${ref.where} is not declared in secrets[]`);
2499
+ }
2500
+ }
2501
+ const declaredKv = new Set((manifest.bindings.kvNamespaces ?? []).map((kv) => kv.binding));
2502
+ if (!declaredKv.has(manifest.signing.replay.nonceKv)) {
2503
+ errors.push(
2504
+ `${label}signing.replay.nonceKv "${manifest.signing.replay.nonceKv}" does not match any bindings.kvNamespaces[].binding`
2505
+ );
2506
+ }
2507
+ for (const id of manifest.skills.alwaysLoad ?? []) {
2508
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
2509
+ errors.push(`${label}skills.alwaysLoad entry "${id}" is not a valid kebab-case skill id`);
2510
+ }
2511
+ }
2512
+ if (manifest.tags) {
2513
+ const declaredTags = new Set(manifest.tags.map((t) => t.name));
2514
+ const tagFilter = manifest.skills.tags;
2515
+ for (const t of [...tagFilter?.include ?? [], ...tagFilter?.exclude ?? []]) {
2516
+ if (!declaredTags.has(t)) {
2517
+ errors.push(`${label}skills.tags references unknown tag "${t}" (not in tags[])`);
2518
+ }
2519
+ }
2520
+ }
2521
+ }
2522
+ function crossValidateManifest(manifest) {
2523
+ const errors = [];
2524
+ validateEffectiveManifest(manifest, "", errors);
2525
+ for (const [envName, overlay] of Object.entries(manifest.environments ?? {})) {
2526
+ const effective = applyEnvironmentOverlay(manifest, overlay);
2527
+ validateEffectiveManifest(effective, `environments.${envName}: `, errors);
2528
+ }
2529
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
2530
+ }
2531
+
2532
+ // libs/adapters/src/skills/classifier/openapi-classify.ts
2533
+ function classifyOperations(specId, ops) {
2534
+ if (!specId || specId.length === 0) {
2535
+ throw new Error("classifyOperations: specId is required");
2536
+ }
2537
+ if (!Array.isArray(ops)) return [];
2538
+ const pathsWithGet = /* @__PURE__ */ new Set();
2539
+ for (const op of ops) {
2540
+ if (op && typeof op.path === "string" && typeof op.method === "string" && op.method.toUpperCase() === "GET") {
2541
+ pathsWithGet.add(op.path);
2542
+ }
2543
+ }
2544
+ return ops.map((op) => classifyOne(specId, op, pathsWithGet));
2545
+ }
2546
+ function classifyOne(specId, op, pathsWithGet) {
2547
+ const method = normaliseMethod(op.method);
2548
+ const path = op.path;
2549
+ const hasTerminalParam = pathHasTerminalParam(path);
2550
+ const parent = parentPath(path);
2551
+ const thisIsResource = pathsWithGet.has(path);
2552
+ const parentIsResource = parent !== null && pathsWithGet.has(parent);
2553
+ const selfUri = pathToResourceUri(specId, path);
2554
+ const parentUri = parent !== null ? pathToResourceUri(specId, parent) : null;
2555
+ if (method === "GET") {
2556
+ if (hasTerminalParam) {
2557
+ return {
2558
+ operationId: op.operationId,
2559
+ method,
2560
+ path,
2561
+ specId,
2562
+ expose: "both",
2563
+ resourceUriTemplate: selfUri
2564
+ };
2565
+ }
2566
+ return {
2567
+ operationId: op.operationId,
2568
+ method,
2569
+ path,
2570
+ specId,
2571
+ expose: "resource",
2572
+ resourceUriTemplate: selfUri
2573
+ };
2574
+ }
2575
+ const base = {
2576
+ operationId: op.operationId,
2577
+ method,
2578
+ path,
2579
+ specId,
2580
+ expose: "tool"
2581
+ // `resourceUriTemplate` left undefined for mutations — the emit target
2582
+ // is the authoritative thing to look at.
2583
+ };
2584
+ if (method === "POST") {
2585
+ if (thisIsResource && !hasTerminalParam) {
2586
+ return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
2587
+ }
2588
+ if (thisIsResource && hasTerminalParam) {
2589
+ return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
2590
+ }
2591
+ if (parentIsResource && parentUri !== null && parent !== null) {
2592
+ return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
2593
+ }
2594
+ return base;
2595
+ }
2596
+ if (method === "PUT" || method === "PATCH") {
2597
+ if (thisIsResource) {
2598
+ return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
2599
+ }
2600
+ if (parentIsResource && parentUri !== null && parent !== null) {
2601
+ return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
2602
+ }
2603
+ return base;
2604
+ }
2605
+ if (method === "DELETE") {
2606
+ if (hasTerminalParam && parentIsResource && parentUri !== null && parent !== null) {
2607
+ return { ...base, emit: { kind: "listChanged", pathTemplate: parent, resourceUriTemplate: parentUri } };
2608
+ }
2609
+ if (!hasTerminalParam && thisIsResource) {
2610
+ return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
2611
+ }
2612
+ return base;
2613
+ }
2614
+ return base;
2615
+ }
2616
+ function applyClassificationOverrides(classified, rules) {
2617
+ if (!Array.isArray(rules) || rules.length === 0) return classified.slice();
2618
+ const compiled = rules.map((rule) => ({ rule, matcher: compileMatcher(rule.match) }));
2619
+ return classified.map((op) => {
2620
+ for (const { rule, matcher } of compiled) {
2621
+ if (!matcher(op)) continue;
2622
+ return applyOne(op, rule);
2623
+ }
2624
+ return op;
2625
+ });
2626
+ }
2627
+ var KNOWN_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"]);
2628
+ function normaliseMethod(m) {
2629
+ const up = (m ?? "").toUpperCase();
2630
+ return KNOWN_METHODS.has(up) ? up : up;
2631
+ }
2632
+ function pathHasTerminalParam(path) {
2633
+ if (typeof path !== "string" || path.length === 0) return false;
2634
+ const segments = path.split("/").filter((s) => s.length > 0);
2635
+ if (segments.length === 0) return false;
2636
+ const last = segments[segments.length - 1];
2637
+ return last.startsWith("{") && last.endsWith("}") && last.length >= 3;
2638
+ }
2639
+ function parentPath(path) {
2640
+ if (typeof path !== "string" || path.length === 0) return null;
2641
+ const segments = path.split("/").filter((s) => s.length > 0);
2642
+ if (segments.length <= 1) return null;
2643
+ return "/" + segments.slice(0, -1).join("/");
2644
+ }
2645
+ function pathToResourceUri(specId, path) {
2646
+ const cleaned = path.startsWith("/") ? path.slice(1) : path;
2647
+ return `mcp+op://${specId}/${cleaned}`;
2648
+ }
2649
+ function compileMatcher(match) {
2650
+ const trimmed = (match ?? "").trim();
2651
+ const sep2 = trimmed.indexOf(" ");
2652
+ if (sep2 <= 0 || sep2 === trimmed.length - 1) {
2653
+ return () => false;
2654
+ }
2655
+ const methodPattern = trimmed.slice(0, sep2).toUpperCase();
2656
+ const pathPattern = trimmed.slice(sep2 + 1);
2657
+ const pathRe = globToRegExp(pathPattern);
2658
+ return (op) => {
2659
+ if (methodPattern !== "*" && methodPattern !== op.method) return false;
2660
+ return pathRe.test(op.path);
2661
+ };
2662
+ }
2663
+ function globToRegExp(glob) {
2664
+ const STAR_STAR = "\0";
2665
+ const STAR = "";
2666
+ const replaced = glob.replace(/\*\*/g, STAR_STAR).replace(/\*/g, STAR);
2667
+ const escaped = replaced.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2668
+ const expanded = escaped.split(STAR_STAR).join(".*").split(STAR).join("[^/]*");
2669
+ return new RegExp(`^${expanded}$`);
2670
+ }
2671
+ function defaultEmitKindForMethod(method) {
2672
+ return method === "DELETE" ? "listChanged" : "updated";
2673
+ }
2674
+ function applyOne(op, rule) {
2675
+ let next = { ...op };
2676
+ if (rule.expose !== void 0) {
2677
+ next = { ...next, expose: rule.expose };
2678
+ }
2679
+ if (rule.emits !== void 0) {
2680
+ if (rule.emits === "none") {
2681
+ const { emit: _drop, ...rest } = next;
2682
+ next = rest;
2683
+ } else if (rule.emits === "self") {
2684
+ const target = pathToResourceUri(op.specId, op.path);
2685
+ next = {
2686
+ ...next,
2687
+ emit: {
2688
+ // Preserve the classifier's original kind if it set one; otherwise
2689
+ // derive from the HTTP method so a DELETE override doesn't end up
2690
+ // emitting `updated` on its own (now-gone) URI.
2691
+ kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
2692
+ pathTemplate: op.path,
2693
+ resourceUriTemplate: target
2694
+ }
2695
+ };
2696
+ } else if (rule.emits === "parent") {
2697
+ const parent = parentPath(op.path);
2698
+ if (parent !== null) {
2699
+ next = {
2700
+ ...next,
2701
+ emit: {
2702
+ kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
2703
+ pathTemplate: parent,
2704
+ resourceUriTemplate: pathToResourceUri(op.specId, parent)
2705
+ }
2706
+ };
2707
+ } else {
2708
+ const { emit: _drop, ...rest } = next;
2709
+ next = rest;
2710
+ }
2711
+ }
2712
+ }
2713
+ return next;
2714
+ }
2715
+
2716
+ // libs/adapters/src/skills/classifier/render-resource-uri.ts
2717
+ var TEMPLATE_PLACEHOLDER_RE = /\{([^{}]+)\}/g;
2718
+ function renderResourceUri(template, args) {
2719
+ if (typeof template !== "string" || template.length === 0) {
2720
+ return { ok: false, missing: [] };
2721
+ }
2722
+ const argObj = args !== null && typeof args === "object" ? args : {};
2723
+ const missing = [];
2724
+ const uri = template.replace(TEMPLATE_PLACEHOLDER_RE, (_match, name) => {
2725
+ const value = argObj[name];
2726
+ if (value === void 0 || value === null) {
2727
+ missing.push(name);
2728
+ return "";
2729
+ }
2730
+ if (typeof value === "object") {
2731
+ missing.push(name);
2732
+ return "";
2733
+ }
2734
+ if (typeof value === "number" && !Number.isFinite(value)) {
2735
+ missing.push(name);
2736
+ return "";
2737
+ }
2738
+ return encodeURIComponent(String(value));
2739
+ });
2740
+ if (missing.length > 0) return { ok: false, missing };
2741
+ return { ok: true, uri };
2742
+ }
2743
+
2744
+ // libs/adapters/src/skills/classifier/resource-change-notification.ts
2745
+ function buildResourceChangeNotification(classification, args) {
2746
+ const emit = classification.emit;
2747
+ if (!emit) return { notification: null, reason: "no-emit" };
2748
+ if (emit.kind === "listChanged") {
2749
+ const rendered2 = renderResourceUri(emit.resourceUriTemplate, args);
2750
+ if (!rendered2.ok) {
2751
+ return { notification: null, reason: "unresolved-template", missing: rendered2.missing };
2752
+ }
2753
+ return { notification: { method: "notifications/resources/list_changed" } };
2754
+ }
2755
+ const rendered = renderResourceUri(emit.resourceUriTemplate, args);
2756
+ if (!rendered.ok) {
2757
+ return { notification: null, reason: "unresolved-template", missing: rendered.missing };
2758
+ }
2759
+ return {
2760
+ notification: {
2761
+ method: "notifications/resources/updated",
2762
+ params: { uri: rendered.uri }
2763
+ }
2764
+ };
2765
+ }
2766
+
2767
+ // libs/adapters/src/skills/classifier/classification-registry.ts
2768
+ var ClassificationRegistry = class {
2769
+ byToolName = /* @__PURE__ */ new Map();
2770
+ /**
2771
+ * Register (or replace) a classification for a tool name. Returns the
2772
+ * previous classification if one existed.
2773
+ *
2774
+ * Useful for hot-reload: the deploy pipeline re-builds classifications
2775
+ * from the new bundle and overwrites in place.
2776
+ */
2777
+ register(toolName, classification) {
2778
+ if (typeof toolName !== "string" || toolName.length === 0) {
2779
+ throw new Error("ClassificationRegistry.register: toolName must be a non-empty string");
2780
+ }
2781
+ const prev = this.byToolName.get(toolName);
2782
+ this.byToolName.set(toolName, classification);
2783
+ return prev;
2784
+ }
2785
+ /**
2786
+ * Bulk-register an array of classifications keyed by `${specId}.${operationId}`.
2787
+ * Returns the count of newly-added entries (existing entries are overwritten
2788
+ * but not counted toward `added`).
2789
+ */
2790
+ registerAll(classifications) {
2791
+ let added = 0;
2792
+ let replaced = 0;
2793
+ for (const c of classifications) {
2794
+ const toolName = `${c.specId}.${c.operationId}`;
2795
+ const prev = this.byToolName.get(toolName);
2796
+ this.byToolName.set(toolName, c);
2797
+ if (prev) replaced++;
2798
+ else added++;
2799
+ }
2800
+ return { added, replaced };
2801
+ }
2802
+ /** Look up the classification for a tool. */
2803
+ lookup(toolName) {
2804
+ return this.byToolName.get(toolName);
2805
+ }
2806
+ /** Remove a single classification. Returns true if it was present. */
2807
+ unregister(toolName) {
2808
+ return this.byToolName.delete(toolName);
2809
+ }
2810
+ /** Drop every registration; used by the loader on full re-deploy. */
2811
+ clear() {
2812
+ this.byToolName.clear();
2813
+ }
2814
+ /** Current size — handy for tests and metrics. */
2815
+ size() {
2816
+ return this.byToolName.size;
2817
+ }
2818
+ /** Snapshot of every (toolName, classification) pair in insertion order. */
2819
+ getAll() {
2820
+ const out = [];
2821
+ for (const [toolName, classification] of this.byToolName.entries()) {
2822
+ out.push({ toolName, classification });
2823
+ }
2824
+ return out;
2825
+ }
2826
+ /**
2827
+ * Convenience: in one step, look up the classification for `toolName`
2828
+ * and build the resource-change notification it should produce after a
2829
+ * successful call with `args`. Returns `{ notification: null }` when the
2830
+ * tool has no classification (e.g. it's not an openapi-derived tool) or
2831
+ * when its classification has no `emit`.
2832
+ *
2833
+ * The dispatcher is expected to call this from a successful-call hook
2834
+ * and forward the resulting notification through whichever notification
2835
+ * channel the host provides.
2836
+ */
2837
+ buildNotificationForCall(toolName, args) {
2838
+ const classification = this.byToolName.get(toolName);
2839
+ if (!classification) return { notification: null, reason: "no-emit" };
2840
+ return buildResourceChangeNotification(classification, args);
2841
+ }
2842
+ };
2143
2843
  // Annotate the CommonJS export names for ESM import in node:
2144
2844
  0 && (module.exports = {
2145
2845
  BundlePinnedError,
2146
2846
  BundlePushJwtVerifier,
2147
2847
  BundleStore,
2848
+ ClassificationRegistry,
2148
2849
  FilesystemSkillsSource,
2149
2850
  Hs256AuditSigner,
2150
2851
  MemoryAuditStore,
@@ -2164,15 +2865,25 @@ var import_sdk = require("@frontmcp/sdk");
2164
2865
  StaticSource,
2165
2866
  StorageAdapterAuditStore,
2166
2867
  WebhookReplayGuard,
2868
+ applyClassificationOverrides,
2869
+ applyEnvironmentOverlay,
2870
+ buildKnownOps,
2871
+ buildResourceChangeNotification,
2167
2872
  bundleDigest,
2168
2873
  bundleSkillToActions,
2169
2874
  bundleSourceSchema,
2170
2875
  canonicalize,
2171
2876
  canonicalizeRecordForSigning,
2877
+ classifyOne,
2878
+ classifyOperations,
2172
2879
  createBundleSource,
2173
2880
  crossValidate,
2881
+ crossValidateManifest,
2882
+ dedupeOpReferences,
2174
2883
  defaultAuditSignatureVerifier,
2884
+ deployManifestSchema,
2175
2885
  diffBundles,
2886
+ extractOpReferences,
2176
2887
  formatDiffSummary,
2177
2888
  hasSkillAuditFactory,
2178
2889
  linkRecord,
@@ -2181,12 +2892,14 @@ var import_sdk = require("@frontmcp/sdk");
2181
2892
  npmSourceSchema,
2182
2893
  parseOverlay,
2183
2894
  parseSkillFrontmatter,
2895
+ renderResourceUri,
2184
2896
  resolveSkillLoadOrder,
2185
2897
  resolvedBundleSchema,
2186
2898
  saasSourceSchema,
2187
2899
  setSkillAuditFactory,
2188
2900
  signatureKeySchema,
2189
2901
  staticSourceSchema,
2902
+ validateOpReferences,
2190
2903
  verifyBundleSignature,
2191
2904
  verifyChain
2192
2905
  });