toolgate 1.0.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/dist/index.js ADDED
@@ -0,0 +1,616 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ToolGate: () => ToolGate,
34
+ autoConfigFromMCP: () => autoConfigFromMCP,
35
+ classifyTool: () => classifyTool,
36
+ cliApproval: () => cliApproval,
37
+ discoverMCPTools: () => discoverMCPTools,
38
+ mcpToolGate: () => mcpToolGate
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/core/toolgate.ts
43
+ var import_crypto = require("crypto");
44
+
45
+ // src/classifiers/classify.ts
46
+ var READ_PATTERNS = [
47
+ /^(get|read|fetch|list|search|find|query|lookup|describe|show|view|check|inspect|retrieve|browse|scan|count|exists|head|peek|poll|watch|observe|select|load)/i,
48
+ /([-_]get$|[-_]read$|[-_]list$|[-_]fetch$|[-_]search$|[-_]find$|[-_]query$|[-_]describe$|[-_]show$|[-_]view$|[-_]check$)/i,
49
+ /^(is[-_]|has[-_]|can[-_]|does[-_])/i
50
+ ];
51
+ var WRITE_PATTERNS = [
52
+ /^(create|write|put|set|add|insert|post|save|store|upload|push|append|upsert|make|generate|build|init|register|submit|publish)/i,
53
+ /([-_]create$|[-_]write$|[-_]add$|[-_]insert$|[-_]post$|[-_]save$|[-_]upload$|[-_]push$|[-_]set$|[-_]submit$|[-_]publish$)/i
54
+ ];
55
+ var DELETE_PATTERNS = [
56
+ /^(delete|remove|destroy|drop|purge|clear|clean|unset|revoke|cancel|terminate|kill|close|archive|trash|wipe|reset)/i,
57
+ /([-_]delete$|[-_]remove$|[-_]destroy$|[-_]drop$|[-_]clear$|[-_]cancel$|[-_]revoke$|[-_]close$|[-_]archive$|[-_]trash$)/i
58
+ ];
59
+ var UPDATE_PATTERNS = [
60
+ /^(update|edit|modify|patch|change|alter|replace|rename|move|merge|assign|reassign|transfer|swap|toggle|enable|disable|approve|reject|resolve|reopen)/i,
61
+ /([-_]update$|[-_]edit$|[-_]modify$|[-_]patch$|[-_]change$|[-_]rename$|[-_]move$|[-_]merge$|[-_]assign$|[-_]toggle$)/i
62
+ ];
63
+ var EXECUTE_PATTERNS = [
64
+ /^(run|exec|execute|invoke|trigger|fire|dispatch|emit|call|start|restart|stop|deploy|rollback|migrate|sync|process|transform|convert|compile|evaluate)/i,
65
+ /([-_]run$|[-_]exec$|[-_]execute$|[-_]trigger$|[-_]deploy$|[-_]start$|[-_]stop$|[-_]restart$)/i
66
+ ];
67
+ var SEND_PATTERNS = [
68
+ /^(send|email|mail|notify|message|broadcast|forward|reply|share|invite|alert|ping|sms|slack|tweet|dm|comment)/i,
69
+ /([-_]send$|[-_]email$|[-_]notify$|[-_]message$|[-_]share$|[-_]forward$|[-_]reply$|[-_]comment$|[-_]invite$)/i
70
+ ];
71
+ var READ_DESCRIPTION_SIGNALS = [
72
+ "retrieve",
73
+ "get",
74
+ "fetch",
75
+ "list",
76
+ "search",
77
+ "find",
78
+ "query",
79
+ "read",
80
+ "show",
81
+ "view",
82
+ "check",
83
+ "look up",
84
+ "returns",
85
+ "display",
86
+ "browse",
87
+ "inspect",
88
+ "describe",
89
+ "status",
90
+ "info",
91
+ "details"
92
+ ];
93
+ var MUTATE_DESCRIPTION_SIGNALS = [
94
+ "create",
95
+ "update",
96
+ "delete",
97
+ "remove",
98
+ "modify",
99
+ "write",
100
+ "set",
101
+ "send",
102
+ "post",
103
+ "put",
104
+ "patch",
105
+ "add",
106
+ "insert",
107
+ "push",
108
+ "publish",
109
+ "execute",
110
+ "run",
111
+ "trigger",
112
+ "invoke",
113
+ "deploy",
114
+ "move",
115
+ "rename",
116
+ "edit",
117
+ "change",
118
+ "assign",
119
+ "transfer",
120
+ "submit",
121
+ "approve",
122
+ "reject",
123
+ "archive",
124
+ "trash",
125
+ "clear",
126
+ "revoke",
127
+ "cancel",
128
+ "forward",
129
+ "reply",
130
+ "share",
131
+ "invite",
132
+ "notify",
133
+ "message",
134
+ "email",
135
+ "broadcast"
136
+ ];
137
+ function classifyFromDescription(description) {
138
+ const lower = description.toLowerCase();
139
+ let readScore = 0;
140
+ let mutateScore = 0;
141
+ for (const signal of READ_DESCRIPTION_SIGNALS) {
142
+ if (lower.includes(signal)) readScore++;
143
+ }
144
+ for (const signal of MUTATE_DESCRIPTION_SIGNALS) {
145
+ if (lower.includes(signal)) mutateScore++;
146
+ }
147
+ if (readScore > 0 && mutateScore === 0) return { intent: "read", confidence: 0.8 };
148
+ if (mutateScore > 0 && readScore === 0) return { intent: "write", confidence: 0.8 };
149
+ if (mutateScore > readScore) return { intent: "write", confidence: 0.6 };
150
+ if (readScore > mutateScore) return { intent: "read", confidence: 0.6 };
151
+ return { intent: "unknown", confidence: 0.3 };
152
+ }
153
+ function classifyFromHTTPHints(params) {
154
+ const method = params.method?.toUpperCase?.();
155
+ if (!method) return null;
156
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS") return "read";
157
+ if (method === "POST") return "create";
158
+ if (method === "PUT" || method === "PATCH") return "update";
159
+ if (method === "DELETE") return "delete";
160
+ return null;
161
+ }
162
+ function classifyTool(toolName, params, mcpToolDef) {
163
+ const httpIntent = classifyFromHTTPHints(params);
164
+ if (httpIntent) {
165
+ return {
166
+ intent: httpIntent,
167
+ isPassthrough: httpIntent === "read",
168
+ confidence: 0.85,
169
+ reason: `HTTP method in params indicates ${httpIntent}`
170
+ };
171
+ }
172
+ for (const pat of READ_PATTERNS) {
173
+ if (pat.test(toolName)) {
174
+ return { intent: "read", isPassthrough: true, confidence: 0.9, reason: `Tool name matches read pattern: ${pat}` };
175
+ }
176
+ }
177
+ for (const pat of DELETE_PATTERNS) {
178
+ if (pat.test(toolName)) {
179
+ return { intent: "delete", isPassthrough: false, confidence: 0.9, reason: `Tool name matches delete pattern: ${pat}` };
180
+ }
181
+ }
182
+ for (const pat of SEND_PATTERNS) {
183
+ if (pat.test(toolName)) {
184
+ return { intent: "send", isPassthrough: false, confidence: 0.9, reason: `Tool name matches send pattern: ${pat}` };
185
+ }
186
+ }
187
+ for (const pat of WRITE_PATTERNS) {
188
+ if (pat.test(toolName)) {
189
+ return { intent: "write", isPassthrough: false, confidence: 0.9, reason: `Tool name matches write pattern: ${pat}` };
190
+ }
191
+ }
192
+ for (const pat of UPDATE_PATTERNS) {
193
+ if (pat.test(toolName)) {
194
+ return { intent: "update", isPassthrough: false, confidence: 0.9, reason: `Tool name matches update pattern: ${pat}` };
195
+ }
196
+ }
197
+ for (const pat of EXECUTE_PATTERNS) {
198
+ if (pat.test(toolName)) {
199
+ return { intent: "execute", isPassthrough: false, confidence: 0.85, reason: `Tool name matches execute pattern: ${pat}` };
200
+ }
201
+ }
202
+ if (mcpToolDef?.description) {
203
+ const descResult = classifyFromDescription(mcpToolDef.description);
204
+ return {
205
+ intent: descResult.intent,
206
+ isPassthrough: descResult.intent === "read",
207
+ confidence: descResult.confidence,
208
+ reason: `MCP tool description analysis: "${mcpToolDef.description.slice(0, 80)}..."`
209
+ };
210
+ }
211
+ return {
212
+ intent: "unknown",
213
+ isPassthrough: false,
214
+ confidence: 0.2,
215
+ reason: "Could not classify tool \u2014 intercepting as precaution"
216
+ };
217
+ }
218
+
219
+ // src/core/persistence.ts
220
+ var SupabasePersistence = class {
221
+ url;
222
+ key;
223
+ constructor(url, key) {
224
+ this.url = url.replace(/\/$/, "");
225
+ this.key = key;
226
+ }
227
+ async rpc(table, method, body, match) {
228
+ let endpoint = `${this.url}/rest/v1/${table}`;
229
+ const headers = {
230
+ apikey: this.key,
231
+ Authorization: `Bearer ${this.key}`,
232
+ "Content-Type": "application/json",
233
+ Prefer: method === "POST" ? "return=representation" : "return=representation"
234
+ };
235
+ if (match) {
236
+ const qs = Object.entries(match).map(([k, v]) => `${k}=eq.${v}`).join("&");
237
+ endpoint += `?${qs}`;
238
+ }
239
+ const res = await fetch(endpoint, { method, headers, body: JSON.stringify(body) });
240
+ if (!res.ok) {
241
+ const text = await res.text();
242
+ throw new Error(`Supabase ${method} ${table} failed: ${res.status} ${text}`);
243
+ }
244
+ return res.json();
245
+ }
246
+ async saveSession(session) {
247
+ await this.rpc("toolgate_sessions", "POST", {
248
+ id: session.id,
249
+ agent_name: session.agentName,
250
+ task_description: session.taskDescription,
251
+ status: session.status,
252
+ started_at: new Date(session.startedAt).toISOString(),
253
+ completed_at: session.completedAt ? new Date(session.completedAt).toISOString() : null,
254
+ reads: JSON.stringify(session.reads),
255
+ pending_actions: JSON.stringify(session.pendingActions),
256
+ metadata: session.metadata ? JSON.stringify(session.metadata) : null
257
+ });
258
+ }
259
+ async updateSession(session) {
260
+ await this.rpc(
261
+ "toolgate_sessions",
262
+ "PATCH",
263
+ {
264
+ status: session.status,
265
+ completed_at: session.completedAt ? new Date(session.completedAt).toISOString() : null,
266
+ pending_actions: JSON.stringify(session.pendingActions)
267
+ },
268
+ { id: session.id }
269
+ );
270
+ }
271
+ };
272
+
273
+ // src/core/toolgate.ts
274
+ var ToolGate = class {
275
+ config;
276
+ executor;
277
+ session;
278
+ mcpToolIndex = /* @__PURE__ */ new Map();
279
+ readSet;
280
+ actionSet;
281
+ persistence = null;
282
+ constructor(executor, config = {}) {
283
+ this.executor = executor;
284
+ this.config = config;
285
+ this.readSet = new Set(config.readTools ?? []);
286
+ this.actionSet = new Set(config.actionTools ?? []);
287
+ for (const server of config.mcpServers ?? []) {
288
+ for (const tool of server.tools ?? []) {
289
+ this.mcpToolIndex.set(tool.name, tool);
290
+ }
291
+ }
292
+ if (config.supabaseUrl && config.supabaseKey) {
293
+ this.persistence = new SupabasePersistence(config.supabaseUrl, config.supabaseKey);
294
+ }
295
+ this.session = {
296
+ id: (0, import_crypto.randomUUID)(),
297
+ agentName: config.agentName ?? "agent",
298
+ taskDescription: "",
299
+ startedAt: Date.now(),
300
+ status: "running",
301
+ reads: [],
302
+ pendingActions: []
303
+ };
304
+ }
305
+ /** Set a human-readable description for this task/session */
306
+ describe(taskDescription) {
307
+ this.session.taskDescription = taskDescription;
308
+ return this;
309
+ }
310
+ /** The proxy function you hand to the agent instead of the real executor */
311
+ get proxy() {
312
+ return this.handle.bind(this);
313
+ }
314
+ /** Wrap an existing tool map (name → function) into a proxied tool map */
315
+ wrapTools(tools) {
316
+ const proxied = {};
317
+ for (const [name, fn] of Object.entries(tools)) {
318
+ proxied[name] = async (...args) => {
319
+ const params = args[0] && typeof args[0] === "object" ? args[0] : { args };
320
+ return this.handle(name, params);
321
+ };
322
+ }
323
+ return proxied;
324
+ }
325
+ /** Core: classify and either passthrough or intercept */
326
+ async handle(toolName, params) {
327
+ const classification = this.classify(toolName, params);
328
+ const call = {
329
+ id: (0, import_crypto.randomUUID)(),
330
+ name: toolName,
331
+ params: structuredClone(params),
332
+ timestamp: Date.now(),
333
+ classification
334
+ };
335
+ if (classification.isPassthrough) {
336
+ const result = await this.executor(toolName, params);
337
+ const read = { ...call, result };
338
+ this.session.reads.push(read);
339
+ return result;
340
+ }
341
+ const phantomResult = this.config.phantomResponse ? this.config.phantomResponse(call) : this.defaultPhantom(call);
342
+ const pending = { ...call, phantomResult };
343
+ this.session.pendingActions.push(pending);
344
+ return phantomResult;
345
+ }
346
+ classify(toolName, params) {
347
+ if (this.readSet.has(toolName)) {
348
+ return { intent: "read", isPassthrough: true, confidence: 1, reason: "Explicitly listed as read tool" };
349
+ }
350
+ if (this.actionSet.has(toolName)) {
351
+ return { intent: "write", isPassthrough: false, confidence: 1, reason: "Explicitly listed as action tool" };
352
+ }
353
+ if (this.config.classifier) {
354
+ return this.config.classifier(toolName, params);
355
+ }
356
+ const mcpDef = this.mcpToolIndex.get(toolName);
357
+ return classifyTool(toolName, params, mcpDef);
358
+ }
359
+ defaultPhantom(call) {
360
+ const { intent } = call.classification;
361
+ const base = { success: true, _phantom: true, toolCallId: call.id };
362
+ switch (intent) {
363
+ case "create":
364
+ case "write":
365
+ return { ...base, id: `phantom_${(0, import_crypto.randomUUID)().slice(0, 8)}`, message: "Created successfully" };
366
+ case "update":
367
+ return { ...base, message: "Updated successfully" };
368
+ case "delete":
369
+ return { ...base, message: "Deleted successfully" };
370
+ case "send":
371
+ return { ...base, message: "Sent successfully", messageId: `msg_${(0, import_crypto.randomUUID)().slice(0, 8)}` };
372
+ case "execute":
373
+ return { ...base, message: "Executed successfully", exitCode: 0 };
374
+ default:
375
+ return { ...base, message: "Completed successfully" };
376
+ }
377
+ }
378
+ // ─── Finalization ───────────────────────────────────────────────────
379
+ /** Call when agent is done. Returns the approval request. */
380
+ async finalize() {
381
+ this.session.completedAt = Date.now();
382
+ this.session.status = "pending_approval";
383
+ const request = {
384
+ sessionId: this.session.id,
385
+ agentName: this.session.agentName,
386
+ taskDescription: this.session.taskDescription,
387
+ reads: this.session.reads,
388
+ pendingActions: this.session.pendingActions,
389
+ createdAt: Date.now()
390
+ };
391
+ if (this.persistence) {
392
+ await this.persistence.saveSession(this.session);
393
+ }
394
+ if (this.config.onApprovalNeeded) {
395
+ const result = await this.config.onApprovalNeeded(request);
396
+ await this.executeApproval(result);
397
+ }
398
+ return request;
399
+ }
400
+ /** Execute approved actions for real */
401
+ async executeApproval(result) {
402
+ const results = /* @__PURE__ */ new Map();
403
+ for (const action of this.session.pendingActions) {
404
+ const decision = result.decisions.get(action.id);
405
+ if (!decision || decision.action === "reject") {
406
+ action.approved = false;
407
+ continue;
408
+ }
409
+ if (decision.action === "approve") {
410
+ action.approved = true;
411
+ const realResult = await this.executor(action.name, action.params);
412
+ results.set(action.id, realResult);
413
+ }
414
+ if (decision.action === "edit") {
415
+ action.approved = true;
416
+ action.editedParams = decision.newParams;
417
+ const realResult = await this.executor(action.name, decision.newParams);
418
+ results.set(action.id, realResult);
419
+ }
420
+ }
421
+ this.session.status = this.session.pendingActions.every((a) => a.approved) ? "approved" : this.session.pendingActions.some((a) => a.approved) ? "partial" : "rejected";
422
+ if (this.persistence) {
423
+ await this.persistence.updateSession(this.session);
424
+ }
425
+ return results;
426
+ }
427
+ /** Approve all pending actions at once */
428
+ async approveAll() {
429
+ const decisions = new Map(
430
+ this.session.pendingActions.map((a) => [a.id, { action: "approve" }])
431
+ );
432
+ return this.executeApproval({ sessionId: this.session.id, decisions, approvedAt: Date.now() });
433
+ }
434
+ /** Reject all pending actions */
435
+ async rejectAll() {
436
+ for (const action of this.session.pendingActions) {
437
+ action.approved = false;
438
+ }
439
+ this.session.status = "rejected";
440
+ if (this.persistence) {
441
+ await this.persistence.updateSession(this.session);
442
+ }
443
+ }
444
+ // ─── Inspection ─────────────────────────────────────────────────────
445
+ get sessionId() {
446
+ return this.session.id;
447
+ }
448
+ get reads() {
449
+ return this.session.reads;
450
+ }
451
+ get pending() {
452
+ return this.session.pendingActions;
453
+ }
454
+ get currentSession() {
455
+ return structuredClone(this.session);
456
+ }
457
+ /** Pretty-print a summary for CLI / logging */
458
+ summary() {
459
+ const lines = [
460
+ `
461
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`,
462
+ `\u2551 ToolGate Session: ${this.session.id.slice(0, 8)}\u2026`,
463
+ `\u2551 Agent: ${this.session.agentName}`,
464
+ `\u2551 Task: ${this.session.taskDescription || "(none)"}`,
465
+ `\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
466
+ `\u2551 \u2705 Reads executed: ${this.session.reads.length}`
467
+ ];
468
+ for (const r of this.session.reads) {
469
+ lines.push(`\u2551 \u2022 ${r.name}(${JSON.stringify(r.params).slice(0, 60)}\u2026)`);
470
+ }
471
+ lines.push(`\u2551 \u23F8 Actions pending approval: ${this.session.pendingActions.length}`);
472
+ for (const a of this.session.pendingActions) {
473
+ const badge2 = a.classification.intent.toUpperCase();
474
+ lines.push(`\u2551 \u{1F536} [${badge2}] ${a.name}`);
475
+ lines.push(`\u2551 params: ${JSON.stringify(a.params).slice(0, 70)}\u2026`);
476
+ }
477
+ lines.push(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
478
+ `);
479
+ return lines.join("\n");
480
+ }
481
+ };
482
+
483
+ // src/core/cli-approval.ts
484
+ var readline = __toESM(require("readline"));
485
+ var INTENT_COLORS = {
486
+ write: "\x1B[33m",
487
+ // yellow
488
+ create: "\x1B[32m",
489
+ // green
490
+ delete: "\x1B[31m",
491
+ // red
492
+ update: "\x1B[36m",
493
+ // cyan
494
+ send: "\x1B[35m",
495
+ // magenta
496
+ execute: "\x1B[34m",
497
+ // blue
498
+ unknown: "\x1B[37m"
499
+ // white
500
+ };
501
+ var RESET = "\x1B[0m";
502
+ var BOLD = "\x1B[1m";
503
+ var DIM = "\x1B[2m";
504
+ function badge(intent) {
505
+ const color = INTENT_COLORS[intent] ?? INTENT_COLORS.unknown;
506
+ return `${color}${BOLD}[${intent.toUpperCase()}]${RESET}`;
507
+ }
508
+ function ask(rl, question) {
509
+ return new Promise((resolve) => rl.question(question, resolve));
510
+ }
511
+ async function cliApproval(request) {
512
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
513
+ console.log(`
514
+ ${BOLD}\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550${RESET}`);
515
+ console.log(`${BOLD} \u{1F6E1}\uFE0F ToolGate \u2014 Approval Required${RESET}`);
516
+ console.log(`${BOLD}\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550${RESET}`);
517
+ console.log(` Agent: ${request.agentName}`);
518
+ console.log(` Task: ${request.taskDescription || "(no description)"}`);
519
+ console.log(` Session: ${request.sessionId.slice(0, 8)}\u2026`);
520
+ console.log();
521
+ console.log(`${DIM}\u2500\u2500 Reads performed (${request.reads.length}) \u2500\u2500${RESET}`);
522
+ for (const r of request.reads) {
523
+ console.log(` \u2705 ${r.name}(${JSON.stringify(r.params).slice(0, 80)})`);
524
+ }
525
+ console.log();
526
+ console.log(`${BOLD}\u2500\u2500 Actions awaiting approval (${request.pendingActions.length}) \u2500\u2500${RESET}`);
527
+ for (let i = 0; i < request.pendingActions.length; i++) {
528
+ const a = request.pendingActions[i];
529
+ console.log(` ${i + 1}. ${badge(a.classification.intent)} ${a.name}`);
530
+ console.log(` ${DIM}params:${RESET} ${JSON.stringify(a.params, null, 2).split("\n").join("\n ")}`);
531
+ console.log();
532
+ }
533
+ console.log(`${BOLD}Options:${RESET}`);
534
+ console.log(` ${BOLD}a${RESET} = approve all ${BOLD}r${RESET} = reject all ${BOLD}i${RESET} = review individually`);
535
+ const choice = (await ask(rl, "\n> ")).trim().toLowerCase();
536
+ const decisions = /* @__PURE__ */ new Map();
537
+ if (choice === "a") {
538
+ for (const a of request.pendingActions) {
539
+ decisions.set(a.id, { action: "approve" });
540
+ }
541
+ console.log("\n\u2705 All actions approved.");
542
+ } else if (choice === "r") {
543
+ for (const a of request.pendingActions) {
544
+ decisions.set(a.id, { action: "reject" });
545
+ }
546
+ console.log("\n\u274C All actions rejected.");
547
+ } else {
548
+ for (let i = 0; i < request.pendingActions.length; i++) {
549
+ const a = request.pendingActions[i];
550
+ console.log(`
551
+ ${badge(a.classification.intent)} ${a.name}`);
552
+ console.log(` params: ${JSON.stringify(a.params, null, 2)}`);
553
+ const d = (await ask(rl, ` [a]pprove / [r]eject / [e]dit > `)).trim().toLowerCase();
554
+ if (d === "a" || d === "approve") {
555
+ decisions.set(a.id, { action: "approve" });
556
+ } else if (d === "e" || d === "edit") {
557
+ console.log(` Enter new params as JSON:`);
558
+ const raw = await ask(rl, " > ");
559
+ try {
560
+ const newParams = JSON.parse(raw);
561
+ decisions.set(a.id, { action: "edit", actionId: a.id, newParams });
562
+ } catch {
563
+ console.log(" \u26A0\uFE0F Invalid JSON \u2014 rejecting this action.");
564
+ decisions.set(a.id, { action: "reject" });
565
+ }
566
+ } else {
567
+ decisions.set(a.id, { action: "reject" });
568
+ }
569
+ }
570
+ }
571
+ rl.close();
572
+ return { sessionId: request.sessionId, decisions, approvedAt: Date.now() };
573
+ }
574
+
575
+ // src/mcp/index.ts
576
+ function mcpToolGate(executor, config = {}) {
577
+ return new ToolGate(executor, config);
578
+ }
579
+ async function discoverMCPTools(serverUrl) {
580
+ const res = await fetch(serverUrl, {
581
+ method: "POST",
582
+ headers: { "Content-Type": "application/json" },
583
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} })
584
+ });
585
+ if (!res.ok) throw new Error(`MCP discovery failed: ${res.status}`);
586
+ const data = await res.json();
587
+ const tools = (data.result?.tools ?? []).map((t) => ({
588
+ name: t.name,
589
+ description: t.description ?? "",
590
+ inputSchema: t.inputSchema
591
+ }));
592
+ return tools;
593
+ }
594
+ async function autoConfigFromMCP(servers, overrides = {}) {
595
+ const mcpServers = await Promise.all(
596
+ servers.map(async (s) => {
597
+ try {
598
+ const tools = await discoverMCPTools(s.url);
599
+ return { ...s, tools };
600
+ } catch {
601
+ console.warn(`[toolgate] Could not discover tools from ${s.name} (${s.url})`);
602
+ return { ...s, tools: [] };
603
+ }
604
+ })
605
+ );
606
+ return { ...overrides, mcpServers };
607
+ }
608
+ // Annotate the CommonJS export names for ESM import in node:
609
+ 0 && (module.exports = {
610
+ ToolGate,
611
+ autoConfigFromMCP,
612
+ classifyTool,
613
+ cliApproval,
614
+ discoverMCPTools,
615
+ mcpToolGate
616
+ });