@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/esm/package.json +8 -8
- package/esm/skills/index.mjs +742 -32
- package/package.json +8 -8
- package/skills/classifier/classification-registry.d.ts +59 -0
- package/skills/classifier/classification-registry.d.ts.map +1 -0
- package/skills/classifier/openapi-classify.d.ts +67 -0
- package/skills/classifier/openapi-classify.d.ts.map +1 -0
- package/skills/classifier/render-resource-uri.d.ts +28 -0
- package/skills/classifier/render-resource-uri.d.ts.map +1 -0
- package/skills/classifier/resource-change-notification.d.ts +41 -0
- package/skills/classifier/resource-change-notification.d.ts.map +1 -0
- package/skills/deploy/deploy-manifest.schema.d.ts +448 -0
- package/skills/deploy/deploy-manifest.schema.d.ts.map +1 -0
- package/skills/harvester/op-reference.d.ts +96 -0
- package/skills/harvester/op-reference.d.ts.map +1 -0
- package/skills/index.d.ts +6 -0
- package/skills/index.d.ts.map +1 -1
- package/skills/index.js +781 -68
- package/skills/sources/filesystem-skills.source.d.ts.map +1 -1
- package/skills/sources/saas-pull.source.d.ts.map +1 -1
- package/skills/sources/static.source.d.ts.map +1 -1
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
|
|
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
|
|
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
|
|
896
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
963
|
-
const raw = await (0,
|
|
964
|
-
const ext =
|
|
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 =
|
|
996
|
+
const absPath = (0, import_utils2.pathResolve)(this.options.path);
|
|
987
997
|
try {
|
|
988
|
-
this.watcher = (0,
|
|
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
|
|
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,
|
|
1067
|
+
const absRoot = (0, import_utils3.pathResolve)(this.options.skillsDir);
|
|
1060
1068
|
try {
|
|
1061
|
-
this.rootResolved = await (0,
|
|
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,
|
|
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,
|
|
1113
|
+
const abs = (0, import_utils3.joinPath)(this.rootResolved, name);
|
|
1106
1114
|
let resolved;
|
|
1107
1115
|
try {
|
|
1108
|
-
resolved = await (0,
|
|
1116
|
+
resolved = await (0, import_utils3.realpath)(abs);
|
|
1109
1117
|
} catch {
|
|
1110
1118
|
continue;
|
|
1111
1119
|
}
|
|
1112
|
-
if (!resolved.startsWith(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,
|
|
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,
|
|
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,
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
this.
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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,
|
|
1247
|
-
const raw = await (0,
|
|
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,
|
|
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,
|
|
1274
|
+
const subAbs = (0, import_utils3.joinPath)(absDir, sub);
|
|
1263
1275
|
try {
|
|
1264
|
-
const st = await (0,
|
|
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
|
|
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,
|
|
1375
|
+
return (0, import_utils4.sha256Hex)(canonicalize(rest));
|
|
1364
1376
|
}
|
|
1365
1377
|
function decodeSignatureBytes(signatureBase64Url) {
|
|
1366
1378
|
try {
|
|
1367
|
-
return (0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
1714
|
+
const mac = (0, import_utils7.hmacSha256)(this.key, canonicalBytes);
|
|
1703
1715
|
return {
|
|
1704
|
-
signature: (0,
|
|
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,
|
|
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,
|
|
1781
|
+
const expected = (0, import_utils7.hmacSha256)(trusted.secret, data);
|
|
1770
1782
|
let actual;
|
|
1771
1783
|
try {
|
|
1772
|
-
actual = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
1801
|
+
sigBytes = (0, import_utils7.base64urlDecode)(signatureBase64Url);
|
|
1790
1802
|
} catch {
|
|
1791
1803
|
return false;
|
|
1792
1804
|
}
|
|
1793
|
-
return (0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
2081
|
-
const hex = (0,
|
|
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,
|
|
2120
|
+
return (0, import_utils8.sha256Hex)(canonicalize(value ?? null));
|
|
2109
2121
|
} catch {
|
|
2110
|
-
return (0,
|
|
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
|
});
|