@frontmcp/plugin-approval 0.0.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/approval/errors.d.ts +149 -0
  2. package/approval/errors.d.ts.map +1 -0
  3. package/approval/factories.d.ts +251 -0
  4. package/approval/factories.d.ts.map +1 -0
  5. package/approval/guards.d.ts +61 -0
  6. package/approval/guards.d.ts.map +1 -0
  7. package/approval/index.d.ts +43 -0
  8. package/approval/index.d.ts.map +1 -0
  9. package/approval/schemas.d.ts +179 -0
  10. package/approval/schemas.d.ts.map +1 -0
  11. package/approval/types.d.ts +252 -0
  12. package/approval/types.d.ts.map +1 -0
  13. package/approval.context-extension.d.ts +21 -0
  14. package/approval.context-extension.d.ts.map +1 -0
  15. package/approval.plugin.d.ts +128 -0
  16. package/approval.plugin.d.ts.map +1 -0
  17. package/approval.symbols.d.ts +22 -0
  18. package/approval.symbols.d.ts.map +1 -0
  19. package/esm/index.mjs +1228 -0
  20. package/esm/package.json +66 -0
  21. package/flows/index.d.ts +9 -0
  22. package/flows/index.d.ts.map +1 -0
  23. package/hooks/approval-check.hook.d.ts +25 -0
  24. package/hooks/approval-check.hook.d.ts.map +1 -0
  25. package/hooks/index.d.ts +7 -0
  26. package/hooks/index.d.ts.map +1 -0
  27. package/index.d.ts +44 -0
  28. package/index.d.ts.map +1 -0
  29. package/index.js +1279 -0
  30. package/package.json +3 -3
  31. package/services/approval.service.d.ts +85 -0
  32. package/services/approval.service.d.ts.map +1 -0
  33. package/services/challenge.service.d.ts +115 -0
  34. package/services/challenge.service.d.ts.map +1 -0
  35. package/services/index.d.ts +8 -0
  36. package/services/index.d.ts.map +1 -0
  37. package/stores/approval-storage.store.d.ts +71 -0
  38. package/stores/approval-storage.store.d.ts.map +1 -0
  39. package/stores/approval-store.interface.d.ts +121 -0
  40. package/stores/approval-store.interface.d.ts.map +1 -0
  41. package/stores/index.d.ts +8 -0
  42. package/stores/index.d.ts.map +1 -0
  43. package/types/approval.types.d.ts +98 -0
  44. package/types/approval.types.d.ts.map +1 -0
  45. package/types/index.d.ts +7 -0
  46. package/types/index.d.ts.map +1 -0
package/index.js ADDED
@@ -0,0 +1,1279 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __decorateClass = (decorators, target, key, kind) => {
21
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
22
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
23
+ if (decorator = decorators[i])
24
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
25
+ if (kind && result) __defProp(target, key, result);
26
+ return result;
27
+ };
28
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
29
+
30
+ // plugins/plugin-approval/src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApprovalCheckPlugin: () => ApprovalCheckPlugin,
34
+ ApprovalError: () => ApprovalError,
35
+ ApprovalExpiredError: () => ApprovalExpiredError,
36
+ ApprovalOperationError: () => ApprovalOperationError,
37
+ ApprovalPlugin: () => ApprovalPlugin,
38
+ ApprovalPluginClass: () => ApprovalPlugin,
39
+ ApprovalRequiredError: () => ApprovalRequiredError,
40
+ ApprovalScope: () => ApprovalScope,
41
+ ApprovalScopeNotAllowedError: () => ApprovalScopeNotAllowedError,
42
+ ApprovalService: () => ApprovalService,
43
+ ApprovalServiceToken: () => ApprovalServiceToken,
44
+ ApprovalState: () => ApprovalState,
45
+ ApprovalStorageStore: () => ApprovalStorageStore,
46
+ ApprovalStoreToken: () => ApprovalStoreToken,
47
+ ChallengeService: () => ChallengeService,
48
+ ChallengeServiceToken: () => ChallengeServiceToken,
49
+ ChallengeValidationError: () => ChallengeValidationError,
50
+ adminGrantor: () => adminGrantor,
51
+ adminRevoker: () => adminRevoker,
52
+ agentGrantor: () => agentGrantor,
53
+ apiGrantor: () => apiGrantor,
54
+ createApprovalMemoryStore: () => createApprovalMemoryStore,
55
+ createApprovalService: () => createApprovalService,
56
+ createMemoryChallengeService: () => createMemoryChallengeService,
57
+ customGrantor: () => customGrantor,
58
+ expiryRevoker: () => expiryRevoker,
59
+ installApprovalContextExtension: () => installApprovalContextExtension,
60
+ isApiGrantor: () => isApiGrantor,
61
+ isAutoGrantor: () => isAutoGrantor,
62
+ isDelegatedGrantor: () => isDelegatedGrantor,
63
+ isGrantorSource: () => isGrantorSource,
64
+ isHumanGrantor: () => isHumanGrantor,
65
+ normalizeGrantor: () => normalizeGrantor,
66
+ normalizeRevoker: () => normalizeRevoker,
67
+ oauthGrantor: () => oauthGrantor,
68
+ policyGrantor: () => policyGrantor,
69
+ policyRevoker: () => policyRevoker,
70
+ sessionEndRevoker: () => sessionEndRevoker,
71
+ systemGrantor: () => systemGrantor,
72
+ testGrantor: () => testGrantor,
73
+ userGrantor: () => userGrantor,
74
+ userRevoker: () => userRevoker
75
+ });
76
+ module.exports = __toCommonJS(index_exports);
77
+
78
+ // plugins/plugin-approval/src/approval.plugin.ts
79
+ var import_sdk5 = require("@frontmcp/sdk");
80
+
81
+ // plugins/plugin-approval/src/stores/approval-storage.store.ts
82
+ var import_sdk = require("@frontmcp/sdk");
83
+ var import_utils = require("@frontmcp/utils");
84
+
85
+ // plugins/plugin-approval/src/approval/types.ts
86
+ var ApprovalScope = /* @__PURE__ */ ((ApprovalScope2) => {
87
+ ApprovalScope2["SESSION"] = "session";
88
+ ApprovalScope2["USER"] = "user";
89
+ ApprovalScope2["TIME_LIMITED"] = "time_limited";
90
+ ApprovalScope2["TOOL_SPECIFIC"] = "tool_specific";
91
+ ApprovalScope2["CONTEXT_SPECIFIC"] = "context_specific";
92
+ return ApprovalScope2;
93
+ })(ApprovalScope || {});
94
+ var ApprovalState = /* @__PURE__ */ ((ApprovalState2) => {
95
+ ApprovalState2["PENDING"] = "pending";
96
+ ApprovalState2["APPROVED"] = "approved";
97
+ ApprovalState2["DENIED"] = "denied";
98
+ ApprovalState2["EXPIRED"] = "expired";
99
+ return ApprovalState2;
100
+ })(ApprovalState || {});
101
+
102
+ // plugins/plugin-approval/src/approval/schemas.ts
103
+ var import_zod = require("zod");
104
+ var approvalScopeSchema = import_zod.z.nativeEnum(ApprovalScope);
105
+ var approvalStateSchema = import_zod.z.nativeEnum(ApprovalState);
106
+ var approvalMethodSchema = import_zod.z.enum(["interactive", "implicit", "delegation", "batch", "api"]);
107
+ var approvalSourceTypeSchema = import_zod.z.string().min(1);
108
+ var revocationMethodSchema = import_zod.z.enum(["interactive", "implicit", "policy", "expiry"]);
109
+ var approvalCategorySchema = import_zod.z.enum(["read", "write", "delete", "execute", "admin"]);
110
+ var riskLevelSchema = import_zod.z.enum(["low", "medium", "high", "critical"]);
111
+ var approvalContextSchema = import_zod.z.object({
112
+ type: import_zod.z.string().min(1),
113
+ identifier: import_zod.z.string().min(1),
114
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
115
+ });
116
+ var delegationContextSchema = import_zod.z.object({
117
+ delegatorId: import_zod.z.string().min(1),
118
+ delegateId: import_zod.z.string().min(1),
119
+ purpose: import_zod.z.string().optional(),
120
+ constraints: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
121
+ });
122
+ var approvalGrantorSchema = import_zod.z.object({
123
+ source: approvalSourceTypeSchema,
124
+ identifier: import_zod.z.string().optional(),
125
+ displayName: import_zod.z.string().optional(),
126
+ method: approvalMethodSchema.optional(),
127
+ origin: import_zod.z.string().optional(),
128
+ delegationContext: delegationContextSchema.optional()
129
+ });
130
+ var approvalRevokerSchema = import_zod.z.object({
131
+ source: import_zod.z.string().min(1),
132
+ identifier: import_zod.z.string().optional(),
133
+ displayName: import_zod.z.string().optional(),
134
+ method: revocationMethodSchema.optional()
135
+ });
136
+ var approvalRecordSchema = import_zod.z.object({
137
+ toolId: import_zod.z.string().min(1),
138
+ state: approvalStateSchema,
139
+ scope: approvalScopeSchema,
140
+ grantedAt: import_zod.z.number(),
141
+ expiresAt: import_zod.z.number().optional(),
142
+ ttlMs: import_zod.z.number().optional(),
143
+ sessionId: import_zod.z.string().optional(),
144
+ userId: import_zod.z.string().optional(),
145
+ context: approvalContextSchema.optional(),
146
+ grantedBy: approvalGrantorSchema,
147
+ approvalChain: import_zod.z.array(approvalGrantorSchema).optional(),
148
+ reason: import_zod.z.string().optional(),
149
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
150
+ revokedAt: import_zod.z.number().optional(),
151
+ revokedBy: approvalRevokerSchema.optional(),
152
+ revocationReason: import_zod.z.string().optional()
153
+ });
154
+ var toolApprovalRequirementSchema = import_zod.z.object({
155
+ required: import_zod.z.boolean().optional(),
156
+ defaultScope: approvalScopeSchema.optional(),
157
+ allowedScopes: import_zod.z.array(approvalScopeSchema).optional(),
158
+ maxTtlMs: import_zod.z.number().positive().optional(),
159
+ alwaysPrompt: import_zod.z.boolean().optional(),
160
+ skipApproval: import_zod.z.boolean().optional(),
161
+ approvalMessage: import_zod.z.string().optional(),
162
+ category: approvalCategorySchema.optional(),
163
+ riskLevel: riskLevelSchema.optional(),
164
+ preApprovedContexts: import_zod.z.array(approvalContextSchema).optional()
165
+ });
166
+
167
+ // plugins/plugin-approval/src/approval/factories.ts
168
+ function userGrantor(userId, displayName, options) {
169
+ return {
170
+ source: "user",
171
+ identifier: userId,
172
+ displayName,
173
+ method: options?.method ?? "interactive",
174
+ origin: options?.origin
175
+ };
176
+ }
177
+ function policyGrantor(policyId, policyName) {
178
+ return {
179
+ source: "policy",
180
+ identifier: policyId,
181
+ displayName: policyName,
182
+ method: "implicit"
183
+ };
184
+ }
185
+ function adminGrantor(adminId, displayName, options) {
186
+ return {
187
+ source: "admin",
188
+ identifier: adminId,
189
+ displayName,
190
+ method: options?.method ?? "interactive",
191
+ origin: options?.origin
192
+ };
193
+ }
194
+ function systemGrantor(systemId = "system") {
195
+ return {
196
+ source: "system",
197
+ identifier: systemId,
198
+ method: "implicit"
199
+ };
200
+ }
201
+ function agentGrantor(agentId, delegationContext, displayName) {
202
+ return {
203
+ source: "agent",
204
+ identifier: agentId,
205
+ displayName,
206
+ method: "delegation",
207
+ delegationContext
208
+ };
209
+ }
210
+ function apiGrantor(apiKeyPrefix, serviceName) {
211
+ return {
212
+ source: "api",
213
+ identifier: apiKeyPrefix,
214
+ displayName: serviceName,
215
+ method: "api",
216
+ origin: "api"
217
+ };
218
+ }
219
+ function oauthGrantor(tokenId, provider) {
220
+ return {
221
+ source: "oauth",
222
+ identifier: tokenId,
223
+ displayName: provider,
224
+ method: "api",
225
+ origin: "oauth"
226
+ };
227
+ }
228
+ function testGrantor() {
229
+ return {
230
+ source: "test",
231
+ identifier: "test",
232
+ method: "implicit"
233
+ };
234
+ }
235
+ function customGrantor(source, identifier, options) {
236
+ return {
237
+ source,
238
+ identifier,
239
+ displayName: options?.displayName,
240
+ method: options?.method,
241
+ origin: options?.origin,
242
+ delegationContext: options?.delegationContext
243
+ };
244
+ }
245
+ function userRevoker(userId, displayName) {
246
+ return {
247
+ source: "user",
248
+ identifier: userId,
249
+ displayName,
250
+ method: "interactive"
251
+ };
252
+ }
253
+ function adminRevoker(adminId, displayName) {
254
+ return {
255
+ source: "admin",
256
+ identifier: adminId,
257
+ displayName,
258
+ method: "interactive"
259
+ };
260
+ }
261
+ function expiryRevoker() {
262
+ return {
263
+ source: "expiry",
264
+ method: "expiry"
265
+ };
266
+ }
267
+ function sessionEndRevoker(sessionId) {
268
+ return {
269
+ source: "session_end",
270
+ identifier: sessionId,
271
+ method: "implicit"
272
+ };
273
+ }
274
+ function policyRevoker(policyId, policyName) {
275
+ return {
276
+ source: "policy",
277
+ identifier: policyId,
278
+ displayName: policyName,
279
+ method: "policy"
280
+ };
281
+ }
282
+ function normalizeGrantor(input) {
283
+ if (!input) {
284
+ return { source: "user" };
285
+ }
286
+ if (typeof input === "string") {
287
+ return { source: input };
288
+ }
289
+ return input;
290
+ }
291
+ function normalizeRevoker(input) {
292
+ if (!input) {
293
+ return { source: "user" };
294
+ }
295
+ if (typeof input === "string") {
296
+ return { source: input };
297
+ }
298
+ return input;
299
+ }
300
+
301
+ // plugins/plugin-approval/src/approval/guards.ts
302
+ function isGrantorSource(grantor, source) {
303
+ return grantor.source === source;
304
+ }
305
+ function isHumanGrantor(grantor) {
306
+ return grantor.source === "user" || grantor.source === "admin";
307
+ }
308
+ function isAutoGrantor(grantor) {
309
+ return grantor.source === "policy" || grantor.source === "system" || grantor.source === "test";
310
+ }
311
+ function isDelegatedGrantor(grantor) {
312
+ return grantor.source === "agent" && !!grantor.delegationContext;
313
+ }
314
+ function isApiGrantor(grantor) {
315
+ return grantor.source === "api" || grantor.source === "oauth";
316
+ }
317
+
318
+ // plugins/plugin-approval/src/approval/errors.ts
319
+ var ApprovalError = class extends Error {
320
+ constructor(message) {
321
+ super(message);
322
+ this.name = "ApprovalError";
323
+ }
324
+ };
325
+ var ApprovalRequiredError = class extends ApprovalError {
326
+ constructor(details) {
327
+ super(details.message);
328
+ this.details = details;
329
+ this.name = "ApprovalRequiredError";
330
+ }
331
+ /**
332
+ * Convert to a JSON-RPC compatible error structure.
333
+ */
334
+ toJsonRpcError() {
335
+ return {
336
+ code: -32600,
337
+ // Invalid Request
338
+ message: this.details.message,
339
+ data: {
340
+ type: "approval_required",
341
+ toolId: this.details.toolId,
342
+ state: this.details.state,
343
+ options: this.details.approvalOptions
344
+ }
345
+ };
346
+ }
347
+ };
348
+ var ApprovalOperationError = class extends ApprovalError {
349
+ constructor(operation, reason) {
350
+ super(`Approval ${operation} failed: ${reason}`);
351
+ this.operation = operation;
352
+ this.reason = reason;
353
+ this.name = "ApprovalOperationError";
354
+ }
355
+ /**
356
+ * Convert to a JSON-RPC compatible error structure.
357
+ */
358
+ toJsonRpcError() {
359
+ return {
360
+ code: -32603,
361
+ // Internal Error
362
+ message: "Approval operation failed",
363
+ data: {
364
+ type: "approval_operation_error",
365
+ operation: this.operation
366
+ }
367
+ };
368
+ }
369
+ };
370
+ var ApprovalScopeNotAllowedError = class extends ApprovalError {
371
+ constructor(requestedScope, allowedScopes) {
372
+ super(
373
+ `Approval scope '${requestedScope}' is not allowed for this tool. Allowed scopes: ${allowedScopes.join(", ")}`
374
+ );
375
+ this.requestedScope = requestedScope;
376
+ this.allowedScopes = allowedScopes;
377
+ this.name = "ApprovalScopeNotAllowedError";
378
+ }
379
+ /**
380
+ * Convert to a JSON-RPC compatible error structure.
381
+ */
382
+ toJsonRpcError() {
383
+ return {
384
+ code: -32602,
385
+ // Invalid Params
386
+ message: this.message,
387
+ data: {
388
+ type: "approval_scope_not_allowed",
389
+ requestedScope: this.requestedScope,
390
+ allowedScopes: this.allowedScopes
391
+ }
392
+ };
393
+ }
394
+ };
395
+ var ApprovalExpiredError = class extends ApprovalError {
396
+ constructor(toolId, expiredAt) {
397
+ super(`Approval for tool '${toolId}' expired at ${new Date(expiredAt).toISOString()}`);
398
+ this.toolId = toolId;
399
+ this.expiredAt = expiredAt;
400
+ this.name = "ApprovalExpiredError";
401
+ }
402
+ /**
403
+ * Convert to a JSON-RPC compatible error structure.
404
+ */
405
+ toJsonRpcError() {
406
+ return {
407
+ code: -32600,
408
+ // Invalid Request
409
+ message: this.message,
410
+ data: {
411
+ type: "approval_expired",
412
+ toolId: this.toolId,
413
+ expiredAt: this.expiredAt
414
+ }
415
+ };
416
+ }
417
+ };
418
+ var ChallengeValidationError = class extends ApprovalError {
419
+ constructor(reason = "invalid", message) {
420
+ super(message ?? `PKCE challenge validation failed: ${reason}`);
421
+ this.reason = reason;
422
+ this.name = "ChallengeValidationError";
423
+ }
424
+ /**
425
+ * Convert to a JSON-RPC compatible error structure.
426
+ */
427
+ toJsonRpcError() {
428
+ return {
429
+ code: -32600,
430
+ // Invalid Request
431
+ message: this.message,
432
+ data: {
433
+ type: "challenge_validation_error",
434
+ reason: this.reason
435
+ }
436
+ };
437
+ }
438
+ };
439
+
440
+ // plugins/plugin-approval/src/stores/approval-storage.store.ts
441
+ function escapePattern(str) {
442
+ return str.replace(/[*?[\]\\]/g, "\\$&");
443
+ }
444
+ var ApprovalStorageStore = class {
445
+ storage;
446
+ options;
447
+ cleanupInterval;
448
+ initialized = false;
449
+ ownedStorage = false;
450
+ constructor(options = {}) {
451
+ this.options = {
452
+ storage: options.storage ?? { type: "auto" },
453
+ storageInstance: options.storageInstance,
454
+ namespace: options.namespace ?? "approval",
455
+ cleanupIntervalSeconds: options.cleanupIntervalSeconds ?? 60
456
+ };
457
+ }
458
+ /**
459
+ * Initialize the storage connection.
460
+ */
461
+ async initialize() {
462
+ if (this.initialized) {
463
+ return;
464
+ }
465
+ if (this.options.storageInstance) {
466
+ this.storage = this.options.storageInstance.namespace(this.options.namespace);
467
+ this.ownedStorage = false;
468
+ } else {
469
+ const rootStorage = await (0, import_utils.createStorage)(this.options.storage);
470
+ this.storage = rootStorage.namespace(this.options.namespace);
471
+ this.ownedStorage = true;
472
+ }
473
+ if (this.options.cleanupIntervalSeconds > 0) {
474
+ this.cleanupInterval = setInterval(() => {
475
+ void this.clearExpiredApprovals();
476
+ }, this.options.cleanupIntervalSeconds * 1e3);
477
+ this.cleanupInterval.unref?.();
478
+ }
479
+ this.initialized = true;
480
+ }
481
+ ensureInitialized() {
482
+ if (!this.initialized) {
483
+ throw new Error("ApprovalStorageStore not initialized. Call initialize() first.");
484
+ }
485
+ }
486
+ buildKey(toolId, sessionId, userId, context) {
487
+ const parts = [toolId];
488
+ if (sessionId) parts.push(`session:${sessionId}`);
489
+ if (userId) parts.push(`user:${userId}`);
490
+ if (context) parts.push(`ctx:${context.type}:${context.identifier}`);
491
+ return parts.join(":");
492
+ }
493
+ parseRecord(value) {
494
+ if (!value) return void 0;
495
+ try {
496
+ const parsed = JSON.parse(value);
497
+ const result = approvalRecordSchema.safeParse(parsed);
498
+ if (!result.success) {
499
+ return void 0;
500
+ }
501
+ return result.data;
502
+ } catch {
503
+ return void 0;
504
+ }
505
+ }
506
+ isExpired(approval) {
507
+ return approval.expiresAt !== void 0 && Date.now() > approval.expiresAt;
508
+ }
509
+ async getApproval(toolId, sessionId, userId) {
510
+ this.ensureInitialized();
511
+ const sessionKey = this.buildKey(toolId, sessionId);
512
+ const sessionValue = await this.storage.get(sessionKey);
513
+ const sessionApproval = this.parseRecord(sessionValue);
514
+ if (sessionApproval && !this.isExpired(sessionApproval)) {
515
+ return sessionApproval;
516
+ }
517
+ if (userId) {
518
+ const userKey = this.buildKey(toolId, void 0, userId);
519
+ const userValue = await this.storage.get(userKey);
520
+ const userApproval = this.parseRecord(userValue);
521
+ if (userApproval && !this.isExpired(userApproval)) {
522
+ return userApproval;
523
+ }
524
+ }
525
+ return void 0;
526
+ }
527
+ async queryApprovals(query) {
528
+ this.ensureInitialized();
529
+ const results = [];
530
+ const pattern = query.toolId ? `${query.toolId}:*` : "*";
531
+ const keys = await this.storage.keys(pattern);
532
+ const values = await this.storage.mget(keys);
533
+ for (const value of values) {
534
+ const approval = this.parseRecord(value);
535
+ if (!approval) continue;
536
+ if (!query.includeExpired && this.isExpired(approval)) {
537
+ continue;
538
+ }
539
+ if (query.toolId && approval.toolId !== query.toolId) continue;
540
+ if (query.toolIds && !query.toolIds.includes(approval.toolId)) continue;
541
+ if (query.scope && approval.scope !== query.scope) continue;
542
+ if (query.scopes && !query.scopes.includes(approval.scope)) continue;
543
+ if (query.state && approval.state !== query.state) continue;
544
+ if (query.states && !query.states.includes(approval.state)) continue;
545
+ if (query.sessionId && approval.sessionId !== query.sessionId) continue;
546
+ if (query.userId && approval.userId !== query.userId) continue;
547
+ if (query.context) {
548
+ if (!approval.context || approval.context.type !== query.context.type || approval.context.identifier !== query.context.identifier) {
549
+ continue;
550
+ }
551
+ }
552
+ results.push(approval);
553
+ }
554
+ return results;
555
+ }
556
+ async grantApproval(options) {
557
+ this.ensureInitialized();
558
+ const now = Date.now();
559
+ const expiresAt = options.ttlMs ? now + options.ttlMs : void 0;
560
+ const grantedBy = normalizeGrantor(options.grantedBy);
561
+ const record = {
562
+ toolId: options.toolId,
563
+ state: "approved" /* APPROVED */,
564
+ scope: options.scope,
565
+ grantedAt: now,
566
+ expiresAt,
567
+ ttlMs: options.ttlMs,
568
+ sessionId: options.sessionId,
569
+ userId: options.userId,
570
+ context: options.context,
571
+ grantedBy,
572
+ reason: options.reason,
573
+ metadata: options.metadata
574
+ };
575
+ const key = this.buildKey(options.toolId, options.sessionId, options.userId, options.context);
576
+ const ttlSeconds = options.ttlMs ? Math.ceil(options.ttlMs / 1e3) : void 0;
577
+ await this.storage.set(key, JSON.stringify(record), { ttlSeconds });
578
+ return record;
579
+ }
580
+ async revokeApproval(options) {
581
+ this.ensureInitialized();
582
+ const key = this.buildKey(options.toolId, options.sessionId, options.userId, options.context);
583
+ const exists = await this.storage.exists(key);
584
+ if (exists) {
585
+ await this.storage.delete(key);
586
+ return true;
587
+ }
588
+ return false;
589
+ }
590
+ async isApproved(toolId, sessionId, userId, context) {
591
+ this.ensureInitialized();
592
+ if (context) {
593
+ const contextKey = this.buildKey(toolId, sessionId, userId, context);
594
+ const contextValue = await this.storage.get(contextKey);
595
+ const contextApproval = this.parseRecord(contextValue);
596
+ if (contextApproval && contextApproval.state === "approved" /* APPROVED */ && !this.isExpired(contextApproval)) {
597
+ return true;
598
+ }
599
+ }
600
+ const sessionKey = this.buildKey(toolId, sessionId);
601
+ const sessionValue = await this.storage.get(sessionKey);
602
+ const sessionApproval = this.parseRecord(sessionValue);
603
+ if (sessionApproval && sessionApproval.state === "approved" /* APPROVED */ && !this.isExpired(sessionApproval)) {
604
+ return true;
605
+ }
606
+ if (userId) {
607
+ const userKey = this.buildKey(toolId, void 0, userId);
608
+ const userValue = await this.storage.get(userKey);
609
+ const userApproval = this.parseRecord(userValue);
610
+ if (userApproval && userApproval.state === "approved" /* APPROVED */ && !this.isExpired(userApproval)) {
611
+ return true;
612
+ }
613
+ }
614
+ return false;
615
+ }
616
+ async clearSessionApprovals(sessionId) {
617
+ this.ensureInitialized();
618
+ const escapedSessionId = escapePattern(sessionId);
619
+ const pattern = `*:session:${escapedSessionId}*`;
620
+ const keys = await this.storage.keys(pattern);
621
+ if (keys.length === 0) {
622
+ return 0;
623
+ }
624
+ return await this.storage.mdelete(keys);
625
+ }
626
+ async clearExpiredApprovals() {
627
+ this.ensureInitialized();
628
+ const now = Date.now();
629
+ const keys = await this.storage.keys("*");
630
+ const values = await this.storage.mget(keys);
631
+ const keysToDelete = [];
632
+ for (let i = 0; i < keys.length; i++) {
633
+ const approval = this.parseRecord(values[i]);
634
+ if (approval && approval.expiresAt && approval.expiresAt <= now) {
635
+ keysToDelete.push(keys[i]);
636
+ }
637
+ }
638
+ if (keysToDelete.length > 0) {
639
+ return await this.storage.mdelete(keysToDelete);
640
+ }
641
+ return 0;
642
+ }
643
+ async getStats() {
644
+ this.ensureInitialized();
645
+ const byScope = {
646
+ ["session" /* SESSION */]: 0,
647
+ ["user" /* USER */]: 0,
648
+ ["time_limited" /* TIME_LIMITED */]: 0,
649
+ ["tool_specific" /* TOOL_SPECIFIC */]: 0,
650
+ ["context_specific" /* CONTEXT_SPECIFIC */]: 0
651
+ };
652
+ const byState = {
653
+ ["pending" /* PENDING */]: 0,
654
+ ["approved" /* APPROVED */]: 0,
655
+ ["denied" /* DENIED */]: 0,
656
+ ["expired" /* EXPIRED */]: 0
657
+ };
658
+ const keys = await this.storage.keys("*");
659
+ const values = await this.storage.mget(keys);
660
+ let total = 0;
661
+ for (const value of values) {
662
+ const approval = this.parseRecord(value);
663
+ if (approval) {
664
+ total++;
665
+ byScope[approval.scope]++;
666
+ byState[approval.state]++;
667
+ }
668
+ }
669
+ return {
670
+ totalApprovals: total,
671
+ byScope,
672
+ byState
673
+ };
674
+ }
675
+ async close() {
676
+ if (this.cleanupInterval) {
677
+ clearInterval(this.cleanupInterval);
678
+ this.cleanupInterval = void 0;
679
+ }
680
+ if (this.ownedStorage && this.storage) {
681
+ await this.storage.root.disconnect();
682
+ }
683
+ this.initialized = false;
684
+ }
685
+ };
686
+ ApprovalStorageStore = __decorateClass([
687
+ (0, import_sdk.Provider)({
688
+ name: "provider:approval:store:storage",
689
+ description: "Storage-backed approval store (supports Memory, Redis, Vercel KV, Upstash)",
690
+ scope: import_sdk.ProviderScope.GLOBAL
691
+ })
692
+ ], ApprovalStorageStore);
693
+ function createApprovalMemoryStore(options = {}) {
694
+ const memoryStorage = (0, import_utils.createMemoryStorage)();
695
+ return new ApprovalStorageStore({
696
+ ...options,
697
+ storageInstance: memoryStorage
698
+ });
699
+ }
700
+
701
+ // plugins/plugin-approval/src/services/approval.service.ts
702
+ var import_sdk2 = require("@frontmcp/sdk");
703
+ var ApprovalService = class {
704
+ constructor(store, sessionId, userId) {
705
+ this.store = store;
706
+ this.sessionId = sessionId;
707
+ this.userId = userId;
708
+ }
709
+ // ─────────────────────────────────────────────────────────────────────────────
710
+ // Query Methods
711
+ // ─────────────────────────────────────────────────────────────────────────────
712
+ /**
713
+ * Check if a tool is approved for current session/user.
714
+ */
715
+ async isApproved(toolId, context) {
716
+ return this.store.isApproved(toolId, this.sessionId, this.userId, context);
717
+ }
718
+ /**
719
+ * Get approval record for a tool.
720
+ */
721
+ async getApproval(toolId) {
722
+ return this.store.getApproval(toolId, this.sessionId, this.userId);
723
+ }
724
+ /**
725
+ * Get all approvals for current session.
726
+ */
727
+ async getSessionApprovals() {
728
+ return this.store.queryApprovals({
729
+ sessionId: this.sessionId,
730
+ states: ["approved" /* APPROVED */],
731
+ includeExpired: false
732
+ });
733
+ }
734
+ /**
735
+ * Get all approvals for current user (across sessions).
736
+ */
737
+ async getUserApprovals() {
738
+ if (!this.userId) return [];
739
+ return this.store.queryApprovals({
740
+ userId: this.userId,
741
+ scope: "user" /* USER */,
742
+ states: ["approved" /* APPROVED */],
743
+ includeExpired: false
744
+ });
745
+ }
746
+ /**
747
+ * Query approvals with custom filters.
748
+ */
749
+ async queryApprovals(query) {
750
+ return this.store.queryApprovals({
751
+ ...query,
752
+ sessionId: query.sessionId ?? this.sessionId,
753
+ userId: query.userId ?? this.userId
754
+ });
755
+ }
756
+ // ─────────────────────────────────────────────────────────────────────────────
757
+ // Grant Methods
758
+ // ─────────────────────────────────────────────────────────────────────────────
759
+ /**
760
+ * Grant session-scoped approval for a tool.
761
+ */
762
+ async grantSessionApproval(toolId, options = {}) {
763
+ return this.store.grantApproval({
764
+ toolId,
765
+ scope: "session" /* SESSION */,
766
+ sessionId: this.sessionId,
767
+ grantedBy: options.grantedBy ?? "policy",
768
+ reason: options.reason,
769
+ metadata: options.metadata
770
+ });
771
+ }
772
+ /**
773
+ * Grant user-scoped approval for a tool.
774
+ */
775
+ async grantUserApproval(toolId, options = {}) {
776
+ if (!this.userId) {
777
+ throw new Error("Cannot grant user approval without userId");
778
+ }
779
+ return this.store.grantApproval({
780
+ toolId,
781
+ scope: "user" /* USER */,
782
+ userId: this.userId,
783
+ grantedBy: options.grantedBy ?? "policy",
784
+ reason: options.reason,
785
+ metadata: options.metadata
786
+ });
787
+ }
788
+ /**
789
+ * Grant time-limited approval for a tool.
790
+ */
791
+ async grantTimeLimitedApproval(toolId, ttlMs, options = {}) {
792
+ return this.store.grantApproval({
793
+ toolId,
794
+ scope: "time_limited" /* TIME_LIMITED */,
795
+ ttlMs,
796
+ sessionId: this.sessionId,
797
+ userId: this.userId,
798
+ grantedBy: options.grantedBy ?? "policy",
799
+ reason: options.reason,
800
+ metadata: options.metadata
801
+ });
802
+ }
803
+ /**
804
+ * Grant context-specific approval for a tool.
805
+ */
806
+ async grantContextApproval(toolId, context, options = {}) {
807
+ return this.store.grantApproval({
808
+ toolId,
809
+ scope: "context_specific" /* CONTEXT_SPECIFIC */,
810
+ context,
811
+ sessionId: this.sessionId,
812
+ userId: this.userId,
813
+ grantedBy: options.grantedBy ?? "policy",
814
+ reason: options.reason,
815
+ metadata: options.metadata
816
+ });
817
+ }
818
+ // ─────────────────────────────────────────────────────────────────────────────
819
+ // Revoke Methods
820
+ // ─────────────────────────────────────────────────────────────────────────────
821
+ /**
822
+ * Revoke approval for a tool.
823
+ */
824
+ async revokeApproval(toolId, options = {}) {
825
+ return this.store.revokeApproval({
826
+ toolId,
827
+ sessionId: this.sessionId,
828
+ userId: this.userId,
829
+ revokedBy: options.revokedBy ?? "policy",
830
+ reason: options.reason
831
+ });
832
+ }
833
+ /**
834
+ * Clear all session approvals.
835
+ */
836
+ async clearSessionApprovals() {
837
+ return this.store.clearSessionApprovals(this.sessionId);
838
+ }
839
+ };
840
+ ApprovalService = __decorateClass([
841
+ (0, import_sdk2.Provider)({
842
+ name: "provider:approval:service",
843
+ description: "Service for managing tool approvals",
844
+ scope: import_sdk2.ProviderScope.CONTEXT
845
+ })
846
+ ], ApprovalService);
847
+ function createApprovalService(store, sessionId, userId) {
848
+ return new ApprovalService(store, sessionId, userId);
849
+ }
850
+
851
+ // plugins/plugin-approval/src/services/challenge.service.ts
852
+ var import_sdk3 = require("@frontmcp/sdk");
853
+ var import_utils2 = require("@frontmcp/utils");
854
+ var ChallengeService = class {
855
+ storage;
856
+ options;
857
+ initialized = false;
858
+ ownedStorage = false;
859
+ constructor(options = {}) {
860
+ this.options = {
861
+ storage: options.storage ?? { type: "auto" },
862
+ storageInstance: options.storageInstance,
863
+ namespace: options.namespace ?? "approval:challenge",
864
+ defaultTtlSeconds: options.defaultTtlSeconds ?? 300
865
+ };
866
+ }
867
+ /**
868
+ * Initialize the service.
869
+ */
870
+ async initialize() {
871
+ if (this.initialized) return;
872
+ if (this.options.storageInstance) {
873
+ this.storage = this.options.storageInstance.namespace(this.options.namespace);
874
+ this.ownedStorage = false;
875
+ } else {
876
+ const rootStorage = await (0, import_utils2.createStorage)(this.options.storage);
877
+ this.storage = rootStorage.namespace(this.options.namespace);
878
+ this.ownedStorage = true;
879
+ }
880
+ this.initialized = true;
881
+ }
882
+ ensureInitialized() {
883
+ if (!this.initialized) {
884
+ throw new Error("ChallengeService not initialized. Call initialize() first.");
885
+ }
886
+ }
887
+ /**
888
+ * Create a new PKCE challenge for a tool approval request.
889
+ *
890
+ * @returns Object containing code_verifier (keep secret) and code_challenge (send to webhook)
891
+ */
892
+ async createChallenge(options) {
893
+ this.ensureInitialized();
894
+ const { codeVerifier, codeChallenge } = (0, import_utils2.generatePkcePair)();
895
+ const now = Date.now();
896
+ const ttlSeconds = options.ttlSeconds ?? this.options.defaultTtlSeconds;
897
+ const expiresAt = now + ttlSeconds * 1e3;
898
+ const record = {
899
+ toolId: options.toolId,
900
+ sessionId: options.sessionId,
901
+ userId: options.userId,
902
+ requestedScope: options.requestedScope,
903
+ requestInfo: options.requestInfo,
904
+ createdAt: now,
905
+ expiresAt,
906
+ webhookSent: false
907
+ };
908
+ await this.storage.set(codeChallenge, JSON.stringify(record), { ttlSeconds });
909
+ return { codeVerifier, codeChallenge, expiresAt };
910
+ }
911
+ /**
912
+ * Verify a code verifier and retrieve the challenge record.
913
+ *
914
+ * @throws ChallengeValidationError if verification fails
915
+ */
916
+ async verifyAndConsume(codeVerifier) {
917
+ this.ensureInitialized();
918
+ const { codeChallenge } = generatePkcePairFromVerifier(codeVerifier);
919
+ const recordJson = await this.storage.get(codeChallenge);
920
+ if (!recordJson) {
921
+ throw new ChallengeValidationError("not_found", "Invalid or expired challenge");
922
+ }
923
+ const record = JSON.parse(recordJson);
924
+ if (Date.now() > record.expiresAt) {
925
+ await this.storage.delete(codeChallenge);
926
+ throw new ChallengeValidationError("expired", "Challenge expired");
927
+ }
928
+ await this.storage.delete(codeChallenge);
929
+ return record;
930
+ }
931
+ /**
932
+ * Mark a challenge as having been sent to webhook.
933
+ */
934
+ async markWebhookSent(codeChallenge) {
935
+ this.ensureInitialized();
936
+ const recordJson = await this.storage.get(codeChallenge);
937
+ if (!recordJson) return false;
938
+ const record = JSON.parse(recordJson);
939
+ record.webhookSent = true;
940
+ const remainingMs = record.expiresAt - Date.now();
941
+ if (remainingMs <= 0) {
942
+ await this.storage.delete(codeChallenge);
943
+ return false;
944
+ }
945
+ const ttlSeconds = Math.ceil(remainingMs / 1e3);
946
+ await this.storage.set(codeChallenge, JSON.stringify(record), { ttlSeconds });
947
+ return true;
948
+ }
949
+ /**
950
+ * Get a challenge record without consuming it.
951
+ */
952
+ async getChallenge(codeChallenge) {
953
+ this.ensureInitialized();
954
+ const recordJson = await this.storage.get(codeChallenge);
955
+ if (!recordJson) return null;
956
+ const record = JSON.parse(recordJson);
957
+ if (Date.now() > record.expiresAt) {
958
+ await this.storage.delete(codeChallenge);
959
+ return null;
960
+ }
961
+ return record;
962
+ }
963
+ /**
964
+ * Delete a challenge.
965
+ */
966
+ async deleteChallenge(codeChallenge) {
967
+ this.ensureInitialized();
968
+ const exists = await this.storage.exists(codeChallenge);
969
+ if (exists) {
970
+ await this.storage.delete(codeChallenge);
971
+ return true;
972
+ }
973
+ return false;
974
+ }
975
+ /**
976
+ * Close the service.
977
+ */
978
+ async close() {
979
+ if (this.ownedStorage && this.storage) {
980
+ await this.storage.root.disconnect();
981
+ }
982
+ this.initialized = false;
983
+ }
984
+ };
985
+ ChallengeService = __decorateClass([
986
+ (0, import_sdk3.Provider)({
987
+ name: "provider:approval:challenge-service",
988
+ description: "PKCE challenge service for webhook approval flows",
989
+ scope: import_sdk3.ProviderScope.GLOBAL
990
+ })
991
+ ], ChallengeService);
992
+ function generatePkcePairFromVerifier(codeVerifier) {
993
+ return {
994
+ codeVerifier,
995
+ codeChallenge: (0, import_utils2.generateCodeChallenge)(codeVerifier)
996
+ };
997
+ }
998
+ function createMemoryChallengeService(options = {}) {
999
+ const memoryStorage = (0, import_utils2.createMemoryStorage)();
1000
+ return new ChallengeService({
1001
+ ...options,
1002
+ storageInstance: memoryStorage
1003
+ });
1004
+ }
1005
+
1006
+ // plugins/plugin-approval/src/approval.symbols.ts
1007
+ var ApprovalStoreToken = /* @__PURE__ */ Symbol.for(
1008
+ "plugin:approval:store"
1009
+ );
1010
+ var ApprovalServiceToken = /* @__PURE__ */ Symbol.for(
1011
+ "plugin:approval:service"
1012
+ );
1013
+ var ChallengeServiceToken = /* @__PURE__ */ Symbol.for(
1014
+ "plugin:approval:challenge-service"
1015
+ );
1016
+
1017
+ // plugins/plugin-approval/src/hooks/approval-check.hook.ts
1018
+ var import_sdk4 = require("@frontmcp/sdk");
1019
+ var ApprovalCheckPlugin = class extends import_sdk4.DynamicPlugin {
1020
+ async checkApproval(flowCtx) {
1021
+ const { tool, toolContext } = flowCtx.state;
1022
+ if (!tool || !toolContext) return;
1023
+ const metadata = tool.metadata;
1024
+ const approvalConfig = this.resolveApprovalConfig(
1025
+ metadata["approval"]
1026
+ );
1027
+ if (!approvalConfig.required) {
1028
+ return;
1029
+ }
1030
+ if (approvalConfig.skipApproval) {
1031
+ return;
1032
+ }
1033
+ const ctx = toolContext.tryGetContext?.();
1034
+ const sessionId = ctx?.sessionId ?? "unknown";
1035
+ const userId = this.getStringExtra(ctx?.authInfo?.extra, "userId") ?? this.getStringExtra(ctx?.authInfo?.extra, "sub") ?? ctx?.authInfo?.clientId;
1036
+ const currentContext = this.getCurrentContext(flowCtx);
1037
+ if (this.isPreApprovedContext(approvalConfig, currentContext)) {
1038
+ return;
1039
+ }
1040
+ const approvalStore = this.get(ApprovalStoreToken);
1041
+ const approval = await approvalStore.getApproval(tool.fullName, sessionId, userId);
1042
+ if (approvalConfig.alwaysPrompt) {
1043
+ await this.handleApprovalRequired(flowCtx, approvalConfig, approval);
1044
+ return;
1045
+ }
1046
+ if (approval?.state === "approved" /* APPROVED */) {
1047
+ if (!this.isExpired(approval)) {
1048
+ return;
1049
+ }
1050
+ }
1051
+ if (approval?.state === "denied" /* DENIED */) {
1052
+ throw new ApprovalRequiredError({
1053
+ toolId: tool.fullName,
1054
+ state: "denied",
1055
+ message: `Tool "${tool.fullName}" execution denied.`
1056
+ });
1057
+ }
1058
+ await this.handleApprovalRequired(flowCtx, approvalConfig, approval);
1059
+ }
1060
+ resolveApprovalConfig(config) {
1061
+ if (config === true) {
1062
+ return { required: true, defaultScope: "session" /* SESSION */ };
1063
+ }
1064
+ if (config === false || config === void 0) {
1065
+ return { required: false };
1066
+ }
1067
+ return {
1068
+ ...config,
1069
+ required: config.required ?? true,
1070
+ defaultScope: config.defaultScope ?? "session" /* SESSION */
1071
+ };
1072
+ }
1073
+ isExpired(approval) {
1074
+ if (!approval.expiresAt) return false;
1075
+ return Date.now() > approval.expiresAt;
1076
+ }
1077
+ getCurrentContext(flowCtx) {
1078
+ const { toolContext } = flowCtx.state;
1079
+ const ctx = toolContext?.tryGetContext?.();
1080
+ const inputContext = toolContext?.input?.["context"];
1081
+ const contextFromInput = this.isApprovalContext(inputContext) ? inputContext : void 0;
1082
+ const sessionContext = ctx?.authInfo?.extra?.["approvalContext"];
1083
+ const contextFromSession = this.isApprovalContext(sessionContext) ? sessionContext : void 0;
1084
+ return contextFromInput ?? contextFromSession;
1085
+ }
1086
+ isApprovalContext(value) {
1087
+ return typeof value === "object" && value !== null && "type" in value && "identifier" in value && typeof value.type === "string" && typeof value.identifier === "string";
1088
+ }
1089
+ getStringExtra(extra, key) {
1090
+ if (!extra) return void 0;
1091
+ const value = extra[key];
1092
+ return typeof value === "string" ? value : void 0;
1093
+ }
1094
+ isPreApprovedContext(config, currentContext) {
1095
+ if (!currentContext || !config.preApprovedContexts?.length) {
1096
+ return false;
1097
+ }
1098
+ return config.preApprovedContexts.some(
1099
+ (preApproved) => preApproved.type === currentContext.type && preApproved.identifier === currentContext.identifier
1100
+ );
1101
+ }
1102
+ async handleApprovalRequired(flowCtx, config, existingApproval) {
1103
+ const { tool } = flowCtx.state;
1104
+ const message = config.approvalMessage ?? `Tool "${tool?.fullName}" requires approval to execute. Allow?`;
1105
+ const isExpiredApproval = existingApproval ? this.isExpired(existingApproval) : false;
1106
+ throw new ApprovalRequiredError({
1107
+ toolId: tool?.fullName ?? "unknown",
1108
+ state: isExpiredApproval ? "expired" : "pending",
1109
+ message,
1110
+ approvalOptions: {
1111
+ allowedScopes: config.allowedScopes,
1112
+ defaultScope: config.defaultScope,
1113
+ maxTtlMs: config.maxTtlMs,
1114
+ category: config.category,
1115
+ riskLevel: config.riskLevel
1116
+ }
1117
+ });
1118
+ }
1119
+ };
1120
+ __decorateClass([
1121
+ import_sdk4.ToolHook.Will("execute", { priority: 100 })
1122
+ ], ApprovalCheckPlugin.prototype, "checkApproval", 1);
1123
+ ApprovalCheckPlugin = __decorateClass([
1124
+ (0, import_sdk4.Plugin)({
1125
+ name: "approval:check",
1126
+ description: "Checks tool approval state before execution"
1127
+ })
1128
+ ], ApprovalCheckPlugin);
1129
+
1130
+ // plugins/plugin-approval/src/approval.plugin.ts
1131
+ var ApprovalPlugin = class extends import_sdk5.DynamicPlugin {
1132
+ options;
1133
+ constructor(options = {}) {
1134
+ super();
1135
+ this.options = {
1136
+ ...ApprovalPlugin.defaultOptions,
1137
+ ...options
1138
+ };
1139
+ }
1140
+ /**
1141
+ * Get plugin metadata including nested plugins.
1142
+ */
1143
+ static getPluginMetadata(_options) {
1144
+ return { plugins: [ApprovalCheckPlugin] };
1145
+ }
1146
+ };
1147
+ __publicField(ApprovalPlugin, "defaultOptions", {
1148
+ namespace: "approval",
1149
+ mode: "recheck",
1150
+ enableAudit: true,
1151
+ maxDelegationDepth: 3,
1152
+ cleanupIntervalSeconds: 60
1153
+ });
1154
+ /**
1155
+ * Dynamic providers based on plugin options.
1156
+ */
1157
+ __publicField(ApprovalPlugin, "dynamicProviders", (options) => {
1158
+ const providers = [];
1159
+ const config = {
1160
+ ...ApprovalPlugin.defaultOptions,
1161
+ ...options
1162
+ };
1163
+ providers.push({
1164
+ name: "approval:store",
1165
+ provide: ApprovalStoreToken,
1166
+ inject: () => [],
1167
+ useFactory: async () => {
1168
+ const store = new ApprovalStorageStore({
1169
+ storage: config.storage,
1170
+ storageInstance: config.storageInstance,
1171
+ namespace: config.namespace,
1172
+ cleanupIntervalSeconds: config.cleanupIntervalSeconds
1173
+ });
1174
+ await store.initialize();
1175
+ return store;
1176
+ }
1177
+ });
1178
+ if (config.mode === "webhook") {
1179
+ providers.push({
1180
+ name: "approval:challenge-service",
1181
+ provide: ChallengeServiceToken,
1182
+ inject: () => [],
1183
+ useFactory: async () => {
1184
+ const service = new ChallengeService({
1185
+ storage: config.storage,
1186
+ storageInstance: config.storageInstance,
1187
+ namespace: `${config.namespace}:challenge`,
1188
+ defaultTtlSeconds: config.webhook?.challengeTtl ?? 300
1189
+ });
1190
+ await service.initialize();
1191
+ return service;
1192
+ }
1193
+ });
1194
+ }
1195
+ providers.push({
1196
+ name: "approval:service",
1197
+ provide: ApprovalServiceToken,
1198
+ scope: import_sdk5.ProviderScope.CONTEXT,
1199
+ inject: () => [ApprovalStoreToken, import_sdk5.FRONTMCP_CONTEXT],
1200
+ useFactory: (store, ctx) => {
1201
+ const userId = ctx.authInfo?.extra?.["userId"] ?? ctx.authInfo?.extra?.["sub"] ?? ctx.authInfo?.clientId;
1202
+ return createApprovalService(store, ctx.sessionId, userId);
1203
+ }
1204
+ });
1205
+ return providers;
1206
+ });
1207
+ ApprovalPlugin = __decorateClass([
1208
+ (0, import_sdk5.Plugin)({
1209
+ name: "approval",
1210
+ description: "Tool authorization workflow with PKCE webhook security",
1211
+ contextExtensions: [
1212
+ {
1213
+ property: "approval",
1214
+ token: ApprovalServiceToken,
1215
+ errorMessage: "ApprovalPlugin is not installed. Add ApprovalPlugin.init() to your plugins array."
1216
+ }
1217
+ ]
1218
+ })
1219
+ ], ApprovalPlugin);
1220
+
1221
+ // plugins/plugin-approval/src/approval.context-extension.ts
1222
+ var installed = false;
1223
+ function installApprovalContextExtension() {
1224
+ if (installed) return;
1225
+ const { ExecutionContextBase } = require("@frontmcp/sdk");
1226
+ Object.defineProperty(ExecutionContextBase.prototype, "approval", {
1227
+ get: function() {
1228
+ return this.get(ApprovalServiceToken);
1229
+ },
1230
+ configurable: true,
1231
+ enumerable: false
1232
+ });
1233
+ installed = true;
1234
+ }
1235
+ // Annotate the CommonJS export names for ESM import in node:
1236
+ 0 && (module.exports = {
1237
+ ApprovalCheckPlugin,
1238
+ ApprovalError,
1239
+ ApprovalExpiredError,
1240
+ ApprovalOperationError,
1241
+ ApprovalPlugin,
1242
+ ApprovalPluginClass,
1243
+ ApprovalRequiredError,
1244
+ ApprovalScope,
1245
+ ApprovalScopeNotAllowedError,
1246
+ ApprovalService,
1247
+ ApprovalServiceToken,
1248
+ ApprovalState,
1249
+ ApprovalStorageStore,
1250
+ ApprovalStoreToken,
1251
+ ChallengeService,
1252
+ ChallengeServiceToken,
1253
+ ChallengeValidationError,
1254
+ adminGrantor,
1255
+ adminRevoker,
1256
+ agentGrantor,
1257
+ apiGrantor,
1258
+ createApprovalMemoryStore,
1259
+ createApprovalService,
1260
+ createMemoryChallengeService,
1261
+ customGrantor,
1262
+ expiryRevoker,
1263
+ installApprovalContextExtension,
1264
+ isApiGrantor,
1265
+ isAutoGrantor,
1266
+ isDelegatedGrantor,
1267
+ isGrantorSource,
1268
+ isHumanGrantor,
1269
+ normalizeGrantor,
1270
+ normalizeRevoker,
1271
+ oauthGrantor,
1272
+ policyGrantor,
1273
+ policyRevoker,
1274
+ sessionEndRevoker,
1275
+ systemGrantor,
1276
+ testGrantor,
1277
+ userGrantor,
1278
+ userRevoker
1279
+ });