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