@agentwares/agentguard 0.1.7 → 0.1.9
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/CHANGELOG.md +75 -0
- package/README.md +223 -16
- package/dist/audit-2YCMHMCP.js +17 -0
- package/dist/audit-2YCMHMCP.js.map +1 -0
- package/dist/chunk-3O6KWT3E.js +345 -0
- package/dist/chunk-3O6KWT3E.js.map +1 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/chunk-6S7LYVOC.js +1033 -0
- package/dist/chunk-6S7LYVOC.js.map +1 -0
- package/dist/chunk-6TUQD666.js +828 -0
- package/dist/chunk-6TUQD666.js.map +1 -0
- package/dist/chunk-BKXXTWIS.js +119 -0
- package/dist/chunk-BKXXTWIS.js.map +1 -0
- package/dist/chunk-D66U3PVB.js +529 -0
- package/dist/chunk-D66U3PVB.js.map +1 -0
- package/dist/chunk-HPYZ3JDX.js +206 -0
- package/dist/chunk-HPYZ3JDX.js.map +1 -0
- package/dist/chunk-KRKZGAJ3.js +769 -0
- package/dist/chunk-KRKZGAJ3.js.map +1 -0
- package/dist/chunk-SUXQTT3X.js +1268 -0
- package/dist/chunk-SUXQTT3X.js.map +1 -0
- package/dist/chunk-TW5KJREH.js +190 -0
- package/dist/chunk-TW5KJREH.js.map +1 -0
- package/dist/chunk-VQWQ5TOM.js +113 -0
- package/dist/chunk-VQWQ5TOM.js.map +1 -0
- package/dist/chunk-X4M7YWQA.js +270 -0
- package/dist/chunk-X4M7YWQA.js.map +1 -0
- package/dist/chunk-ZSLWPKXI.js +1144 -0
- package/dist/chunk-ZSLWPKXI.js.map +1 -0
- package/dist/cli.js +8 -4775
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +6 -1272
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/fixtures/demo-agent.js +4 -115
- package/dist/fixtures/demo-agent.js.map +1 -1
- package/dist/index.d.ts +69 -10
- package/dist/index.js +245 -5138
- package/dist/index.js.map +1 -1
- package/dist/init-CWROGV5I.js +15 -0
- package/dist/init-CWROGV5I.js.map +1 -0
- package/dist/proxy-3Z5YEZ4R.js +154 -0
- package/dist/proxy-3Z5YEZ4R.js.map +1 -0
- package/dist/tools-3LLDG3XQ.js +15 -0
- package/dist/tools-3LLDG3XQ.js.map +1 -0
- package/llms.txt +4 -0
- package/package.json +9 -3
- package/server.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,4781 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
|
|
28
|
-
// ../../node_modules/.pnpm/standardwebhooks@1.0.0/node_modules/standardwebhooks/dist/timing_safe_equal.js
|
|
29
|
-
var require_timing_safe_equal = __commonJS({
|
|
30
|
-
"../../node_modules/.pnpm/standardwebhooks@1.0.0/node_modules/standardwebhooks/dist/timing_safe_equal.js"(exports) {
|
|
31
|
-
"use strict";
|
|
32
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.timingSafeEqual = void 0;
|
|
34
|
-
function assert(expr, msg = "") {
|
|
35
|
-
if (!expr) {
|
|
36
|
-
throw new Error(msg);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function timingSafeEqual(a, b) {
|
|
40
|
-
if (a.byteLength !== b.byteLength) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
if (!(a instanceof DataView)) {
|
|
44
|
-
a = new DataView(ArrayBuffer.isView(a) ? a.buffer : a);
|
|
45
|
-
}
|
|
46
|
-
if (!(b instanceof DataView)) {
|
|
47
|
-
b = new DataView(ArrayBuffer.isView(b) ? b.buffer : b);
|
|
48
|
-
}
|
|
49
|
-
assert(a instanceof DataView);
|
|
50
|
-
assert(b instanceof DataView);
|
|
51
|
-
const length = a.byteLength;
|
|
52
|
-
let out = 0;
|
|
53
|
-
let i = -1;
|
|
54
|
-
while (++i < length) {
|
|
55
|
-
out |= a.getUint8(i) ^ b.getUint8(i);
|
|
56
|
-
}
|
|
57
|
-
return out === 0;
|
|
58
|
-
}
|
|
59
|
-
exports.timingSafeEqual = timingSafeEqual;
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// ../../node_modules/.pnpm/@stablelib+base64@1.0.1/node_modules/@stablelib/base64/lib/base64.js
|
|
64
|
-
var require_base64 = __commonJS({
|
|
65
|
-
"../../node_modules/.pnpm/@stablelib+base64@1.0.1/node_modules/@stablelib/base64/lib/base64.js"(exports) {
|
|
66
|
-
"use strict";
|
|
67
|
-
var __extends = exports && exports.__extends || /* @__PURE__ */ (function() {
|
|
68
|
-
var extendStatics = function(d, b) {
|
|
69
|
-
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
|
|
70
|
-
d2.__proto__ = b2;
|
|
71
|
-
} || function(d2, b2) {
|
|
72
|
-
for (var p in b2) if (b2.hasOwnProperty(p)) d2[p] = b2[p];
|
|
73
|
-
};
|
|
74
|
-
return extendStatics(d, b);
|
|
75
|
-
};
|
|
76
|
-
return function(d, b) {
|
|
77
|
-
extendStatics(d, b);
|
|
78
|
-
function __() {
|
|
79
|
-
this.constructor = d;
|
|
80
|
-
}
|
|
81
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
82
|
-
};
|
|
83
|
-
})();
|
|
84
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
85
|
-
var INVALID_BYTE = 256;
|
|
86
|
-
var Coder = (
|
|
87
|
-
/** @class */
|
|
88
|
-
(function() {
|
|
89
|
-
function Coder2(_paddingCharacter) {
|
|
90
|
-
if (_paddingCharacter === void 0) {
|
|
91
|
-
_paddingCharacter = "=";
|
|
92
|
-
}
|
|
93
|
-
this._paddingCharacter = _paddingCharacter;
|
|
94
|
-
}
|
|
95
|
-
Coder2.prototype.encodedLength = function(length) {
|
|
96
|
-
if (!this._paddingCharacter) {
|
|
97
|
-
return (length * 8 + 5) / 6 | 0;
|
|
98
|
-
}
|
|
99
|
-
return (length + 2) / 3 * 4 | 0;
|
|
100
|
-
};
|
|
101
|
-
Coder2.prototype.encode = function(data) {
|
|
102
|
-
var out = "";
|
|
103
|
-
var i = 0;
|
|
104
|
-
for (; i < data.length - 2; i += 3) {
|
|
105
|
-
var c = data[i] << 16 | data[i + 1] << 8 | data[i + 2];
|
|
106
|
-
out += this._encodeByte(c >>> 3 * 6 & 63);
|
|
107
|
-
out += this._encodeByte(c >>> 2 * 6 & 63);
|
|
108
|
-
out += this._encodeByte(c >>> 1 * 6 & 63);
|
|
109
|
-
out += this._encodeByte(c >>> 0 * 6 & 63);
|
|
110
|
-
}
|
|
111
|
-
var left = data.length - i;
|
|
112
|
-
if (left > 0) {
|
|
113
|
-
var c = data[i] << 16 | (left === 2 ? data[i + 1] << 8 : 0);
|
|
114
|
-
out += this._encodeByte(c >>> 3 * 6 & 63);
|
|
115
|
-
out += this._encodeByte(c >>> 2 * 6 & 63);
|
|
116
|
-
if (left === 2) {
|
|
117
|
-
out += this._encodeByte(c >>> 1 * 6 & 63);
|
|
118
|
-
} else {
|
|
119
|
-
out += this._paddingCharacter || "";
|
|
120
|
-
}
|
|
121
|
-
out += this._paddingCharacter || "";
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
};
|
|
125
|
-
Coder2.prototype.maxDecodedLength = function(length) {
|
|
126
|
-
if (!this._paddingCharacter) {
|
|
127
|
-
return (length * 6 + 7) / 8 | 0;
|
|
128
|
-
}
|
|
129
|
-
return length / 4 * 3 | 0;
|
|
130
|
-
};
|
|
131
|
-
Coder2.prototype.decodedLength = function(s) {
|
|
132
|
-
return this.maxDecodedLength(s.length - this._getPaddingLength(s));
|
|
133
|
-
};
|
|
134
|
-
Coder2.prototype.decode = function(s) {
|
|
135
|
-
if (s.length === 0) {
|
|
136
|
-
return new Uint8Array(0);
|
|
137
|
-
}
|
|
138
|
-
var paddingLength = this._getPaddingLength(s);
|
|
139
|
-
var length = s.length - paddingLength;
|
|
140
|
-
var out = new Uint8Array(this.maxDecodedLength(length));
|
|
141
|
-
var op = 0;
|
|
142
|
-
var i = 0;
|
|
143
|
-
var haveBad = 0;
|
|
144
|
-
var v0 = 0, v1 = 0, v2 = 0, v3 = 0;
|
|
145
|
-
for (; i < length - 4; i += 4) {
|
|
146
|
-
v0 = this._decodeChar(s.charCodeAt(i + 0));
|
|
147
|
-
v1 = this._decodeChar(s.charCodeAt(i + 1));
|
|
148
|
-
v2 = this._decodeChar(s.charCodeAt(i + 2));
|
|
149
|
-
v3 = this._decodeChar(s.charCodeAt(i + 3));
|
|
150
|
-
out[op++] = v0 << 2 | v1 >>> 4;
|
|
151
|
-
out[op++] = v1 << 4 | v2 >>> 2;
|
|
152
|
-
out[op++] = v2 << 6 | v3;
|
|
153
|
-
haveBad |= v0 & INVALID_BYTE;
|
|
154
|
-
haveBad |= v1 & INVALID_BYTE;
|
|
155
|
-
haveBad |= v2 & INVALID_BYTE;
|
|
156
|
-
haveBad |= v3 & INVALID_BYTE;
|
|
157
|
-
}
|
|
158
|
-
if (i < length - 1) {
|
|
159
|
-
v0 = this._decodeChar(s.charCodeAt(i));
|
|
160
|
-
v1 = this._decodeChar(s.charCodeAt(i + 1));
|
|
161
|
-
out[op++] = v0 << 2 | v1 >>> 4;
|
|
162
|
-
haveBad |= v0 & INVALID_BYTE;
|
|
163
|
-
haveBad |= v1 & INVALID_BYTE;
|
|
164
|
-
}
|
|
165
|
-
if (i < length - 2) {
|
|
166
|
-
v2 = this._decodeChar(s.charCodeAt(i + 2));
|
|
167
|
-
out[op++] = v1 << 4 | v2 >>> 2;
|
|
168
|
-
haveBad |= v2 & INVALID_BYTE;
|
|
169
|
-
}
|
|
170
|
-
if (i < length - 3) {
|
|
171
|
-
v3 = this._decodeChar(s.charCodeAt(i + 3));
|
|
172
|
-
out[op++] = v2 << 6 | v3;
|
|
173
|
-
haveBad |= v3 & INVALID_BYTE;
|
|
174
|
-
}
|
|
175
|
-
if (haveBad !== 0) {
|
|
176
|
-
throw new Error("Base64Coder: incorrect characters for decoding");
|
|
177
|
-
}
|
|
178
|
-
return out;
|
|
179
|
-
};
|
|
180
|
-
Coder2.prototype._encodeByte = function(b) {
|
|
181
|
-
var result = b;
|
|
182
|
-
result += 65;
|
|
183
|
-
result += 25 - b >>> 8 & 0 - 65 - 26 + 97;
|
|
184
|
-
result += 51 - b >>> 8 & 26 - 97 - 52 + 48;
|
|
185
|
-
result += 61 - b >>> 8 & 52 - 48 - 62 + 43;
|
|
186
|
-
result += 62 - b >>> 8 & 62 - 43 - 63 + 47;
|
|
187
|
-
return String.fromCharCode(result);
|
|
188
|
-
};
|
|
189
|
-
Coder2.prototype._decodeChar = function(c) {
|
|
190
|
-
var result = INVALID_BYTE;
|
|
191
|
-
result += (42 - c & c - 44) >>> 8 & -INVALID_BYTE + c - 43 + 62;
|
|
192
|
-
result += (46 - c & c - 48) >>> 8 & -INVALID_BYTE + c - 47 + 63;
|
|
193
|
-
result += (47 - c & c - 58) >>> 8 & -INVALID_BYTE + c - 48 + 52;
|
|
194
|
-
result += (64 - c & c - 91) >>> 8 & -INVALID_BYTE + c - 65 + 0;
|
|
195
|
-
result += (96 - c & c - 123) >>> 8 & -INVALID_BYTE + c - 97 + 26;
|
|
196
|
-
return result;
|
|
197
|
-
};
|
|
198
|
-
Coder2.prototype._getPaddingLength = function(s) {
|
|
199
|
-
var paddingLength = 0;
|
|
200
|
-
if (this._paddingCharacter) {
|
|
201
|
-
for (var i = s.length - 1; i >= 0; i--) {
|
|
202
|
-
if (s[i] !== this._paddingCharacter) {
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
paddingLength++;
|
|
206
|
-
}
|
|
207
|
-
if (s.length < 4 || paddingLength > 2) {
|
|
208
|
-
throw new Error("Base64Coder: incorrect padding");
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return paddingLength;
|
|
212
|
-
};
|
|
213
|
-
return Coder2;
|
|
214
|
-
})()
|
|
215
|
-
);
|
|
216
|
-
exports.Coder = Coder;
|
|
217
|
-
var stdCoder = new Coder();
|
|
218
|
-
function encode(data) {
|
|
219
|
-
return stdCoder.encode(data);
|
|
220
|
-
}
|
|
221
|
-
exports.encode = encode;
|
|
222
|
-
function decode(s) {
|
|
223
|
-
return stdCoder.decode(s);
|
|
224
|
-
}
|
|
225
|
-
exports.decode = decode;
|
|
226
|
-
var URLSafeCoder = (
|
|
227
|
-
/** @class */
|
|
228
|
-
(function(_super) {
|
|
229
|
-
__extends(URLSafeCoder2, _super);
|
|
230
|
-
function URLSafeCoder2() {
|
|
231
|
-
return _super !== null && _super.apply(this, arguments) || this;
|
|
232
|
-
}
|
|
233
|
-
URLSafeCoder2.prototype._encodeByte = function(b) {
|
|
234
|
-
var result = b;
|
|
235
|
-
result += 65;
|
|
236
|
-
result += 25 - b >>> 8 & 0 - 65 - 26 + 97;
|
|
237
|
-
result += 51 - b >>> 8 & 26 - 97 - 52 + 48;
|
|
238
|
-
result += 61 - b >>> 8 & 52 - 48 - 62 + 45;
|
|
239
|
-
result += 62 - b >>> 8 & 62 - 45 - 63 + 95;
|
|
240
|
-
return String.fromCharCode(result);
|
|
241
|
-
};
|
|
242
|
-
URLSafeCoder2.prototype._decodeChar = function(c) {
|
|
243
|
-
var result = INVALID_BYTE;
|
|
244
|
-
result += (44 - c & c - 46) >>> 8 & -INVALID_BYTE + c - 45 + 62;
|
|
245
|
-
result += (94 - c & c - 96) >>> 8 & -INVALID_BYTE + c - 95 + 63;
|
|
246
|
-
result += (47 - c & c - 58) >>> 8 & -INVALID_BYTE + c - 48 + 52;
|
|
247
|
-
result += (64 - c & c - 91) >>> 8 & -INVALID_BYTE + c - 65 + 0;
|
|
248
|
-
result += (96 - c & c - 123) >>> 8 & -INVALID_BYTE + c - 97 + 26;
|
|
249
|
-
return result;
|
|
250
|
-
};
|
|
251
|
-
return URLSafeCoder2;
|
|
252
|
-
})(Coder)
|
|
253
|
-
);
|
|
254
|
-
exports.URLSafeCoder = URLSafeCoder;
|
|
255
|
-
var urlSafeCoder = new URLSafeCoder();
|
|
256
|
-
function encodeURLSafe(data) {
|
|
257
|
-
return urlSafeCoder.encode(data);
|
|
258
|
-
}
|
|
259
|
-
exports.encodeURLSafe = encodeURLSafe;
|
|
260
|
-
function decodeURLSafe(s) {
|
|
261
|
-
return urlSafeCoder.decode(s);
|
|
262
|
-
}
|
|
263
|
-
exports.decodeURLSafe = decodeURLSafe;
|
|
264
|
-
exports.encodedLength = function(length) {
|
|
265
|
-
return stdCoder.encodedLength(length);
|
|
266
|
-
};
|
|
267
|
-
exports.maxDecodedLength = function(length) {
|
|
268
|
-
return stdCoder.maxDecodedLength(length);
|
|
269
|
-
};
|
|
270
|
-
exports.decodedLength = function(s) {
|
|
271
|
-
return stdCoder.decodedLength(s);
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// ../../node_modules/.pnpm/fast-sha256@1.3.0/node_modules/fast-sha256/sha256.js
|
|
277
|
-
var require_sha256 = __commonJS({
|
|
278
|
-
"../../node_modules/.pnpm/fast-sha256@1.3.0/node_modules/fast-sha256/sha256.js"(exports, module) {
|
|
279
|
-
"use strict";
|
|
280
|
-
(function(root, factory) {
|
|
281
|
-
var exports2 = {};
|
|
282
|
-
factory(exports2);
|
|
283
|
-
var sha256 = exports2["default"];
|
|
284
|
-
for (var k in exports2) {
|
|
285
|
-
sha256[k] = exports2[k];
|
|
286
|
-
}
|
|
287
|
-
if (typeof module === "object" && typeof module.exports === "object") {
|
|
288
|
-
module.exports = sha256;
|
|
289
|
-
} else if (typeof define === "function" && define.amd) {
|
|
290
|
-
define(function() {
|
|
291
|
-
return sha256;
|
|
292
|
-
});
|
|
293
|
-
} else {
|
|
294
|
-
root.sha256 = sha256;
|
|
295
|
-
}
|
|
296
|
-
})(exports, function(exports2) {
|
|
297
|
-
"use strict";
|
|
298
|
-
exports2.__esModule = true;
|
|
299
|
-
exports2.digestLength = 32;
|
|
300
|
-
exports2.blockSize = 64;
|
|
301
|
-
var K = new Uint32Array([
|
|
302
|
-
1116352408,
|
|
303
|
-
1899447441,
|
|
304
|
-
3049323471,
|
|
305
|
-
3921009573,
|
|
306
|
-
961987163,
|
|
307
|
-
1508970993,
|
|
308
|
-
2453635748,
|
|
309
|
-
2870763221,
|
|
310
|
-
3624381080,
|
|
311
|
-
310598401,
|
|
312
|
-
607225278,
|
|
313
|
-
1426881987,
|
|
314
|
-
1925078388,
|
|
315
|
-
2162078206,
|
|
316
|
-
2614888103,
|
|
317
|
-
3248222580,
|
|
318
|
-
3835390401,
|
|
319
|
-
4022224774,
|
|
320
|
-
264347078,
|
|
321
|
-
604807628,
|
|
322
|
-
770255983,
|
|
323
|
-
1249150122,
|
|
324
|
-
1555081692,
|
|
325
|
-
1996064986,
|
|
326
|
-
2554220882,
|
|
327
|
-
2821834349,
|
|
328
|
-
2952996808,
|
|
329
|
-
3210313671,
|
|
330
|
-
3336571891,
|
|
331
|
-
3584528711,
|
|
332
|
-
113926993,
|
|
333
|
-
338241895,
|
|
334
|
-
666307205,
|
|
335
|
-
773529912,
|
|
336
|
-
1294757372,
|
|
337
|
-
1396182291,
|
|
338
|
-
1695183700,
|
|
339
|
-
1986661051,
|
|
340
|
-
2177026350,
|
|
341
|
-
2456956037,
|
|
342
|
-
2730485921,
|
|
343
|
-
2820302411,
|
|
344
|
-
3259730800,
|
|
345
|
-
3345764771,
|
|
346
|
-
3516065817,
|
|
347
|
-
3600352804,
|
|
348
|
-
4094571909,
|
|
349
|
-
275423344,
|
|
350
|
-
430227734,
|
|
351
|
-
506948616,
|
|
352
|
-
659060556,
|
|
353
|
-
883997877,
|
|
354
|
-
958139571,
|
|
355
|
-
1322822218,
|
|
356
|
-
1537002063,
|
|
357
|
-
1747873779,
|
|
358
|
-
1955562222,
|
|
359
|
-
2024104815,
|
|
360
|
-
2227730452,
|
|
361
|
-
2361852424,
|
|
362
|
-
2428436474,
|
|
363
|
-
2756734187,
|
|
364
|
-
3204031479,
|
|
365
|
-
3329325298
|
|
366
|
-
]);
|
|
367
|
-
function hashBlocks(w, v, p, pos, len) {
|
|
368
|
-
var a, b, c, d, e, f, g, h, u, i, j, t1, t2;
|
|
369
|
-
while (len >= 64) {
|
|
370
|
-
a = v[0];
|
|
371
|
-
b = v[1];
|
|
372
|
-
c = v[2];
|
|
373
|
-
d = v[3];
|
|
374
|
-
e = v[4];
|
|
375
|
-
f = v[5];
|
|
376
|
-
g = v[6];
|
|
377
|
-
h = v[7];
|
|
378
|
-
for (i = 0; i < 16; i++) {
|
|
379
|
-
j = pos + i * 4;
|
|
380
|
-
w[i] = (p[j] & 255) << 24 | (p[j + 1] & 255) << 16 | (p[j + 2] & 255) << 8 | p[j + 3] & 255;
|
|
381
|
-
}
|
|
382
|
-
for (i = 16; i < 64; i++) {
|
|
383
|
-
u = w[i - 2];
|
|
384
|
-
t1 = (u >>> 17 | u << 32 - 17) ^ (u >>> 19 | u << 32 - 19) ^ u >>> 10;
|
|
385
|
-
u = w[i - 15];
|
|
386
|
-
t2 = (u >>> 7 | u << 32 - 7) ^ (u >>> 18 | u << 32 - 18) ^ u >>> 3;
|
|
387
|
-
w[i] = (t1 + w[i - 7] | 0) + (t2 + w[i - 16] | 0);
|
|
388
|
-
}
|
|
389
|
-
for (i = 0; i < 64; i++) {
|
|
390
|
-
t1 = (((e >>> 6 | e << 32 - 6) ^ (e >>> 11 | e << 32 - 11) ^ (e >>> 25 | e << 32 - 25)) + (e & f ^ ~e & g) | 0) + (h + (K[i] + w[i] | 0) | 0) | 0;
|
|
391
|
-
t2 = ((a >>> 2 | a << 32 - 2) ^ (a >>> 13 | a << 32 - 13) ^ (a >>> 22 | a << 32 - 22)) + (a & b ^ a & c ^ b & c) | 0;
|
|
392
|
-
h = g;
|
|
393
|
-
g = f;
|
|
394
|
-
f = e;
|
|
395
|
-
e = d + t1 | 0;
|
|
396
|
-
d = c;
|
|
397
|
-
c = b;
|
|
398
|
-
b = a;
|
|
399
|
-
a = t1 + t2 | 0;
|
|
400
|
-
}
|
|
401
|
-
v[0] += a;
|
|
402
|
-
v[1] += b;
|
|
403
|
-
v[2] += c;
|
|
404
|
-
v[3] += d;
|
|
405
|
-
v[4] += e;
|
|
406
|
-
v[5] += f;
|
|
407
|
-
v[6] += g;
|
|
408
|
-
v[7] += h;
|
|
409
|
-
pos += 64;
|
|
410
|
-
len -= 64;
|
|
411
|
-
}
|
|
412
|
-
return pos;
|
|
413
|
-
}
|
|
414
|
-
var Hash = (
|
|
415
|
-
/** @class */
|
|
416
|
-
(function() {
|
|
417
|
-
function Hash2() {
|
|
418
|
-
this.digestLength = exports2.digestLength;
|
|
419
|
-
this.blockSize = exports2.blockSize;
|
|
420
|
-
this.state = new Int32Array(8);
|
|
421
|
-
this.temp = new Int32Array(64);
|
|
422
|
-
this.buffer = new Uint8Array(128);
|
|
423
|
-
this.bufferLength = 0;
|
|
424
|
-
this.bytesHashed = 0;
|
|
425
|
-
this.finished = false;
|
|
426
|
-
this.reset();
|
|
427
|
-
}
|
|
428
|
-
Hash2.prototype.reset = function() {
|
|
429
|
-
this.state[0] = 1779033703;
|
|
430
|
-
this.state[1] = 3144134277;
|
|
431
|
-
this.state[2] = 1013904242;
|
|
432
|
-
this.state[3] = 2773480762;
|
|
433
|
-
this.state[4] = 1359893119;
|
|
434
|
-
this.state[5] = 2600822924;
|
|
435
|
-
this.state[6] = 528734635;
|
|
436
|
-
this.state[7] = 1541459225;
|
|
437
|
-
this.bufferLength = 0;
|
|
438
|
-
this.bytesHashed = 0;
|
|
439
|
-
this.finished = false;
|
|
440
|
-
return this;
|
|
441
|
-
};
|
|
442
|
-
Hash2.prototype.clean = function() {
|
|
443
|
-
for (var i = 0; i < this.buffer.length; i++) {
|
|
444
|
-
this.buffer[i] = 0;
|
|
445
|
-
}
|
|
446
|
-
for (var i = 0; i < this.temp.length; i++) {
|
|
447
|
-
this.temp[i] = 0;
|
|
448
|
-
}
|
|
449
|
-
this.reset();
|
|
450
|
-
};
|
|
451
|
-
Hash2.prototype.update = function(data, dataLength) {
|
|
452
|
-
if (dataLength === void 0) {
|
|
453
|
-
dataLength = data.length;
|
|
454
|
-
}
|
|
455
|
-
if (this.finished) {
|
|
456
|
-
throw new Error("SHA256: can't update because hash was finished.");
|
|
457
|
-
}
|
|
458
|
-
var dataPos = 0;
|
|
459
|
-
this.bytesHashed += dataLength;
|
|
460
|
-
if (this.bufferLength > 0) {
|
|
461
|
-
while (this.bufferLength < 64 && dataLength > 0) {
|
|
462
|
-
this.buffer[this.bufferLength++] = data[dataPos++];
|
|
463
|
-
dataLength--;
|
|
464
|
-
}
|
|
465
|
-
if (this.bufferLength === 64) {
|
|
466
|
-
hashBlocks(this.temp, this.state, this.buffer, 0, 64);
|
|
467
|
-
this.bufferLength = 0;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
if (dataLength >= 64) {
|
|
471
|
-
dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength);
|
|
472
|
-
dataLength %= 64;
|
|
473
|
-
}
|
|
474
|
-
while (dataLength > 0) {
|
|
475
|
-
this.buffer[this.bufferLength++] = data[dataPos++];
|
|
476
|
-
dataLength--;
|
|
477
|
-
}
|
|
478
|
-
return this;
|
|
479
|
-
};
|
|
480
|
-
Hash2.prototype.finish = function(out) {
|
|
481
|
-
if (!this.finished) {
|
|
482
|
-
var bytesHashed = this.bytesHashed;
|
|
483
|
-
var left = this.bufferLength;
|
|
484
|
-
var bitLenHi = bytesHashed / 536870912 | 0;
|
|
485
|
-
var bitLenLo = bytesHashed << 3;
|
|
486
|
-
var padLength = bytesHashed % 64 < 56 ? 64 : 128;
|
|
487
|
-
this.buffer[left] = 128;
|
|
488
|
-
for (var i = left + 1; i < padLength - 8; i++) {
|
|
489
|
-
this.buffer[i] = 0;
|
|
490
|
-
}
|
|
491
|
-
this.buffer[padLength - 8] = bitLenHi >>> 24 & 255;
|
|
492
|
-
this.buffer[padLength - 7] = bitLenHi >>> 16 & 255;
|
|
493
|
-
this.buffer[padLength - 6] = bitLenHi >>> 8 & 255;
|
|
494
|
-
this.buffer[padLength - 5] = bitLenHi >>> 0 & 255;
|
|
495
|
-
this.buffer[padLength - 4] = bitLenLo >>> 24 & 255;
|
|
496
|
-
this.buffer[padLength - 3] = bitLenLo >>> 16 & 255;
|
|
497
|
-
this.buffer[padLength - 2] = bitLenLo >>> 8 & 255;
|
|
498
|
-
this.buffer[padLength - 1] = bitLenLo >>> 0 & 255;
|
|
499
|
-
hashBlocks(this.temp, this.state, this.buffer, 0, padLength);
|
|
500
|
-
this.finished = true;
|
|
501
|
-
}
|
|
502
|
-
for (var i = 0; i < 8; i++) {
|
|
503
|
-
out[i * 4 + 0] = this.state[i] >>> 24 & 255;
|
|
504
|
-
out[i * 4 + 1] = this.state[i] >>> 16 & 255;
|
|
505
|
-
out[i * 4 + 2] = this.state[i] >>> 8 & 255;
|
|
506
|
-
out[i * 4 + 3] = this.state[i] >>> 0 & 255;
|
|
507
|
-
}
|
|
508
|
-
return this;
|
|
509
|
-
};
|
|
510
|
-
Hash2.prototype.digest = function() {
|
|
511
|
-
var out = new Uint8Array(this.digestLength);
|
|
512
|
-
this.finish(out);
|
|
513
|
-
return out;
|
|
514
|
-
};
|
|
515
|
-
Hash2.prototype._saveState = function(out) {
|
|
516
|
-
for (var i = 0; i < this.state.length; i++) {
|
|
517
|
-
out[i] = this.state[i];
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
Hash2.prototype._restoreState = function(from, bytesHashed) {
|
|
521
|
-
for (var i = 0; i < this.state.length; i++) {
|
|
522
|
-
this.state[i] = from[i];
|
|
523
|
-
}
|
|
524
|
-
this.bytesHashed = bytesHashed;
|
|
525
|
-
this.finished = false;
|
|
526
|
-
this.bufferLength = 0;
|
|
527
|
-
};
|
|
528
|
-
return Hash2;
|
|
529
|
-
})()
|
|
530
|
-
);
|
|
531
|
-
exports2.Hash = Hash;
|
|
532
|
-
var HMAC = (
|
|
533
|
-
/** @class */
|
|
534
|
-
(function() {
|
|
535
|
-
function HMAC2(key) {
|
|
536
|
-
this.inner = new Hash();
|
|
537
|
-
this.outer = new Hash();
|
|
538
|
-
this.blockSize = this.inner.blockSize;
|
|
539
|
-
this.digestLength = this.inner.digestLength;
|
|
540
|
-
var pad = new Uint8Array(this.blockSize);
|
|
541
|
-
if (key.length > this.blockSize) {
|
|
542
|
-
new Hash().update(key).finish(pad).clean();
|
|
543
|
-
} else {
|
|
544
|
-
for (var i = 0; i < key.length; i++) {
|
|
545
|
-
pad[i] = key[i];
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
for (var i = 0; i < pad.length; i++) {
|
|
549
|
-
pad[i] ^= 54;
|
|
550
|
-
}
|
|
551
|
-
this.inner.update(pad);
|
|
552
|
-
for (var i = 0; i < pad.length; i++) {
|
|
553
|
-
pad[i] ^= 54 ^ 92;
|
|
554
|
-
}
|
|
555
|
-
this.outer.update(pad);
|
|
556
|
-
this.istate = new Uint32Array(8);
|
|
557
|
-
this.ostate = new Uint32Array(8);
|
|
558
|
-
this.inner._saveState(this.istate);
|
|
559
|
-
this.outer._saveState(this.ostate);
|
|
560
|
-
for (var i = 0; i < pad.length; i++) {
|
|
561
|
-
pad[i] = 0;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
HMAC2.prototype.reset = function() {
|
|
565
|
-
this.inner._restoreState(this.istate, this.inner.blockSize);
|
|
566
|
-
this.outer._restoreState(this.ostate, this.outer.blockSize);
|
|
567
|
-
return this;
|
|
568
|
-
};
|
|
569
|
-
HMAC2.prototype.clean = function() {
|
|
570
|
-
for (var i = 0; i < this.istate.length; i++) {
|
|
571
|
-
this.ostate[i] = this.istate[i] = 0;
|
|
572
|
-
}
|
|
573
|
-
this.inner.clean();
|
|
574
|
-
this.outer.clean();
|
|
575
|
-
};
|
|
576
|
-
HMAC2.prototype.update = function(data) {
|
|
577
|
-
this.inner.update(data);
|
|
578
|
-
return this;
|
|
579
|
-
};
|
|
580
|
-
HMAC2.prototype.finish = function(out) {
|
|
581
|
-
if (this.outer.finished) {
|
|
582
|
-
this.outer.finish(out);
|
|
583
|
-
} else {
|
|
584
|
-
this.inner.finish(out);
|
|
585
|
-
this.outer.update(out, this.digestLength).finish(out);
|
|
586
|
-
}
|
|
587
|
-
return this;
|
|
588
|
-
};
|
|
589
|
-
HMAC2.prototype.digest = function() {
|
|
590
|
-
var out = new Uint8Array(this.digestLength);
|
|
591
|
-
this.finish(out);
|
|
592
|
-
return out;
|
|
593
|
-
};
|
|
594
|
-
return HMAC2;
|
|
595
|
-
})()
|
|
596
|
-
);
|
|
597
|
-
exports2.HMAC = HMAC;
|
|
598
|
-
function hash(data) {
|
|
599
|
-
var h = new Hash().update(data);
|
|
600
|
-
var digest = h.digest();
|
|
601
|
-
h.clean();
|
|
602
|
-
return digest;
|
|
603
|
-
}
|
|
604
|
-
exports2.hash = hash;
|
|
605
|
-
exports2["default"] = hash;
|
|
606
|
-
function hmac(key, data) {
|
|
607
|
-
var h = new HMAC(key).update(data);
|
|
608
|
-
var digest = h.digest();
|
|
609
|
-
h.clean();
|
|
610
|
-
return digest;
|
|
611
|
-
}
|
|
612
|
-
exports2.hmac = hmac;
|
|
613
|
-
function fillBuffer(buffer, hmac2, info, counter) {
|
|
614
|
-
var num = counter[0];
|
|
615
|
-
if (num === 0) {
|
|
616
|
-
throw new Error("hkdf: cannot expand more");
|
|
617
|
-
}
|
|
618
|
-
hmac2.reset();
|
|
619
|
-
if (num > 1) {
|
|
620
|
-
hmac2.update(buffer);
|
|
621
|
-
}
|
|
622
|
-
if (info) {
|
|
623
|
-
hmac2.update(info);
|
|
624
|
-
}
|
|
625
|
-
hmac2.update(counter);
|
|
626
|
-
hmac2.finish(buffer);
|
|
627
|
-
counter[0]++;
|
|
628
|
-
}
|
|
629
|
-
var hkdfSalt = new Uint8Array(exports2.digestLength);
|
|
630
|
-
function hkdf(key, salt, info, length) {
|
|
631
|
-
if (salt === void 0) {
|
|
632
|
-
salt = hkdfSalt;
|
|
633
|
-
}
|
|
634
|
-
if (length === void 0) {
|
|
635
|
-
length = 32;
|
|
636
|
-
}
|
|
637
|
-
var counter = new Uint8Array([1]);
|
|
638
|
-
var okm = hmac(salt, key);
|
|
639
|
-
var hmac_ = new HMAC(okm);
|
|
640
|
-
var buffer = new Uint8Array(hmac_.digestLength);
|
|
641
|
-
var bufpos = buffer.length;
|
|
642
|
-
var out = new Uint8Array(length);
|
|
643
|
-
for (var i = 0; i < length; i++) {
|
|
644
|
-
if (bufpos === buffer.length) {
|
|
645
|
-
fillBuffer(buffer, hmac_, info, counter);
|
|
646
|
-
bufpos = 0;
|
|
647
|
-
}
|
|
648
|
-
out[i] = buffer[bufpos++];
|
|
649
|
-
}
|
|
650
|
-
hmac_.clean();
|
|
651
|
-
buffer.fill(0);
|
|
652
|
-
counter.fill(0);
|
|
653
|
-
return out;
|
|
654
|
-
}
|
|
655
|
-
exports2.hkdf = hkdf;
|
|
656
|
-
function pbkdf2(password, salt, iterations, dkLen) {
|
|
657
|
-
var prf = new HMAC(password);
|
|
658
|
-
var len = prf.digestLength;
|
|
659
|
-
var ctr = new Uint8Array(4);
|
|
660
|
-
var t = new Uint8Array(len);
|
|
661
|
-
var u = new Uint8Array(len);
|
|
662
|
-
var dk = new Uint8Array(dkLen);
|
|
663
|
-
for (var i = 0; i * len < dkLen; i++) {
|
|
664
|
-
var c = i + 1;
|
|
665
|
-
ctr[0] = c >>> 24 & 255;
|
|
666
|
-
ctr[1] = c >>> 16 & 255;
|
|
667
|
-
ctr[2] = c >>> 8 & 255;
|
|
668
|
-
ctr[3] = c >>> 0 & 255;
|
|
669
|
-
prf.reset();
|
|
670
|
-
prf.update(salt);
|
|
671
|
-
prf.update(ctr);
|
|
672
|
-
prf.finish(u);
|
|
673
|
-
for (var j = 0; j < len; j++) {
|
|
674
|
-
t[j] = u[j];
|
|
675
|
-
}
|
|
676
|
-
for (var j = 2; j <= iterations; j++) {
|
|
677
|
-
prf.reset();
|
|
678
|
-
prf.update(u).finish(u);
|
|
679
|
-
for (var k = 0; k < len; k++) {
|
|
680
|
-
t[k] ^= u[k];
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
for (var j = 0; j < len && i * len + j < dkLen; j++) {
|
|
684
|
-
dk[i * len + j] = t[j];
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
for (var i = 0; i < len; i++) {
|
|
688
|
-
t[i] = u[i] = 0;
|
|
689
|
-
}
|
|
690
|
-
for (var i = 0; i < 4; i++) {
|
|
691
|
-
ctr[i] = 0;
|
|
692
|
-
}
|
|
693
|
-
prf.clean();
|
|
694
|
-
return dk;
|
|
695
|
-
}
|
|
696
|
-
exports2.pbkdf2 = pbkdf2;
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// ../../node_modules/.pnpm/standardwebhooks@1.0.0/node_modules/standardwebhooks/dist/index.js
|
|
702
|
-
var require_dist = __commonJS({
|
|
703
|
-
"../../node_modules/.pnpm/standardwebhooks@1.0.0/node_modules/standardwebhooks/dist/index.js"(exports) {
|
|
704
|
-
"use strict";
|
|
705
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
706
|
-
exports.Webhook = exports.WebhookVerificationError = void 0;
|
|
707
|
-
var timing_safe_equal_1 = require_timing_safe_equal();
|
|
708
|
-
var base64 = require_base64();
|
|
709
|
-
var sha256 = require_sha256();
|
|
710
|
-
var WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60;
|
|
711
|
-
var ExtendableError = class _ExtendableError extends Error {
|
|
712
|
-
constructor(message) {
|
|
713
|
-
super(message);
|
|
714
|
-
Object.setPrototypeOf(this, _ExtendableError.prototype);
|
|
715
|
-
this.name = "ExtendableError";
|
|
716
|
-
this.stack = new Error(message).stack;
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
var WebhookVerificationError = class _WebhookVerificationError extends ExtendableError {
|
|
720
|
-
constructor(message) {
|
|
721
|
-
super(message);
|
|
722
|
-
Object.setPrototypeOf(this, _WebhookVerificationError.prototype);
|
|
723
|
-
this.name = "WebhookVerificationError";
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
exports.WebhookVerificationError = WebhookVerificationError;
|
|
727
|
-
var Webhook2 = class _Webhook {
|
|
728
|
-
constructor(secret, options) {
|
|
729
|
-
if (!secret) {
|
|
730
|
-
throw new Error("Secret can't be empty.");
|
|
731
|
-
}
|
|
732
|
-
if ((options === null || options === void 0 ? void 0 : options.format) === "raw") {
|
|
733
|
-
if (secret instanceof Uint8Array) {
|
|
734
|
-
this.key = secret;
|
|
735
|
-
} else {
|
|
736
|
-
this.key = Uint8Array.from(secret, (c) => c.charCodeAt(0));
|
|
737
|
-
}
|
|
738
|
-
} else {
|
|
739
|
-
if (typeof secret !== "string") {
|
|
740
|
-
throw new Error("Expected secret to be of type string");
|
|
741
|
-
}
|
|
742
|
-
if (secret.startsWith(_Webhook.prefix)) {
|
|
743
|
-
secret = secret.substring(_Webhook.prefix.length);
|
|
744
|
-
}
|
|
745
|
-
this.key = base64.decode(secret);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
verify(payload, headers_) {
|
|
749
|
-
const headers = {};
|
|
750
|
-
for (const key of Object.keys(headers_)) {
|
|
751
|
-
headers[key.toLowerCase()] = headers_[key];
|
|
752
|
-
}
|
|
753
|
-
const msgId = headers["webhook-id"];
|
|
754
|
-
const msgSignature = headers["webhook-signature"];
|
|
755
|
-
const msgTimestamp = headers["webhook-timestamp"];
|
|
756
|
-
if (!msgSignature || !msgId || !msgTimestamp) {
|
|
757
|
-
throw new WebhookVerificationError("Missing required headers");
|
|
758
|
-
}
|
|
759
|
-
const timestamp = this.verifyTimestamp(msgTimestamp);
|
|
760
|
-
const computedSignature = this.sign(msgId, timestamp, payload);
|
|
761
|
-
const expectedSignature = computedSignature.split(",")[1];
|
|
762
|
-
const passedSignatures = msgSignature.split(" ");
|
|
763
|
-
const encoder2 = new globalThis.TextEncoder();
|
|
764
|
-
for (const versionedSignature of passedSignatures) {
|
|
765
|
-
const [version3, signature] = versionedSignature.split(",");
|
|
766
|
-
if (version3 !== "v1") {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
if ((0, timing_safe_equal_1.timingSafeEqual)(encoder2.encode(signature), encoder2.encode(expectedSignature))) {
|
|
770
|
-
return JSON.parse(payload.toString());
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
throw new WebhookVerificationError("No matching signature found");
|
|
774
|
-
}
|
|
775
|
-
sign(msgId, timestamp, payload) {
|
|
776
|
-
if (typeof payload === "string") {
|
|
777
|
-
} else if (payload.constructor.name === "Buffer") {
|
|
778
|
-
payload = payload.toString();
|
|
779
|
-
} else {
|
|
780
|
-
throw new Error("Expected payload to be of type string or Buffer.");
|
|
781
|
-
}
|
|
782
|
-
const encoder2 = new TextEncoder();
|
|
783
|
-
const timestampNumber = Math.floor(timestamp.getTime() / 1e3);
|
|
784
|
-
const toSign = encoder2.encode(`${msgId}.${timestampNumber}.${payload}`);
|
|
785
|
-
const expectedSignature = base64.encode(sha256.hmac(this.key, toSign));
|
|
786
|
-
return `v1,${expectedSignature}`;
|
|
787
|
-
}
|
|
788
|
-
verifyTimestamp(timestampHeader) {
|
|
789
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
790
|
-
const timestamp = parseInt(timestampHeader, 10);
|
|
791
|
-
if (isNaN(timestamp)) {
|
|
792
|
-
throw new WebhookVerificationError("Invalid Signature Headers");
|
|
793
|
-
}
|
|
794
|
-
if (now - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
795
|
-
throw new WebhookVerificationError("Message timestamp too old");
|
|
796
|
-
}
|
|
797
|
-
if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
798
|
-
throw new WebhookVerificationError("Message timestamp too new");
|
|
799
|
-
}
|
|
800
|
-
return new Date(timestamp * 1e3);
|
|
801
|
-
}
|
|
802
|
-
};
|
|
803
|
-
exports.Webhook = Webhook2;
|
|
804
|
-
Webhook2.prefix = "whsec_";
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// src/args.ts
|
|
809
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
810
|
-
"config",
|
|
811
|
-
"client",
|
|
812
|
-
"mode",
|
|
813
|
-
"port",
|
|
814
|
-
"host",
|
|
815
|
-
"agent",
|
|
816
|
-
"run-id",
|
|
817
|
-
"run",
|
|
818
|
-
"base",
|
|
819
|
-
"head",
|
|
820
|
-
"paths",
|
|
821
|
-
"format",
|
|
822
|
-
"by",
|
|
823
|
-
"note",
|
|
824
|
-
"allow",
|
|
825
|
-
"deny",
|
|
826
|
-
"writes",
|
|
827
|
-
"spend",
|
|
828
|
-
"tool-calls",
|
|
829
|
-
"deletes",
|
|
830
|
-
"emails",
|
|
831
|
-
"reason",
|
|
832
|
-
"url",
|
|
833
|
-
"timeout",
|
|
834
|
-
"out",
|
|
835
|
-
"upstream"
|
|
836
|
-
]);
|
|
837
|
-
function parseArgs(argv) {
|
|
838
|
-
const out = { command: void 0, positionals: [], flags: {} };
|
|
839
|
-
const push = (key, value) => {
|
|
840
|
-
const existing = out.flags[key];
|
|
841
|
-
if (existing === void 0) out.flags[key] = value;
|
|
842
|
-
else if (Array.isArray(existing)) existing.push(String(value));
|
|
843
|
-
else out.flags[key] = [String(existing), String(value)];
|
|
844
|
-
};
|
|
845
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
846
|
-
const arg = argv[i];
|
|
847
|
-
if (arg === "--") {
|
|
848
|
-
out.positionals.push(...argv.slice(i + 1));
|
|
849
|
-
break;
|
|
850
|
-
}
|
|
851
|
-
if (arg.startsWith("--")) {
|
|
852
|
-
const body = arg.slice(2);
|
|
853
|
-
const eq = body.indexOf("=");
|
|
854
|
-
if (eq >= 0) {
|
|
855
|
-
push(body.slice(0, eq), body.slice(eq + 1));
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
if (body.startsWith("no-")) {
|
|
859
|
-
push(body.slice(3), false);
|
|
860
|
-
continue;
|
|
861
|
-
}
|
|
862
|
-
const next = argv[i + 1];
|
|
863
|
-
if (VALUE_FLAGS.has(body) && next !== void 0 && !next.startsWith("--")) {
|
|
864
|
-
push(body, next);
|
|
865
|
-
i += 1;
|
|
866
|
-
} else push(body, true);
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
if (arg.startsWith("-") && arg.length === 2) {
|
|
870
|
-
const short = {
|
|
871
|
-
c: "config",
|
|
872
|
-
y: "yes",
|
|
873
|
-
h: "help",
|
|
874
|
-
v: "version",
|
|
875
|
-
j: "json",
|
|
876
|
-
a: "all"
|
|
877
|
-
};
|
|
878
|
-
const key = short[arg[1]] ?? arg[1];
|
|
879
|
-
const next = argv[i + 1];
|
|
880
|
-
if (VALUE_FLAGS.has(key) && next !== void 0 && !next.startsWith("-")) {
|
|
881
|
-
push(key, next);
|
|
882
|
-
i += 1;
|
|
883
|
-
} else push(key, true);
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
if (out.command === void 0) out.command = arg;
|
|
887
|
-
else out.positionals.push(arg);
|
|
888
|
-
}
|
|
889
|
-
return out;
|
|
890
|
-
}
|
|
891
|
-
function flagString(args, name) {
|
|
892
|
-
const v = args.flags[name];
|
|
893
|
-
if (Array.isArray(v)) return v[v.length - 1];
|
|
894
|
-
return typeof v === "string" ? v : void 0;
|
|
895
|
-
}
|
|
896
|
-
function flagList(args, name) {
|
|
897
|
-
const v = args.flags[name];
|
|
898
|
-
if (Array.isArray(v)) return v.flatMap((s) => s.split(","));
|
|
899
|
-
return typeof v === "string" ? v.split(",") : [];
|
|
900
|
-
}
|
|
901
|
-
function flagBool(args, name) {
|
|
902
|
-
const v = args.flags[name];
|
|
903
|
-
if (typeof v === "boolean") return v;
|
|
904
|
-
if (typeof v === "string") return v !== "false" && v !== "0";
|
|
905
|
-
return void 0;
|
|
906
|
-
}
|
|
907
|
-
function flagNumber(args, name) {
|
|
908
|
-
const s = flagString(args, name);
|
|
909
|
-
if (s === void 0) return void 0;
|
|
910
|
-
const n = Number(s);
|
|
911
|
-
return Number.isFinite(n) ? n : void 0;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// src/version.ts
|
|
915
|
-
import { readFileSync } from "fs";
|
|
916
|
-
var cached;
|
|
917
|
-
function version() {
|
|
918
|
-
if (cached !== void 0) return cached;
|
|
919
|
-
try {
|
|
920
|
-
cached = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
921
|
-
} catch {
|
|
922
|
-
cached = "0.0.0";
|
|
923
|
-
}
|
|
924
|
-
return cached;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// src/context.ts
|
|
928
|
-
import { existsSync as existsSync3 } from "fs";
|
|
929
|
-
import { resolve } from "path";
|
|
930
|
-
import { Guard } from "@agentwares/agentguard-core";
|
|
931
|
-
import {
|
|
932
|
-
FileApprovalStore,
|
|
933
|
-
FileAuditSink,
|
|
934
|
-
FileKillSwitch as FileKillSwitch2,
|
|
935
|
-
FileStateStore,
|
|
936
|
-
loadPolicyFile
|
|
937
|
-
} from "@agentwares/agentguard-core/node";
|
|
938
|
-
import { existsSync as exists, readFileSync as readFileSync3 } from "fs";
|
|
939
|
-
|
|
940
|
-
// ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
|
|
941
|
-
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
942
|
-
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
943
|
-
import {
|
|
944
|
-
CreateMessageResultSchema,
|
|
945
|
-
ElicitResultSchema,
|
|
946
|
-
SubscribeRequestSchema,
|
|
947
|
-
UnsubscribeRequestSchema
|
|
948
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
949
|
-
import { z } from "zod";
|
|
950
|
-
|
|
951
|
-
// ../../packages/x402/dist/index.js
|
|
952
|
-
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
953
|
-
var USDC_DECIMALS = 6;
|
|
954
|
-
var USDC_EIP712_DOMAIN = { name: "USD Coin", version: "2" };
|
|
955
|
-
function toAtomicUnits(amountUsd, decimals = USDC_DECIMALS) {
|
|
956
|
-
const trimmed = amountUsd.trim();
|
|
957
|
-
if (!/^\d+(\.\d+)?$/.test(trimmed)) throw new Error(`not a decimal amount: ${amountUsd}`);
|
|
958
|
-
const [whole = "0", fraction = ""] = trimmed.split(".");
|
|
959
|
-
if (fraction.length > decimals) {
|
|
960
|
-
throw new Error(`${amountUsd} has more than ${decimals} decimal places`);
|
|
961
|
-
}
|
|
962
|
-
return `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
963
|
-
}
|
|
964
|
-
var DEFAULT_X402_NETWORK = "eip155:8453";
|
|
965
|
-
var X402_HEADER = "X-Payment-Requirements";
|
|
966
|
-
var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
967
|
-
function toAmount(priceUsd) {
|
|
968
|
-
if (!Number.isFinite(priceUsd) || priceUsd < 0)
|
|
969
|
-
throw new Error("priceUsd must be a non-negative number");
|
|
970
|
-
return priceUsd.toFixed(6).replace(/0+$/, "").replace(/\.$/, ".0");
|
|
971
|
-
}
|
|
972
|
-
function buildPaymentRequired(opts) {
|
|
973
|
-
const amountUsd = toAmount(opts.priceUsd);
|
|
974
|
-
const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
|
|
975
|
-
const accepts = [];
|
|
976
|
-
if (opts.payTo) {
|
|
977
|
-
accepts.push({
|
|
978
|
-
rail: "x402",
|
|
979
|
-
scheme: "exact",
|
|
980
|
-
amountUsd,
|
|
981
|
-
amount: toAtomicUnits(amountUsd, opts.assetDecimals),
|
|
982
|
-
resource: opts.resource,
|
|
983
|
-
description: opts.description,
|
|
984
|
-
mimeType: "application/json",
|
|
985
|
-
network: opts.network ?? DEFAULT_X402_NETWORK,
|
|
986
|
-
asset: opts.asset ?? USDC_BASE,
|
|
987
|
-
extra: opts.assetDomain ?? USDC_EIP712_DOMAIN,
|
|
988
|
-
payTo: opts.payTo,
|
|
989
|
-
maxTimeoutSeconds: 300,
|
|
990
|
-
...opts.bazaar ? { bazaar: opts.bazaar } : {}
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
if (creditsPayTo) {
|
|
994
|
-
accepts.push({
|
|
995
|
-
rail: "prepaid_credits",
|
|
996
|
-
amountUsd,
|
|
997
|
-
resource: opts.resource,
|
|
998
|
-
description: opts.description,
|
|
999
|
-
currency: "usd",
|
|
1000
|
-
payTo: creditsPayTo,
|
|
1001
|
-
maxTimeoutSeconds: 300,
|
|
1002
|
-
// funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
|
|
1003
|
-
humanRequired: true
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
const live = opts.live === true;
|
|
1007
|
-
const selfServe = opts.selfServe;
|
|
1008
|
-
const x402Rail = accepts.find((a) => a.rail === "x402" && a.payTo);
|
|
1009
|
-
const usdcFix = x402Rail ? `Pay in USDC with no account and no human: take the \`accepts\` entry with \`rail: "x402"\`, sign an EIP-3009 transferWithAuthorization for ${x402Rail.amountUsd} USDC to ${x402Rail.payTo} on ${x402Rail.network}, and retry with it base64 in the X-PAYMENT header. The facilitator submits the transfer, so you need USDC and no gas. ` : "";
|
|
1010
|
-
const sampleFix = opts.sampleMode ? "Free first: retry with sample=true \u2014 no key, no payment. " : "";
|
|
1011
|
-
const cardFix = selfServe ? `Or pay by card: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person at a browser; if you are running unattended, surface the URL to your operator rather than retrying, or use the USDC rail above. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "";
|
|
1012
|
-
const liveFix = usdcFix || cardFix ? `${sampleFix}${usdcFix}${cardFix}`.trim() : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
|
|
1013
|
-
const body = {
|
|
1014
|
-
code: "PAYMENT_REQUIRED",
|
|
1015
|
-
cause: live ? `This call costs $${amountUsd}. No valid payment or credit was presented.` : `This call costs $${amountUsd}. Pay-per-call (x402 / Stripe MPP) is coming soon; use an API key with prepaid credits.`,
|
|
1016
|
-
fix: live ? liveFix : `Create an API key${opts.signupUrl ? ` at ${opts.signupUrl}` : ""} and retry with Authorization: Bearer <key>. Try the free sample mode first.`,
|
|
1017
|
-
retryable: true,
|
|
1018
|
-
priceUsd: opts.priceUsd,
|
|
1019
|
-
accepts,
|
|
1020
|
-
comingSoon: !live,
|
|
1021
|
-
alternatives: [
|
|
1022
|
-
...creditsPayTo ? ["api_key_credits"] : [],
|
|
1023
|
-
...opts.sampleMode ? ["sample_mode"] : []
|
|
1024
|
-
],
|
|
1025
|
-
...selfServe ? { selfServe } : {},
|
|
1026
|
-
...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
|
|
1027
|
-
};
|
|
1028
|
-
const encoded = JSON.stringify({
|
|
1029
|
-
x402Version: 2,
|
|
1030
|
-
accepts,
|
|
1031
|
-
resource: opts.resource,
|
|
1032
|
-
...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
|
|
1033
|
-
});
|
|
1034
|
-
const headers = {
|
|
1035
|
-
"Content-Type": "application/json",
|
|
1036
|
-
"WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
|
|
1037
|
-
[X402_HEADER]: encoded,
|
|
1038
|
-
[X402_REQUIRED_HEADER]: btoa(encoded),
|
|
1039
|
-
"Cache-Control": "no-store"
|
|
1040
|
-
};
|
|
1041
|
-
return { status: 402, headers, body };
|
|
1042
|
-
}
|
|
1043
|
-
var StubVerifier = class {
|
|
1044
|
-
async verify(request, requirement) {
|
|
1045
|
-
const hasHeader = request.headers.has("PAYMENT") || request.headers.has("X-PAYMENT");
|
|
1046
|
-
return {
|
|
1047
|
-
ok: false,
|
|
1048
|
-
rail: requirement.rail,
|
|
1049
|
-
reason: hasHeader ? "payments are not live yet (phase 2: wallet). Use an API key with credits." : "no payment header present"
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
// ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
|
|
1055
|
-
import { z as z2 } from "zod";
|
|
1056
|
-
import { z as z3 } from "zod";
|
|
1057
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1058
|
-
import {
|
|
1059
|
-
CallToolRequestSchema,
|
|
1060
|
-
ErrorCode,
|
|
1061
|
-
ListToolsRequestSchema,
|
|
1062
|
-
McpError
|
|
1063
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
1064
|
-
import { z as z4 } from "zod";
|
|
1065
|
-
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
1066
|
-
var ERROR_CODES = [
|
|
1067
|
-
"INVALID_INPUT",
|
|
1068
|
-
"NOT_FOUND",
|
|
1069
|
-
"UNAUTHORIZED",
|
|
1070
|
-
"PAYMENT_REQUIRED",
|
|
1071
|
-
"RATE_LIMITED",
|
|
1072
|
-
"UPSTREAM_ERROR",
|
|
1073
|
-
"INTERNAL"
|
|
1074
|
-
];
|
|
1075
|
-
var DEFAULT_RETRYABLE = {
|
|
1076
|
-
INVALID_INPUT: false,
|
|
1077
|
-
NOT_FOUND: false,
|
|
1078
|
-
UNAUTHORIZED: false,
|
|
1079
|
-
PAYMENT_REQUIRED: true,
|
|
1080
|
-
RATE_LIMITED: true,
|
|
1081
|
-
UPSTREAM_ERROR: true,
|
|
1082
|
-
INTERNAL: false
|
|
1083
|
-
};
|
|
1084
|
-
var DEFAULT_HTTP_STATUS = {
|
|
1085
|
-
INVALID_INPUT: 400,
|
|
1086
|
-
NOT_FOUND: 404,
|
|
1087
|
-
UNAUTHORIZED: 401,
|
|
1088
|
-
PAYMENT_REQUIRED: 402,
|
|
1089
|
-
RATE_LIMITED: 429,
|
|
1090
|
-
UPSTREAM_ERROR: 502,
|
|
1091
|
-
INTERNAL: 500
|
|
1092
|
-
};
|
|
1093
|
-
var INTERNAL_FIX = "retry; if it persists, report the request id";
|
|
1094
|
-
function isStandardCode(code) {
|
|
1095
|
-
return ERROR_CODES.includes(code);
|
|
1096
|
-
}
|
|
1097
|
-
var McpToolError = class extends Error {
|
|
1098
|
-
code;
|
|
1099
|
-
cause;
|
|
1100
|
-
fix;
|
|
1101
|
-
retryable;
|
|
1102
|
-
details;
|
|
1103
|
-
httpStatus;
|
|
1104
|
-
extra;
|
|
1105
|
-
constructor(init) {
|
|
1106
|
-
super(`${init.code}: ${init.cause}`);
|
|
1107
|
-
this.name = "McpToolError";
|
|
1108
|
-
this.code = init.code;
|
|
1109
|
-
this.cause = init.cause;
|
|
1110
|
-
this.fix = init.fix;
|
|
1111
|
-
this.retryable = init.retryable ?? (isStandardCode(init.code) ? DEFAULT_RETRYABLE[init.code] : false);
|
|
1112
|
-
this.details = init.details;
|
|
1113
|
-
this.httpStatus = init.httpStatus ?? (isStandardCode(init.code) ? DEFAULT_HTTP_STATUS[init.code] : 500);
|
|
1114
|
-
this.extra = init.extra;
|
|
1115
|
-
}
|
|
1116
|
-
/** The body agents see: `{ code, cause, fix, retryable, details?, ...extra }`. */
|
|
1117
|
-
toJSON() {
|
|
1118
|
-
const body = {
|
|
1119
|
-
...this.extra,
|
|
1120
|
-
code: this.code,
|
|
1121
|
-
cause: this.cause,
|
|
1122
|
-
fix: this.fix,
|
|
1123
|
-
retryable: this.retryable
|
|
1124
|
-
};
|
|
1125
|
-
if (this.details !== void 0) body.details = this.details;
|
|
1126
|
-
return body;
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
function toolError(codeOrInit, cause, fix, extra) {
|
|
1130
|
-
if (typeof codeOrInit === "object") return new McpToolError(codeOrInit);
|
|
1131
|
-
return new McpToolError({ code: codeOrInit, cause: cause ?? "", fix: fix ?? "", ...extra });
|
|
1132
|
-
}
|
|
1133
|
-
function describeUnknown(err) {
|
|
1134
|
-
if (err instanceof Error) return err.message || err.name;
|
|
1135
|
-
if (typeof err === "string") return err;
|
|
1136
|
-
try {
|
|
1137
|
-
return JSON.stringify(err);
|
|
1138
|
-
} catch {
|
|
1139
|
-
return String(err);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
function normalizeError(err) {
|
|
1143
|
-
if (err instanceof McpToolError) return err;
|
|
1144
|
-
return new McpToolError({
|
|
1145
|
-
code: "INTERNAL",
|
|
1146
|
-
cause: describeUnknown(err),
|
|
1147
|
-
fix: INTERNAL_FIX,
|
|
1148
|
-
retryable: false
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
function errorResult(err, opts = {}) {
|
|
1152
|
-
const error = normalizeError(err);
|
|
1153
|
-
const body = error.toJSON();
|
|
1154
|
-
const meta = { httpStatus: error.httpStatus };
|
|
1155
|
-
if (opts.requestId !== void 0) meta.requestId = opts.requestId;
|
|
1156
|
-
Object.assign(meta, opts.meta);
|
|
1157
|
-
return {
|
|
1158
|
-
isError: true,
|
|
1159
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
1160
|
-
structuredContent: body,
|
|
1161
|
-
_meta: meta
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
function createToolContext(init = {}) {
|
|
1165
|
-
const { headers, ...rest } = init;
|
|
1166
|
-
return { headers: headers ?? new Headers(), ...rest };
|
|
1167
|
-
}
|
|
1168
|
-
var TOOL_NAME_PATTERN = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
|
|
1169
|
-
var MIN_NAMESPACE_LENGTH = 3;
|
|
1170
|
-
var MIN_DESCRIPTION_LENGTH = 20;
|
|
1171
|
-
function isValidToolName(name) {
|
|
1172
|
-
if (!TOOL_NAME_PATTERN.test(name)) return false;
|
|
1173
|
-
const namespace = name.split("_")[0] ?? "";
|
|
1174
|
-
return namespace.length >= MIN_NAMESPACE_LENGTH;
|
|
1175
|
-
}
|
|
1176
|
-
function assertToolName(name) {
|
|
1177
|
-
if (!isValidToolName(name)) {
|
|
1178
|
-
throw new Error(
|
|
1179
|
-
`tool name must be namespaced: <namespace>_<verb>_<object>, got ${JSON.stringify(name)}`
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
function isPlainObject(value) {
|
|
1184
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1185
|
-
const proto = Object.getPrototypeOf(value);
|
|
1186
|
-
return proto === Object.prototype || proto === null;
|
|
1187
|
-
}
|
|
1188
|
-
function isCallToolResult(value) {
|
|
1189
|
-
if (!isPlainObject(value) || !Array.isArray(value.content)) return false;
|
|
1190
|
-
return value.content.every(
|
|
1191
|
-
(block) => isPlainObject(block) && typeof block.type === "string"
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
function toCallToolResult(value) {
|
|
1195
|
-
if (isCallToolResult(value)) return value;
|
|
1196
|
-
const text = typeof value === "string" ? value : JSON.stringify(value ?? null, null, 2);
|
|
1197
|
-
const result = { content: [{ type: "text", text }] };
|
|
1198
|
-
if (isPlainObject(value)) result.structuredContent = value;
|
|
1199
|
-
return result;
|
|
1200
|
-
}
|
|
1201
|
-
function issuePath(issue) {
|
|
1202
|
-
return issue.path.map(String).join(".") || "(root)";
|
|
1203
|
-
}
|
|
1204
|
-
function invalidInputError(issues) {
|
|
1205
|
-
const fields = [...new Set(issues.map(issuePath))];
|
|
1206
|
-
const summary = issues.map((issue) => `${issuePath(issue)}: ${issue.message}`).join("; ");
|
|
1207
|
-
return toolError({
|
|
1208
|
-
code: "INVALID_INPUT",
|
|
1209
|
-
cause: `invalid input for ${fields.join(", ")} \u2014 ${summary}`,
|
|
1210
|
-
fix: `fix ${fields.join(", ")} to match the tool's inputSchema (see details.issues), then retry`,
|
|
1211
|
-
retryable: false,
|
|
1212
|
-
details: {
|
|
1213
|
-
fields,
|
|
1214
|
-
issues: issues.map((issue) => ({
|
|
1215
|
-
path: issue.path.map(String),
|
|
1216
|
-
code: issue.code,
|
|
1217
|
-
message: issue.message
|
|
1218
|
-
}))
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
function defineTool(opts) {
|
|
1223
|
-
assertToolName(opts.name);
|
|
1224
|
-
if (typeof opts.description !== "string" || opts.description.trim().length < MIN_DESCRIPTION_LENGTH) {
|
|
1225
|
-
throw new Error(
|
|
1226
|
-
`tool ${opts.name}: description must be at least ${MIN_DESCRIPTION_LENGTH} characters \u2014 say what it does, when to use it and what it returns`
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
const { name, title, description, input, output, annotations } = opts;
|
|
1230
|
-
const handler = opts.handler.bind(opts);
|
|
1231
|
-
const tool = {
|
|
1232
|
-
name,
|
|
1233
|
-
title,
|
|
1234
|
-
description,
|
|
1235
|
-
input,
|
|
1236
|
-
output,
|
|
1237
|
-
annotations,
|
|
1238
|
-
handler,
|
|
1239
|
-
async invoke(rawInput, ctx) {
|
|
1240
|
-
const fail2 = (err) => {
|
|
1241
|
-
const result = errorResult(err, { requestId: ctx.requestId });
|
|
1242
|
-
if (output) delete result.structuredContent;
|
|
1243
|
-
return result;
|
|
1244
|
-
};
|
|
1245
|
-
const parsed = input.safeParse(rawInput ?? {});
|
|
1246
|
-
if (!parsed.success) return fail2(invalidInputError(parsed.error.issues));
|
|
1247
|
-
try {
|
|
1248
|
-
const value = await handler(parsed.data, ctx);
|
|
1249
|
-
if (output && !isCallToolResult(value)) {
|
|
1250
|
-
const checked = output.safeParse(value);
|
|
1251
|
-
if (!checked.success) {
|
|
1252
|
-
throw toolError({
|
|
1253
|
-
code: "INTERNAL",
|
|
1254
|
-
cause: `tool ${name} returned a value that does not match its output schema \u2014 ${checked.error.issues.map((issue) => `${issuePath(issue)}: ${issue.message}`).join("; ")}`,
|
|
1255
|
-
fix: "this is a bug in the tool, not in your call; report it with the request id",
|
|
1256
|
-
retryable: false,
|
|
1257
|
-
details: { issues: checked.error.issues }
|
|
1258
|
-
});
|
|
1259
|
-
}
|
|
1260
|
-
return toCallToolResult(checked.data);
|
|
1261
|
-
}
|
|
1262
|
-
return toCallToolResult(value);
|
|
1263
|
-
} catch (err) {
|
|
1264
|
-
return fail2(err);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
};
|
|
1268
|
-
return tool;
|
|
1269
|
-
}
|
|
1270
|
-
var PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
1271
|
-
var WAV_BASE64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
|
|
1272
|
-
var STATIC_TEXT_URI = "test://static-text";
|
|
1273
|
-
var STATIC_TEXT = "This is a static text resource for testing.";
|
|
1274
|
-
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1275
|
-
function embeddedResource() {
|
|
1276
|
-
return {
|
|
1277
|
-
type: "resource",
|
|
1278
|
-
resource: { uri: STATIC_TEXT_URI, mimeType: "text/plain", text: STATIC_TEXT }
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
function requireExtra(ctx) {
|
|
1282
|
-
if (!ctx.extra) {
|
|
1283
|
-
throw toolError("INTERNAL", "no client connection in this context", "call this tool over MCP");
|
|
1284
|
-
}
|
|
1285
|
-
return ctx.extra;
|
|
1286
|
-
}
|
|
1287
|
-
async function elicit(ctx, message, requestedSchema) {
|
|
1288
|
-
const result = await requireExtra(ctx).sendRequest(
|
|
1289
|
-
{ method: "elicitation/create", params: { message, requestedSchema } },
|
|
1290
|
-
ElicitResultSchema
|
|
1291
|
-
);
|
|
1292
|
-
return `Elicitation completed: action=${result.action}, content=${JSON.stringify(result.content ?? {})}`;
|
|
1293
|
-
}
|
|
1294
|
-
var conformanceTools = [
|
|
1295
|
-
defineTool({
|
|
1296
|
-
name: "test_simple_text",
|
|
1297
|
-
description: "Conformance fixture: returns a fixed simple text response.",
|
|
1298
|
-
input: z.object({}),
|
|
1299
|
-
handler: () => "This is a simple text response for testing."
|
|
1300
|
-
}),
|
|
1301
|
-
defineTool({
|
|
1302
|
-
name: "test_error_handling",
|
|
1303
|
-
description: "Conformance fixture: always returns an error result (isError: true).",
|
|
1304
|
-
input: z.object({}),
|
|
1305
|
-
handler: () => {
|
|
1306
|
-
throw toolError({
|
|
1307
|
-
code: "UPSTREAM_ERROR",
|
|
1308
|
-
cause: "This tool intentionally returns an error for testing",
|
|
1309
|
-
fix: "nothing to fix; this fixture always fails",
|
|
1310
|
-
retryable: false
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
}),
|
|
1314
|
-
defineTool({
|
|
1315
|
-
name: "test_image_content",
|
|
1316
|
-
description: "Conformance fixture: returns a 1x1 PNG image content block.",
|
|
1317
|
-
input: z.object({}),
|
|
1318
|
-
handler: () => ({ content: [{ type: "image", data: PNG_BASE64, mimeType: "image/png" }] })
|
|
1319
|
-
}),
|
|
1320
|
-
defineTool({
|
|
1321
|
-
name: "test_audio_content",
|
|
1322
|
-
description: "Conformance fixture: returns an empty WAV audio content block.",
|
|
1323
|
-
input: z.object({}),
|
|
1324
|
-
handler: () => ({ content: [{ type: "audio", data: WAV_BASE64, mimeType: "audio/wav" }] })
|
|
1325
|
-
}),
|
|
1326
|
-
defineTool({
|
|
1327
|
-
name: "test_embedded_resource",
|
|
1328
|
-
description: "Conformance fixture: returns an embedded text resource content block.",
|
|
1329
|
-
input: z.object({}),
|
|
1330
|
-
handler: () => ({ content: [embeddedResource()] })
|
|
1331
|
-
}),
|
|
1332
|
-
defineTool({
|
|
1333
|
-
name: "test_multiple_content_types",
|
|
1334
|
-
description: "Conformance fixture: returns text, image and resource content blocks together.",
|
|
1335
|
-
input: z.object({}),
|
|
1336
|
-
handler: () => ({
|
|
1337
|
-
content: [
|
|
1338
|
-
{ type: "text", text: "Mixed content response" },
|
|
1339
|
-
{ type: "image", data: PNG_BASE64, mimeType: "image/png" },
|
|
1340
|
-
embeddedResource()
|
|
1341
|
-
]
|
|
1342
|
-
})
|
|
1343
|
-
}),
|
|
1344
|
-
defineTool({
|
|
1345
|
-
name: "test_tool_with_logging",
|
|
1346
|
-
description: "Conformance fixture: emits three info log notifications while running.",
|
|
1347
|
-
input: z.object({}),
|
|
1348
|
-
handler: async (_input, ctx) => {
|
|
1349
|
-
await ctx.log?.("info", "Tool execution started");
|
|
1350
|
-
await sleep(50);
|
|
1351
|
-
await ctx.log?.("info", "Tool processing data");
|
|
1352
|
-
await sleep(50);
|
|
1353
|
-
await ctx.log?.("info", "Tool execution completed");
|
|
1354
|
-
return "Tool execution completed with logging";
|
|
1355
|
-
}
|
|
1356
|
-
}),
|
|
1357
|
-
defineTool({
|
|
1358
|
-
name: "test_tool_with_progress",
|
|
1359
|
-
description: "Conformance fixture: reports progress 0/100, 50/100, 100/100 while running.",
|
|
1360
|
-
input: z.object({}),
|
|
1361
|
-
handler: async (_input, ctx) => {
|
|
1362
|
-
await ctx.progress?.(0, 100, "starting");
|
|
1363
|
-
await sleep(50);
|
|
1364
|
-
await ctx.progress?.(50, 100, "halfway");
|
|
1365
|
-
await sleep(50);
|
|
1366
|
-
await ctx.progress?.(100, 100, "done");
|
|
1367
|
-
return "Tool execution completed with progress";
|
|
1368
|
-
}
|
|
1369
|
-
}),
|
|
1370
|
-
defineTool({
|
|
1371
|
-
name: "test_sampling",
|
|
1372
|
-
description: "Conformance fixture: asks the client to sample a completion and returns it.",
|
|
1373
|
-
input: z.object({ prompt: z.string().describe("Prompt to send to the client's model") }),
|
|
1374
|
-
handler: async ({ prompt }, ctx) => {
|
|
1375
|
-
const result = await requireExtra(ctx).sendRequest(
|
|
1376
|
-
{
|
|
1377
|
-
method: "sampling/createMessage",
|
|
1378
|
-
params: {
|
|
1379
|
-
messages: [{ role: "user", content: { type: "text", text: prompt } }],
|
|
1380
|
-
maxTokens: 100
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
|
-
CreateMessageResultSchema
|
|
1384
|
-
);
|
|
1385
|
-
const content = Array.isArray(result.content) ? result.content[0] : result.content;
|
|
1386
|
-
const text = content && content.type === "text" ? content.text : JSON.stringify(content);
|
|
1387
|
-
return `Sampling completed (${result.model}): ${text}`;
|
|
1388
|
-
}
|
|
1389
|
-
}),
|
|
1390
|
-
defineTool({
|
|
1391
|
-
name: "test_elicitation",
|
|
1392
|
-
description: "Conformance fixture: asks the client for user input and reports the outcome.",
|
|
1393
|
-
input: z.object({ message: z.string().describe("Message shown to the user") }),
|
|
1394
|
-
handler: ({ message }, ctx) => elicit(ctx, message, {
|
|
1395
|
-
type: "object",
|
|
1396
|
-
properties: {
|
|
1397
|
-
name: { type: "string", title: "Name", description: "Your name" },
|
|
1398
|
-
email: { type: "string", title: "Email", format: "email" }
|
|
1399
|
-
},
|
|
1400
|
-
required: ["name"]
|
|
1401
|
-
})
|
|
1402
|
-
}),
|
|
1403
|
-
defineTool({
|
|
1404
|
-
name: "test_elicitation_sep1034_defaults",
|
|
1405
|
-
description: "Conformance fixture: elicitation schema with defaults for every primitive type.",
|
|
1406
|
-
input: z.object({}),
|
|
1407
|
-
handler: (_input, ctx) => elicit(ctx, "Please confirm your details", {
|
|
1408
|
-
type: "object",
|
|
1409
|
-
properties: {
|
|
1410
|
-
name: { type: "string", title: "Name", default: "John Doe" },
|
|
1411
|
-
age: { type: "integer", title: "Age", default: 30 },
|
|
1412
|
-
score: { type: "number", title: "Score", default: 95.5 },
|
|
1413
|
-
status: {
|
|
1414
|
-
type: "string",
|
|
1415
|
-
title: "Status",
|
|
1416
|
-
enum: ["active", "inactive", "pending"],
|
|
1417
|
-
default: "active"
|
|
1418
|
-
},
|
|
1419
|
-
verified: { type: "boolean", title: "Verified", default: true }
|
|
1420
|
-
}
|
|
1421
|
-
})
|
|
1422
|
-
}),
|
|
1423
|
-
defineTool({
|
|
1424
|
-
name: "test_elicitation_sep1330_enums",
|
|
1425
|
-
description: "Conformance fixture: elicitation schema with titled, untitled and multi-select enums.",
|
|
1426
|
-
input: z.object({}),
|
|
1427
|
-
handler: (_input, ctx) => elicit(ctx, "Pick some options", {
|
|
1428
|
-
type: "object",
|
|
1429
|
-
properties: {
|
|
1430
|
-
untitledSingle: {
|
|
1431
|
-
type: "string",
|
|
1432
|
-
title: "Untitled single",
|
|
1433
|
-
enum: ["red", "green", "blue"]
|
|
1434
|
-
},
|
|
1435
|
-
titledSingle: {
|
|
1436
|
-
type: "string",
|
|
1437
|
-
title: "Titled single",
|
|
1438
|
-
oneOf: [
|
|
1439
|
-
{ const: "red", title: "Red" },
|
|
1440
|
-
{ const: "green", title: "Green" },
|
|
1441
|
-
{ const: "blue", title: "Blue" }
|
|
1442
|
-
]
|
|
1443
|
-
},
|
|
1444
|
-
legacyEnum: {
|
|
1445
|
-
type: "string",
|
|
1446
|
-
title: "Legacy titled",
|
|
1447
|
-
enum: ["red", "green", "blue"],
|
|
1448
|
-
enumNames: ["Red", "Green", "Blue"]
|
|
1449
|
-
},
|
|
1450
|
-
untitledMulti: {
|
|
1451
|
-
type: "array",
|
|
1452
|
-
title: "Untitled multi",
|
|
1453
|
-
items: { type: "string", enum: ["red", "green", "blue"] }
|
|
1454
|
-
},
|
|
1455
|
-
titledMulti: {
|
|
1456
|
-
type: "array",
|
|
1457
|
-
title: "Titled multi",
|
|
1458
|
-
items: {
|
|
1459
|
-
anyOf: [
|
|
1460
|
-
{ const: "red", title: "Red" },
|
|
1461
|
-
{ const: "green", title: "Green" },
|
|
1462
|
-
{ const: "blue", title: "Blue" }
|
|
1463
|
-
]
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
})
|
|
1468
|
-
}),
|
|
1469
|
-
defineTool({
|
|
1470
|
-
name: "json_schema_2020_12_tool",
|
|
1471
|
-
description: "Conformance fixture: input schema with a $defs reference to an address object.",
|
|
1472
|
-
input: z.object({
|
|
1473
|
-
name: z.string(),
|
|
1474
|
-
address: z.object({ street: z.string(), city: z.string() }).meta({ id: "address" })
|
|
1475
|
-
}),
|
|
1476
|
-
handler: (input) => input
|
|
1477
|
-
})
|
|
1478
|
-
];
|
|
1479
|
-
function paymentRequiredError(body, reason) {
|
|
1480
|
-
const { code, cause, fix, retryable, ...rest } = body;
|
|
1481
|
-
return toolError({
|
|
1482
|
-
code,
|
|
1483
|
-
cause,
|
|
1484
|
-
fix,
|
|
1485
|
-
retryable,
|
|
1486
|
-
httpStatus: 402,
|
|
1487
|
-
details: reason !== void 0 ? { reason } : void 0,
|
|
1488
|
-
extra: rest
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
function formatUsd(priceUsd) {
|
|
1492
|
-
return priceUsd.toFixed(6).replace(/0+$/, "").replace(/\.$/, ".00").replace(/\.(\d)$/, ".$10");
|
|
1493
|
-
}
|
|
1494
|
-
function withPayment(tool, opts) {
|
|
1495
|
-
if (!Number.isFinite(opts.priceUsd) || opts.priceUsd < 0) {
|
|
1496
|
-
throw new Error(`withPayment(${tool.name}): priceUsd must be a non-negative number`);
|
|
1497
|
-
}
|
|
1498
|
-
const sampleField = opts.sampleField ?? "sample";
|
|
1499
|
-
if (sampleField in tool.input.shape) {
|
|
1500
|
-
throw new Error(
|
|
1501
|
-
`withPayment(${tool.name}): input already has a field named ${JSON.stringify(sampleField)}; pass sampleField to rename it`
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1504
|
-
const verifier = opts.verifier ?? new StubVerifier();
|
|
1505
|
-
const liveRail = opts.verifier !== void 0;
|
|
1506
|
-
const input = tool.input.extend({
|
|
1507
|
-
[sampleField]: z2.boolean().default(false).describe(
|
|
1508
|
-
"Set true to return an example response at no charge (sample mode). Default false."
|
|
1509
|
-
)
|
|
1510
|
-
});
|
|
1511
|
-
const description = `${tool.description.trim()} Paid: $${formatUsd(opts.priceUsd)} per call; without credit you get a PAYMENT_REQUIRED result. Set ${sampleField}=true for a free example response.`;
|
|
1512
|
-
return defineTool({
|
|
1513
|
-
name: tool.name,
|
|
1514
|
-
title: tool.title,
|
|
1515
|
-
description,
|
|
1516
|
-
input,
|
|
1517
|
-
output: tool.output,
|
|
1518
|
-
annotations: tool.annotations,
|
|
1519
|
-
async handler(raw, ctx) {
|
|
1520
|
-
const record = { ...raw };
|
|
1521
|
-
const sample = record[sampleField] === true;
|
|
1522
|
-
delete record[sampleField];
|
|
1523
|
-
if (sample) return tool.handler(record, { ...ctx, sample: true });
|
|
1524
|
-
let reason;
|
|
1525
|
-
if (opts.authorize) {
|
|
1526
|
-
const auth = await opts.authorize(ctx);
|
|
1527
|
-
if (auth.ok) return tool.handler(record, { ...ctx, userId: auth.userId });
|
|
1528
|
-
reason = auth.reason;
|
|
1529
|
-
}
|
|
1530
|
-
const resource = ctx.resourceUrl ?? tool.name;
|
|
1531
|
-
const required = buildPaymentRequired({
|
|
1532
|
-
resource,
|
|
1533
|
-
priceUsd: opts.priceUsd,
|
|
1534
|
-
live: opts.live,
|
|
1535
|
-
description: opts.description,
|
|
1536
|
-
payTo: opts.payTo,
|
|
1537
|
-
creditsPayTo: opts.creditsPayTo,
|
|
1538
|
-
signupUrl: opts.signupUrl,
|
|
1539
|
-
selfServe: opts.selfServe,
|
|
1540
|
-
// withPayment always adds a free `sample` flag to the tool's input schema
|
|
1541
|
-
sampleMode: true,
|
|
1542
|
-
...opts.bazaar ? { bazaar: opts.bazaar } : {}
|
|
1543
|
-
});
|
|
1544
|
-
const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
|
|
1545
|
-
const requirement = advertised ?? {
|
|
1546
|
-
rail: "prepaid_credits",
|
|
1547
|
-
amountUsd: String(required.body.priceUsd),
|
|
1548
|
-
resource,
|
|
1549
|
-
...opts.description ? { description: opts.description } : {}
|
|
1550
|
-
};
|
|
1551
|
-
const verified = await verifier.verify(
|
|
1552
|
-
new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
|
|
1553
|
-
requirement
|
|
1554
|
-
);
|
|
1555
|
-
if (verified.ok) {
|
|
1556
|
-
let result;
|
|
1557
|
-
try {
|
|
1558
|
-
result = await tool.handler(record, ctx);
|
|
1559
|
-
} catch (error) {
|
|
1560
|
-
await opts.onPaidFailure?.(ctx, verified, error);
|
|
1561
|
-
throw error;
|
|
1562
|
-
}
|
|
1563
|
-
const settled = await verifier.settle?.(verified);
|
|
1564
|
-
if (settled && !settled.ok) {
|
|
1565
|
-
opts.onSettlementFailure?.(ctx, verified, settled.reason);
|
|
1566
|
-
}
|
|
1567
|
-
return result;
|
|
1568
|
-
}
|
|
1569
|
-
reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
|
|
1570
|
-
throw paymentRequiredError(required.body, reason);
|
|
1571
|
-
}
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
var exampleEcho = defineTool({
|
|
1575
|
-
name: "example_echo",
|
|
1576
|
-
title: "Echo",
|
|
1577
|
-
description: "Echo a message back unchanged. Use it to check the connection and to see the result shape mcp-kit tools return (JSON text plus structuredContent).",
|
|
1578
|
-
input: z3.object({ message: z3.string().describe("Text to echo back") }),
|
|
1579
|
-
output: z3.object({ message: z3.string() }),
|
|
1580
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1581
|
-
handler: ({ message }) => ({ message })
|
|
1582
|
-
});
|
|
1583
|
-
var exampleAdd = defineTool({
|
|
1584
|
-
name: "example_add",
|
|
1585
|
-
title: "Add",
|
|
1586
|
-
description: "Add two numbers and return their sum as { sum }. Demonstrates typed input and output schemas; send non-numbers to see an INVALID_INPUT error result.",
|
|
1587
|
-
input: z3.object({
|
|
1588
|
-
a: z3.number().describe("First addend"),
|
|
1589
|
-
b: z3.number().describe("Second addend")
|
|
1590
|
-
}),
|
|
1591
|
-
output: z3.object({ sum: z3.number() }),
|
|
1592
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1593
|
-
handler: ({ a, b }) => ({ sum: a + b })
|
|
1594
|
-
});
|
|
1595
|
-
var examplePaidLookup = withPayment(
|
|
1596
|
-
defineTool({
|
|
1597
|
-
name: "example_paid_lookup",
|
|
1598
|
-
title: "Paid lookup",
|
|
1599
|
-
description: "Look up a record by query and return { query, result, sample } (fake data). Demonstrates a paid tool: without an entitlement the call returns a PAYMENT_REQUIRED result with x402 and MPP payment options.",
|
|
1600
|
-
input: z3.object({ query: z3.string().min(1).describe("What to look up") }),
|
|
1601
|
-
output: z3.object({ query: z3.string(), result: z3.string(), sample: z3.boolean() }),
|
|
1602
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1603
|
-
handler: ({ query }, ctx) => ({
|
|
1604
|
-
query,
|
|
1605
|
-
result: ctx.sample ? `SAMPLE: example record for "${query}"` : `record for "${query}"`,
|
|
1606
|
-
sample: ctx.sample === true
|
|
1607
|
-
})
|
|
1608
|
-
}),
|
|
1609
|
-
{ priceUsd: 0.05, description: "One record lookup" }
|
|
1610
|
-
);
|
|
1611
|
-
function isZodObject(schema) {
|
|
1612
|
-
return schema instanceof z4.ZodObject;
|
|
1613
|
-
}
|
|
1614
|
-
function contextFromExtra(extra) {
|
|
1615
|
-
const headers = new Headers();
|
|
1616
|
-
const raw = extra.requestInfo?.headers;
|
|
1617
|
-
if (raw) {
|
|
1618
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
1619
|
-
if (Array.isArray(value)) for (const item of value) headers.append(key, item);
|
|
1620
|
-
else if (typeof value === "string") headers.append(key, value);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
const progressToken = extra._meta?.progressToken;
|
|
1624
|
-
return {
|
|
1625
|
-
headers,
|
|
1626
|
-
requestId: String(extra.requestId),
|
|
1627
|
-
signal: extra.signal,
|
|
1628
|
-
sessionId: extra.sessionId,
|
|
1629
|
-
meta: extra._meta,
|
|
1630
|
-
extra,
|
|
1631
|
-
log: async (level, data) => {
|
|
1632
|
-
await extra.sendNotification({ method: "notifications/message", params: { level, data } });
|
|
1633
|
-
},
|
|
1634
|
-
progress: async (progress, total, message) => {
|
|
1635
|
-
if (progressToken === void 0) return;
|
|
1636
|
-
await extra.sendNotification({
|
|
1637
|
-
method: "notifications/progress",
|
|
1638
|
-
params: { progressToken, progress, total, message }
|
|
1639
|
-
});
|
|
1640
|
-
}
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
function createMcpServer(opts) {
|
|
1644
|
-
const byName = /* @__PURE__ */ new Map();
|
|
1645
|
-
for (const tool of opts.tools) {
|
|
1646
|
-
if (byName.has(tool.name)) throw new Error(`duplicate tool name: ${tool.name}`);
|
|
1647
|
-
byName.set(tool.name, tool);
|
|
1648
|
-
}
|
|
1649
|
-
const server = new McpServer(
|
|
1650
|
-
{ name: opts.name, version: opts.version, title: opts.title, websiteUrl: opts.websiteUrl },
|
|
1651
|
-
{ instructions: opts.instructions, capabilities: { logging: {}, ...opts.capabilities } }
|
|
1652
|
-
);
|
|
1653
|
-
const registered = /* @__PURE__ */ new Map();
|
|
1654
|
-
for (const tool of byName.values()) {
|
|
1655
|
-
const config = {
|
|
1656
|
-
title: tool.title,
|
|
1657
|
-
description: tool.description,
|
|
1658
|
-
inputSchema: tool.input,
|
|
1659
|
-
annotations: tool.annotations
|
|
1660
|
-
};
|
|
1661
|
-
if (isZodObject(tool.output)) config.outputSchema = tool.output;
|
|
1662
|
-
registered.set(
|
|
1663
|
-
tool.name,
|
|
1664
|
-
server.registerTool(tool.name, config, () => {
|
|
1665
|
-
throw new Error("unreachable: mcp-kit dispatches tools/call itself");
|
|
1666
|
-
})
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
if (byName.size > 0) {
|
|
1670
|
-
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1671
|
-
const enabled = [...byName.values()].filter(
|
|
1672
|
-
(tool) => registered.get(tool.name)?.enabled !== false
|
|
1673
|
-
);
|
|
1674
|
-
return {
|
|
1675
|
-
tools: listToolManifest(enabled).map((entry) => ({
|
|
1676
|
-
...entry,
|
|
1677
|
-
inputSchema: entry.inputSchema,
|
|
1678
|
-
outputSchema: entry.outputSchema
|
|
1679
|
-
}))
|
|
1680
|
-
};
|
|
1681
|
-
});
|
|
1682
|
-
server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
1683
|
-
const name = request.params.name;
|
|
1684
|
-
const tool = byName.get(name);
|
|
1685
|
-
const entry = registered.get(name);
|
|
1686
|
-
if (!tool || !entry) throw new McpError(ErrorCode.InvalidParams, `Tool ${name} not found`);
|
|
1687
|
-
if (!entry.enabled) throw new McpError(ErrorCode.InvalidParams, `Tool ${name} disabled`);
|
|
1688
|
-
return tool.invoke(request.params.arguments ?? {}, contextFromExtra(extra));
|
|
1689
|
-
});
|
|
1690
|
-
}
|
|
1691
|
-
return server;
|
|
1692
|
-
}
|
|
1693
|
-
function toJsonSchema(schema, io = "input") {
|
|
1694
|
-
const json2 = z4.toJSONSchema(schema, { io, target: "draft-2020-12" });
|
|
1695
|
-
if (isZodObject(schema) && schema._zod.def.catchall === void 0 && json2.additionalProperties === void 0) {
|
|
1696
|
-
json2.additionalProperties = false;
|
|
1697
|
-
}
|
|
1698
|
-
return json2;
|
|
1699
|
-
}
|
|
1700
|
-
function listToolManifest(tools) {
|
|
1701
|
-
return tools.map((tool) => {
|
|
1702
|
-
const entry = {
|
|
1703
|
-
name: tool.name,
|
|
1704
|
-
description: tool.description,
|
|
1705
|
-
inputSchema: toJsonSchema(tool.input, "input")
|
|
1706
|
-
};
|
|
1707
|
-
if (tool.title !== void 0) entry.title = tool.title;
|
|
1708
|
-
if (isZodObject(tool.output)) entry.outputSchema = toJsonSchema(tool.output, "output");
|
|
1709
|
-
if (tool.annotations !== void 0) entry.annotations = tool.annotations;
|
|
1710
|
-
return entry;
|
|
1711
|
-
});
|
|
1712
|
-
}
|
|
1713
|
-
var DEFAULT_CORS = {
|
|
1714
|
-
origin: "*",
|
|
1715
|
-
methods: "GET, POST, DELETE, OPTIONS",
|
|
1716
|
-
headers: "Content-Type, Accept, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, PAYMENT, X-PAYMENT",
|
|
1717
|
-
exposeHeaders: "Mcp-Session-Id, Mcp-Protocol-Version, WWW-Authenticate, X-Payment-Requirements, PAYMENT-REQUIRED",
|
|
1718
|
-
maxAge: 86400
|
|
1719
|
-
};
|
|
1720
|
-
var DEFAULT_SESSION_TTL_MS = 30 * 6e4;
|
|
1721
|
-
function corsHeaders(cors) {
|
|
1722
|
-
if (!cors) return {};
|
|
1723
|
-
const c = { ...DEFAULT_CORS, ...typeof cors === "object" ? cors : {} };
|
|
1724
|
-
return {
|
|
1725
|
-
"Access-Control-Allow-Origin": c.origin,
|
|
1726
|
-
"Access-Control-Allow-Methods": c.methods,
|
|
1727
|
-
"Access-Control-Allow-Headers": c.headers,
|
|
1728
|
-
"Access-Control-Expose-Headers": c.exposeHeaders,
|
|
1729
|
-
"Access-Control-Max-Age": String(c.maxAge)
|
|
1730
|
-
};
|
|
1731
|
-
}
|
|
1732
|
-
function jsonResponse(body, status, headers = {}) {
|
|
1733
|
-
return new Response(JSON.stringify(body), {
|
|
1734
|
-
status,
|
|
1735
|
-
headers: { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers }
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
function methodNotAllowed(method, allow, cors) {
|
|
1739
|
-
return jsonResponse(
|
|
1740
|
-
{
|
|
1741
|
-
code: "METHOD_NOT_ALLOWED",
|
|
1742
|
-
cause: `${method} is not supported here without a session`,
|
|
1743
|
-
fix: "send JSON-RPC over POST with `Accept: application/json, text/event-stream` (start with `initialize`)",
|
|
1744
|
-
retryable: false
|
|
1745
|
-
},
|
|
1746
|
-
405,
|
|
1747
|
-
{ Allow: allow, ...cors }
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
|
-
function splitOptions(opts, transport) {
|
|
1751
|
-
if (typeof opts === "function") return { factory: opts, transport };
|
|
1752
|
-
const {
|
|
1753
|
-
cors,
|
|
1754
|
-
jsonResponse: jsonResponse2,
|
|
1755
|
-
sessions,
|
|
1756
|
-
allowedHosts,
|
|
1757
|
-
allowedOrigins,
|
|
1758
|
-
enableDnsRebindingProtection,
|
|
1759
|
-
...server
|
|
1760
|
-
} = opts;
|
|
1761
|
-
return {
|
|
1762
|
-
factory: () => createMcpServer(server),
|
|
1763
|
-
transport: {
|
|
1764
|
-
cors,
|
|
1765
|
-
jsonResponse: jsonResponse2,
|
|
1766
|
-
sessions,
|
|
1767
|
-
allowedHosts,
|
|
1768
|
-
allowedOrigins,
|
|
1769
|
-
enableDnsRebindingProtection,
|
|
1770
|
-
...transport
|
|
1771
|
-
}
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
function withCors(response, cors) {
|
|
1775
|
-
const keys = Object.keys(cors);
|
|
1776
|
-
if (keys.length === 0) return response;
|
|
1777
|
-
const headers = new Headers(response.headers);
|
|
1778
|
-
for (const key of keys) headers.set(key, cors[key] ?? "");
|
|
1779
|
-
return new Response(response.body, {
|
|
1780
|
-
status: response.status,
|
|
1781
|
-
statusText: response.statusText,
|
|
1782
|
-
headers
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
function errorResponse(err, cors) {
|
|
1786
|
-
const error = normalizeError(err);
|
|
1787
|
-
return jsonResponse(error.toJSON(), error.httpStatus, cors);
|
|
1788
|
-
}
|
|
1789
|
-
function createStatelessHandler({ factory, cors, transportBase }) {
|
|
1790
|
-
return async (request) => {
|
|
1791
|
-
if (request.method !== "POST") return methodNotAllowed(request.method, "POST, OPTIONS", cors);
|
|
1792
|
-
const server = factory();
|
|
1793
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1794
|
-
...transportBase,
|
|
1795
|
-
sessionIdGenerator: void 0
|
|
1796
|
-
});
|
|
1797
|
-
let closed = false;
|
|
1798
|
-
const cleanup = async () => {
|
|
1799
|
-
if (closed) return;
|
|
1800
|
-
closed = true;
|
|
1801
|
-
try {
|
|
1802
|
-
await server.close();
|
|
1803
|
-
} catch {
|
|
1804
|
-
}
|
|
1805
|
-
};
|
|
1806
|
-
try {
|
|
1807
|
-
await server.connect(transport);
|
|
1808
|
-
const response = withCors(await transport.handleRequest(request), cors);
|
|
1809
|
-
if (!response.body) {
|
|
1810
|
-
await cleanup();
|
|
1811
|
-
return response;
|
|
1812
|
-
}
|
|
1813
|
-
request.signal?.addEventListener("abort", () => void cleanup(), { once: true });
|
|
1814
|
-
const body = response.body.pipeThrough(
|
|
1815
|
-
new TransformStream({ flush: () => cleanup() })
|
|
1816
|
-
);
|
|
1817
|
-
return new Response(body, {
|
|
1818
|
-
status: response.status,
|
|
1819
|
-
statusText: response.statusText,
|
|
1820
|
-
headers: response.headers
|
|
1821
|
-
});
|
|
1822
|
-
} catch (err) {
|
|
1823
|
-
await cleanup();
|
|
1824
|
-
return errorResponse(err, cors);
|
|
1825
|
-
}
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
|
-
function createStatefulHandler({ factory, cors, transportBase }, options) {
|
|
1829
|
-
const ttlMs = options.ttlMs ?? DEFAULT_SESSION_TTL_MS;
|
|
1830
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
1831
|
-
const drop = (id) => {
|
|
1832
|
-
const session = sessions.get(id);
|
|
1833
|
-
if (!session) return;
|
|
1834
|
-
sessions.delete(id);
|
|
1835
|
-
session.server.close().catch(() => void 0);
|
|
1836
|
-
};
|
|
1837
|
-
const sweep = () => {
|
|
1838
|
-
const now = Date.now();
|
|
1839
|
-
for (const [id, session] of sessions) if (now - session.lastSeen > ttlMs) drop(id);
|
|
1840
|
-
};
|
|
1841
|
-
return async (request) => {
|
|
1842
|
-
const sessionId = request.headers.get("mcp-session-id");
|
|
1843
|
-
if (sessionId) {
|
|
1844
|
-
const session = sessions.get(sessionId);
|
|
1845
|
-
if (!session) {
|
|
1846
|
-
return jsonResponse(
|
|
1847
|
-
{
|
|
1848
|
-
code: "NOT_FOUND",
|
|
1849
|
-
cause: "unknown or expired Mcp-Session-Id",
|
|
1850
|
-
fix: "send `initialize` again without an Mcp-Session-Id header and use the id from the response",
|
|
1851
|
-
retryable: true
|
|
1852
|
-
},
|
|
1853
|
-
404,
|
|
1854
|
-
cors
|
|
1855
|
-
);
|
|
1856
|
-
}
|
|
1857
|
-
session.lastSeen = Date.now();
|
|
1858
|
-
try {
|
|
1859
|
-
return withCors(await session.transport.handleRequest(request), cors);
|
|
1860
|
-
} catch (err) {
|
|
1861
|
-
return errorResponse(err, cors);
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
if (request.method !== "POST") return methodNotAllowed(request.method, "POST, OPTIONS", cors);
|
|
1865
|
-
sweep();
|
|
1866
|
-
const server = factory();
|
|
1867
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1868
|
-
...transportBase,
|
|
1869
|
-
sessionIdGenerator: () => crypto.randomUUID(),
|
|
1870
|
-
onsessioninitialized: (id) => {
|
|
1871
|
-
sessions.set(id, { server, transport, lastSeen: Date.now() });
|
|
1872
|
-
},
|
|
1873
|
-
onsessionclosed: (id) => {
|
|
1874
|
-
sessions.delete(id);
|
|
1875
|
-
}
|
|
1876
|
-
});
|
|
1877
|
-
try {
|
|
1878
|
-
await server.connect(transport);
|
|
1879
|
-
const previousOnClose = transport.onclose;
|
|
1880
|
-
transport.onclose = () => {
|
|
1881
|
-
previousOnClose?.();
|
|
1882
|
-
if (transport.sessionId) sessions.delete(transport.sessionId);
|
|
1883
|
-
};
|
|
1884
|
-
const response = withCors(await transport.handleRequest(request), cors);
|
|
1885
|
-
if (!transport.sessionId) server.close().catch(() => void 0);
|
|
1886
|
-
return response;
|
|
1887
|
-
} catch (err) {
|
|
1888
|
-
server.close().catch(() => void 0);
|
|
1889
|
-
return errorResponse(err, cors);
|
|
1890
|
-
}
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
function createHttpHandler(opts, transportOptions = {}) {
|
|
1894
|
-
const { factory, transport: t } = splitOptions(opts, transportOptions);
|
|
1895
|
-
const cors = corsHeaders(t.cors);
|
|
1896
|
-
const sessions = t.sessions ? typeof t.sessions === "object" ? t.sessions : {} : void 0;
|
|
1897
|
-
const context = {
|
|
1898
|
-
factory,
|
|
1899
|
-
cors,
|
|
1900
|
-
transportBase: {
|
|
1901
|
-
enableJsonResponse: t.jsonResponse ?? sessions === void 0,
|
|
1902
|
-
allowedHosts: t.allowedHosts,
|
|
1903
|
-
allowedOrigins: t.allowedOrigins,
|
|
1904
|
-
enableDnsRebindingProtection: t.enableDnsRebindingProtection
|
|
1905
|
-
}
|
|
1906
|
-
};
|
|
1907
|
-
const handler = sessions ? createStatefulHandler(context, sessions) : createStatelessHandler(context);
|
|
1908
|
-
const allow = sessions ? "GET, POST, DELETE, OPTIONS" : "POST, OPTIONS";
|
|
1909
|
-
return async (request) => {
|
|
1910
|
-
if (request.method === "OPTIONS") {
|
|
1911
|
-
return new Response(null, { status: 204, headers: { Allow: allow, ...cors } });
|
|
1912
|
-
}
|
|
1913
|
-
return handler(request);
|
|
1914
|
-
};
|
|
1915
|
-
}
|
|
1916
|
-
function localhostHosts(port) {
|
|
1917
|
-
const hosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
1918
|
-
return [...hosts, ...hosts.map((h) => `${h}:${port}`)];
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
// ../../packages/mcp-kit/dist/chunk-M567BWMA.js
|
|
1922
|
-
var LIST_MAX_PAGES = 20;
|
|
1923
|
-
var LIST_MAX_ITEMS = 2e3;
|
|
1924
|
-
async function readAllPages(fetchPage, opts = {}) {
|
|
1925
|
-
const maxPages = opts.maxPages ?? LIST_MAX_PAGES;
|
|
1926
|
-
const maxItems = opts.maxItems ?? LIST_MAX_ITEMS;
|
|
1927
|
-
const method = opts.method ?? "tools/list";
|
|
1928
|
-
const items = [];
|
|
1929
|
-
const followed = /* @__PURE__ */ new Set();
|
|
1930
|
-
let cursor;
|
|
1931
|
-
for (let page = 1; ; page++) {
|
|
1932
|
-
const res = await fetchPage(cursor, page);
|
|
1933
|
-
if (Array.isArray(res.items)) items.push(...res.items);
|
|
1934
|
-
const next = res.nextCursor;
|
|
1935
|
-
const more = typeof next === "string" && next !== "";
|
|
1936
|
-
if (items.length > maxItems) {
|
|
1937
|
-
return {
|
|
1938
|
-
items,
|
|
1939
|
-
pages: page,
|
|
1940
|
-
truncated: {
|
|
1941
|
-
ceiling: "items",
|
|
1942
|
-
reason: `${method} listed more than ${maxItems} items in ${page} page${page === 1 ? "" : "s"}${more ? " and had more" : ""}; at most ${maxItems} are read`
|
|
1943
|
-
}
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
if (!more) return { items, pages: page };
|
|
1947
|
-
if (followed.has(next)) return { items, pages: page, repeatedCursor: true };
|
|
1948
|
-
if (page >= maxPages) {
|
|
1949
|
-
return {
|
|
1950
|
-
items,
|
|
1951
|
-
pages: page,
|
|
1952
|
-
truncated: {
|
|
1953
|
-
ceiling: "pages",
|
|
1954
|
-
reason: `${method} had more than ${maxPages} pages (${items.length} items read); at most ${maxPages} pages are read`
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
followed.add(next);
|
|
1959
|
-
cursor = next;
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
// ../../packages/mcp-kit/dist/index.js
|
|
1964
|
-
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1965
|
-
async function serveStdio(serverOrOpts) {
|
|
1966
|
-
const server = serverOrOpts instanceof McpServer2 ? serverOrOpts : createMcpServer(serverOrOpts);
|
|
1967
|
-
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
1968
|
-
const transport = new StdioServerTransport();
|
|
1969
|
-
await server.connect(transport);
|
|
1970
|
-
await new Promise((resolve5) => {
|
|
1971
|
-
const previous = transport.onclose;
|
|
1972
|
-
transport.onclose = () => {
|
|
1973
|
-
previous?.();
|
|
1974
|
-
resolve5();
|
|
1975
|
-
};
|
|
1976
|
-
});
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
// src/proxy/http.ts
|
|
1980
|
-
import { writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
1981
|
-
import { createServer } from "http";
|
|
1982
|
-
import { join } from "path";
|
|
1983
|
-
import { FileKillSwitch } from "@agentwares/agentguard-core/node";
|
|
1984
|
-
|
|
1985
|
-
// src/proxy/server.ts
|
|
1986
|
-
import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1987
|
-
import {
|
|
1988
|
-
CallToolRequestSchema as CallToolRequestSchema2,
|
|
1989
|
-
CompleteRequestSchema,
|
|
1990
|
-
GetPromptRequestSchema,
|
|
1991
|
-
ListPromptsRequestSchema,
|
|
1992
|
-
ListResourceTemplatesRequestSchema,
|
|
1993
|
-
ListResourcesRequestSchema,
|
|
1994
|
-
ListToolsRequestSchema as ListToolsRequestSchema2,
|
|
1995
|
-
ReadResourceRequestSchema,
|
|
1996
|
-
SetLevelRequestSchema,
|
|
1997
|
-
SubscribeRequestSchema as SubscribeRequestSchema2,
|
|
1998
|
-
UnsubscribeRequestSchema as UnsubscribeRequestSchema2
|
|
1999
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
2000
2
|
import {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
} from "
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
import {
|
|
2007
|
-
buildReport,
|
|
2008
|
-
latestRunId,
|
|
2009
|
-
listRuns,
|
|
2010
|
-
parseAuditJsonl,
|
|
2011
|
-
renderReportMarkdown,
|
|
2012
|
-
verifyChain
|
|
2013
|
-
} from "@agentwares/agentguard-core";
|
|
2014
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
2015
|
-
import { z as z5 } from "zod";
|
|
2016
|
-
var INIT_COMMAND = "npx -y @agentwares/agentguard init";
|
|
2017
|
-
var READ_ONLY = {
|
|
2018
|
-
readOnlyHint: true,
|
|
2019
|
-
destructiveHint: false,
|
|
2020
|
-
idempotentHint: true,
|
|
2021
|
-
openWorldHint: false
|
|
2022
|
-
};
|
|
2023
|
-
var runIdInput = z5.string().min(1).max(128).optional().describe(
|
|
2024
|
-
"Run to read, as it appears in the audit log (for example run_20261007093015_ab12, or the X-Run-Id an HTTP client sent). Omit it for the most recent run."
|
|
2025
|
-
);
|
|
2026
|
-
var counters = z5.record(z5.string(), z5.number()).describe(
|
|
2027
|
-
"counter name (tool_calls, writes, deletes, emails, spend_usd or a custom one) \u2192 value"
|
|
2028
|
-
);
|
|
2029
|
-
function readAudit(path) {
|
|
2030
|
-
if (!existsSync(path)) return { exists: false, entries: [] };
|
|
2031
|
-
try {
|
|
2032
|
-
return { exists: true, entries: parseAuditJsonl(readFileSync2(path, "utf8")) };
|
|
2033
|
-
} catch (err) {
|
|
2034
|
-
return { exists: true, entries: [], error: err instanceof Error ? err.message : String(err) };
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
function unreadable(path, error) {
|
|
2038
|
-
return toolError({
|
|
2039
|
-
code: "AUDIT_UNREADABLE",
|
|
2040
|
-
cause: `the audit log at ${path} could not be read: ${error}`,
|
|
2041
|
-
fix: "call agentguard_verify_audit_log to see where it breaks; a hand-edited or truncated line has to be restored from a copy",
|
|
2042
|
-
retryable: false
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
function numbers(caps) {
|
|
2046
|
-
const out = {};
|
|
2047
|
-
for (const [k, v] of Object.entries(caps)) if (typeof v === "number") out[k] = v;
|
|
2048
|
-
return out;
|
|
2049
|
-
}
|
|
2050
|
-
function nextStep(runtime) {
|
|
2051
|
-
if (!runtime.configured)
|
|
2052
|
-
return `agentguard is guarding nothing: no policy file at ${runtime.loaded.path}. Run \`${INIT_COMMAND}\` in the project directory: it writes agentguard.yaml in dry-run mode and routes the project's MCP servers through agentguard (backup kept, \`--undo\` restores). Then restart the MCP client.`;
|
|
2053
|
-
if (runtime.policy.upstreams.length === 0)
|
|
2054
|
-
return `${runtime.loaded.path} lists no upstream servers, so agentguard is guarding nothing. Add the MCP servers your agent uses under \`upstreams:\` in that file (it has commented examples), then restart the MCP client.`;
|
|
2055
|
-
return "agentguard is running; run your agent, then read agentguard_get_report.";
|
|
2056
|
-
}
|
|
2057
|
-
var StatusOutput = z5.object({
|
|
2058
|
-
configured: z5.boolean().describe(
|
|
2059
|
-
"true when a policy file was found and loaded; false means agentguard guards nothing"
|
|
2060
|
-
),
|
|
2061
|
-
policy_path: z5.string().describe("absolute path of the policy file read, or looked for"),
|
|
2062
|
-
mode: z5.enum(["dry-run", "enforce"]).describe("dry-run fakes writes and spends and records them; enforce lets allowed calls run"),
|
|
2063
|
-
upstreams: z5.array(
|
|
2064
|
-
z5.object({
|
|
2065
|
-
name: z5.string().describe("upstream name from the policy"),
|
|
2066
|
-
connected: z5.boolean().describe("whether agentguard is connected to it"),
|
|
2067
|
-
tools: z5.number().int().describe("tools it exposes"),
|
|
2068
|
-
transport: z5.enum(["stdio", "http"]).describe("how agentguard reaches it"),
|
|
2069
|
-
error: z5.string().optional().describe("why it is not connected")
|
|
2070
|
-
})
|
|
2071
|
-
).describe("the MCP servers this proxy fronts; empty means none are configured"),
|
|
2072
|
-
run_id: z5.string().nullable().describe(
|
|
2073
|
-
"run the usage is for: the requested run_id, else the latest run in the log, else null"
|
|
2074
|
-
),
|
|
2075
|
-
caps: z5.object({ per_run: counters, per_day: counters }).describe("limits from the policy; a counter that is absent has no cap"),
|
|
2076
|
-
used: z5.object({ per_run: counters, per_day: counters }).describe("what that run, and today, have used against the caps"),
|
|
2077
|
-
remaining: z5.record(
|
|
2078
|
-
z5.string(),
|
|
2079
|
-
z5.object({ per_run: z5.number().optional(), per_day: z5.number().optional() })
|
|
2080
|
-
).describe("budget left per capped counter, per run and per day"),
|
|
2081
|
-
kill_switch: z5.object({
|
|
2082
|
-
on: z5.boolean().describe("true halts every tool call with KILLED"),
|
|
2083
|
-
reason: z5.string().optional().describe("why it was pulled"),
|
|
2084
|
-
source: z5.string().optional().describe("file, env or memory")
|
|
2085
|
-
}).describe("the kill switch state"),
|
|
2086
|
-
pending_approvals: z5.number().int().describe("destructive calls waiting for a human decision"),
|
|
2087
|
-
audit_log: z5.object({
|
|
2088
|
-
path: z5.string().describe("absolute path of the hash-chained JSONL audit log"),
|
|
2089
|
-
entries: z5.number().int().describe("entries in it; 0 when it does not exist yet")
|
|
2090
|
-
}).describe("where every tool call is recorded"),
|
|
2091
|
-
next_step: z5.string().describe("the one thing to do next, with the command when there is one")
|
|
2092
|
-
});
|
|
2093
|
-
var ReportOutput = z5.object({
|
|
2094
|
-
configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
|
|
2095
|
-
audit_log_path: z5.string().describe("absolute path of the audit log the report was built from"),
|
|
2096
|
-
run_id: z5.string().nullable().describe("the run reported on; null when the log has no runs"),
|
|
2097
|
-
runs_in_log: z5.number().int().describe("how many runs the audit log holds"),
|
|
2098
|
-
tool_calls: z5.number().int().describe("tool calls in this run"),
|
|
2099
|
-
would_have: z5.object({
|
|
2100
|
-
creates: z5.number().int(),
|
|
2101
|
-
updates: z5.number().int(),
|
|
2102
|
-
deletes: z5.number().int(),
|
|
2103
|
-
sends: z5.number().int(),
|
|
2104
|
-
executes: z5.number().int(),
|
|
2105
|
-
spends: z5.number().int(),
|
|
2106
|
-
spend_usd: z5.number()
|
|
2107
|
-
}).describe("what dry-run mode faked: what the run would have done had the calls executed"),
|
|
2108
|
-
did: z5.object({
|
|
2109
|
-
writes: z5.number().int(),
|
|
2110
|
-
deletes: z5.number().int(),
|
|
2111
|
-
sends: z5.number().int(),
|
|
2112
|
-
spend_usd: z5.number()
|
|
2113
|
-
}).describe("what actually executed upstream"),
|
|
2114
|
-
halts: z5.array(
|
|
2115
|
-
z5.object({
|
|
2116
|
-
seq: z5.number().int().describe("audit entry number"),
|
|
2117
|
-
ts: z5.string().describe("ISO time of the call"),
|
|
2118
|
-
code: z5.string().describe("CAP_EXCEEDED, LOOP_DETECTED, KILLED, APPROVAL_REQUIRED, \u2026"),
|
|
2119
|
-
tool: z5.string().describe("the tool that was stopped"),
|
|
2120
|
-
cause: z5.string().describe("why")
|
|
2121
|
-
})
|
|
2122
|
-
).describe("every call agentguard stopped, in order"),
|
|
2123
|
-
top_tools: z5.array(z5.object({ tool: z5.string(), calls: z5.number().int(), class: z5.string() })).describe("the most-called tools with their class (read, write, spend)"),
|
|
2124
|
-
markdown: z5.string().describe("the same report `agentguard report` prints, as Markdown")
|
|
2125
|
-
});
|
|
2126
|
-
var VerifyOutput = z5.object({
|
|
2127
|
-
configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
|
|
2128
|
-
path: z5.string().describe("absolute path of the audit log checked"),
|
|
2129
|
-
exists: z5.boolean().describe("false when no call has been recorded yet; an absent log verifies"),
|
|
2130
|
-
ok: z5.boolean().describe("true when every entry's hash and prev_hash recompute"),
|
|
2131
|
-
entries: z5.number().int().describe("entries checked"),
|
|
2132
|
-
head_hash: z5.string().nullable().describe(
|
|
2133
|
-
"hash of the last entry; record it with the count to detect entries cut from the end"
|
|
2134
|
-
),
|
|
2135
|
-
broken_at: z5.number().int().nullable().describe("seq of the first entry that does not verify"),
|
|
2136
|
-
reason: z5.string().nullable().describe("why verification failed")
|
|
2137
|
-
});
|
|
2138
|
-
function createOwnTools(runtime) {
|
|
2139
|
-
const status = defineTool({
|
|
2140
|
-
name: "agentguard_get_status",
|
|
2141
|
-
title: "agentguard status",
|
|
2142
|
-
description: "Report what agentguard is guarding right now: the policy file it loaded (or that none was found), dry-run or enforce mode, each upstream MCP server and whether it connected, a run's cap usage and remaining budget, the kill switch, and pending approvals. Call it first to learn whether agentguard is set up; next_step says what to do when it is not. For what a run did, use agentguard_get_report; to check the log was not edited, agentguard_verify_audit_log. Reads local files only and changes nothing.",
|
|
2143
|
-
input: z5.strictObject({ run_id: runIdInput }),
|
|
2144
|
-
output: StatusOutput,
|
|
2145
|
-
annotations: { title: "agentguard status", ...READ_ONLY },
|
|
2146
|
-
async handler({ run_id }) {
|
|
2147
|
-
const { loaded, guard } = runtime;
|
|
2148
|
-
const audit = readAudit(loaded.auditPath);
|
|
2149
|
-
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
2150
|
-
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
2151
|
-
const state = await guard.status(runId ?? "(none yet)");
|
|
2152
|
-
const pending = await guard.approvals.list("pending");
|
|
2153
|
-
return {
|
|
2154
|
-
configured: runtime.configured,
|
|
2155
|
-
policy_path: loaded.path,
|
|
2156
|
-
mode: runtime.policy.mode,
|
|
2157
|
-
upstreams: runtime.upstreamStatus(),
|
|
2158
|
-
run_id: runId,
|
|
2159
|
-
caps: { per_run: numbers(state.caps.per_run), per_day: numbers(state.caps.per_day) },
|
|
2160
|
-
used: state.usage,
|
|
2161
|
-
remaining: state.remaining,
|
|
2162
|
-
kill_switch: {
|
|
2163
|
-
on: state.killed.killed,
|
|
2164
|
-
...state.killed.reason ? { reason: state.killed.reason } : {},
|
|
2165
|
-
...state.killed.source ? { source: state.killed.source } : {}
|
|
2166
|
-
},
|
|
2167
|
-
pending_approvals: pending.length,
|
|
2168
|
-
audit_log: { path: loaded.auditPath, entries: audit.entries.length },
|
|
2169
|
-
next_step: nextStep(runtime)
|
|
2170
|
-
};
|
|
2171
|
-
}
|
|
2172
|
-
});
|
|
2173
|
-
const report = defineTool({
|
|
2174
|
-
name: "agentguard_get_report",
|
|
2175
|
-
title: "agentguard run report",
|
|
2176
|
-
description: "Summarize one run from agentguard's audit log: how many tool calls, what dry-run mode faked (records it would have created, updated or deleted, messages sent, dollars spent), what really ran upstream, and every call agentguard halted with its code and cause. Defaults to the most recent run. Use it after an agent run, before switching a policy from dry-run to enforce; for live caps and the kill switch use agentguard_get_status. Read-only: reads the audit file and does not record this call.",
|
|
2177
|
-
input: z5.strictObject({ run_id: runIdInput }),
|
|
2178
|
-
output: ReportOutput,
|
|
2179
|
-
annotations: { title: "agentguard run report", ...READ_ONLY },
|
|
2180
|
-
handler({ run_id }) {
|
|
2181
|
-
const { loaded } = runtime;
|
|
2182
|
-
const audit = readAudit(loaded.auditPath);
|
|
2183
|
-
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
2184
|
-
const runs = listRuns(audit.entries);
|
|
2185
|
-
if (run_id && !runs.some((r) => r.runId === run_id)) {
|
|
2186
|
-
throw toolError({
|
|
2187
|
-
code: "NOT_FOUND",
|
|
2188
|
-
cause: `no run "${run_id}" in the audit log at ${loaded.auditPath}`,
|
|
2189
|
-
fix: `call again without run_id for the latest run${runs.length ? `; runs in the log: ${runs.slice(0, 5).map((r) => r.runId).join(", ")}` : "; the log has no runs yet"}`,
|
|
2190
|
-
retryable: false
|
|
2191
|
-
});
|
|
2192
|
-
}
|
|
2193
|
-
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
2194
|
-
const built = buildReport(audit.entries, { runId: runId ?? void 0 });
|
|
2195
|
-
const markdown = runId ? renderReportMarkdown(built) : `# agentguard report
|
|
2196
|
-
|
|
2197
|
-
No runs recorded in ${loaded.auditPath} yet. ${nextStep(runtime)}`;
|
|
2198
|
-
return {
|
|
2199
|
-
configured: runtime.configured,
|
|
2200
|
-
audit_log_path: loaded.auditPath,
|
|
2201
|
-
run_id: runId,
|
|
2202
|
-
runs_in_log: runs.length,
|
|
2203
|
-
tool_calls: runId ? audit.entries.filter((e) => e.run_id === runId).length : 0,
|
|
2204
|
-
would_have: {
|
|
2205
|
-
creates: built.wouldHave.creates,
|
|
2206
|
-
updates: built.wouldHave.updates,
|
|
2207
|
-
deletes: built.wouldHave.deletes,
|
|
2208
|
-
sends: built.wouldHave.sends,
|
|
2209
|
-
executes: built.wouldHave.executes,
|
|
2210
|
-
spends: built.wouldHave.spends,
|
|
2211
|
-
spend_usd: built.wouldHave.spendUsd
|
|
2212
|
-
},
|
|
2213
|
-
did: {
|
|
2214
|
-
writes: built.did.writes,
|
|
2215
|
-
deletes: built.did.deletes,
|
|
2216
|
-
sends: built.did.sends,
|
|
2217
|
-
spend_usd: built.did.spendUsd
|
|
2218
|
-
},
|
|
2219
|
-
halts: built.halts,
|
|
2220
|
-
top_tools: built.topTools,
|
|
2221
|
-
markdown
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
const verify = defineTool({
|
|
2226
|
-
name: "agentguard_verify_audit_log",
|
|
2227
|
-
title: "Verify agentguard audit log",
|
|
2228
|
-
description: "Recompute the hash chain of agentguard's audit log (JSONL, one entry per tool call) and say whether any entry was edited, removed from the middle or reordered, naming the first broken entry. Returns the entry count and head hash; record both elsewhere, because entries cut from the end of the file still verify. Use it before trusting agentguard_get_report. Takes no arguments: it checks the log the loaded policy names. Read-only; a log that does not exist yet verifies as empty.",
|
|
2229
|
-
input: z5.strictObject({}),
|
|
2230
|
-
output: VerifyOutput,
|
|
2231
|
-
annotations: { title: "Verify agentguard audit log", ...READ_ONLY },
|
|
2232
|
-
async handler() {
|
|
2233
|
-
const { loaded } = runtime;
|
|
2234
|
-
const audit = readAudit(loaded.auditPath);
|
|
2235
|
-
if (audit.error) {
|
|
2236
|
-
return {
|
|
2237
|
-
configured: runtime.configured,
|
|
2238
|
-
path: loaded.auditPath,
|
|
2239
|
-
exists: true,
|
|
2240
|
-
ok: false,
|
|
2241
|
-
entries: 0,
|
|
2242
|
-
head_hash: null,
|
|
2243
|
-
broken_at: null,
|
|
2244
|
-
reason: audit.error
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
const result = await verifyChain(audit.entries);
|
|
2248
|
-
return {
|
|
2249
|
-
configured: runtime.configured,
|
|
2250
|
-
path: loaded.auditPath,
|
|
2251
|
-
exists: audit.exists,
|
|
2252
|
-
ok: result.ok,
|
|
2253
|
-
entries: result.entries,
|
|
2254
|
-
// an empty chain's head is the genesis hash, which is not an entry anyone can record
|
|
2255
|
-
head_hash: result.entries > 0 ? result.head ?? null : null,
|
|
2256
|
-
broken_at: result.brokenAt ?? null,
|
|
2257
|
-
reason: result.reason ?? null
|
|
2258
|
-
};
|
|
2259
|
-
}
|
|
2260
|
-
});
|
|
2261
|
-
return [status, report, verify];
|
|
2262
|
-
}
|
|
2263
|
-
function ownToolsActive(runtime) {
|
|
2264
|
-
return runtime.upstreamStatus().length === 0;
|
|
2265
|
-
}
|
|
2266
|
-
function ownToolList(tools) {
|
|
2267
|
-
return listToolManifest(tools);
|
|
2268
|
-
}
|
|
2269
|
-
async function callOwnTool(tools, name, args, requestId) {
|
|
2270
|
-
const tool = tools.find((t) => t.name === name);
|
|
2271
|
-
if (!tool) return void 0;
|
|
2272
|
-
return tool.invoke(args, createToolContext({ requestId }));
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
// src/proxy/server.ts
|
|
2276
|
-
var PROXY_VERSION = version();
|
|
2277
|
-
function headerValue(extra, name) {
|
|
2278
|
-
const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
|
|
2279
|
-
if (Array.isArray(raw)) return raw[0];
|
|
2280
|
-
return typeof raw === "string" ? raw : void 0;
|
|
2281
|
-
}
|
|
2282
|
-
function headersOf(extra) {
|
|
2283
|
-
const out = {};
|
|
2284
|
-
for (const [k, v] of Object.entries(extra.requestInfo?.headers ?? {}))
|
|
2285
|
-
out[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
|
|
2286
|
-
return out;
|
|
2287
|
-
}
|
|
2288
|
-
function runIdFor(extra, meta, fallback) {
|
|
2289
|
-
const header = headerValue(extra, "x-run-id");
|
|
2290
|
-
if (header && header.trim()) return header.trim().slice(0, 128);
|
|
2291
|
-
const fromMeta = meta?.runId ?? meta?.["agentguard/runId"] ?? meta?.run_id;
|
|
2292
|
-
if (typeof fromMeta === "string" && fromMeta.trim()) return fromMeta.trim().slice(0, 128);
|
|
2293
|
-
if (extra.sessionId) return `sess_${extra.sessionId.slice(0, 12)}`;
|
|
2294
|
-
return fallback;
|
|
2295
|
-
}
|
|
2296
|
-
function toCallToolResult2(result, tool) {
|
|
2297
|
-
const meta = {
|
|
2298
|
-
agentguard: {
|
|
2299
|
-
class: result.decision.classification.class,
|
|
2300
|
-
verb: result.decision.classification.verb,
|
|
2301
|
-
mode: result.decision.mode,
|
|
2302
|
-
outcome: result.outcome,
|
|
2303
|
-
dryRun: result.faked,
|
|
2304
|
-
seq: result.entry.seq,
|
|
2305
|
-
run_id: result.entry.run_id
|
|
2306
|
-
}
|
|
2307
|
-
};
|
|
2308
|
-
const hasOutputSchema = Boolean(tool?.tool.outputSchema);
|
|
2309
|
-
if (result.error) {
|
|
2310
|
-
const upstream2 = result.value;
|
|
2311
|
-
if (upstream2 && upstream2.content) {
|
|
2312
|
-
return { ...upstream2, isError: true, _meta: { ...upstream2._meta ?? {}, ...meta } };
|
|
2313
|
-
}
|
|
2314
|
-
const body = result.error;
|
|
2315
|
-
return {
|
|
2316
|
-
isError: true,
|
|
2317
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
2318
|
-
...hasOutputSchema ? {} : { structuredContent: body },
|
|
2319
|
-
_meta: { ...meta, httpStatus: httpStatusFor(body.code) }
|
|
2320
|
-
};
|
|
2321
|
-
}
|
|
2322
|
-
if (result.faked) {
|
|
2323
|
-
const value = result.value;
|
|
2324
|
-
const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2325
|
-
return {
|
|
2326
|
-
content: [
|
|
2327
|
-
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
2328
|
-
],
|
|
2329
|
-
...isObject ? { structuredContent: value } : {},
|
|
2330
|
-
_meta: meta
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
const upstream = result.value;
|
|
2334
|
-
return { ...upstream, _meta: { ...upstream._meta ?? {}, ...meta } };
|
|
2335
|
-
}
|
|
2336
|
-
function httpStatusFor(code) {
|
|
2337
|
-
switch (code) {
|
|
2338
|
-
case "KILLED":
|
|
2339
|
-
case "TOOL_DENIED":
|
|
2340
|
-
case "APPROVAL_DENIED":
|
|
2341
|
-
return 403;
|
|
2342
|
-
case "APPROVAL_REQUIRED":
|
|
2343
|
-
return 202;
|
|
2344
|
-
case "CAP_EXCEEDED":
|
|
2345
|
-
case "LOOP_DETECTED":
|
|
2346
|
-
return 429;
|
|
2347
|
-
case "UNKNOWN_TOOL":
|
|
2348
|
-
return 404;
|
|
2349
|
-
case "UPSTREAM_ERROR":
|
|
2350
|
-
return 502;
|
|
2351
|
-
default:
|
|
2352
|
-
return 500;
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
function annotateTool(t, mode) {
|
|
2356
|
-
return {
|
|
2357
|
-
...t.tool,
|
|
2358
|
-
name: t.name,
|
|
2359
|
-
_meta: {
|
|
2360
|
-
...t.tool._meta ?? {},
|
|
2361
|
-
agentguard: {
|
|
2362
|
-
upstream: t.upstream,
|
|
2363
|
-
class: t.classification.class,
|
|
2364
|
-
verb: t.classification.verb,
|
|
2365
|
-
mode
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
};
|
|
2369
|
-
}
|
|
2370
|
-
function killedError(state) {
|
|
2371
|
-
return Object.assign(
|
|
2372
|
-
new Error(`agentguard kill switch is on (${state.source ?? "?"}: ${state.reason ?? ""})`),
|
|
2373
|
-
{ code: -32e3 }
|
|
2374
|
-
);
|
|
2375
|
-
}
|
|
2376
|
-
function serverInstructions(runtime) {
|
|
2377
|
-
if (ownToolsActive(runtime))
|
|
2378
|
-
return `agentguard is an MCP policy proxy, and this one fronts no upstream MCP servers${runtime.configured ? "" : ` (no policy file at ${runtime.loaded.path})`}, so it serves only its own read-only tools: agentguard_get_status (what it guards, caps, kill switch), agentguard_get_report (what a run did or would have done) and agentguard_verify_audit_log. To guard the user's MCP servers, run \`${INIT_COMMAND}\` in the project directory and restart the client.`;
|
|
2379
|
-
return `Tool calls pass through agentguard (${runtime.policy.mode} mode). Errors carry { code, cause, fix, retryable }; follow "fix". CAP_EXCEEDED, LOOP_DETECTED and KILLED are not retryable: stop and report to the user. APPROVAL_REQUIRED: relay the approval command to the user, then retry the identical call once.`;
|
|
2380
|
-
}
|
|
2381
|
-
function createDownstreamServer(runtime, opts = {}) {
|
|
2382
|
-
const server = new McpServer3(
|
|
2383
|
-
{ name: "agentguard", version: PROXY_VERSION, title: "agentguard policy proxy" },
|
|
2384
|
-
{
|
|
2385
|
-
capabilities: {
|
|
2386
|
-
tools: { listChanged: true },
|
|
2387
|
-
resources: { subscribe: true, listChanged: true },
|
|
2388
|
-
prompts: { listChanged: true },
|
|
2389
|
-
logging: {},
|
|
2390
|
-
completions: {}
|
|
2391
|
-
},
|
|
2392
|
-
instructions: serverInstructions(runtime)
|
|
2393
|
-
}
|
|
2394
|
-
);
|
|
2395
|
-
const ownTools = createOwnTools(runtime);
|
|
2396
|
-
const session = {
|
|
2397
|
-
id: crypto.randomUUID(),
|
|
2398
|
-
server,
|
|
2399
|
-
lastActive: Date.now(),
|
|
2400
|
-
inflight: /* @__PURE__ */ new Map()
|
|
2401
|
-
};
|
|
2402
|
-
runtime.attach(session);
|
|
2403
|
-
const previousOnClose = server.server.onclose;
|
|
2404
|
-
server.server.onclose = () => {
|
|
2405
|
-
previousOnClose?.();
|
|
2406
|
-
runtime.detach(session);
|
|
2407
|
-
};
|
|
2408
|
-
const touch = () => {
|
|
2409
|
-
session.lastActive = Date.now();
|
|
2410
|
-
};
|
|
2411
|
-
const agentFor = async (extra) => {
|
|
2412
|
-
const key = keyFromHeaders(headersOf(extra));
|
|
2413
|
-
if (!key) return { agent: void 0 };
|
|
2414
|
-
const resolved = await resolveAgent(runtime.policy, { key });
|
|
2415
|
-
if (resolved.error) {
|
|
2416
|
-
return {
|
|
2417
|
-
agent: null,
|
|
2418
|
-
error: {
|
|
2419
|
-
code: "TOOL_DENIED",
|
|
2420
|
-
cause: "the agent key in Authorization / X-Agentguard-Key is not in agentguard.yaml",
|
|
2421
|
-
fix: "use a key created with `agentguard key create <agent>`, or omit the key to use the global policy",
|
|
2422
|
-
retryable: false
|
|
2423
|
-
}
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
return { agent: resolved.agent };
|
|
2427
|
-
};
|
|
2428
|
-
const low = server.server;
|
|
2429
|
-
low.setRequestHandler(ListToolsRequestSchema2, async (_req, extra) => {
|
|
2430
|
-
touch();
|
|
2431
|
-
const { agent } = await agentFor(extra);
|
|
2432
|
-
const scope = agent === void 0 ? runtime.agent : agent;
|
|
2433
|
-
if (ownToolsActive(runtime)) return { tools: ownToolList(ownTools) };
|
|
2434
|
-
return {
|
|
2435
|
-
tools: runtime.listTools(scope).map((t) => annotateTool(t, scope?.mode ?? runtime.policy.mode))
|
|
2436
|
-
};
|
|
2437
|
-
});
|
|
2438
|
-
low.setRequestHandler(CallToolRequestSchema2, async (req, extra) => {
|
|
2439
|
-
touch();
|
|
2440
|
-
if (ownToolsActive(runtime)) {
|
|
2441
|
-
const own = await callOwnTool(
|
|
2442
|
-
ownTools,
|
|
2443
|
-
req.params.name,
|
|
2444
|
-
req.params.arguments ?? {},
|
|
2445
|
-
String(extra.requestId)
|
|
2446
|
-
);
|
|
2447
|
-
if (own) return own;
|
|
2448
|
-
}
|
|
2449
|
-
const { agent, error } = await agentFor(extra);
|
|
2450
|
-
const runId = runIdFor(extra, req.params._meta, opts.defaultRunId ?? runtime.defaultRunId);
|
|
2451
|
-
if (error) {
|
|
2452
|
-
return {
|
|
2453
|
-
isError: true,
|
|
2454
|
-
content: [{ type: "text", text: JSON.stringify(error) }],
|
|
2455
|
-
structuredContent: error,
|
|
2456
|
-
_meta: { httpStatus: 401 }
|
|
2457
|
-
};
|
|
2458
|
-
}
|
|
2459
|
-
const progressToken = req.params._meta?.progressToken;
|
|
2460
|
-
const { result, tool } = await runtime.callTool(req.params.name, req.params.arguments ?? {}, {
|
|
2461
|
-
runId,
|
|
2462
|
-
agent,
|
|
2463
|
-
sessionId: extra.sessionId ?? session.id,
|
|
2464
|
-
session,
|
|
2465
|
-
requestId: extra.requestId,
|
|
2466
|
-
onprogress: progressToken === void 0 ? void 0 : (p) => {
|
|
2467
|
-
void extra.sendNotification({
|
|
2468
|
-
method: "notifications/progress",
|
|
2469
|
-
params: { progressToken, ...p }
|
|
2470
|
-
});
|
|
2471
|
-
}
|
|
2472
|
-
});
|
|
2473
|
-
return toCallToolResult2(result, tool);
|
|
2474
|
-
});
|
|
2475
|
-
const guardRead = async () => {
|
|
2476
|
-
const state = await runtime.guard.killState();
|
|
2477
|
-
if (state.killed) throw killedError(state);
|
|
2478
|
-
};
|
|
2479
|
-
low.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2480
|
-
touch();
|
|
2481
|
-
return runtime.listResources();
|
|
2482
|
-
});
|
|
2483
|
-
low.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
2484
|
-
touch();
|
|
2485
|
-
return runtime.listResourceTemplates();
|
|
2486
|
-
});
|
|
2487
|
-
low.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
2488
|
-
touch();
|
|
2489
|
-
await guardRead();
|
|
2490
|
-
return runtime.readResource(req.params.uri);
|
|
2491
|
-
});
|
|
2492
|
-
low.setRequestHandler(SubscribeRequestSchema2, async (req) => {
|
|
2493
|
-
await runtime.subscribe(req.params.uri, true);
|
|
2494
|
-
return {};
|
|
2495
|
-
});
|
|
2496
|
-
low.setRequestHandler(UnsubscribeRequestSchema2, async (req) => {
|
|
2497
|
-
await runtime.subscribe(req.params.uri, false);
|
|
2498
|
-
return {};
|
|
2499
|
-
});
|
|
2500
|
-
low.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
2501
|
-
touch();
|
|
2502
|
-
return runtime.listPrompts();
|
|
2503
|
-
});
|
|
2504
|
-
low.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
2505
|
-
touch();
|
|
2506
|
-
await guardRead();
|
|
2507
|
-
return runtime.getPrompt(req.params.name, req.params.arguments);
|
|
2508
|
-
});
|
|
2509
|
-
low.setRequestHandler(CompleteRequestSchema, async (req) => {
|
|
2510
|
-
touch();
|
|
2511
|
-
return runtime.complete(req.params);
|
|
2512
|
-
});
|
|
2513
|
-
low.setRequestHandler(SetLevelRequestSchema, async (req) => {
|
|
2514
|
-
await runtime.setLevel(req.params.level);
|
|
2515
|
-
return {};
|
|
2516
|
-
});
|
|
2517
|
-
return server;
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
|
-
// src/proxy/http.ts
|
|
2521
|
-
function registrationPath(stateDir) {
|
|
2522
|
-
return join(stateDir, "http.json");
|
|
2523
|
-
}
|
|
2524
|
-
function json(res, status, body) {
|
|
2525
|
-
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
2526
|
-
res.end(JSON.stringify(body));
|
|
2527
|
-
}
|
|
2528
|
-
function html(res, status, body) {
|
|
2529
|
-
res.writeHead(status, {
|
|
2530
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
2531
|
-
"Cache-Control": "no-store"
|
|
2532
|
-
});
|
|
2533
|
-
res.end(
|
|
2534
|
-
`<!doctype html><meta name="viewport" content="width=device-width"><body style="font:16px system-ui;padding:2rem;max-width:40rem">${body}</body>`
|
|
2535
|
-
);
|
|
2536
|
-
}
|
|
2537
|
-
async function readBody(req) {
|
|
2538
|
-
if (req.method === "GET" || req.method === "HEAD") return void 0;
|
|
2539
|
-
const chunks = [];
|
|
2540
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
2541
|
-
return new Uint8Array(Buffer.concat(chunks));
|
|
2542
|
-
}
|
|
2543
|
-
function isLoopback(host) {
|
|
2544
|
-
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
|
|
2545
|
-
}
|
|
2546
|
-
async function startHttpProxy(opts) {
|
|
2547
|
-
const { runtime } = opts;
|
|
2548
|
-
const host = opts.host ?? "127.0.0.1";
|
|
2549
|
-
const path = opts.path ?? "/mcp";
|
|
2550
|
-
const token = opts.token ?? crypto.randomUUID().replace(/-/g, "");
|
|
2551
|
-
const kill = new FileKillSwitch(runtime.loaded.killPath);
|
|
2552
|
-
const holder = {};
|
|
2553
|
-
const server = createServer((req, res) => {
|
|
2554
|
-
void (async () => {
|
|
2555
|
-
const url2 = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
|
|
2556
|
-
const authorized = () => {
|
|
2557
|
-
const q = url2.searchParams.get("token");
|
|
2558
|
-
const h = req.headers.authorization?.replace(/^Bearer\s+/i, "");
|
|
2559
|
-
return q === token || h === token;
|
|
2560
|
-
};
|
|
2561
|
-
if (url2.pathname === path && holder.mcp) {
|
|
2562
|
-
const headers = new Headers();
|
|
2563
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
2564
|
-
if (Array.isArray(v)) for (const item of v) headers.append(k, item);
|
|
2565
|
-
else if (typeof v === "string") headers.set(k, v);
|
|
2566
|
-
}
|
|
2567
|
-
const controller = new AbortController();
|
|
2568
|
-
res.on("close", () => {
|
|
2569
|
-
if (!res.writableFinished) controller.abort();
|
|
2570
|
-
});
|
|
2571
|
-
const response = await holder.mcp(
|
|
2572
|
-
new Request(url2, {
|
|
2573
|
-
method: req.method ?? "GET",
|
|
2574
|
-
headers,
|
|
2575
|
-
body: await readBody(req),
|
|
2576
|
-
signal: controller.signal
|
|
2577
|
-
})
|
|
2578
|
-
);
|
|
2579
|
-
const out = {};
|
|
2580
|
-
response.headers.forEach((v, k) => {
|
|
2581
|
-
out[k] = v;
|
|
2582
|
-
});
|
|
2583
|
-
res.writeHead(response.status, out);
|
|
2584
|
-
if (!response.body) return res.end();
|
|
2585
|
-
const reader = response.body.getReader();
|
|
2586
|
-
try {
|
|
2587
|
-
for (; ; ) {
|
|
2588
|
-
const { done, value } = await reader.read();
|
|
2589
|
-
if (done) break;
|
|
2590
|
-
res.write(value);
|
|
2591
|
-
}
|
|
2592
|
-
} catch {
|
|
2593
|
-
}
|
|
2594
|
-
return res.end();
|
|
2595
|
-
}
|
|
2596
|
-
if (url2.pathname === "/health") {
|
|
2597
|
-
const killed = await runtime.guard.killState();
|
|
2598
|
-
return json(res, 200, {
|
|
2599
|
-
ok: true,
|
|
2600
|
-
name: "agentguard",
|
|
2601
|
-
version: PROXY_VERSION,
|
|
2602
|
-
mode: runtime.policy.mode,
|
|
2603
|
-
killed: killed.killed,
|
|
2604
|
-
upstreams: runtime.upstreamStatus(),
|
|
2605
|
-
tools: runtime.listTools().length,
|
|
2606
|
-
mcp: `${url2.origin}${path}`
|
|
2607
|
-
});
|
|
2608
|
-
}
|
|
2609
|
-
if (url2.pathname === "/status") {
|
|
2610
|
-
if (!authorized()) return json(res, 401, unauthorized());
|
|
2611
|
-
const runId = url2.searchParams.get("run") ?? runtime.defaultRunId;
|
|
2612
|
-
return json(res, 200, { runId, ...await runtime.guard.status(runId) });
|
|
2613
|
-
}
|
|
2614
|
-
if (url2.pathname === "/kill" && req.method === "POST") {
|
|
2615
|
-
if (!authorized()) return json(res, 401, unauthorized());
|
|
2616
|
-
const body = await readBody(req);
|
|
2617
|
-
let reason = "killed via HTTP";
|
|
2618
|
-
try {
|
|
2619
|
-
const parsed = JSON.parse(Buffer.from(body ?? []).toString("utf8") || "{}");
|
|
2620
|
-
if (parsed.reason) reason = String(parsed.reason).slice(0, 200);
|
|
2621
|
-
} catch {
|
|
2622
|
-
}
|
|
2623
|
-
kill.kill(reason);
|
|
2624
|
-
await runtime.guard.halt(reason);
|
|
2625
|
-
return json(res, 200, { killed: true, reason });
|
|
2626
|
-
}
|
|
2627
|
-
if (url2.pathname === "/resume" && req.method === "POST") {
|
|
2628
|
-
if (!authorized()) return json(res, 401, unauthorized());
|
|
2629
|
-
kill.resume();
|
|
2630
|
-
await runtime.guard.resume();
|
|
2631
|
-
return json(res, 200, { killed: false });
|
|
2632
|
-
}
|
|
2633
|
-
const decision = /^\/(approve|deny)\/([A-Za-z0-9_-]+)$/.exec(url2.pathname);
|
|
2634
|
-
if (decision) {
|
|
2635
|
-
if (!authorized()) return json(res, 401, unauthorized());
|
|
2636
|
-
const [, action, id] = decision;
|
|
2637
|
-
const record = await runtime.guard.decide(
|
|
2638
|
-
id,
|
|
2639
|
-
action === "approve" ? "approved" : "denied",
|
|
2640
|
-
"http"
|
|
2641
|
-
);
|
|
2642
|
-
if (!record)
|
|
2643
|
-
return json(res, 404, {
|
|
2644
|
-
code: "NOT_FOUND",
|
|
2645
|
-
cause: `no approval ${id}`,
|
|
2646
|
-
fix: "list pending approvals with `agentguard approvals`",
|
|
2647
|
-
retryable: false
|
|
2648
|
-
});
|
|
2649
|
-
if (req.headers.accept?.includes("text/html")) {
|
|
2650
|
-
return html(
|
|
2651
|
-
res,
|
|
2652
|
-
200,
|
|
2653
|
-
`<h1>${record.status === "approved" ? "Approved" : record.status === "denied" ? "Denied" : record.status}</h1><p><code>${record.tool}</code> \u2014 approval <code>${record.id}</code>.</p><p>The agent may now retry the call once.</p>`
|
|
2654
|
-
);
|
|
2655
|
-
}
|
|
2656
|
-
return json(res, 200, record);
|
|
2657
|
-
}
|
|
2658
|
-
if (url2.pathname === "/approvals") {
|
|
2659
|
-
if (!authorized()) return json(res, 401, unauthorized());
|
|
2660
|
-
return json(res, 200, {
|
|
2661
|
-
approvals: await runtime.guard.approvals.list(
|
|
2662
|
-
url2.searchParams.get("all") ? void 0 : "pending"
|
|
2663
|
-
)
|
|
2664
|
-
});
|
|
2665
|
-
}
|
|
2666
|
-
return json(res, 404, {
|
|
2667
|
-
code: "NOT_FOUND",
|
|
2668
|
-
cause: `no route for ${url2.pathname}`,
|
|
2669
|
-
fix: `POST JSON-RPC to ${path}; GET /health`,
|
|
2670
|
-
retryable: false
|
|
2671
|
-
});
|
|
2672
|
-
})().catch((err) => {
|
|
2673
|
-
if (!res.headersSent)
|
|
2674
|
-
json(res, 500, {
|
|
2675
|
-
code: "INTERNAL",
|
|
2676
|
-
cause: err instanceof Error ? err.message : String(err),
|
|
2677
|
-
fix: "retry; if it persists, report the request",
|
|
2678
|
-
retryable: false
|
|
2679
|
-
});
|
|
2680
|
-
else res.end();
|
|
2681
|
-
});
|
|
2682
|
-
});
|
|
2683
|
-
await new Promise((resolve5, reject) => {
|
|
2684
|
-
server.once("error", reject);
|
|
2685
|
-
server.listen(opts.port ?? 8788, host, () => resolve5());
|
|
2686
|
-
});
|
|
2687
|
-
const address = server.address();
|
|
2688
|
-
const port = typeof address === "object" && address ? address.port : opts.port ?? 8788;
|
|
2689
|
-
holder.mcp = createHttpHandler(() => createDownstreamServer(runtime), {
|
|
2690
|
-
sessions: true,
|
|
2691
|
-
jsonResponse: false,
|
|
2692
|
-
cors: true,
|
|
2693
|
-
allowedHosts: isLoopback(host) ? localhostHosts(port) : void 0,
|
|
2694
|
-
enableDnsRebindingProtection: isLoopback(host)
|
|
2695
|
-
});
|
|
2696
|
-
const baseUrl = `http://${host}:${port}`;
|
|
2697
|
-
const url = `${baseUrl}${path}`;
|
|
2698
|
-
const regFile = registrationPath(runtime.loaded.stateDir);
|
|
2699
|
-
if (opts.registerFile !== false) {
|
|
2700
|
-
mkdirSync(runtime.loaded.stateDir, { recursive: true });
|
|
2701
|
-
const reg = {
|
|
2702
|
-
url,
|
|
2703
|
-
baseUrl,
|
|
2704
|
-
token,
|
|
2705
|
-
pid: process.pid,
|
|
2706
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2707
|
-
};
|
|
2708
|
-
writeFileSync(regFile, JSON.stringify(reg, null, 2));
|
|
2709
|
-
}
|
|
2710
|
-
return {
|
|
2711
|
-
url,
|
|
2712
|
-
port,
|
|
2713
|
-
token,
|
|
2714
|
-
close: () => new Promise((resolve5, reject) => {
|
|
2715
|
-
if (opts.registerFile !== false && existsSync2(regFile)) {
|
|
2716
|
-
try {
|
|
2717
|
-
unlinkSync(regFile);
|
|
2718
|
-
} catch {
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
server.closeAllConnections();
|
|
2722
|
-
server.close((err) => err ? reject(err) : resolve5());
|
|
2723
|
-
})
|
|
2724
|
-
};
|
|
2725
|
-
}
|
|
2726
|
-
function unauthorized() {
|
|
2727
|
-
return {
|
|
2728
|
-
code: "UNAUTHORIZED",
|
|
2729
|
-
cause: "missing or wrong control token",
|
|
2730
|
-
fix: "pass ?token=<token> or Authorization: Bearer <token> from <state>/http.json",
|
|
2731
|
-
retryable: false
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
// ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/decode-strings.js
|
|
2736
|
-
var textEncoder = new TextEncoder();
|
|
2737
|
-
var base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2738
|
-
var base64Lookup = new Uint8Array(256);
|
|
2739
|
-
for (let i = 0; i < base64Chars.length; i++) {
|
|
2740
|
-
base64Lookup[base64Chars.charCodeAt(i)] = i;
|
|
2741
|
-
}
|
|
2742
|
-
var charsetAliases = /* @__PURE__ */ new Map([
|
|
2743
|
-
["iso-8859-8-i", "iso-8859-8"],
|
|
2744
|
-
["iso-8859-8-e", "iso-8859-8"]
|
|
2745
|
-
]);
|
|
2746
|
-
function getDecoder(charset) {
|
|
2747
|
-
charset = (charset || "utf8").trim().toLowerCase();
|
|
2748
|
-
charset = charsetAliases.get(charset) || charset;
|
|
2749
|
-
let decoder;
|
|
2750
|
-
try {
|
|
2751
|
-
decoder = new TextDecoder(charset);
|
|
2752
|
-
} catch (err) {
|
|
2753
|
-
decoder = new TextDecoder("windows-1252");
|
|
2754
|
-
}
|
|
2755
|
-
return decoder;
|
|
2756
|
-
}
|
|
2757
|
-
|
|
2758
|
-
// ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/mime-node.js
|
|
2759
|
-
var defaultDecoder = getDecoder();
|
|
2760
|
-
|
|
2761
|
-
// ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/postal-mime.js
|
|
2762
|
-
var MAX_HEADERS_SIZE = 2 * 1024 * 1024;
|
|
2763
|
-
|
|
2764
|
-
// ../../node_modules/.pnpm/resend@6.25.0/node_modules/resend/dist/index.mjs
|
|
2765
|
-
var import_standardwebhooks = __toESM(require_dist(), 1);
|
|
2766
|
-
var version2 = "6.25.0";
|
|
2767
|
-
var defaultUserAgent = `resend-node:${version2}`;
|
|
2768
|
-
|
|
2769
|
-
// ../../packages/notify/dist/index.js
|
|
2770
|
-
var palette = {
|
|
2771
|
-
bg: "#f4f5f7",
|
|
2772
|
-
card: "#ffffff",
|
|
2773
|
-
text: "#111827",
|
|
2774
|
-
muted: "#6b7280",
|
|
2775
|
-
border: "#e5e7eb",
|
|
2776
|
-
link: "#2563eb",
|
|
2777
|
-
codeBg: "#f3f4f6",
|
|
2778
|
-
red: "#b91c1c",
|
|
2779
|
-
redBg: "#fef2f2",
|
|
2780
|
-
green: "#15803d",
|
|
2781
|
-
greenBg: "#f0fdf4",
|
|
2782
|
-
amber: "#b45309",
|
|
2783
|
-
amberBg: "#fffbeb",
|
|
2784
|
-
gray: "#374151",
|
|
2785
|
-
grayBg: "#f3f4f6"
|
|
2786
|
-
};
|
|
2787
|
-
var fontStack = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
2788
|
-
var baseText = `font-family:${fontStack};font-size:15px;line-height:1.55;color:${palette.text};`;
|
|
2789
|
-
var USER_AGENT = "agentwares-notify/0.1";
|
|
2790
|
-
function errorMessage(e) {
|
|
2791
|
-
if (e instanceof Error) return e.message;
|
|
2792
|
-
return typeof e === "string" ? e : "unknown error";
|
|
2793
|
-
}
|
|
2794
|
-
async function readBody2(res) {
|
|
2795
|
-
try {
|
|
2796
|
-
return (await res.text()).slice(0, 500);
|
|
2797
|
-
} catch {
|
|
2798
|
-
return "";
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
function defaultFetch() {
|
|
2802
|
-
return typeof fetch === "function" ? (input, init) => fetch(input, init) : void 0;
|
|
2803
|
-
}
|
|
2804
|
-
async function postJson(url, body, headers, fetchImpl) {
|
|
2805
|
-
if (!fetchImpl) return { error: "fetch is not available" };
|
|
2806
|
-
try {
|
|
2807
|
-
const res = await fetchImpl(url, {
|
|
2808
|
-
method: "POST",
|
|
2809
|
-
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT, ...headers },
|
|
2810
|
-
body: typeof body === "string" ? body : JSON.stringify(body)
|
|
2811
|
-
});
|
|
2812
|
-
return { res, text: await readBody2(res) };
|
|
2813
|
-
} catch (e) {
|
|
2814
|
-
return { error: errorMessage(e) };
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
async function sendSlack(webhookUrl, message, opts = {}) {
|
|
2818
|
-
const r = await postJson(webhookUrl, message, {}, opts.fetch ?? defaultFetch());
|
|
2819
|
-
if ("error" in r) return { ok: false, error: r.error };
|
|
2820
|
-
return r.res.ok ? { ok: true, status: r.res.status } : { ok: false, status: r.res.status, error: r.text || r.res.statusText || "slack error" };
|
|
2821
|
-
}
|
|
2822
|
-
var SIGNATURE_HEADER = "X-Agentwares-Signature";
|
|
2823
|
-
var encoder = new TextEncoder();
|
|
2824
|
-
function toHex(bytes) {
|
|
2825
|
-
return Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2826
|
-
}
|
|
2827
|
-
async function hmacSha256Hex(secret, body) {
|
|
2828
|
-
const key = await crypto.subtle.importKey(
|
|
2829
|
-
"raw",
|
|
2830
|
-
encoder.encode(secret),
|
|
2831
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
2832
|
-
false,
|
|
2833
|
-
["sign"]
|
|
2834
|
-
);
|
|
2835
|
-
return toHex(await crypto.subtle.sign("HMAC", key, encoder.encode(body)));
|
|
2836
|
-
}
|
|
2837
|
-
async function sendWebhook(url, payload, opts = {}) {
|
|
2838
|
-
const body = JSON.stringify(payload);
|
|
2839
|
-
const headers = { ...opts.headers ?? {} };
|
|
2840
|
-
if (opts.secret) headers[SIGNATURE_HEADER] = `sha256=${await hmacSha256Hex(opts.secret, body)}`;
|
|
2841
|
-
const r = await postJson(url, body, headers, opts.fetch ?? defaultFetch());
|
|
2842
|
-
if ("error" in r) return { ok: false, error: r.error };
|
|
2843
|
-
return r.res.ok ? { ok: true, status: r.res.status } : { ok: false, status: r.res.status, error: r.text || r.res.statusText || "webhook error" };
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
// src/notify.ts
|
|
2847
|
-
function approvalSlackMessage(event, url) {
|
|
2848
|
-
const a = event.approval;
|
|
2849
|
-
const argsText = JSON.stringify(a.args ?? {}, null, 2).slice(0, 1500);
|
|
2850
|
-
const text = `[agentguard] approval needed: ${a.tool} (run ${a.run_id}) \u2014 run \`agentguard approve ${a.id}\`${url ? ` or open ${url}` : ""}`;
|
|
2851
|
-
const blocks = [
|
|
2852
|
-
{ type: "header", text: { type: "plain_text", text: `Approval needed: ${a.tool}` } },
|
|
2853
|
-
{
|
|
2854
|
-
type: "section",
|
|
2855
|
-
text: {
|
|
2856
|
-
type: "mrkdwn",
|
|
2857
|
-
text: `*run* \`${a.run_id}\`${a.agent ? ` \xB7 *agent* \`${a.agent}\`` : ""}${a.upstream ? ` \xB7 *upstream* \`${a.upstream}\`` : ""}
|
|
2858
|
-
\`\`\`${argsText}\`\`\``
|
|
2859
|
-
}
|
|
2860
|
-
},
|
|
2861
|
-
{
|
|
2862
|
-
type: "context",
|
|
2863
|
-
elements: [
|
|
2864
|
-
{
|
|
2865
|
-
type: "mrkdwn",
|
|
2866
|
-
text: `Approve: \`agentguard approve ${a.id}\` \xB7 Deny: \`agentguard deny ${a.id}\` \xB7 expires ${a.expires_at}`
|
|
2867
|
-
}
|
|
2868
|
-
]
|
|
2869
|
-
}
|
|
2870
|
-
];
|
|
2871
|
-
if (url) {
|
|
2872
|
-
blocks.push({
|
|
2873
|
-
type: "actions",
|
|
2874
|
-
elements: [
|
|
2875
|
-
{ type: "button", text: { type: "plain_text", text: "Approve" }, style: "primary", url },
|
|
2876
|
-
{
|
|
2877
|
-
type: "button",
|
|
2878
|
-
text: { type: "plain_text", text: "Deny" },
|
|
2879
|
-
style: "danger",
|
|
2880
|
-
url: url.replace("/approve/", "/deny/")
|
|
2881
|
-
}
|
|
2882
|
-
]
|
|
2883
|
-
});
|
|
2884
|
-
}
|
|
2885
|
-
return { text, blocks };
|
|
2886
|
-
}
|
|
2887
|
-
function haltSlackMessage(event) {
|
|
2888
|
-
const cause = event.error?.cause ?? "";
|
|
2889
|
-
const text = `[agentguard] ${event.type}: ${event.tool} (run ${event.runId}) \u2014 ${cause}`;
|
|
2890
|
-
return {
|
|
2891
|
-
text,
|
|
2892
|
-
blocks: [
|
|
2893
|
-
{
|
|
2894
|
-
type: "section",
|
|
2895
|
-
text: {
|
|
2896
|
-
type: "mrkdwn",
|
|
2897
|
-
text: `*${event.type}* on \`${event.tool}\` (run \`${event.runId}\`)
|
|
2898
|
-
${cause}`
|
|
2899
|
-
}
|
|
2900
|
-
},
|
|
2901
|
-
{
|
|
2902
|
-
type: "context",
|
|
2903
|
-
elements: [
|
|
2904
|
-
{
|
|
2905
|
-
type: "mrkdwn",
|
|
2906
|
-
text: `\`agentguard report --run ${event.runId}\` for the full picture${event.type === "KILLED" ? "" : " \xB7 `agentguard kill` to stop everything"}`
|
|
2907
|
-
}
|
|
2908
|
-
]
|
|
2909
|
-
}
|
|
2910
|
-
]
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
function createAlerter(opts) {
|
|
2914
|
-
const { policy } = opts;
|
|
2915
|
-
const log = opts.log ?? (() => void 0);
|
|
2916
|
-
const channel = { fetch: opts.fetch };
|
|
2917
|
-
return async (event) => {
|
|
2918
|
-
if (event.type === "APPROVAL_DECIDED") return;
|
|
2919
|
-
if (!policy.alerts.on.includes(event.type)) return;
|
|
2920
|
-
const slack = event.type === "APPROVAL_REQUIRED" ? policy.approval.notify.slack || policy.alerts.slack : policy.alerts.slack;
|
|
2921
|
-
const webhook = event.type === "APPROVAL_REQUIRED" ? policy.approval.notify.webhook || policy.alerts.webhook : policy.alerts.webhook;
|
|
2922
|
-
const url = event.approval ? opts.approvalUrl?.(event.approval) : void 0;
|
|
2923
|
-
const message = event.type === "APPROVAL_REQUIRED" && event.approval ? approvalSlackMessage(event, url) : haltSlackMessage(event);
|
|
2924
|
-
const jobs = [];
|
|
2925
|
-
if (slack) {
|
|
2926
|
-
jobs.push(
|
|
2927
|
-
sendSlack(slack, message, channel).then((r) => {
|
|
2928
|
-
if (!r.ok) log(`slack alert failed: ${r.error ?? r.status}`);
|
|
2929
|
-
})
|
|
2930
|
-
);
|
|
2931
|
-
}
|
|
2932
|
-
if (webhook) {
|
|
2933
|
-
jobs.push(
|
|
2934
|
-
sendWebhook(
|
|
2935
|
-
webhook,
|
|
2936
|
-
{
|
|
2937
|
-
event: event.type,
|
|
2938
|
-
product: "agentguard",
|
|
2939
|
-
subject: message.text,
|
|
2940
|
-
summary: event.error?.cause ?? "",
|
|
2941
|
-
occurredAt: event.at,
|
|
2942
|
-
data: {
|
|
2943
|
-
runId: event.runId,
|
|
2944
|
-
tool: event.tool,
|
|
2945
|
-
error: event.error,
|
|
2946
|
-
approval: event.approval,
|
|
2947
|
-
approveUrl: url
|
|
2948
|
-
}
|
|
2949
|
-
},
|
|
2950
|
-
channel
|
|
2951
|
-
).then((r) => {
|
|
2952
|
-
if (!r.ok) log(`webhook alert failed: ${r.error ?? r.status}`);
|
|
2953
|
-
})
|
|
2954
|
-
);
|
|
2955
|
-
}
|
|
2956
|
-
await Promise.all(jobs);
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
|
|
2960
|
-
// src/context.ts
|
|
2961
|
-
import { homedir } from "os";
|
|
2962
|
-
var DEFAULT_POLICY_FILE = "agentguard.yaml";
|
|
2963
|
-
function homeFrom(io) {
|
|
2964
|
-
return io.env.HOME ?? io.env.USERPROFILE ?? homedir();
|
|
2965
|
-
}
|
|
2966
|
-
function defaultIo() {
|
|
2967
|
-
return {
|
|
2968
|
-
out: (line) => process.stdout.write(line + "\n"),
|
|
2969
|
-
err: (line) => process.stderr.write(line + "\n"),
|
|
2970
|
-
cwd: process.cwd(),
|
|
2971
|
-
env: process.env,
|
|
2972
|
-
tty: process.stdout.isTTY === true
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
2975
|
-
function policyPathFrom(args, io) {
|
|
2976
|
-
return resolve(
|
|
2977
|
-
io.cwd,
|
|
2978
|
-
flagString(args, "config") ?? io.env.AGENTGUARD_CONFIG ?? DEFAULT_POLICY_FILE
|
|
2979
|
-
);
|
|
2980
|
-
}
|
|
2981
|
-
function loadPolicyFor(args, io, opts = {}) {
|
|
2982
|
-
return loadPolicyFile(policyPathFrom(args, io), {
|
|
2983
|
-
env: io.env,
|
|
2984
|
-
allowMissingEnv: opts.allowMissingEnv
|
|
2985
|
-
});
|
|
2986
|
-
}
|
|
2987
|
-
function createFileGuard(loaded, opts = {}) {
|
|
2988
|
-
const kill = new FileKillSwitch2(loaded.killPath);
|
|
2989
|
-
const audit = new FileAuditSink(loaded.auditPath);
|
|
2990
|
-
const approvals = new FileApprovalStore(loaded.stateDir);
|
|
2991
|
-
const alerter = opts.alerts === false ? void 0 : createAlerter({ policy: loaded.policy, approvalUrl: opts.approvalUrl, log: opts.log });
|
|
2992
|
-
const guard = new Guard({
|
|
2993
|
-
policy: loaded.policy,
|
|
2994
|
-
state: new FileStateStore(loaded.stateDir),
|
|
2995
|
-
audit,
|
|
2996
|
-
kill,
|
|
2997
|
-
approvals,
|
|
2998
|
-
env: opts.env ?? process.env,
|
|
2999
|
-
approvalUrl: opts.approvalUrl,
|
|
3000
|
-
onEvent: async (event) => {
|
|
3001
|
-
await opts.onEvent?.(event);
|
|
3002
|
-
await alerter?.(event);
|
|
3003
|
-
}
|
|
3004
|
-
});
|
|
3005
|
-
return { guard, kill, audit, approvals, loaded };
|
|
3006
|
-
}
|
|
3007
|
-
function readRegistration(loaded) {
|
|
3008
|
-
const file = registrationPath(loaded.stateDir);
|
|
3009
|
-
if (!exists(file)) return void 0;
|
|
3010
|
-
try {
|
|
3011
|
-
const reg = JSON.parse(readFileSync3(file, "utf8"));
|
|
3012
|
-
return reg;
|
|
3013
|
-
} catch {
|
|
3014
|
-
return void 0;
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
function describeError(err) {
|
|
3018
|
-
if (err && typeof err === "object" && "code" in err && "cause" in err) {
|
|
3019
|
-
const e = err;
|
|
3020
|
-
return `${e.code}: ${e.cause}${e.fix ? `
|
|
3021
|
-
fix: ${e.fix}` : ""}`;
|
|
3022
|
-
}
|
|
3023
|
-
return err instanceof Error ? err.message : String(err);
|
|
3024
|
-
}
|
|
3025
|
-
|
|
3026
|
-
// src/commands/connect.ts
|
|
3027
|
-
import { existsSync as existsSync5 } from "fs";
|
|
3028
|
-
import { relative } from "path";
|
|
3029
|
-
|
|
3030
|
-
// src/configs.ts
|
|
3031
|
-
import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
3032
|
-
import { homedir as homedir2, platform } from "os";
|
|
3033
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
3034
|
-
function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
|
|
3035
|
-
const desktop = platform() === "darwin" ? join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : platform() === "win32" ? join2(
|
|
3036
|
-
env.APPDATA ?? join2(home, "AppData", "Roaming"),
|
|
3037
|
-
"Claude",
|
|
3038
|
-
"claude_desktop_config.json"
|
|
3039
|
-
) : join2(
|
|
3040
|
-
env.XDG_CONFIG_HOME ?? join2(home, ".config"),
|
|
3041
|
-
"Claude",
|
|
3042
|
-
"claude_desktop_config.json"
|
|
3043
|
-
);
|
|
3044
|
-
return [
|
|
3045
|
-
{
|
|
3046
|
-
kind: "claude-code-project",
|
|
3047
|
-
label: "Claude Code (project .mcp.json)",
|
|
3048
|
-
path: join2(cwd, ".mcp.json"),
|
|
3049
|
-
serversKey: "mcpServers"
|
|
3050
|
-
},
|
|
3051
|
-
{
|
|
3052
|
-
kind: "cursor-project",
|
|
3053
|
-
label: "Cursor (project)",
|
|
3054
|
-
path: join2(cwd, ".cursor", "mcp.json"),
|
|
3055
|
-
serversKey: "mcpServers"
|
|
3056
|
-
},
|
|
3057
|
-
{
|
|
3058
|
-
kind: "vscode",
|
|
3059
|
-
label: "VS Code (project)",
|
|
3060
|
-
path: join2(cwd, ".vscode", "mcp.json"),
|
|
3061
|
-
serversKey: "servers"
|
|
3062
|
-
},
|
|
3063
|
-
{ kind: "generic", label: "mcp.json", path: join2(cwd, "mcp.json"), serversKey: "mcpServers" },
|
|
3064
|
-
{
|
|
3065
|
-
kind: "gemini-project",
|
|
3066
|
-
label: "Gemini CLI (project .gemini/settings.json)",
|
|
3067
|
-
path: join2(cwd, ".gemini", "settings.json"),
|
|
3068
|
-
serversKey: "mcpServers"
|
|
3069
|
-
},
|
|
3070
|
-
{ kind: "claude-desktop", label: "Claude Desktop", path: desktop, serversKey: "mcpServers" },
|
|
3071
|
-
{
|
|
3072
|
-
kind: "cursor-user",
|
|
3073
|
-
label: "Cursor (user)",
|
|
3074
|
-
path: join2(home, ".cursor", "mcp.json"),
|
|
3075
|
-
serversKey: "mcpServers"
|
|
3076
|
-
},
|
|
3077
|
-
{
|
|
3078
|
-
kind: "windsurf",
|
|
3079
|
-
label: "Windsurf",
|
|
3080
|
-
path: join2(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
3081
|
-
serversKey: "mcpServers"
|
|
3082
|
-
},
|
|
3083
|
-
{
|
|
3084
|
-
kind: "claude-code-user",
|
|
3085
|
-
label: "Claude Code (user ~/.claude.json)",
|
|
3086
|
-
path: join2(home, ".claude.json"),
|
|
3087
|
-
serversKey: "mcpServers"
|
|
3088
|
-
},
|
|
3089
|
-
{
|
|
3090
|
-
kind: "gemini-user",
|
|
3091
|
-
label: "Gemini CLI (user ~/.gemini/settings.json)",
|
|
3092
|
-
path: join2(home, ".gemini", "settings.json"),
|
|
3093
|
-
serversKey: "mcpServers"
|
|
3094
|
-
}
|
|
3095
|
-
];
|
|
3096
|
-
}
|
|
3097
|
-
function isGeminiConfig(file) {
|
|
3098
|
-
return file.kind === "gemini-project" || file.kind === "gemini-user" || /[/\\]\.gemini[/\\]settings\.json$/.test(file.path);
|
|
3099
|
-
}
|
|
3100
|
-
function detectClientConfigs(cwd, home, env) {
|
|
3101
|
-
return candidateConfigs(cwd, home, env).filter((c) => existsSync4(c.path));
|
|
3102
|
-
}
|
|
3103
|
-
function describeConfigPath(path) {
|
|
3104
|
-
const abs = resolve2(path);
|
|
3105
|
-
const known = candidateConfigs().find((c) => c.path === abs);
|
|
3106
|
-
if (known) return known;
|
|
3107
|
-
let serversKey = "mcpServers";
|
|
3108
|
-
try {
|
|
3109
|
-
const parsed = JSON.parse(readFileSync4(abs, "utf8"));
|
|
3110
|
-
if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
|
|
3111
|
-
} catch {
|
|
3112
|
-
}
|
|
3113
|
-
return {
|
|
3114
|
-
kind: abs.includes(".vscode") ? "vscode" : "generic",
|
|
3115
|
-
label: abs,
|
|
3116
|
-
path: abs,
|
|
3117
|
-
serversKey
|
|
3118
|
-
};
|
|
3119
|
-
}
|
|
3120
|
-
function readServers(file) {
|
|
3121
|
-
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
3122
|
-
const servers = raw[file.serversKey];
|
|
3123
|
-
if (!servers || typeof servers !== "object") return {};
|
|
3124
|
-
return servers;
|
|
3125
|
-
}
|
|
3126
|
-
var PROXY_SERVER_NAME = "agentguard";
|
|
3127
|
-
var PROXY_PACKAGE = "@agentwares/agentguard";
|
|
3128
|
-
function isProxyEntry(entry) {
|
|
3129
|
-
return (entry.args ?? []).some((a) => a === "agentguard" || /(^|\/)agentguard(\.js)?$/.test(a)) && (entry.args ?? []).includes("proxy");
|
|
3130
|
-
}
|
|
3131
|
-
function serversToUpstreams(servers) {
|
|
3132
|
-
const upstreams = [];
|
|
3133
|
-
const skipped = [];
|
|
3134
|
-
for (const [rawName, entry] of Object.entries(servers)) {
|
|
3135
|
-
if (isProxyEntry(entry)) {
|
|
3136
|
-
skipped.push({ name: rawName, reason: "already the agentguard proxy" });
|
|
3137
|
-
continue;
|
|
3138
|
-
}
|
|
3139
|
-
const name = rawName.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^[^a-zA-Z0-9]+/, "") || "upstream";
|
|
3140
|
-
const url = typeof entry.httpUrl === "string" ? entry.httpUrl : entry.url;
|
|
3141
|
-
if (url) {
|
|
3142
|
-
upstreams.push({
|
|
3143
|
-
name,
|
|
3144
|
-
url,
|
|
3145
|
-
headers: entry.headers ?? {},
|
|
3146
|
-
args: [],
|
|
3147
|
-
env: {},
|
|
3148
|
-
prefix: false,
|
|
3149
|
-
timeout_ms: 6e4
|
|
3150
|
-
});
|
|
3151
|
-
} else if (entry.command) {
|
|
3152
|
-
upstreams.push({
|
|
3153
|
-
name,
|
|
3154
|
-
command: entry.command,
|
|
3155
|
-
args: entry.args ?? [],
|
|
3156
|
-
env: entry.env ?? {},
|
|
3157
|
-
cwd: entry.cwd,
|
|
3158
|
-
headers: {},
|
|
3159
|
-
prefix: false,
|
|
3160
|
-
timeout_ms: 6e4
|
|
3161
|
-
});
|
|
3162
|
-
} else {
|
|
3163
|
-
skipped.push({ name: rawName, reason: "no `command` or `url`" });
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
return { upstreams, skipped };
|
|
3167
|
-
}
|
|
3168
|
-
function backupPath(file) {
|
|
3169
|
-
return `${file.path}.agentguard-backup`;
|
|
3170
|
-
}
|
|
3171
|
-
function proxyEntry(file, policyPath, opts = {}) {
|
|
3172
|
-
const args = ["-y", PROXY_PACKAGE, "proxy", "--config", policyPath];
|
|
3173
|
-
if (opts.agent) args.push("--agent", opts.agent);
|
|
3174
|
-
const entry = { command: "npx", args };
|
|
3175
|
-
if (file.serversKey === "servers") entry.type = "stdio";
|
|
3176
|
-
return entry;
|
|
3177
|
-
}
|
|
3178
|
-
function rewriteClientConfig(file, policyPath, opts = {}) {
|
|
3179
|
-
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
3180
|
-
const servers = raw[file.serversKey] ?? {};
|
|
3181
|
-
const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
|
|
3182
|
-
const backup = backupPath(file);
|
|
3183
|
-
if (!existsSync4(backup)) copyFileSync(file.path, backup);
|
|
3184
|
-
raw[file.serversKey] = { [PROXY_SERVER_NAME]: proxyEntry(file, policyPath, opts) };
|
|
3185
|
-
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
3186
|
-
return { backup, replaced };
|
|
3187
|
-
}
|
|
3188
|
-
function restoreClientConfig(file) {
|
|
3189
|
-
const backup = backupPath(file);
|
|
3190
|
-
if (!existsSync4(backup)) return false;
|
|
3191
|
-
copyFileSync(backup, file.path);
|
|
3192
|
-
return true;
|
|
3193
|
-
}
|
|
3194
|
-
function mergeServerIntoConfig(file, name, entry) {
|
|
3195
|
-
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
3196
|
-
const servers = raw[file.serversKey] ?? {};
|
|
3197
|
-
const replaced = name in servers;
|
|
3198
|
-
const backup = backupPath(file);
|
|
3199
|
-
if (!existsSync4(backup)) copyFileSync(file.path, backup);
|
|
3200
|
-
raw[file.serversKey] = { ...servers, [name]: entry };
|
|
3201
|
-
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
3202
|
-
return { backup, replaced };
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
|
-
// src/commands/connect.ts
|
|
3206
|
-
var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
|
|
3207
|
-
function fail(e) {
|
|
3208
|
-
throw e;
|
|
3209
|
-
}
|
|
3210
|
-
async function connectCommand(args, io, fetchImpl = fetch) {
|
|
3211
|
-
const key = args.positionals[0] ?? io.env.AGENTGUARD_KEY;
|
|
3212
|
-
if (!key) {
|
|
3213
|
-
io.err("usage: agentguard connect <key> [--write] [--client <path>] [--url <proxy-url>]");
|
|
3214
|
-
io.err(" the key is on your dashboard; it starts with agk_");
|
|
3215
|
-
return 2;
|
|
3216
|
-
}
|
|
3217
|
-
const base = (flagString(args, "url") ?? io.env.AGENTGUARD_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
|
|
3218
|
-
let res;
|
|
3219
|
-
try {
|
|
3220
|
-
res = await fetchImpl(`${base}/connect`, {
|
|
3221
|
-
method: "POST",
|
|
3222
|
-
headers: { "content-type": "application/json" },
|
|
3223
|
-
body: JSON.stringify({ key })
|
|
3224
|
-
});
|
|
3225
|
-
} catch (err) {
|
|
3226
|
-
fail({
|
|
3227
|
-
code: "PROXY_UNREACHABLE",
|
|
3228
|
-
cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
|
|
3229
|
-
fix: "check the network, or pass --url if you run your own proxy",
|
|
3230
|
-
retryable: true
|
|
3231
|
-
});
|
|
3232
|
-
}
|
|
3233
|
-
const text = await res.text();
|
|
3234
|
-
let body;
|
|
3235
|
-
try {
|
|
3236
|
-
body = JSON.parse(text);
|
|
3237
|
-
} catch {
|
|
3238
|
-
fail({
|
|
3239
|
-
code: "PROXY_BAD_RESPONSE",
|
|
3240
|
-
cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
|
|
3241
|
-
fix: "check --url points at an agentguard proxy, not at the dashboard",
|
|
3242
|
-
retryable: false
|
|
3243
|
-
});
|
|
3244
|
-
}
|
|
3245
|
-
if (!res.ok) {
|
|
3246
|
-
const e = body;
|
|
3247
|
-
fail({
|
|
3248
|
-
code: e.code ?? "CONNECT_REFUSED",
|
|
3249
|
-
cause: e.cause ?? `${base}/connect returned ${res.status}`,
|
|
3250
|
-
fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
|
|
3251
|
-
retryable: e.retryable ?? false
|
|
3252
|
-
});
|
|
3253
|
-
}
|
|
3254
|
-
const conn = body;
|
|
3255
|
-
if (flagBool(args, "json")) {
|
|
3256
|
-
io.out(JSON.stringify(conn, null, 2));
|
|
3257
|
-
return 0;
|
|
3258
|
-
}
|
|
3259
|
-
const rel = (p) => p.startsWith(io.cwd + "/") ? relative(io.cwd, p) : p;
|
|
3260
|
-
const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? {
|
|
3261
|
-
type: "http",
|
|
3262
|
-
url: conn.mcpUrl,
|
|
3263
|
-
headers: conn.headers
|
|
3264
|
-
};
|
|
3265
|
-
if (!flagBool(args, "write")) {
|
|
3266
|
-
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
3267
|
-
} else {
|
|
3268
|
-
const explicit = flagString(args, "client");
|
|
3269
|
-
const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
|
|
3270
|
-
const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
|
|
3271
|
-
let chosen;
|
|
3272
|
-
if (explicit || flagBool(args, "all")) chosen = configs;
|
|
3273
|
-
else chosen = projectLevel.slice(0, 1);
|
|
3274
|
-
if (chosen.length === 0) {
|
|
3275
|
-
io.err(
|
|
3276
|
-
"no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
|
|
3277
|
-
);
|
|
3278
|
-
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
3279
|
-
return 1;
|
|
3280
|
-
}
|
|
3281
|
-
for (const c of chosen) {
|
|
3282
|
-
if (!existsSync5(c.path)) {
|
|
3283
|
-
io.err(`no MCP config at ${c.path}`);
|
|
3284
|
-
return 1;
|
|
3285
|
-
}
|
|
3286
|
-
const forClient = isGeminiConfig(c) ? { httpUrl: entry.url, ...entry.headers ? { headers: entry.headers } : {} } : entry;
|
|
3287
|
-
const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, forClient);
|
|
3288
|
-
io.out(
|
|
3289
|
-
`${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
|
|
3290
|
-
);
|
|
3291
|
-
io.out(`backup ${rel(backup)}`);
|
|
3292
|
-
}
|
|
3293
|
-
for (const c of configs.filter((c2) => !chosen.includes(c2)))
|
|
3294
|
-
io.out(
|
|
3295
|
-
`found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
|
|
3296
|
-
);
|
|
3297
|
-
}
|
|
3298
|
-
const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
|
|
3299
|
-
io.out(`
|
|
3300
|
-
proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
|
|
3301
|
-
if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
|
|
3302
|
-
if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
|
|
3303
|
-
if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
|
|
3304
|
-
if (!flagBool(args, "write"))
|
|
3305
|
-
io.out(`
|
|
3306
|
-
run again with --write to add this to your MCP client config`);
|
|
3307
|
-
return 0;
|
|
3308
|
-
}
|
|
3309
|
-
|
|
3310
|
-
// src/commands/control.ts
|
|
3311
|
-
async function pokeHttp(io, reg, path, body) {
|
|
3312
|
-
if (!reg) return;
|
|
3313
|
-
try {
|
|
3314
|
-
const res = await fetch(`${reg.baseUrl}${path}`, {
|
|
3315
|
-
method: "POST",
|
|
3316
|
-
headers: { Authorization: `Bearer ${reg.token}`, "Content-Type": "application/json" },
|
|
3317
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
3318
|
-
signal: AbortSignal.timeout(2e3)
|
|
3319
|
-
});
|
|
3320
|
-
if (!res.ok)
|
|
3321
|
-
io.err(
|
|
3322
|
-
`running proxy at ${reg.baseUrl} answered ${res.status} (the file switch still applies)`
|
|
3323
|
-
);
|
|
3324
|
-
} catch {
|
|
3325
|
-
io.err(
|
|
3326
|
-
`no proxy answering at ${reg.baseUrl} (stale ${reg.pid}?) \u2014 the file switch still applies`
|
|
3327
|
-
);
|
|
3328
|
-
}
|
|
3329
|
-
}
|
|
3330
|
-
async function killCommand(args, io) {
|
|
3331
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
3332
|
-
const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
3333
|
-
const reason = args.positionals.join(" ") || flagString(args, "reason") || `agentguard kill by ${io.env.USER ?? "operator"}`;
|
|
3334
|
-
kill.kill(reason);
|
|
3335
|
-
await pokeHttp(io, readRegistration(loaded), "/kill", { reason });
|
|
3336
|
-
io.out(
|
|
3337
|
-
`KILLED: ${kill.path} written \u2014 every tool call through this policy now returns KILLED. Clear with: agentguard resume`
|
|
3338
|
-
);
|
|
3339
|
-
return 0;
|
|
3340
|
-
}
|
|
3341
|
-
async function resumeCommand(args, io) {
|
|
3342
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
3343
|
-
const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
3344
|
-
kill.resume();
|
|
3345
|
-
await pokeHttp(io, readRegistration(loaded), "/resume");
|
|
3346
|
-
io.out(`resumed: ${kill.path} removed`);
|
|
3347
|
-
if (io.env[loaded.policy.kill.env])
|
|
3348
|
-
io.err(
|
|
3349
|
-
`note: ${loaded.policy.kill.env} is set in this shell's environment; proxies started from it stay killed`
|
|
3350
|
-
);
|
|
3351
|
-
return 0;
|
|
3352
|
-
}
|
|
3353
|
-
async function decideCommand(decision, args, io) {
|
|
3354
|
-
const id = args.positionals[0];
|
|
3355
|
-
if (!id) {
|
|
3356
|
-
io.err(
|
|
3357
|
-
`usage: agentguard ${decision === "approved" ? "approve" : "deny"} <approval-id> [--by name] [--note text]`
|
|
3358
|
-
);
|
|
3359
|
-
return 1;
|
|
3360
|
-
}
|
|
3361
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
3362
|
-
const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
3363
|
-
const record = await guard.decide(
|
|
3364
|
-
id,
|
|
3365
|
-
decision,
|
|
3366
|
-
flagString(args, "by") ?? io.env.USER ?? "cli",
|
|
3367
|
-
flagString(args, "note")
|
|
3368
|
-
);
|
|
3369
|
-
if (!record) {
|
|
3370
|
-
io.err(`no approval ${id} \u2014 pending ones: agentguard approvals`);
|
|
3371
|
-
return 1;
|
|
3372
|
-
}
|
|
3373
|
-
io.out(
|
|
3374
|
-
`${record.status}: ${record.tool} (${record.id}, run ${record.run_id})${record.status === "approved" ? " \u2014 the agent may now retry the identical call once" : ""}`
|
|
3375
|
-
);
|
|
3376
|
-
return 0;
|
|
3377
|
-
}
|
|
3378
|
-
async function approvalsCommand(args, io) {
|
|
3379
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
3380
|
-
const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
3381
|
-
const list = await guard.approvals.list(flagBool(args, "all") ? void 0 : "pending");
|
|
3382
|
-
if (flagBool(args, "json")) {
|
|
3383
|
-
io.out(JSON.stringify(list, null, 2));
|
|
3384
|
-
return 0;
|
|
3385
|
-
}
|
|
3386
|
-
if (list.length === 0) {
|
|
3387
|
-
io.out(flagBool(args, "all") ? "no approvals recorded" : "no pending approvals");
|
|
3388
|
-
return 0;
|
|
3389
|
-
}
|
|
3390
|
-
for (const r of list) {
|
|
3391
|
-
io.out(
|
|
3392
|
-
`${r.id} ${r.status.padEnd(9)} ${r.tool} run ${r.run_id}${r.agent ? ` agent ${r.agent}` : ""} expires ${r.expires_at}`
|
|
3393
|
-
);
|
|
3394
|
-
io.out(` args: ${JSON.stringify(r.args).slice(0, 300)}`);
|
|
3395
|
-
if (r.status === "pending")
|
|
3396
|
-
io.out(` \u2192 agentguard approve ${r.id} | agentguard deny ${r.id}`);
|
|
3397
|
-
}
|
|
3398
|
-
return 0;
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
// src/commands/init.ts
|
|
3402
|
-
import { existsSync as existsSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
3403
|
-
import { relative as relative2, resolve as resolve3 } from "path";
|
|
3404
|
-
import YAML from "yaml";
|
|
3405
|
-
|
|
3406
|
-
// src/commands/tools.ts
|
|
3407
|
-
import {
|
|
3408
|
-
Guard as Guard2,
|
|
3409
|
-
classifyTool as classifyTool2,
|
|
3410
|
-
parsePolicy
|
|
3411
|
-
} from "@agentwares/agentguard-core";
|
|
3412
|
-
|
|
3413
|
-
// src/proxy/runtime.ts
|
|
3414
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3415
|
-
import {
|
|
3416
|
-
getDefaultEnvironment,
|
|
3417
|
-
StdioClientTransport
|
|
3418
|
-
} from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3419
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3420
|
-
import {
|
|
3421
|
-
CreateMessageRequestSchema,
|
|
3422
|
-
ElicitRequestSchema,
|
|
3423
|
-
LoggingMessageNotificationSchema,
|
|
3424
|
-
PromptListChangedNotificationSchema,
|
|
3425
|
-
ResourceListChangedNotificationSchema,
|
|
3426
|
-
ResourceUpdatedNotificationSchema,
|
|
3427
|
-
ToolListChangedNotificationSchema
|
|
3428
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
3429
|
-
import {
|
|
3430
|
-
GuardError,
|
|
3431
|
-
classifyTool,
|
|
3432
|
-
upstreamHeaders
|
|
3433
|
-
} from "@agentwares/agentguard-core";
|
|
3434
|
-
var ProxyRuntime = class {
|
|
3435
|
-
loaded;
|
|
3436
|
-
guard;
|
|
3437
|
-
agent;
|
|
3438
|
-
defaultRunId;
|
|
3439
|
-
configured;
|
|
3440
|
-
sessions = /* @__PURE__ */ new Set();
|
|
3441
|
-
connections = /* @__PURE__ */ new Map();
|
|
3442
|
-
exposed = [];
|
|
3443
|
-
resourceOwner = /* @__PURE__ */ new Map();
|
|
3444
|
-
templateOwner = /* @__PURE__ */ new Map();
|
|
3445
|
-
promptOwner = /* @__PURE__ */ new Map();
|
|
3446
|
-
log;
|
|
3447
|
-
connectTimeoutMs;
|
|
3448
|
-
testClients;
|
|
3449
|
-
/** stop relaying upstream stderr once we are tearing down — shutdown noise is not the user's problem */
|
|
3450
|
-
closing = false;
|
|
3451
|
-
constructor(opts) {
|
|
3452
|
-
this.loaded = opts.loaded;
|
|
3453
|
-
this.guard = opts.guard;
|
|
3454
|
-
this.agent = opts.agent;
|
|
3455
|
-
this.defaultRunId = opts.defaultRunId;
|
|
3456
|
-
this.configured = opts.configured ?? true;
|
|
3457
|
-
this.log = opts.log ?? ((line) => process.stderr.write(`[agentguard] ${line}
|
|
3458
|
-
`));
|
|
3459
|
-
this.connectTimeoutMs = opts.connectTimeoutMs ?? 15e3;
|
|
3460
|
-
this.testClients = opts.clients ?? {};
|
|
3461
|
-
}
|
|
3462
|
-
get policy() {
|
|
3463
|
-
return this.loaded.policy;
|
|
3464
|
-
}
|
|
3465
|
-
async start() {
|
|
3466
|
-
await Promise.all(this.policy.upstreams.map((u) => this.connect(u)));
|
|
3467
|
-
for (const [name, client] of Object.entries(this.testClients)) {
|
|
3468
|
-
if (this.connections.has(name)) continue;
|
|
3469
|
-
const upstream = {
|
|
3470
|
-
name,
|
|
3471
|
-
command: "in-memory",
|
|
3472
|
-
args: [],
|
|
3473
|
-
env: {},
|
|
3474
|
-
headers: {},
|
|
3475
|
-
prefix: false,
|
|
3476
|
-
timeout_ms: 6e4
|
|
3477
|
-
};
|
|
3478
|
-
const conn = {
|
|
3479
|
-
upstream,
|
|
3480
|
-
client,
|
|
3481
|
-
tools: [],
|
|
3482
|
-
capabilities: client.getServerCapabilities(),
|
|
3483
|
-
connected: true
|
|
3484
|
-
};
|
|
3485
|
-
this.connections.set(name, conn);
|
|
3486
|
-
this.wire(conn);
|
|
3487
|
-
await this.refreshTools(name);
|
|
3488
|
-
}
|
|
3489
|
-
this.rebuildExposed();
|
|
3490
|
-
const status = this.upstreamStatus();
|
|
3491
|
-
this.log(
|
|
3492
|
-
`connected ${status.filter((s) => s.connected).length}/${status.length} upstreams, ${this.exposed.length} tools, mode ${this.policy.mode}`
|
|
3493
|
-
);
|
|
3494
|
-
}
|
|
3495
|
-
async connect(upstream) {
|
|
3496
|
-
const client = new Client(
|
|
3497
|
-
{ name: "agentguard", version: version() },
|
|
3498
|
-
{ capabilities: { sampling: {}, elicitation: {} } }
|
|
3499
|
-
);
|
|
3500
|
-
const conn = {
|
|
3501
|
-
upstream,
|
|
3502
|
-
client,
|
|
3503
|
-
tools: [],
|
|
3504
|
-
capabilities: void 0,
|
|
3505
|
-
connected: false
|
|
3506
|
-
};
|
|
3507
|
-
this.connections.set(upstream.name, conn);
|
|
3508
|
-
try {
|
|
3509
|
-
const transport = upstream.url ? new StreamableHTTPClientTransport(new URL(upstream.url), {
|
|
3510
|
-
requestInit: { headers: upstreamHeaders(upstream) }
|
|
3511
|
-
}) : new StdioClientTransport({
|
|
3512
|
-
command: upstream.command,
|
|
3513
|
-
args: upstream.args,
|
|
3514
|
-
env: { ...getDefaultEnvironment(), ...upstream.env },
|
|
3515
|
-
cwd: upstream.cwd,
|
|
3516
|
-
stderr: "pipe"
|
|
3517
|
-
});
|
|
3518
|
-
if (transport instanceof StdioClientTransport) {
|
|
3519
|
-
transport.stderr?.on("data", (chunk) => {
|
|
3520
|
-
if (this.closing) return;
|
|
3521
|
-
for (const line of chunk.toString("utf8").split("\n"))
|
|
3522
|
-
if (line.trim()) this.log(`${upstream.name}: ${line}`);
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
await withTimeout(
|
|
3526
|
-
client.connect(transport),
|
|
3527
|
-
this.connectTimeoutMs,
|
|
3528
|
-
`connect to upstream "${upstream.name}"`
|
|
3529
|
-
);
|
|
3530
|
-
conn.capabilities = client.getServerCapabilities();
|
|
3531
|
-
conn.connected = true;
|
|
3532
|
-
this.wire(conn);
|
|
3533
|
-
await this.refreshTools(upstream.name);
|
|
3534
|
-
} catch (err) {
|
|
3535
|
-
conn.connected = false;
|
|
3536
|
-
conn.error = err instanceof Error ? err.message : String(err);
|
|
3537
|
-
this.log(`upstream "${upstream.name}" unavailable: ${conn.error}`);
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
wire(conn) {
|
|
3541
|
-
const { client, upstream } = conn;
|
|
3542
|
-
client.onclose = () => {
|
|
3543
|
-
conn.connected = false;
|
|
3544
|
-
conn.error = "connection closed";
|
|
3545
|
-
if (!this.closing) this.log(`upstream "${upstream.name}" closed`);
|
|
3546
|
-
};
|
|
3547
|
-
client.setNotificationHandler(LoggingMessageNotificationSchema, async (n) => {
|
|
3548
|
-
await this.broadcast(upstream.name, n.method, n.params);
|
|
3549
|
-
});
|
|
3550
|
-
client.setNotificationHandler(ResourceUpdatedNotificationSchema, async (n) => {
|
|
3551
|
-
await this.broadcast(upstream.name, n.method, n.params);
|
|
3552
|
-
});
|
|
3553
|
-
client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
|
|
3554
|
-
this.resourceOwner.clear();
|
|
3555
|
-
await this.broadcast(upstream.name, "notifications/resources/list_changed", void 0);
|
|
3556
|
-
});
|
|
3557
|
-
client.setNotificationHandler(PromptListChangedNotificationSchema, async () => {
|
|
3558
|
-
this.promptOwner.clear();
|
|
3559
|
-
await this.broadcast(upstream.name, "notifications/prompts/list_changed", void 0);
|
|
3560
|
-
});
|
|
3561
|
-
client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
|
|
3562
|
-
await this.refreshTools(upstream.name);
|
|
3563
|
-
this.rebuildExposed();
|
|
3564
|
-
for (const s of this.sessions) s.server.server.sendToolListChanged().catch(() => void 0);
|
|
3565
|
-
});
|
|
3566
|
-
try {
|
|
3567
|
-
client.setRequestHandler(CreateMessageRequestSchema, async (req) => {
|
|
3568
|
-
const session = this.pickSession(upstream.name);
|
|
3569
|
-
if (!session) throw new Error("no downstream client is connected to relay sampling to");
|
|
3570
|
-
const related = session.inflight.get(upstream.name)?.at(-1);
|
|
3571
|
-
return session.server.server.createMessage(req.params, { relatedRequestId: related });
|
|
3572
|
-
});
|
|
3573
|
-
client.setRequestHandler(ElicitRequestSchema, async (req) => {
|
|
3574
|
-
const session = this.pickSession(upstream.name);
|
|
3575
|
-
if (!session) throw new Error("no downstream client is connected to relay elicitation to");
|
|
3576
|
-
const related = session.inflight.get(upstream.name)?.at(-1);
|
|
3577
|
-
return session.server.server.elicitInput(req.params, { relatedRequestId: related });
|
|
3578
|
-
});
|
|
3579
|
-
} catch {
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
pickSession(upstream) {
|
|
3583
|
-
let best;
|
|
3584
|
-
for (const s of this.sessions) {
|
|
3585
|
-
if ((s.inflight.get(upstream)?.length ?? 0) > 0) return s;
|
|
3586
|
-
if (!best || s.lastActive > best.lastActive) best = s;
|
|
3587
|
-
}
|
|
3588
|
-
return best;
|
|
3589
|
-
}
|
|
3590
|
-
async broadcast(upstream, method, params) {
|
|
3591
|
-
for (const s of this.sessions) {
|
|
3592
|
-
const related = s.inflight.get(upstream)?.at(-1);
|
|
3593
|
-
try {
|
|
3594
|
-
await s.server.server.notification(
|
|
3595
|
-
{ method, params },
|
|
3596
|
-
related !== void 0 ? { relatedRequestId: related } : void 0
|
|
3597
|
-
);
|
|
3598
|
-
} catch {
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
}
|
|
3602
|
-
/**
|
|
3603
|
-
* Re-read an upstream's tools, every page (mcp-kit's `readAllPages`): at most 20 requests, each
|
|
3604
|
-
* under the upstream's `timeout_ms`, ending at a cursor already followed. It used to follow
|
|
3605
|
-
* `nextCursor` with no bound, so an upstream that echoed its cursor kept the proxy asking for
|
|
3606
|
-
* the same page forever. A refresh that fails, or stops at the 20-page / 2,000-tool ceiling,
|
|
3607
|
-
* changes nothing: the agent keeps the last good list, and the log says why.
|
|
3608
|
-
*/
|
|
3609
|
-
async refreshTools(name) {
|
|
3610
|
-
const conn = this.connections.get(name);
|
|
3611
|
-
if (!conn?.connected) return;
|
|
3612
|
-
if (conn.capabilities && !conn.capabilities.tools) {
|
|
3613
|
-
conn.tools = [];
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
|
-
const kept = () => `keeping the last good list (${conn.tools.length} tool${conn.tools.length === 1 ? "" : "s"})`;
|
|
3617
|
-
try {
|
|
3618
|
-
const read = await readAllPages(async (cursor) => {
|
|
3619
|
-
const page = await conn.client.listTools(cursor ? { cursor } : void 0, {
|
|
3620
|
-
timeout: conn.upstream.timeout_ms
|
|
3621
|
-
});
|
|
3622
|
-
return { items: page.tools, nextCursor: page.nextCursor };
|
|
3623
|
-
});
|
|
3624
|
-
if (read.truncated) {
|
|
3625
|
-
this.log(`tools/list for "${name}" not applied: ${read.truncated.reason}; ${kept()}`);
|
|
3626
|
-
return;
|
|
3627
|
-
}
|
|
3628
|
-
conn.tools = read.items;
|
|
3629
|
-
} catch (err) {
|
|
3630
|
-
this.log(
|
|
3631
|
-
`tools/list failed for "${name}": ${err instanceof Error ? err.message : String(err)}; ${kept()}`
|
|
3632
|
-
);
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
rebuildExposed() {
|
|
3636
|
-
const taken = /* @__PURE__ */ new Set();
|
|
3637
|
-
const out = [];
|
|
3638
|
-
const order = [...this.policy.upstreams.map((u) => u.name), ...Object.keys(this.testClients)];
|
|
3639
|
-
for (const name of order) {
|
|
3640
|
-
const conn = this.connections.get(name);
|
|
3641
|
-
if (!conn) continue;
|
|
3642
|
-
const prefix = conn.upstream.prefix === true ? name : typeof conn.upstream.prefix === "string" ? conn.upstream.prefix : void 0;
|
|
3643
|
-
for (const tool of conn.tools) {
|
|
3644
|
-
let exposedName = prefix ? `${prefix}__${tool.name}` : tool.name;
|
|
3645
|
-
if (taken.has(exposedName)) exposedName = `${name}__${tool.name}`;
|
|
3646
|
-
if (taken.has(exposedName)) {
|
|
3647
|
-
this.log(`skipping duplicate tool ${exposedName} from ${name}`);
|
|
3648
|
-
continue;
|
|
3649
|
-
}
|
|
3650
|
-
taken.add(exposedName);
|
|
3651
|
-
out.push({
|
|
3652
|
-
name: exposedName,
|
|
3653
|
-
upstream: name,
|
|
3654
|
-
original: tool.name,
|
|
3655
|
-
tool,
|
|
3656
|
-
classification: classifyTool(
|
|
3657
|
-
{
|
|
3658
|
-
name: exposedName,
|
|
3659
|
-
description: tool.description,
|
|
3660
|
-
annotations: tool.annotations,
|
|
3661
|
-
inputSchema: tool.inputSchema,
|
|
3662
|
-
outputSchema: tool.outputSchema
|
|
3663
|
-
},
|
|
3664
|
-
this.policy
|
|
3665
|
-
)
|
|
3666
|
-
});
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
this.exposed = out;
|
|
3670
|
-
}
|
|
3671
|
-
listTools(agent) {
|
|
3672
|
-
if (agent?.upstreams) return this.exposed.filter((t) => agent.upstreams.includes(t.upstream));
|
|
3673
|
-
return this.exposed;
|
|
3674
|
-
}
|
|
3675
|
-
findTool(name) {
|
|
3676
|
-
return this.exposed.find((t) => t.name === name);
|
|
3677
|
-
}
|
|
3678
|
-
upstreamStatus() {
|
|
3679
|
-
return [...this.connections.values()].map((c) => ({
|
|
3680
|
-
name: c.upstream.name,
|
|
3681
|
-
connected: c.connected,
|
|
3682
|
-
tools: c.tools.length,
|
|
3683
|
-
transport: c.upstream.url ? "http" : "stdio",
|
|
3684
|
-
error: c.error
|
|
3685
|
-
}));
|
|
3686
|
-
}
|
|
3687
|
-
attach(session) {
|
|
3688
|
-
this.sessions.add(session);
|
|
3689
|
-
}
|
|
3690
|
-
detach(session) {
|
|
3691
|
-
this.sessions.delete(session);
|
|
3692
|
-
}
|
|
3693
|
-
/** Run a tool call through the policy engine and, if allowed, the upstream. */
|
|
3694
|
-
async callTool(name, args, ctx) {
|
|
3695
|
-
const tool = this.findTool(name);
|
|
3696
|
-
const agent = ctx.agent === void 0 ? this.agent : ctx.agent;
|
|
3697
|
-
if (!tool) {
|
|
3698
|
-
const result2 = await this.guard.run(
|
|
3699
|
-
{ tool: { name }, args, runId: ctx.runId, agent, sessionId: ctx.sessionId },
|
|
3700
|
-
async () => {
|
|
3701
|
-
throw new GuardError({
|
|
3702
|
-
code: "UNKNOWN_TOOL",
|
|
3703
|
-
cause: `no upstream exposes a tool named "${name}"`,
|
|
3704
|
-
fix: "call tools/list and use one of the listed names",
|
|
3705
|
-
retryable: false
|
|
3706
|
-
});
|
|
3707
|
-
}
|
|
3708
|
-
);
|
|
3709
|
-
return { result: result2 };
|
|
3710
|
-
}
|
|
3711
|
-
const conn = this.connections.get(tool.upstream);
|
|
3712
|
-
const track = () => {
|
|
3713
|
-
if (!ctx.session || ctx.requestId === void 0) return () => void 0;
|
|
3714
|
-
const list = ctx.session.inflight.get(tool.upstream) ?? [];
|
|
3715
|
-
list.push(ctx.requestId);
|
|
3716
|
-
ctx.session.inflight.set(tool.upstream, list);
|
|
3717
|
-
return () => {
|
|
3718
|
-
const l = ctx.session.inflight.get(tool.upstream) ?? [];
|
|
3719
|
-
const i = l.indexOf(ctx.requestId);
|
|
3720
|
-
if (i >= 0) l.splice(i, 1);
|
|
3721
|
-
};
|
|
3722
|
-
};
|
|
3723
|
-
const result = await this.guard.run(
|
|
3724
|
-
{
|
|
3725
|
-
tool: {
|
|
3726
|
-
name: tool.name,
|
|
3727
|
-
description: tool.tool.description,
|
|
3728
|
-
annotations: tool.tool.annotations,
|
|
3729
|
-
inputSchema: tool.tool.inputSchema,
|
|
3730
|
-
outputSchema: tool.tool.outputSchema
|
|
3731
|
-
},
|
|
3732
|
-
args,
|
|
3733
|
-
runId: ctx.runId,
|
|
3734
|
-
agent,
|
|
3735
|
-
sessionId: ctx.sessionId,
|
|
3736
|
-
upstream: tool.upstream
|
|
3737
|
-
},
|
|
3738
|
-
async (finalArgs) => {
|
|
3739
|
-
if (!conn?.connected) {
|
|
3740
|
-
throw new GuardError({
|
|
3741
|
-
code: "UPSTREAM_ERROR",
|
|
3742
|
-
cause: `upstream "${tool.upstream}" is not connected${conn?.error ? ` (${conn.error})` : ""}`,
|
|
3743
|
-
fix: "check the upstream command/url in agentguard.yaml and restart the proxy",
|
|
3744
|
-
retryable: true
|
|
3745
|
-
});
|
|
3746
|
-
}
|
|
3747
|
-
const untrack = track();
|
|
3748
|
-
try {
|
|
3749
|
-
return await conn.client.callTool(
|
|
3750
|
-
{ name: tool.original, arguments: finalArgs ?? {} },
|
|
3751
|
-
void 0,
|
|
3752
|
-
{
|
|
3753
|
-
timeout: conn.upstream.timeout_ms,
|
|
3754
|
-
resetTimeoutOnProgress: true,
|
|
3755
|
-
onprogress: ctx.onprogress
|
|
3756
|
-
}
|
|
3757
|
-
);
|
|
3758
|
-
} finally {
|
|
3759
|
-
untrack();
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
);
|
|
3763
|
-
return { result, tool };
|
|
3764
|
-
}
|
|
3765
|
-
connectedWith(capability) {
|
|
3766
|
-
return [...this.connections.values()].filter(
|
|
3767
|
-
(c) => c.connected && (c.capabilities?.[capability] || c.capabilities === void 0)
|
|
3768
|
-
);
|
|
3769
|
-
}
|
|
3770
|
-
async listResources() {
|
|
3771
|
-
const resources = [];
|
|
3772
|
-
for (const conn of this.connectedWith("resources")) {
|
|
3773
|
-
try {
|
|
3774
|
-
const page = await conn.client.listResources(void 0, {
|
|
3775
|
-
timeout: conn.upstream.timeout_ms
|
|
3776
|
-
});
|
|
3777
|
-
for (const r of page.resources) {
|
|
3778
|
-
this.resourceOwner.set(r.uri, conn.upstream.name);
|
|
3779
|
-
resources.push(r);
|
|
3780
|
-
}
|
|
3781
|
-
} catch {
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3784
|
-
return { resources };
|
|
3785
|
-
}
|
|
3786
|
-
async listResourceTemplates() {
|
|
3787
|
-
const resourceTemplates = [];
|
|
3788
|
-
for (const conn of this.connectedWith("resources")) {
|
|
3789
|
-
try {
|
|
3790
|
-
const page = await conn.client.listResourceTemplates(void 0, {
|
|
3791
|
-
timeout: conn.upstream.timeout_ms
|
|
3792
|
-
});
|
|
3793
|
-
for (const t of page.resourceTemplates) {
|
|
3794
|
-
this.templateOwner.set(t.uriTemplate, conn.upstream.name);
|
|
3795
|
-
resourceTemplates.push(t);
|
|
3796
|
-
}
|
|
3797
|
-
} catch {
|
|
3798
|
-
}
|
|
3799
|
-
}
|
|
3800
|
-
return { resourceTemplates };
|
|
3801
|
-
}
|
|
3802
|
-
ownerForUri(uri) {
|
|
3803
|
-
const owner = this.resourceOwner.get(uri);
|
|
3804
|
-
if (owner && this.connections.get(owner)?.connected) return [this.connections.get(owner)];
|
|
3805
|
-
for (const [template, name] of this.templateOwner) {
|
|
3806
|
-
if (templateMatches(template, uri) && this.connections.get(name)?.connected)
|
|
3807
|
-
return [this.connections.get(name)];
|
|
3808
|
-
}
|
|
3809
|
-
return this.connectedWith("resources");
|
|
3810
|
-
}
|
|
3811
|
-
async readResource(uri) {
|
|
3812
|
-
let lastError;
|
|
3813
|
-
for (const conn of this.ownerForUri(uri)) {
|
|
3814
|
-
try {
|
|
3815
|
-
const result = await conn.client.readResource(
|
|
3816
|
-
{ uri },
|
|
3817
|
-
{ timeout: conn.upstream.timeout_ms }
|
|
3818
|
-
);
|
|
3819
|
-
this.resourceOwner.set(uri, conn.upstream.name);
|
|
3820
|
-
return result;
|
|
3821
|
-
} catch (err) {
|
|
3822
|
-
lastError = err;
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
throw lastError ?? new Error(`no upstream serves resource ${uri}`);
|
|
3826
|
-
}
|
|
3827
|
-
async subscribe(uri, on) {
|
|
3828
|
-
for (const conn of this.ownerForUri(uri)) {
|
|
3829
|
-
try {
|
|
3830
|
-
if (on) await conn.client.subscribeResource({ uri });
|
|
3831
|
-
else await conn.client.unsubscribeResource({ uri });
|
|
3832
|
-
return;
|
|
3833
|
-
} catch {
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
async listPrompts() {
|
|
3838
|
-
const prompts = [];
|
|
3839
|
-
const taken = /* @__PURE__ */ new Set();
|
|
3840
|
-
this.promptOwner.clear();
|
|
3841
|
-
for (const conn of this.connectedWith("prompts")) {
|
|
3842
|
-
try {
|
|
3843
|
-
const page = await conn.client.listPrompts(void 0, {
|
|
3844
|
-
timeout: conn.upstream.timeout_ms
|
|
3845
|
-
});
|
|
3846
|
-
for (const p of page.prompts) {
|
|
3847
|
-
const name = taken.has(p.name) ? `${conn.upstream.name}__${p.name}` : p.name;
|
|
3848
|
-
taken.add(name);
|
|
3849
|
-
this.promptOwner.set(name, { upstream: conn.upstream.name, original: p.name });
|
|
3850
|
-
prompts.push({ ...p, name });
|
|
3851
|
-
}
|
|
3852
|
-
} catch {
|
|
3853
|
-
}
|
|
3854
|
-
}
|
|
3855
|
-
return { prompts };
|
|
3856
|
-
}
|
|
3857
|
-
async getPrompt(name, args) {
|
|
3858
|
-
if (this.promptOwner.size === 0) await this.listPrompts();
|
|
3859
|
-
const owner = this.promptOwner.get(name);
|
|
3860
|
-
const conns = owner ? [this.connections.get(owner.upstream)] : this.connectedWith("prompts");
|
|
3861
|
-
let lastError;
|
|
3862
|
-
for (const conn of conns) {
|
|
3863
|
-
try {
|
|
3864
|
-
return await conn.client.getPrompt(
|
|
3865
|
-
{ name: owner?.original ?? name, arguments: args },
|
|
3866
|
-
{ timeout: conn.upstream.timeout_ms }
|
|
3867
|
-
);
|
|
3868
|
-
} catch (err) {
|
|
3869
|
-
lastError = err;
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
throw lastError ?? new Error(`no upstream serves prompt ${name}`);
|
|
3873
|
-
}
|
|
3874
|
-
async complete(params) {
|
|
3875
|
-
let conns;
|
|
3876
|
-
if (params.ref.type === "ref/prompt") {
|
|
3877
|
-
if (this.promptOwner.size === 0) await this.listPrompts();
|
|
3878
|
-
const owner = this.promptOwner.get(params.ref.name);
|
|
3879
|
-
conns = owner ? [this.connections.get(owner.upstream)] : this.connectedWith("completions");
|
|
3880
|
-
if (owner) params = { ...params, ref: { ...params.ref, name: owner.original } };
|
|
3881
|
-
} else {
|
|
3882
|
-
if (this.templateOwner.size === 0) await this.listResourceTemplates();
|
|
3883
|
-
conns = this.ownerForUri(params.ref.uri);
|
|
3884
|
-
}
|
|
3885
|
-
let lastError;
|
|
3886
|
-
for (const conn of conns) {
|
|
3887
|
-
try {
|
|
3888
|
-
return await conn.client.complete(params, { timeout: conn.upstream.timeout_ms });
|
|
3889
|
-
} catch (err) {
|
|
3890
|
-
lastError = err;
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
throw lastError ?? new Error("no upstream supports completions");
|
|
3894
|
-
}
|
|
3895
|
-
async setLevel(level) {
|
|
3896
|
-
await Promise.all(
|
|
3897
|
-
this.connectedWith("logging").map(
|
|
3898
|
-
(c) => c.client.setLoggingLevel(level).catch(() => void 0)
|
|
3899
|
-
)
|
|
3900
|
-
);
|
|
3901
|
-
}
|
|
3902
|
-
async close() {
|
|
3903
|
-
this.closing = true;
|
|
3904
|
-
for (const conn of this.connections.values()) {
|
|
3905
|
-
try {
|
|
3906
|
-
await conn.client.close();
|
|
3907
|
-
} catch {
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
this.connections.clear();
|
|
3911
|
-
}
|
|
3912
|
-
};
|
|
3913
|
-
function templateMatches(template, uri) {
|
|
3914
|
-
const re = new RegExp(
|
|
3915
|
-
"^" + template.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\{[^}]+\\\}/g, "[^/]+") + "$"
|
|
3916
|
-
);
|
|
3917
|
-
return re.test(uri);
|
|
3918
|
-
}
|
|
3919
|
-
async function withTimeout(promise, ms, what) {
|
|
3920
|
-
let timer;
|
|
3921
|
-
const timeout = new Promise((_, reject) => {
|
|
3922
|
-
timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms);
|
|
3923
|
-
});
|
|
3924
|
-
try {
|
|
3925
|
-
return await Promise.race([promise, timeout]);
|
|
3926
|
-
} finally {
|
|
3927
|
-
if (timer) clearTimeout(timer);
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
// src/commands/tools.ts
|
|
3932
|
-
async function probeUpstreams(upstreams, opts = {}) {
|
|
3933
|
-
const policy = opts.policy ?? parsePolicy({ upstreams });
|
|
3934
|
-
const loaded = {
|
|
3935
|
-
policy: { ...policy, upstreams },
|
|
3936
|
-
path: "",
|
|
3937
|
-
baseDir: process.cwd(),
|
|
3938
|
-
stateDir: "",
|
|
3939
|
-
auditPath: "",
|
|
3940
|
-
killPath: ""
|
|
3941
|
-
};
|
|
3942
|
-
const guard = new Guard2({ policy: loaded.policy, env: opts.env ?? {} });
|
|
3943
|
-
const runtime = new ProxyRuntime({
|
|
3944
|
-
loaded,
|
|
3945
|
-
guard,
|
|
3946
|
-
defaultRunId: "probe",
|
|
3947
|
-
log: opts.log ?? (() => void 0),
|
|
3948
|
-
connectTimeoutMs: opts.timeoutMs ?? 15e3
|
|
3949
|
-
});
|
|
3950
|
-
try {
|
|
3951
|
-
await runtime.start();
|
|
3952
|
-
return {
|
|
3953
|
-
tools: runtime.listTools().map((t) => ({
|
|
3954
|
-
upstream: t.upstream,
|
|
3955
|
-
name: t.name,
|
|
3956
|
-
tool: t.tool,
|
|
3957
|
-
classification: classifyTool2(
|
|
3958
|
-
{ name: t.name, annotations: t.tool.annotations },
|
|
3959
|
-
loaded.policy
|
|
3960
|
-
)
|
|
3961
|
-
})),
|
|
3962
|
-
upstreams: runtime.upstreamStatus()
|
|
3963
|
-
};
|
|
3964
|
-
} finally {
|
|
3965
|
-
await runtime.close();
|
|
3966
|
-
}
|
|
3967
|
-
}
|
|
3968
|
-
function formatToolTable(tools) {
|
|
3969
|
-
const width = Math.min(48, Math.max(12, ...tools.map((t) => t.name.length)));
|
|
3970
|
-
const lines = [
|
|
3971
|
-
`${"tool".padEnd(width)} ${"class".padEnd(8)}${"verb".padEnd(9)}${"upstream".padEnd(14)}why`
|
|
3972
|
-
];
|
|
3973
|
-
for (const t of tools) {
|
|
3974
|
-
const c = t.classification;
|
|
3975
|
-
lines.push(
|
|
3976
|
-
`${t.name.padEnd(width)} ${c.class.padEnd(8)}${c.verb.padEnd(9)}${t.upstream.padEnd(14)}${c.source}: ${c.reason}`
|
|
3977
|
-
);
|
|
3978
|
-
}
|
|
3979
|
-
return lines;
|
|
3980
|
-
}
|
|
3981
|
-
async function toolsCommand(args, io) {
|
|
3982
|
-
const loaded = loadPolicyFor(args, io);
|
|
3983
|
-
const result = await probeUpstreams(loaded.policy.upstreams, {
|
|
3984
|
-
policy: loaded.policy,
|
|
3985
|
-
log: io.err,
|
|
3986
|
-
env: io.env
|
|
3987
|
-
});
|
|
3988
|
-
if (flagBool(args, "json")) {
|
|
3989
|
-
io.out(
|
|
3990
|
-
JSON.stringify(
|
|
3991
|
-
{
|
|
3992
|
-
upstreams: result.upstreams,
|
|
3993
|
-
tools: result.tools.map((t) => ({
|
|
3994
|
-
name: t.name,
|
|
3995
|
-
upstream: t.upstream,
|
|
3996
|
-
...t.classification
|
|
3997
|
-
}))
|
|
3998
|
-
},
|
|
3999
|
-
null,
|
|
4000
|
-
2
|
|
4001
|
-
)
|
|
4002
|
-
);
|
|
4003
|
-
return 0;
|
|
4004
|
-
}
|
|
4005
|
-
for (const u of result.upstreams)
|
|
4006
|
-
io.err(
|
|
4007
|
-
`${u.connected ? "ok " : "down"} ${u.name}: ${u.connected ? `${u.tools} tools` : u.error}`
|
|
4008
|
-
);
|
|
4009
|
-
if (result.upstreams.length === 0)
|
|
4010
|
-
io.err(
|
|
4011
|
-
"no upstreams in the policy: an MCP client sees agentguard's own read-only tools instead (agentguard_get_status, agentguard_get_report, agentguard_verify_audit_log)"
|
|
4012
|
-
);
|
|
4013
|
-
for (const line of formatToolTable(result.tools)) io.out(line);
|
|
4014
|
-
const counts = result.tools.reduce(
|
|
4015
|
-
(acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
|
|
4016
|
-
{}
|
|
4017
|
-
);
|
|
4018
|
-
io.err(
|
|
4019
|
-
`
|
|
4020
|
-
${result.tools.length} tools: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")} \xB7 mode ${loaded.policy.mode}`
|
|
4021
|
-
);
|
|
4022
|
-
return result.upstreams.every((u) => u.connected) ? 0 : 2;
|
|
4023
|
-
}
|
|
4024
|
-
|
|
4025
|
-
// src/commands/init.ts
|
|
4026
|
-
function dump(value, indent = 0) {
|
|
4027
|
-
const text = YAML.stringify(value, { indent: 2, lineWidth: 100 }).trimEnd();
|
|
4028
|
-
const pad = " ".repeat(indent);
|
|
4029
|
-
return text.split("\n").map((l) => pad + l).join("\n");
|
|
4030
|
-
}
|
|
4031
|
-
function flow(list) {
|
|
4032
|
-
return list.length === 0 ? "[]" : `[${list.map((s) => JSON.stringify(s)).join(", ")}]`;
|
|
4033
|
-
}
|
|
4034
|
-
function renderStarterPolicy(opts) {
|
|
4035
|
-
const tools = opts.tools ?? [];
|
|
4036
|
-
const unknown = tools.filter((t) => t.classification.class === "unknown").map((t) => t.name);
|
|
4037
|
-
const destructive = tools.filter((t) => t.classification.destructive || t.classification.verb === "delete").map((t) => t.name);
|
|
4038
|
-
const spend = tools.filter((t) => t.classification.class === "spend").map((t) => t.name);
|
|
4039
|
-
const date = (opts.generatedAt ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4040
|
-
const counts = tools.reduce(
|
|
4041
|
-
(acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
|
|
4042
|
-
{}
|
|
4043
|
-
);
|
|
4044
|
-
const detected = tools.length === 0 ? ["# (no upstreams probed \u2014 run `agentguard tools` after fixing the upstreams above)"] : tools.map(
|
|
4045
|
-
(t) => `# ${t.name.padEnd(36)} ${t.classification.class.padEnd(8)} ${t.classification.verb.padEnd(8)} ${t.classification.source}: ${t.classification.reason}`
|
|
4046
|
-
);
|
|
4047
|
-
const probeNotes = (opts.probeErrors ?? []).map((e) => `# ! ${e}`);
|
|
4048
|
-
const upstreamsYaml = opts.upstreams.length ? dump({ upstreams: opts.upstreams.map(cleanUpstream) }) : 'upstreams: []\n # - name: crm\n # url: https://mcp.example.com/mcp\n # auth: ${CRM_TOKEN}\n # - name: files\n # command: npx\n # args: [-y, "@modelcontextprotocol/server-filesystem", "."]';
|
|
4049
|
-
return `# agentguard.yaml \u2014 generated by \`agentguard init\` on ${date}
|
|
4050
|
-
# Every knob is explained inline. Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#policy
|
|
4051
|
-
version: 1
|
|
4052
|
-
|
|
4053
|
-
# dry-run: reads pass through; writes and spends are FAKED (plausible success, nothing executes)
|
|
4054
|
-
# and recorded. Run your agent once, then \`agentguard report\` and \`agentguard diff\`.
|
|
4055
|
-
# Flip to \`enforce\` when the diff looks right. Caps and the loop breaker apply in both modes.
|
|
4056
|
-
mode: ${opts.mode ?? "dry-run"}
|
|
4057
|
-
|
|
4058
|
-
# The servers your agent used to talk to directly. agentguard connects to them and holds their
|
|
4059
|
-
# credentials; your agent only talks to agentguard. Use \${ENV_VAR} for secrets.
|
|
4060
|
-
${upstreamsYaml}
|
|
4061
|
-
|
|
4062
|
-
# Detected tools (${tools.length}${tools.length ? `: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}` : ""}):
|
|
4063
|
-
${[...detected, ...probeNotes].join("\n")}
|
|
4064
|
-
# readOnlyHint annotations and verb heuristics classify most tools. List anything that is wrong here;
|
|
4065
|
-
# these patterns win over annotations. \`*\` matches anything.
|
|
4066
|
-
classify:
|
|
4067
|
-
read: []
|
|
4068
|
-
write: ${flow(unknown)}${unknown.length ? " # could not be classified \u2014 treated as writes; move to read: if they only read" : ""}
|
|
4069
|
-
spend: ${flow(spend)}
|
|
4070
|
-
unknown: write # a tool nobody can classify counts as a write (alternatives: read | block)
|
|
4071
|
-
|
|
4072
|
-
# Blast-radius caps. The call that would exceed a cap gets CAP_EXCEEDED with the remaining budget.
|
|
4073
|
-
# \`writes\` counts every write or spend; \`deletes\`/\`emails\` count the subsets matched by \`counters\`.
|
|
4074
|
-
caps:
|
|
4075
|
-
per_run:
|
|
4076
|
-
tool_calls: 400
|
|
4077
|
-
writes: 50
|
|
4078
|
-
deletes: 10
|
|
4079
|
-
emails: 5
|
|
4080
|
-
spend_usd: 25
|
|
4081
|
-
per_day:
|
|
4082
|
-
spend_usd: 200
|
|
4083
|
-
|
|
4084
|
-
# Dollar amounts. Declare how to read the amount from a spend tool's arguments; results reporting
|
|
4085
|
-
# cost_usd / amount_usd are picked up automatically. Unknown spend tools cost \`default_usd\`.
|
|
4086
|
-
spend:
|
|
4087
|
-
tools: {}
|
|
4088
|
-
# stripe_create_charge: { amount_arg: amount, divisor: 100, currency_arg: currency }
|
|
4089
|
-
# openai_*: { fixed_usd: 0.01 }
|
|
4090
|
-
default_usd: 0
|
|
4091
|
-
|
|
4092
|
-
# Semantic loop breaker: the same (tool, normalized arguments) ${3} times in the last 30 calls, or a
|
|
4093
|
-
# repeating A\u2192B\u2192A\u2192B cycle, returns LOOP_DETECTED. Timestamps, ids and whitespace are ignored.
|
|
4094
|
-
loop:
|
|
4095
|
-
window: 30
|
|
4096
|
-
max_repeats: 3
|
|
4097
|
-
max_cycle_len: 4
|
|
4098
|
-
max_read_repeats: 10
|
|
4099
|
-
|
|
4100
|
-
# Always fake these, even in enforce mode. Remove entries as you gain trust.
|
|
4101
|
-
dry_run:
|
|
4102
|
-
tools: []
|
|
4103
|
-
synthesize: true
|
|
4104
|
-
|
|
4105
|
-
# Destructive actions that need a human. The agent gets APPROVAL_REQUIRED with an id; you run
|
|
4106
|
-
# \`agentguard approve <id>\` (or click the link in Slack); the agent retries the identical call once.
|
|
4107
|
-
approval:
|
|
4108
|
-
tools: ${flow(destructive)}
|
|
4109
|
-
wait_s: 0 # > 0 holds the call open this long waiting for the decision
|
|
4110
|
-
ttl_s: 3600
|
|
4111
|
-
# notify:
|
|
4112
|
-
# slack: \${SLACK_WEBHOOK_URL}
|
|
4113
|
-
|
|
4114
|
-
# Kill switch: \`agentguard kill\` creates this file (or POST /kill on the HTTP proxy); every call
|
|
4115
|
-
# returns KILLED until \`agentguard resume\`. AGENTGUARD_KILL=1 in the environment does the same.
|
|
4116
|
-
kill:
|
|
4117
|
-
file: .agentguard/KILL
|
|
4118
|
-
|
|
4119
|
-
# Scoped agent keys for the HTTP proxy: \`agentguard key create deployer --allow 'crm_get_*'\`.
|
|
4120
|
-
# Only the key's hash is stored here; the agent sends the key as a Bearer token.
|
|
4121
|
-
agents: []
|
|
4122
|
-
|
|
4123
|
-
# Alerts on halts (Slack incoming webhook or any HTTPS endpoint; signed with X-Agentwares-Signature).
|
|
4124
|
-
# alerts:
|
|
4125
|
-
# slack: \${SLACK_WEBHOOK_URL}
|
|
4126
|
-
# on: [LOOP_DETECTED, CAP_EXCEEDED, KILLED, APPROVAL_REQUIRED]
|
|
4127
|
-
|
|
4128
|
-
# Hash-chained JSONL of every call; \`agentguard verify\` proves it was not edited.
|
|
4129
|
-
audit:
|
|
4130
|
-
path: .agentguard/audit.jsonl
|
|
4131
|
-
redact: true
|
|
4132
|
-
include_results: false
|
|
4133
|
-
`;
|
|
4134
|
-
}
|
|
4135
|
-
function cleanUpstream(u) {
|
|
4136
|
-
const out = { name: u.name };
|
|
4137
|
-
if (u.url) {
|
|
4138
|
-
out.url = u.url;
|
|
4139
|
-
if (Object.keys(u.headers).length) out.headers = u.headers;
|
|
4140
|
-
} else {
|
|
4141
|
-
out.command = u.command;
|
|
4142
|
-
if (u.args.length) out.args = u.args;
|
|
4143
|
-
if (Object.keys(u.env).length) out.env = u.env;
|
|
4144
|
-
if (u.cwd) out.cwd = u.cwd;
|
|
4145
|
-
}
|
|
4146
|
-
return out;
|
|
4147
|
-
}
|
|
4148
|
-
async function initCommand(args, io) {
|
|
4149
|
-
const policyPath = policyPathFrom(args, io);
|
|
4150
|
-
const explicit = flagString(args, "client");
|
|
4151
|
-
const configs = explicit ? [describeConfigPath(resolve3(io.cwd, explicit))] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
|
|
4152
|
-
const rel = (p) => {
|
|
4153
|
-
const r = relative2(io.cwd, p);
|
|
4154
|
-
return r.startsWith("..") ? p : r || ".";
|
|
4155
|
-
};
|
|
4156
|
-
if (flagBool(args, "undo")) {
|
|
4157
|
-
let restored = 0;
|
|
4158
|
-
for (const c of configs) {
|
|
4159
|
-
if (restoreClientConfig(c)) {
|
|
4160
|
-
io.out(`restored ${rel(c.path)} from backup`);
|
|
4161
|
-
restored += 1;
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
4164
|
-
if (restored === 0)
|
|
4165
|
-
io.err("nothing to undo (no *.agentguard-backup files next to your MCP configs)");
|
|
4166
|
-
return 0;
|
|
4167
|
-
}
|
|
4168
|
-
const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
|
|
4169
|
-
const userLevel = configs.filter((c) => !projectLevel.includes(c));
|
|
4170
|
-
let chosen;
|
|
4171
|
-
if (explicit) chosen = configs;
|
|
4172
|
-
else if (flagBool(args, "all")) chosen = configs;
|
|
4173
|
-
else chosen = projectLevel.slice(0, 1);
|
|
4174
|
-
if (!explicit && !flagBool(args, "all")) {
|
|
4175
|
-
for (const c of userLevel)
|
|
4176
|
-
io.out(
|
|
4177
|
-
`found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
|
|
4178
|
-
);
|
|
4179
|
-
}
|
|
4180
|
-
if (!chosen.every((c) => existsSync6(c.path))) {
|
|
4181
|
-
io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
|
|
4182
|
-
return 1;
|
|
4183
|
-
}
|
|
4184
|
-
const servers = {};
|
|
4185
|
-
for (const c of chosen) {
|
|
4186
|
-
const found = readServers(c);
|
|
4187
|
-
io.out(
|
|
4188
|
-
`found ${rel(c.path)} (${c.label}) \u2192 ${Object.keys(found).length} server${Object.keys(found).length === 1 ? "" : "s"}: ${Object.keys(found).join(", ") || "none"}`
|
|
4189
|
-
);
|
|
4190
|
-
for (const [name, entry] of Object.entries(found))
|
|
4191
|
-
if (!(name in servers)) servers[name] = entry;
|
|
4192
|
-
}
|
|
4193
|
-
if (chosen.length === 0)
|
|
4194
|
-
io.out(
|
|
4195
|
-
"found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) \u2014 writing a policy with no upstreams"
|
|
4196
|
-
);
|
|
4197
|
-
const { upstreams, skipped } = serversToUpstreams(servers);
|
|
4198
|
-
for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
|
|
4199
|
-
if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync6(policyPath)) {
|
|
4200
|
-
io.out(
|
|
4201
|
-
`already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
|
|
4202
|
-
);
|
|
4203
|
-
return 0;
|
|
4204
|
-
}
|
|
4205
|
-
let probed = [];
|
|
4206
|
-
const probeErrors = [];
|
|
4207
|
-
if (flagBool(args, "probe") !== false && upstreams.length > 0) {
|
|
4208
|
-
io.out(`probing ${upstreams.length} upstream${upstreams.length === 1 ? "" : "s"}\u2026`);
|
|
4209
|
-
const result = await probeUpstreams(upstreams, {
|
|
4210
|
-
log: () => void 0,
|
|
4211
|
-
env: io.env,
|
|
4212
|
-
timeoutMs: 2e4
|
|
4213
|
-
});
|
|
4214
|
-
probed = result.tools;
|
|
4215
|
-
for (const u of result.upstreams) {
|
|
4216
|
-
if (u.connected) io.out(` ${u.name}: ${u.tools} tools`);
|
|
4217
|
-
else {
|
|
4218
|
-
io.err(
|
|
4219
|
-
` ${u.name}: could not connect (${u.error}) \u2014 the proxy will keep retrying at start; fix the entry in agentguard.yaml`
|
|
4220
|
-
);
|
|
4221
|
-
probeErrors.push(`${u.name}: ${u.error}`);
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
}
|
|
4225
|
-
const mode = flagString(args, "mode") ?? "dry-run";
|
|
4226
|
-
if (existsSync6(policyPath) && !flagBool(args, "force")) {
|
|
4227
|
-
io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
|
|
4228
|
-
} else {
|
|
4229
|
-
writeFileSync3(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
|
|
4230
|
-
const counts = probed.reduce(
|
|
4231
|
-
(acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
|
|
4232
|
-
{}
|
|
4233
|
-
);
|
|
4234
|
-
io.out(
|
|
4235
|
-
`wrote ${rel(policyPath)} (mode: ${mode}, ${upstreams.length} upstream${upstreams.length === 1 ? "" : "s"}${probed.length ? `, ${probed.length} tools: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}` : ""})`
|
|
4236
|
-
);
|
|
4237
|
-
}
|
|
4238
|
-
if (flagBool(args, "rewrite") !== false) {
|
|
4239
|
-
for (const c of chosen) {
|
|
4240
|
-
if (resolve3(c.path) === resolve3(policyPath)) {
|
|
4241
|
-
throw new Error(
|
|
4242
|
-
`refusing: ${rel(c.path)} is both the policy file (--config) and a client config. Rewriting it would replace the policy with the proxy entry and leave the proxy pointing at itself. Use --config for the policy path and --client for the client's config, or let --config default to agentguard.yaml.`
|
|
4243
|
-
);
|
|
4244
|
-
}
|
|
4245
|
-
const { backup, replaced } = rewriteClientConfig(c, policyPath, {
|
|
4246
|
-
agent: flagString(args, "agent")
|
|
4247
|
-
});
|
|
4248
|
-
io.out(
|
|
4249
|
-
`rewrote ${rel(c.path)} \u2192 ${replaced.length ? `${replaced.join(", ")} now go through` : "points at"} \`npx @agentwares/agentguard proxy\` (backup: ${rel(backup)})`
|
|
4250
|
-
);
|
|
4251
|
-
}
|
|
4252
|
-
}
|
|
4253
|
-
io.out("");
|
|
4254
|
-
io.out("Next:");
|
|
4255
|
-
io.out(
|
|
4256
|
-
` 1. restart your MCP client so it re-reads ${chosen.map((c) => rel(c.path)).join(", ") || "its config"}`
|
|
4257
|
-
);
|
|
4258
|
-
io.out(
|
|
4259
|
-
` 2. run your agent once \u2014 ${mode === "dry-run" ? "writes are faked; nothing executes upstream" : "enforce mode: caps, loop breaker and approvals are live"}`
|
|
4260
|
-
);
|
|
4261
|
-
io.out(" 3. agentguard report # what this run would have destroyed / spent");
|
|
4262
|
-
io.out(" agentguard diff # the record-by-record mutation diff");
|
|
4263
|
-
if (mode === "dry-run")
|
|
4264
|
-
io.out(` 4. set \`mode: enforce\` in ${rel(policyPath)} when it looks right`);
|
|
4265
|
-
io.out(" undo any time: agentguard init --undo");
|
|
4266
|
-
return 0;
|
|
4267
|
-
}
|
|
4268
|
-
|
|
4269
|
-
// src/commands/keys.ts
|
|
4270
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4271
|
-
import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
|
|
4272
|
-
import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
|
|
4273
|
-
async function keyCommand(args, io) {
|
|
4274
|
-
const [sub, name] = args.positionals;
|
|
4275
|
-
const path = policyPathFrom(args, io);
|
|
4276
|
-
const doc = YAML2.parseDocument(readFileSync5(path, "utf8"));
|
|
4277
|
-
let agents = doc.get("agents");
|
|
4278
|
-
if (!isSeq(agents)) {
|
|
4279
|
-
agents = new YAMLSeq();
|
|
4280
|
-
doc.set("agents", agents);
|
|
4281
|
-
}
|
|
4282
|
-
const seq = agents;
|
|
4283
|
-
const items = seq.items.filter(isMap);
|
|
4284
|
-
if (sub === "list" || sub === void 0) {
|
|
4285
|
-
if (items.length === 0)
|
|
4286
|
-
io.out(
|
|
4287
|
-
"no agents in agentguard.yaml \u2014 create one: agentguard key create <name> --allow 'crm_get_*'"
|
|
4288
|
-
);
|
|
4289
|
-
for (const a of items) {
|
|
4290
|
-
const allow2 = a.get("allow");
|
|
4291
|
-
io.out(
|
|
4292
|
-
`${String(a.get("name")).padEnd(20)} key: ${a.get("key_hash") ? "set" : "none (stdio --agent only)"} allow: ${isSeq(allow2) ? allow2.items.map(String).join(", ") : "all"}`
|
|
4293
|
-
);
|
|
4294
|
-
}
|
|
4295
|
-
return 0;
|
|
4296
|
-
}
|
|
4297
|
-
if (!name) {
|
|
4298
|
-
io.err(
|
|
4299
|
-
"usage: agentguard key create <agent> [--allow pattern]... [--deny pattern]... [--writes n] [--spend n] [--deletes n] [--emails n] [--tool-calls n] [--mode dry-run|enforce] [--upstream name]..."
|
|
4300
|
-
);
|
|
4301
|
-
return 1;
|
|
4302
|
-
}
|
|
4303
|
-
if (sub === "revoke") {
|
|
4304
|
-
const idx = items.findIndex((a) => a.get("name") === name);
|
|
4305
|
-
if (idx < 0) {
|
|
4306
|
-
io.err(`no agent "${name}"`);
|
|
4307
|
-
return 1;
|
|
4308
|
-
}
|
|
4309
|
-
seq.items.splice(seq.items.indexOf(items[idx]), 1);
|
|
4310
|
-
writeFileSync4(path, doc.toString());
|
|
4311
|
-
io.out(`revoked ${name}: removed from agentguard.yaml (running proxies pick it up on restart)`);
|
|
4312
|
-
return 0;
|
|
4313
|
-
}
|
|
4314
|
-
if (sub !== "create") {
|
|
4315
|
-
io.err(`unknown subcommand "${sub}" \u2014 use create, list or revoke`);
|
|
4316
|
-
return 1;
|
|
4317
|
-
}
|
|
4318
|
-
const key = generateAgentKey();
|
|
4319
|
-
const keyHash = await hashAgentKey(key);
|
|
4320
|
-
const existing = items.find((a) => a.get("name") === name);
|
|
4321
|
-
const entry = { name, key_hash: keyHash };
|
|
4322
|
-
const allow = flagList(args, "allow");
|
|
4323
|
-
const deny = flagList(args, "deny");
|
|
4324
|
-
if (allow.length) entry.allow = allow;
|
|
4325
|
-
if (deny.length) entry.deny = deny;
|
|
4326
|
-
const upstreams = flagList(args, "upstream");
|
|
4327
|
-
if (upstreams.length) entry.upstreams = upstreams;
|
|
4328
|
-
const mode = flagString(args, "mode");
|
|
4329
|
-
if (mode) entry.mode = mode;
|
|
4330
|
-
const perRun = {};
|
|
4331
|
-
for (const [flag, counter] of [
|
|
4332
|
-
["writes", "writes"],
|
|
4333
|
-
["spend", "spend_usd"],
|
|
4334
|
-
["deletes", "deletes"],
|
|
4335
|
-
["emails", "emails"],
|
|
4336
|
-
["tool-calls", "tool_calls"]
|
|
4337
|
-
]) {
|
|
4338
|
-
const n = flagNumber(args, flag);
|
|
4339
|
-
if (n !== void 0) perRun[counter] = n;
|
|
4340
|
-
}
|
|
4341
|
-
if (Object.keys(perRun).length) entry.caps = { per_run: perRun };
|
|
4342
|
-
if (existing) {
|
|
4343
|
-
for (const [k, v] of Object.entries(entry)) existing.set(k, v);
|
|
4344
|
-
} else {
|
|
4345
|
-
seq.add(doc.createNode(entry));
|
|
4346
|
-
}
|
|
4347
|
-
writeFileSync4(path, doc.toString());
|
|
4348
|
-
io.err(
|
|
4349
|
-
`${existing ? "rotated" : "created"} agent "${name}" in ${path}${allow.length ? ` (allow: ${allow.join(", ")})` : " (allow: all tools)"}`
|
|
4350
|
-
);
|
|
4351
|
-
io.err(
|
|
4352
|
-
"the key is shown once; give it to the agent as `Authorization: Bearer <key>` (HTTP) \u2014 over stdio use `agentguard proxy --agent " + name + "`"
|
|
4353
|
-
);
|
|
4354
|
-
io.out(key);
|
|
4355
|
-
return 0;
|
|
4356
|
-
}
|
|
4357
|
-
|
|
4358
|
-
// src/commands/permission-diff.ts
|
|
4359
|
-
import { execFileSync } from "child_process";
|
|
4360
|
-
import {
|
|
4361
|
-
diffPermissionFile,
|
|
4362
|
-
renderPermissionDiffMarkdown
|
|
4363
|
-
} from "@agentwares/agentguard-core";
|
|
4364
|
-
var DEFAULT_PERMISSION_PATHS = [
|
|
4365
|
-
"agentguard.yaml",
|
|
4366
|
-
"agentguard.yml",
|
|
4367
|
-
".claude/settings.json",
|
|
4368
|
-
".claude/settings.local.json",
|
|
4369
|
-
".mcp.json",
|
|
4370
|
-
"mcp.json",
|
|
4371
|
-
".cursor/mcp.json",
|
|
4372
|
-
".vscode/mcp.json"
|
|
4373
|
-
];
|
|
4374
|
-
var PERMISSION_FILE = /(^|\/)(agentguard[^/]*\.ya?ml|\.claude\/settings(\.local)?\.json|[^/]*mcp[^/]*\.json|claude_desktop_config\.json)$/;
|
|
4375
|
-
function git(cwd, argv) {
|
|
4376
|
-
try {
|
|
4377
|
-
return execFileSync("git", argv, {
|
|
4378
|
-
cwd,
|
|
4379
|
-
encoding: "utf8",
|
|
4380
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4381
|
-
});
|
|
4382
|
-
} catch {
|
|
4383
|
-
return void 0;
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
function showAt(cwd, ref, path) {
|
|
4387
|
-
return git(cwd, ["show", `${ref}:${path}`]);
|
|
4388
|
-
}
|
|
4389
|
-
function changedPermissionFiles(cwd, base, head) {
|
|
4390
|
-
const out = git(cwd, ["diff", "--name-only", base, head]) ?? "";
|
|
4391
|
-
return out.split("\n").map((l) => l.trim()).filter((l) => l && PERMISSION_FILE.test(l));
|
|
4392
|
-
}
|
|
4393
|
-
function resolveBase(cwd, explicit) {
|
|
4394
|
-
if (explicit) return explicit;
|
|
4395
|
-
for (const candidate of ["origin/main", "main", "origin/master", "master"]) {
|
|
4396
|
-
if (git(cwd, ["rev-parse", "--verify", "--quiet", candidate]) !== void 0) return candidate;
|
|
4397
|
-
}
|
|
4398
|
-
return "HEAD~1";
|
|
4399
|
-
}
|
|
4400
|
-
function runPermissionDiff(cwd, opts) {
|
|
4401
|
-
const base = resolveBase(cwd, opts.base);
|
|
4402
|
-
const head = opts.head ?? "HEAD";
|
|
4403
|
-
const files = [
|
|
4404
|
-
.../* @__PURE__ */ new Set([
|
|
4405
|
-
...opts.paths ?? DEFAULT_PERMISSION_PATHS,
|
|
4406
|
-
...changedPermissionFiles(cwd, base, head)
|
|
4407
|
-
])
|
|
4408
|
-
];
|
|
4409
|
-
const findings = [];
|
|
4410
|
-
const touched = [];
|
|
4411
|
-
for (const file of files) {
|
|
4412
|
-
const before = showAt(cwd, base, file);
|
|
4413
|
-
const after = showAt(cwd, head, file);
|
|
4414
|
-
if (before === void 0 && after === void 0) continue;
|
|
4415
|
-
if (before === after) continue;
|
|
4416
|
-
touched.push(file);
|
|
4417
|
-
findings.push(...diffPermissionFile(file, before, after));
|
|
4418
|
-
}
|
|
4419
|
-
return {
|
|
4420
|
-
base,
|
|
4421
|
-
head,
|
|
4422
|
-
files: touched,
|
|
4423
|
-
findings,
|
|
4424
|
-
markdown: renderPermissionDiffMarkdown(findings, { title: opts.title })
|
|
4425
|
-
};
|
|
4426
|
-
}
|
|
4427
|
-
async function permissionDiffCommand(args, io) {
|
|
4428
|
-
const paths = flagList(args, "paths");
|
|
4429
|
-
const result = runPermissionDiff(io.cwd, {
|
|
4430
|
-
base: flagString(args, "base"),
|
|
4431
|
-
head: flagString(args, "head"),
|
|
4432
|
-
paths: paths.length ? paths : void 0
|
|
4433
|
-
});
|
|
4434
|
-
const format = flagString(args, "format") ?? (flagBool(args, "json") ? "json" : "markdown");
|
|
4435
|
-
if (format === "json") io.out(JSON.stringify(result, null, 2));
|
|
4436
|
-
else io.out(result.markdown);
|
|
4437
|
-
const widen = result.findings.filter((f) => f.severity === "widen").length;
|
|
4438
|
-
if (flagBool(args, "fail-on-widen") && widen > 0) return 1;
|
|
4439
|
-
return 0;
|
|
4440
|
-
}
|
|
4441
|
-
|
|
4442
|
-
// src/commands/proxy.ts
|
|
4443
|
-
import { existsSync as existsSync7 } from "fs";
|
|
4444
|
-
import { dirname } from "path";
|
|
4445
|
-
import { Guard as Guard3, defaultPolicy, resolveAgent as resolveAgent2 } from "@agentwares/agentguard-core";
|
|
4446
|
-
import { FileKillSwitch as FileKillSwitch3, resolvePath } from "@agentwares/agentguard-core/node";
|
|
4447
|
-
function newRunId(now = /* @__PURE__ */ new Date()) {
|
|
4448
|
-
return `run_${now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
4449
|
-
}
|
|
4450
|
-
function unconfiguredPolicy(args, io) {
|
|
4451
|
-
if (flagString(args, "config") !== void 0 || io.env.AGENTGUARD_CONFIG) return void 0;
|
|
4452
|
-
const path = policyPathFrom(args, io);
|
|
4453
|
-
if (existsSync7(path)) return void 0;
|
|
4454
|
-
const policy = defaultPolicy();
|
|
4455
|
-
const baseDir = dirname(path);
|
|
4456
|
-
return {
|
|
4457
|
-
policy,
|
|
4458
|
-
path,
|
|
4459
|
-
baseDir,
|
|
4460
|
-
stateDir: resolvePath(policy.state.dir, baseDir),
|
|
4461
|
-
auditPath: resolvePath(policy.audit.path, baseDir),
|
|
4462
|
-
killPath: resolvePath(policy.kill.file, baseDir)
|
|
4463
|
-
};
|
|
4464
|
-
}
|
|
4465
|
-
async function serveOverStdio(runtime) {
|
|
4466
|
-
const server = createDownstreamServer(runtime);
|
|
4467
|
-
const stop = async () => {
|
|
4468
|
-
await runtime.close();
|
|
4469
|
-
};
|
|
4470
|
-
process.once("SIGINT", () => void stop().then(() => process.exit(0)));
|
|
4471
|
-
process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
|
|
4472
|
-
await serveStdio(server);
|
|
4473
|
-
await stop();
|
|
4474
|
-
return 0;
|
|
4475
|
-
}
|
|
4476
|
-
async function proxyCommand(args, io) {
|
|
4477
|
-
const log = (line) => io.err(`[agentguard] ${line}`);
|
|
4478
|
-
const modeOverride = flagString(args, "mode");
|
|
4479
|
-
const http = flagBool(args, "http") === true;
|
|
4480
|
-
const unconfigured = http ? void 0 : unconfiguredPolicy(args, io);
|
|
4481
|
-
if (unconfigured) {
|
|
4482
|
-
if (modeOverride === "dry-run" || modeOverride === "enforce")
|
|
4483
|
-
unconfigured.policy.mode = modeOverride;
|
|
4484
|
-
const runtime2 = new ProxyRuntime({
|
|
4485
|
-
loaded: unconfigured,
|
|
4486
|
-
guard: new Guard3({
|
|
4487
|
-
policy: unconfigured.policy,
|
|
4488
|
-
env: io.env,
|
|
4489
|
-
kill: new FileKillSwitch3(unconfigured.killPath)
|
|
4490
|
-
}),
|
|
4491
|
-
configured: false,
|
|
4492
|
-
defaultRunId: flagString(args, "run-id") ?? newRunId(),
|
|
4493
|
-
log
|
|
4494
|
-
});
|
|
4495
|
-
await runtime2.start();
|
|
4496
|
-
log(
|
|
4497
|
-
`no policy file at ${unconfigured.path}: serving agentguard's own read-only tools only. To guard your MCP servers, run \`${INIT_COMMAND}\` in the project directory.`
|
|
4498
|
-
);
|
|
4499
|
-
return serveOverStdio(runtime2);
|
|
4500
|
-
}
|
|
4501
|
-
const loaded = loadPolicyFor(args, io);
|
|
4502
|
-
if (modeOverride === "dry-run" || modeOverride === "enforce") loaded.policy.mode = modeOverride;
|
|
4503
|
-
const agentName = flagString(args, "agent");
|
|
4504
|
-
let agent = void 0;
|
|
4505
|
-
if (agentName) {
|
|
4506
|
-
const resolved = await resolveAgent2(loaded.policy, { name: agentName });
|
|
4507
|
-
if (resolved.error) {
|
|
4508
|
-
io.err(
|
|
4509
|
-
`unknown agent "${agentName}" \u2014 agents in agentguard.yaml: ${loaded.policy.agents.map((a) => a.name).join(", ") || "none"}`
|
|
4510
|
-
);
|
|
4511
|
-
return 1;
|
|
4512
|
-
}
|
|
4513
|
-
agent = resolved.agent;
|
|
4514
|
-
}
|
|
4515
|
-
let approvalBase;
|
|
4516
|
-
const fileGuard = createFileGuard(loaded, {
|
|
4517
|
-
log,
|
|
4518
|
-
env: io.env,
|
|
4519
|
-
approvalUrl: (r) => approvalBase ? `${approvalBase.baseUrl}/approve/${r.id}?token=${approvalBase.token}` : void 0,
|
|
4520
|
-
onEvent: (e) => {
|
|
4521
|
-
if (e.type === "APPROVAL_DECIDED") return;
|
|
4522
|
-
log(`${e.type} ${e.tool} (run ${e.runId})${e.error ? `: ${e.error.cause}` : ""}`);
|
|
4523
|
-
if (e.type === "APPROVAL_REQUIRED" && e.approval)
|
|
4524
|
-
log(`approve with: agentguard approve ${e.approval.id}`);
|
|
4525
|
-
}
|
|
4526
|
-
});
|
|
4527
|
-
const runtime = new ProxyRuntime({
|
|
4528
|
-
loaded,
|
|
4529
|
-
guard: fileGuard.guard,
|
|
4530
|
-
agent,
|
|
4531
|
-
defaultRunId: flagString(args, "run-id") ?? newRunId(),
|
|
4532
|
-
log
|
|
4533
|
-
});
|
|
4534
|
-
await runtime.start();
|
|
4535
|
-
const killed = await fileGuard.kill.check();
|
|
4536
|
-
if (killed.killed)
|
|
4537
|
-
log(
|
|
4538
|
-
`kill switch is ON (${killed.reason}); every call returns KILLED until \`agentguard resume\``
|
|
4539
|
-
);
|
|
4540
|
-
if (runtime.upstreamStatus().length === 0)
|
|
4541
|
-
log(
|
|
4542
|
-
`${loaded.path} lists no upstreams: serving agentguard's own read-only tools only. Add servers under \`upstreams:\` to guard them.`
|
|
4543
|
-
);
|
|
4544
|
-
if (http) {
|
|
4545
|
-
const server = await startHttpProxy({
|
|
4546
|
-
runtime,
|
|
4547
|
-
port: flagNumber(args, "port") ?? 8788,
|
|
4548
|
-
host: flagString(args, "host") ?? "127.0.0.1",
|
|
4549
|
-
token: io.env.AGENTGUARD_HTTP_TOKEN
|
|
4550
|
-
});
|
|
4551
|
-
approvalBase = { baseUrl: server.url.replace(/\/mcp$/, ""), token: server.token };
|
|
4552
|
-
log(
|
|
4553
|
-
`Streamable HTTP at ${server.url} \xB7 GET ${approvalBase.baseUrl}/health \xB7 control token in ${loaded.stateDir}/http.json`
|
|
4554
|
-
);
|
|
4555
|
-
log(
|
|
4556
|
-
`point your agent at ${server.url} (send X-Run-Id per run; Authorization: Bearer <agent key> for scoped agents)`
|
|
4557
|
-
);
|
|
4558
|
-
const stop = async () => {
|
|
4559
|
-
await server.close();
|
|
4560
|
-
await runtime.close();
|
|
4561
|
-
process.exit(0);
|
|
4562
|
-
};
|
|
4563
|
-
process.once("SIGINT", () => void stop());
|
|
4564
|
-
process.once("SIGTERM", () => void stop());
|
|
4565
|
-
await new Promise(() => void 0);
|
|
4566
|
-
}
|
|
4567
|
-
return serveOverStdio(runtime);
|
|
4568
|
-
}
|
|
4569
|
-
|
|
4570
|
-
// src/commands/report.ts
|
|
4571
|
-
import { resolve as resolve4 } from "path";
|
|
3
|
+
HELP,
|
|
4
|
+
main
|
|
5
|
+
} from "./chunk-6TUQD666.js";
|
|
6
|
+
import "./chunk-3O6KWT3E.js";
|
|
7
|
+
import "./chunk-TW5KJREH.js";
|
|
4572
8
|
import {
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
renderMutationDiff,
|
|
4577
|
-
renderReportMarkdown as renderReportMarkdown2
|
|
4578
|
-
} from "@agentwares/agentguard-core";
|
|
4579
|
-
import { readAuditFile, verifyAuditFile } from "@agentwares/agentguard-core/node";
|
|
4580
|
-
function pickRun(args, entries) {
|
|
4581
|
-
if (flagBool(args, "all")) return void 0;
|
|
4582
|
-
return flagString(args, "run") ?? latestRunId2(entries);
|
|
4583
|
-
}
|
|
4584
|
-
async function reportCommand(args, io) {
|
|
4585
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
4586
|
-
const entries = readAuditFile(loaded.auditPath);
|
|
4587
|
-
const runId = pickRun(args, entries);
|
|
4588
|
-
const report = buildReport2(entries, { runId });
|
|
4589
|
-
if (flagBool(args, "json")) {
|
|
4590
|
-
io.out(
|
|
4591
|
-
JSON.stringify(
|
|
4592
|
-
{
|
|
4593
|
-
...report,
|
|
4594
|
-
mutations: report.mutations.map((m) => ({
|
|
4595
|
-
seq: m.seq,
|
|
4596
|
-
ts: m.ts,
|
|
4597
|
-
tool: m.tool,
|
|
4598
|
-
mutation: m.mutation
|
|
4599
|
-
}))
|
|
4600
|
-
},
|
|
4601
|
-
null,
|
|
4602
|
-
2
|
|
4603
|
-
)
|
|
4604
|
-
);
|
|
4605
|
-
return 0;
|
|
4606
|
-
}
|
|
4607
|
-
io.out(renderReportMarkdown2(report));
|
|
4608
|
-
const chain = await verifyAuditFile(loaded.auditPath);
|
|
4609
|
-
io.out(
|
|
4610
|
-
chain.ok ? `Audit chain: ${chain.entries} entries, verified (${loaded.auditPath}).` : `Audit chain BROKEN at entry ${chain.brokenAt}: ${chain.reason}`
|
|
4611
|
-
);
|
|
4612
|
-
if (runId && listRuns2(entries).length > 1) io.out(`Other runs: agentguard report --all`);
|
|
4613
|
-
return 0;
|
|
4614
|
-
}
|
|
4615
|
-
async function diffCommand(args, io) {
|
|
4616
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
4617
|
-
const entries = readAuditFile(loaded.auditPath);
|
|
4618
|
-
const runId = pickRun(args, entries);
|
|
4619
|
-
io.out(renderMutationDiff(entries, { runId }).trimEnd());
|
|
4620
|
-
return 0;
|
|
4621
|
-
}
|
|
4622
|
-
async function verifyCommand(args, io) {
|
|
4623
|
-
let path = args.positionals[0];
|
|
4624
|
-
if (!path) path = loadPolicyFor(args, io, { allowMissingEnv: true }).auditPath;
|
|
4625
|
-
path = resolve4(io.cwd, path);
|
|
4626
|
-
const result = await verifyAuditFile(path);
|
|
4627
|
-
if (flagBool(args, "json")) io.out(JSON.stringify({ path, ...result }));
|
|
4628
|
-
else if (result.ok)
|
|
4629
|
-
io.out(
|
|
4630
|
-
`ok: ${result.entries} entries, chain intact, head ${result.head?.slice(0, 16)}\u2026 (${path})`
|
|
4631
|
-
);
|
|
4632
|
-
else io.out(`BROKEN at entry ${result.brokenAt}: ${result.reason} (${path})`);
|
|
4633
|
-
return result.ok ? 0 : 1;
|
|
4634
|
-
}
|
|
4635
|
-
async function statusCommand(args, io) {
|
|
4636
|
-
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
4637
|
-
const { guard, kill } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
4638
|
-
const entries = readAuditFile(loaded.auditPath);
|
|
4639
|
-
const runId = flagString(args, "run") ?? latestRunId2(entries) ?? "(none yet)";
|
|
4640
|
-
const status = await guard.status(runId);
|
|
4641
|
-
const killed = kill.check();
|
|
4642
|
-
const reg = readRegistration(loaded);
|
|
4643
|
-
const pending = await guard.approvals.list("pending");
|
|
4644
|
-
if (flagBool(args, "json")) {
|
|
4645
|
-
io.out(
|
|
4646
|
-
JSON.stringify(
|
|
4647
|
-
{
|
|
4648
|
-
policy: loaded.path,
|
|
4649
|
-
mode: loaded.policy.mode,
|
|
4650
|
-
runId,
|
|
4651
|
-
...status,
|
|
4652
|
-
killed,
|
|
4653
|
-
pendingApprovals: pending.length,
|
|
4654
|
-
http: reg ? { url: reg.url, pid: reg.pid } : void 0
|
|
4655
|
-
},
|
|
4656
|
-
null,
|
|
4657
|
-
2
|
|
4658
|
-
)
|
|
4659
|
-
);
|
|
4660
|
-
return 0;
|
|
4661
|
-
}
|
|
4662
|
-
io.out(
|
|
4663
|
-
`policy ${loaded.path} (mode: ${loaded.policy.mode}, ${loaded.policy.upstreams.length} upstreams)`
|
|
4664
|
-
);
|
|
4665
|
-
io.out(
|
|
4666
|
-
`kill ${killed.killed ? `ON \u2014 ${killed.reason} (agentguard resume to clear)` : "off"}`
|
|
4667
|
-
);
|
|
4668
|
-
io.out(
|
|
4669
|
-
`http ${reg ? `${reg.url} (pid ${reg.pid})` : "not running (agentguard proxy --http)"}`
|
|
4670
|
-
);
|
|
4671
|
-
io.out(`audit ${entries.length} entries in ${loaded.auditPath}`);
|
|
4672
|
-
io.out(
|
|
4673
|
-
`pending ${pending.length} approval${pending.length === 1 ? "" : "s"}${pending.length ? ` \u2014 agentguard approvals` : ""}`
|
|
4674
|
-
);
|
|
4675
|
-
io.out(`run ${runId}`);
|
|
4676
|
-
const fmt = (scope) => Object.entries(status.caps[scope]).map(([k, limit]) => `${k} ${status.usage[scope][k] ?? 0}/${limit}`).join(" ") || "no caps";
|
|
4677
|
-
io.out(` per_run ${fmt("per_run")}`);
|
|
4678
|
-
io.out(` per_day ${fmt("per_day")}`);
|
|
4679
|
-
return 0;
|
|
4680
|
-
}
|
|
4681
|
-
|
|
4682
|
-
// src/cli.ts
|
|
4683
|
-
var HELP = `agentguard \u2014 MCP policy proxy for agents that touch production
|
|
4684
|
-
|
|
4685
|
-
npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy
|
|
4686
|
-
agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
|
|
4687
|
-
agentguard diff [--run id] record-by-record mutation diff from a dry run
|
|
4688
|
-
agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited
|
|
4689
|
-
agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)
|
|
4690
|
-
|
|
4691
|
-
agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
|
|
4692
|
-
agentguard tools [--json] the tools your agent will see, with class and why
|
|
4693
|
-
agentguard status [--run id] counters vs caps, kill state, pending approvals
|
|
4694
|
-
|
|
4695
|
-
agentguard kill [reason] halt every run now (file + running HTTP proxy); agentguard resume
|
|
4696
|
-
agentguard approvals [--all] pending approvals; agentguard approve <id> | deny <id> [--note text]
|
|
4697
|
-
agentguard key create <agent> [--allow 'crm_get_*']... [--deny p] [--writes n] [--spend n] [--mode m]
|
|
4698
|
-
agentguard key list | revoke <agent>
|
|
4699
|
-
agentguard permission-diff [--base ref] [--head ref] [--paths a,b] [--format markdown|json] [--fail-on-widen]
|
|
4700
|
-
|
|
4701
|
-
Options: --config <agentguard.yaml> (or AGENTGUARD_CONFIG), --json, --help, --version
|
|
4702
|
-
Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
|
|
4703
|
-
async function main(argv, io = defaultIo()) {
|
|
4704
|
-
const args = parseArgs(argv);
|
|
4705
|
-
if (args.flags.version) {
|
|
4706
|
-
io.out(version());
|
|
4707
|
-
return 0;
|
|
4708
|
-
}
|
|
4709
|
-
if (args.command === void 0 && !args.flags.help && io.tty === false) {
|
|
4710
|
-
try {
|
|
4711
|
-
return await proxyCommand(args, io);
|
|
4712
|
-
} catch (err) {
|
|
4713
|
-
io.err(describeError(err));
|
|
4714
|
-
return 1;
|
|
4715
|
-
}
|
|
4716
|
-
}
|
|
4717
|
-
if (args.flags.help || args.command === void 0 || args.command === "help") {
|
|
4718
|
-
io.out(HELP);
|
|
4719
|
-
return args.command === void 0 && !args.flags.help ? 1 : 0;
|
|
4720
|
-
}
|
|
4721
|
-
try {
|
|
4722
|
-
switch (args.command) {
|
|
4723
|
-
case "init":
|
|
4724
|
-
return await initCommand(args, io);
|
|
4725
|
-
case "proxy":
|
|
4726
|
-
case "serve":
|
|
4727
|
-
return await proxyCommand(args, io);
|
|
4728
|
-
case "report":
|
|
4729
|
-
return await reportCommand(args, io);
|
|
4730
|
-
case "diff":
|
|
4731
|
-
return await diffCommand(args, io);
|
|
4732
|
-
case "verify":
|
|
4733
|
-
return await verifyCommand(args, io);
|
|
4734
|
-
case "connect":
|
|
4735
|
-
return await connectCommand(args, io);
|
|
4736
|
-
case "status":
|
|
4737
|
-
return await statusCommand(args, io);
|
|
4738
|
-
case "tools":
|
|
4739
|
-
return await toolsCommand(args, io);
|
|
4740
|
-
case "kill":
|
|
4741
|
-
return await killCommand(args, io);
|
|
4742
|
-
case "resume":
|
|
4743
|
-
return await resumeCommand(args, io);
|
|
4744
|
-
case "approve":
|
|
4745
|
-
return await decideCommand("approved", args, io);
|
|
4746
|
-
case "deny":
|
|
4747
|
-
return await decideCommand("denied", args, io);
|
|
4748
|
-
case "approvals":
|
|
4749
|
-
return await approvalsCommand(args, io);
|
|
4750
|
-
case "key":
|
|
4751
|
-
case "keys":
|
|
4752
|
-
return await keyCommand(args, io);
|
|
4753
|
-
case "permission-diff":
|
|
4754
|
-
return await permissionDiffCommand(args, io);
|
|
4755
|
-
default:
|
|
4756
|
-
io.err(`unknown command "${args.command}"
|
|
4757
|
-
`);
|
|
4758
|
-
io.err(HELP);
|
|
4759
|
-
return 1;
|
|
4760
|
-
}
|
|
4761
|
-
} catch (err) {
|
|
4762
|
-
io.err(describeError(err));
|
|
4763
|
-
return 1;
|
|
4764
|
-
}
|
|
4765
|
-
}
|
|
4766
|
-
var isMain = process.argv[1] !== void 0 && /(^|\/)(cli\.[cm]?[jt]s|agentguard)$/.test(process.argv[1]);
|
|
4767
|
-
if (isMain) {
|
|
4768
|
-
main(process.argv.slice(2)).then(
|
|
4769
|
-
(code) => {
|
|
4770
|
-
if (code !== 0) process.exitCode = code;
|
|
4771
|
-
},
|
|
4772
|
-
(err) => {
|
|
4773
|
-
process.stderr.write(`${describeError(err)}
|
|
4774
|
-
`);
|
|
4775
|
-
process.exitCode = 1;
|
|
4776
|
-
}
|
|
4777
|
-
);
|
|
4778
|
-
}
|
|
9
|
+
version
|
|
10
|
+
} from "./chunk-SUXQTT3X.js";
|
|
11
|
+
import "./chunk-5WRI5ZAA.js";
|
|
4779
12
|
export {
|
|
4780
13
|
HELP,
|
|
4781
14
|
main,
|