@frontmcp/plugin-approval 1.8.4 → 1.8.6
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 +18 -0
- package/approval/errors.d.ts +6 -5
- package/approval/errors.d.ts.map +1 -1
- package/esm/index.mjs +34 -13
- package/esm/package.json +4 -4
- package/index.js +51 -30
- package/package.json +4 -4
- package/services/approval.service.d.ts +12 -2
- package/services/approval.service.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,24 @@ class DangerousTool extends ToolContext {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
A refused call reaches the client as an error result whose text is exactly the tool's `approvalMessage` and whose
|
|
76
|
+
`_meta.code` is `APPROVAL_REQUIRED`, in production too: the approval errors extend `PublicMcpError`.
|
|
77
|
+
|
|
78
|
+
A grant made through `this.approval` records the signed-in caller as its grantor, `userGrantor(<user id>)`
|
|
79
|
+
(`{ source: 'user', identifier, method: 'interactive' }`), or `{ source: 'user' }` when there is no signed-in user
|
|
80
|
+
(an anonymous `anon:` subject included). Pass `grantedBy` to record something else:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { policyGrantor, userGrantor } from '@frontmcp/plugin-approval';
|
|
84
|
+
|
|
85
|
+
await this.approval.grantSessionApproval('ops:deploy', { grantedBy: policyGrantor('safe-list') });
|
|
86
|
+
await this.approval.grantUserApproval('ops:deploy', {
|
|
87
|
+
grantedBy: userGrantor('user-123', 'Jane Doe', { method: 'interactive' }),
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`revokeApproval()` records `revokedBy` by the same rule.
|
|
92
|
+
|
|
75
93
|
### Approval Scopes
|
|
76
94
|
|
|
77
95
|
| Scope | Description |
|
package/approval/errors.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error classes for approval operations.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* They are public MCP errors: a refusal is an answer for the caller, so its message reaches the
|
|
5
|
+
* client as written, in every environment, instead of being wrapped as an internal server error.
|
|
6
6
|
*
|
|
7
|
-
* @module @frontmcp/
|
|
7
|
+
* @module @frontmcp/plugin-approval
|
|
8
8
|
*/
|
|
9
|
+
import { PublicMcpError } from '@frontmcp/sdk';
|
|
9
10
|
import type { ApprovalScope } from './types';
|
|
10
11
|
/**
|
|
11
12
|
* Base class for approval-related errors.
|
|
12
13
|
*/
|
|
13
|
-
export declare class ApprovalError extends
|
|
14
|
-
constructor(message: string);
|
|
14
|
+
export declare class ApprovalError extends PublicMcpError {
|
|
15
|
+
constructor(message: string, code?: string, statusCode?: number);
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
18
|
* Error thrown when tool approval is required but not granted.
|
package/approval/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/approval/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;GAEG;AACH,qBAAa,aAAc,SAAQ,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/approval/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;GAEG;AACH,qBAAa,aAAc,SAAQ,cAAc;gBACnC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAsB,EAAE,UAAU,SAAM;CAI1E;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;aAEpC,OAAO,EAAE;QACvB,sBAAsB;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,6BAA6B;QAC7B,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;QACxC,0BAA0B;QAC1B,OAAO,EAAE,MAAM,CAAC;QAChB,0CAA0C;QAC1C,eAAe,CAAC,EAAE;YAChB,aAAa,CAAC,EAAE,aAAa,EAAE,CAAC;YAChC,YAAY,CAAC,EAAE,aAAa,CAAC;YAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;KACH;gBAfe,OAAO,EAAE;QACvB,sBAAsB;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,6BAA6B;QAC7B,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;QACxC,0BAA0B;QAC1B,OAAO,EAAE,MAAM,CAAC;QAChB,0CAA0C;QAC1C,eAAe,CAAC,EAAE;YAChB,aAAa,CAAC,EAAE,aAAa,EAAE,CAAC;YAChC,YAAY,CAAC,EAAE,aAAa,CAAC;YAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;KACH;IAMH;;OAEG;IACH,cAAc;;;;;;;;gCAfQ,aAAa,EAAE;+BAChB,aAAa;2BACjB,MAAM;2BACN,MAAM;4BACL,MAAM;;;;CAuBzB;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,aAAa;aAErC,SAAS,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO;aACvC,MAAM,EAAE,MAAM;gBADd,SAAS,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,EACvC,MAAM,EAAE,MAAM;IAMhC;;OAEG;IACH,cAAc;;;;;;;;CAUf;AAED;;GAEG;AACH,qBAAa,4BAA6B,SAAQ,aAAa;aAE3C,cAAc,EAAE,aAAa;aAC7B,aAAa,EAAE,aAAa,EAAE;gBAD9B,cAAc,EAAE,aAAa,EAC7B,aAAa,EAAE,aAAa,EAAE;IAWhD;;OAEG;IACH,cAAc;;;;;;;;;CAWf;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,aAAa;aAEnC,MAAM,EAAE,MAAM;aACd,SAAS,EAAE,MAAM;gBADjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM;IAMnC;;OAEG;IACH,cAAc;;;;;;;;;CAWf;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,aAAa;aAEvC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc;gBAA5D,MAAM,GAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAA0B,EACxF,OAAO,CAAC,EAAE,MAAM;IAMlB;;OAEG;IACH,cAAc;;;;;;;;CAUf"}
|
package/esm/index.mjs
CHANGED
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
} from "@frontmcp/sdk";
|
|
28
28
|
|
|
29
29
|
// plugins/plugin-approval/src/approval/errors.ts
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
import { PublicMcpError } from "@frontmcp/sdk";
|
|
31
|
+
var ApprovalError = class extends PublicMcpError {
|
|
32
|
+
constructor(message, code = "APPROVAL_REQUIRED", statusCode = 403) {
|
|
33
|
+
super(message, code, statusCode);
|
|
33
34
|
this.name = "ApprovalError";
|
|
34
35
|
}
|
|
35
36
|
};
|
|
@@ -59,7 +60,7 @@ var ApprovalRequiredError = class extends ApprovalError {
|
|
|
59
60
|
};
|
|
60
61
|
var ApprovalOperationError = class extends ApprovalError {
|
|
61
62
|
constructor(operation, reason) {
|
|
62
|
-
super(`Approval ${operation} failed: ${reason}
|
|
63
|
+
super(`Approval ${operation} failed: ${reason}`, "APPROVAL_OPERATION_FAILED", 400);
|
|
63
64
|
this.operation = operation;
|
|
64
65
|
this.reason = reason;
|
|
65
66
|
this.name = "ApprovalOperationError";
|
|
@@ -84,7 +85,9 @@ var ApprovalOperationError = class extends ApprovalError {
|
|
|
84
85
|
var ApprovalScopeNotAllowedError = class extends ApprovalError {
|
|
85
86
|
constructor(requestedScope, allowedScopes) {
|
|
86
87
|
super(
|
|
87
|
-
`Approval scope '${requestedScope}' is not allowed for this tool. Allowed scopes: ${allowedScopes.join(", ")}
|
|
88
|
+
`Approval scope '${requestedScope}' is not allowed for this tool. Allowed scopes: ${allowedScopes.join(", ")}`,
|
|
89
|
+
"APPROVAL_SCOPE_NOT_ALLOWED",
|
|
90
|
+
400
|
|
88
91
|
);
|
|
89
92
|
this.requestedScope = requestedScope;
|
|
90
93
|
this.allowedScopes = allowedScopes;
|
|
@@ -110,7 +113,7 @@ var ApprovalScopeNotAllowedError = class extends ApprovalError {
|
|
|
110
113
|
};
|
|
111
114
|
var ApprovalExpiredError = class extends ApprovalError {
|
|
112
115
|
constructor(toolId, expiredAt) {
|
|
113
|
-
super(`Approval for tool '${toolId}' expired at ${new Date(expiredAt).toISOString()}
|
|
116
|
+
super(`Approval for tool '${toolId}' expired at ${new Date(expiredAt).toISOString()}`, "APPROVAL_EXPIRED", 403);
|
|
114
117
|
this.toolId = toolId;
|
|
115
118
|
this.expiredAt = expiredAt;
|
|
116
119
|
this.name = "ApprovalExpiredError";
|
|
@@ -135,7 +138,7 @@ var ApprovalExpiredError = class extends ApprovalError {
|
|
|
135
138
|
};
|
|
136
139
|
var ChallengeValidationError = class extends ApprovalError {
|
|
137
140
|
constructor(reason = "invalid", message) {
|
|
138
|
-
super(message ?? `PKCE challenge validation failed: ${reason}
|
|
141
|
+
super(message ?? `PKCE challenge validation failed: ${reason}`, "CHALLENGE_VALIDATION_FAILED", 400);
|
|
139
142
|
this.reason = reason;
|
|
140
143
|
this.name = "ChallengeValidationError";
|
|
141
144
|
}
|
|
@@ -589,7 +592,7 @@ ApprovalCheckPlugin = __decorateClass([
|
|
|
589
592
|
], ApprovalCheckPlugin);
|
|
590
593
|
|
|
591
594
|
// plugins/plugin-approval/src/services/approval.service.ts
|
|
592
|
-
import { Provider, ProviderScope } from "@frontmcp/sdk";
|
|
595
|
+
import { isAnonymousSubject, Provider, ProviderScope } from "@frontmcp/sdk";
|
|
593
596
|
var ApprovalService = class {
|
|
594
597
|
constructor(store, sessionId, userId, requirementOf = () => void 0) {
|
|
595
598
|
this.store = store;
|
|
@@ -605,6 +608,24 @@ var ApprovalService = class {
|
|
|
605
608
|
checkGrant(toolId, scope, ttlMs) {
|
|
606
609
|
return checkGrantAgainstPolicy(toolId, scope, ttlMs, this.requirementOf(toolId));
|
|
607
610
|
}
|
|
611
|
+
/** The caller's user id, when it names a principal: an anonymous subject (`anon:…`) names no one. */
|
|
612
|
+
get signedInUserId() {
|
|
613
|
+
return this.userId && !isAnonymousSubject(this.userId) ? this.userId : void 0;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Who a grant is recorded as when the call names no one: the signed-in user whose tool call made it.
|
|
617
|
+
* It used to be `'policy'`, so an audit trail showed every approval a user gave as a policy's.
|
|
618
|
+
* Without a signed-in user it is the store's default, a user with no identifier.
|
|
619
|
+
*/
|
|
620
|
+
defaultGrantor() {
|
|
621
|
+
const userId = this.signedInUserId;
|
|
622
|
+
return userId ? userGrantor(userId) : { source: "user" };
|
|
623
|
+
}
|
|
624
|
+
/** Who a revocation is recorded as when the call names no one, by the same rule as a grant. */
|
|
625
|
+
defaultRevoker() {
|
|
626
|
+
const userId = this.signedInUserId;
|
|
627
|
+
return userId ? userRevoker(userId) : { source: "user" };
|
|
628
|
+
}
|
|
608
629
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
609
630
|
// Query Methods
|
|
610
631
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -673,7 +694,7 @@ var ApprovalService = class {
|
|
|
673
694
|
scope: "session" /* SESSION */,
|
|
674
695
|
ttlMs,
|
|
675
696
|
sessionId: this.sessionId,
|
|
676
|
-
grantedBy: options.grantedBy ??
|
|
697
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
677
698
|
reason: options.reason,
|
|
678
699
|
metadata: options.metadata
|
|
679
700
|
});
|
|
@@ -691,7 +712,7 @@ var ApprovalService = class {
|
|
|
691
712
|
scope: "user" /* USER */,
|
|
692
713
|
ttlMs,
|
|
693
714
|
userId: this.userId,
|
|
694
|
-
grantedBy: options.grantedBy ??
|
|
715
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
695
716
|
reason: options.reason,
|
|
696
717
|
metadata: options.metadata
|
|
697
718
|
});
|
|
@@ -707,7 +728,7 @@ var ApprovalService = class {
|
|
|
707
728
|
ttlMs,
|
|
708
729
|
sessionId: this.sessionId,
|
|
709
730
|
userId: this.userId,
|
|
710
|
-
grantedBy: options.grantedBy ??
|
|
731
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
711
732
|
reason: options.reason,
|
|
712
733
|
metadata: options.metadata
|
|
713
734
|
});
|
|
@@ -724,7 +745,7 @@ var ApprovalService = class {
|
|
|
724
745
|
context,
|
|
725
746
|
sessionId: this.sessionId,
|
|
726
747
|
userId: this.userId,
|
|
727
|
-
grantedBy: options.grantedBy ??
|
|
748
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
728
749
|
reason: options.reason,
|
|
729
750
|
metadata: options.metadata
|
|
730
751
|
});
|
|
@@ -741,7 +762,7 @@ var ApprovalService = class {
|
|
|
741
762
|
toolId,
|
|
742
763
|
sessionId: this.sessionId,
|
|
743
764
|
userId: this.userId,
|
|
744
|
-
revokedBy: options.revokedBy ??
|
|
765
|
+
revokedBy: options.revokedBy ?? this.defaultRevoker(),
|
|
745
766
|
reason: options.reason
|
|
746
767
|
});
|
|
747
768
|
}
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-approval",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "Approval plugin for FrontMCP - tool authorization workflow with PKCE webhook security",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.8.
|
|
53
|
-
"@frontmcp/utils": "1.8.
|
|
52
|
+
"@frontmcp/sdk": "1.8.6",
|
|
53
|
+
"@frontmcp/utils": "1.8.6",
|
|
54
54
|
"ioredis": "^5.8.0",
|
|
55
55
|
"reflect-metadata": "^0.2.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
58
|
+
"@frontmcp/lazy-zod": "1.8.6",
|
|
59
59
|
"@vercel/kv": "^2.0.0 || ^3.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
package/index.js
CHANGED
|
@@ -76,12 +76,13 @@ __export(index_exports, {
|
|
|
76
76
|
module.exports = __toCommonJS(index_exports);
|
|
77
77
|
|
|
78
78
|
// plugins/plugin-approval/src/approval.plugin.ts
|
|
79
|
-
var
|
|
79
|
+
var import_sdk6 = require("@frontmcp/sdk");
|
|
80
80
|
|
|
81
81
|
// plugins/plugin-approval/src/approval/errors.ts
|
|
82
|
-
var
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
var import_sdk = require("@frontmcp/sdk");
|
|
83
|
+
var ApprovalError = class extends import_sdk.PublicMcpError {
|
|
84
|
+
constructor(message, code = "APPROVAL_REQUIRED", statusCode = 403) {
|
|
85
|
+
super(message, code, statusCode);
|
|
85
86
|
this.name = "ApprovalError";
|
|
86
87
|
}
|
|
87
88
|
};
|
|
@@ -111,7 +112,7 @@ var ApprovalRequiredError = class extends ApprovalError {
|
|
|
111
112
|
};
|
|
112
113
|
var ApprovalOperationError = class extends ApprovalError {
|
|
113
114
|
constructor(operation, reason) {
|
|
114
|
-
super(`Approval ${operation} failed: ${reason}
|
|
115
|
+
super(`Approval ${operation} failed: ${reason}`, "APPROVAL_OPERATION_FAILED", 400);
|
|
115
116
|
this.operation = operation;
|
|
116
117
|
this.reason = reason;
|
|
117
118
|
this.name = "ApprovalOperationError";
|
|
@@ -136,7 +137,9 @@ var ApprovalOperationError = class extends ApprovalError {
|
|
|
136
137
|
var ApprovalScopeNotAllowedError = class extends ApprovalError {
|
|
137
138
|
constructor(requestedScope, allowedScopes) {
|
|
138
139
|
super(
|
|
139
|
-
`Approval scope '${requestedScope}' is not allowed for this tool. Allowed scopes: ${allowedScopes.join(", ")}
|
|
140
|
+
`Approval scope '${requestedScope}' is not allowed for this tool. Allowed scopes: ${allowedScopes.join(", ")}`,
|
|
141
|
+
"APPROVAL_SCOPE_NOT_ALLOWED",
|
|
142
|
+
400
|
|
140
143
|
);
|
|
141
144
|
this.requestedScope = requestedScope;
|
|
142
145
|
this.allowedScopes = allowedScopes;
|
|
@@ -162,7 +165,7 @@ var ApprovalScopeNotAllowedError = class extends ApprovalError {
|
|
|
162
165
|
};
|
|
163
166
|
var ApprovalExpiredError = class extends ApprovalError {
|
|
164
167
|
constructor(toolId, expiredAt) {
|
|
165
|
-
super(`Approval for tool '${toolId}' expired at ${new Date(expiredAt).toISOString()}
|
|
168
|
+
super(`Approval for tool '${toolId}' expired at ${new Date(expiredAt).toISOString()}`, "APPROVAL_EXPIRED", 403);
|
|
166
169
|
this.toolId = toolId;
|
|
167
170
|
this.expiredAt = expiredAt;
|
|
168
171
|
this.name = "ApprovalExpiredError";
|
|
@@ -187,7 +190,7 @@ var ApprovalExpiredError = class extends ApprovalError {
|
|
|
187
190
|
};
|
|
188
191
|
var ChallengeValidationError = class extends ApprovalError {
|
|
189
192
|
constructor(reason = "invalid", message) {
|
|
190
|
-
super(message ?? `PKCE challenge validation failed: ${reason}
|
|
193
|
+
super(message ?? `PKCE challenge validation failed: ${reason}`, "CHALLENGE_VALIDATION_FAILED", 400);
|
|
191
194
|
this.reason = reason;
|
|
192
195
|
this.name = "ChallengeValidationError";
|
|
193
196
|
}
|
|
@@ -304,7 +307,7 @@ var ChallengeServiceToken = /* @__PURE__ */ Symbol.for(
|
|
|
304
307
|
);
|
|
305
308
|
|
|
306
309
|
// plugins/plugin-approval/src/hooks/approval-check.hook.ts
|
|
307
|
-
var
|
|
310
|
+
var import_sdk2 = require("@frontmcp/sdk");
|
|
308
311
|
|
|
309
312
|
// plugins/plugin-approval/src/approval/schemas.ts
|
|
310
313
|
var import_lazy_zod = require("@frontmcp/lazy-zod");
|
|
@@ -524,7 +527,7 @@ function isApiGrantor(grantor) {
|
|
|
524
527
|
|
|
525
528
|
// plugins/plugin-approval/src/hooks/approval-check.hook.ts
|
|
526
529
|
var passedApprovalStores = /* @__PURE__ */ new WeakMap();
|
|
527
|
-
var ApprovalCheckPlugin = class extends
|
|
530
|
+
var ApprovalCheckPlugin = class extends import_sdk2.DynamicPlugin {
|
|
528
531
|
async checkApproval(flowCtx) {
|
|
529
532
|
const { tool, toolContext } = flowCtx.state;
|
|
530
533
|
if (!tool || !toolContext) return;
|
|
@@ -629,10 +632,10 @@ var ApprovalCheckPlugin = class extends import_sdk.DynamicPlugin {
|
|
|
629
632
|
}
|
|
630
633
|
};
|
|
631
634
|
__decorateClass([
|
|
632
|
-
|
|
635
|
+
import_sdk2.ToolHook.Will("execute", { priority: 100, appliesTo: "uncovered-apps" })
|
|
633
636
|
], ApprovalCheckPlugin.prototype, "checkApproval", 1);
|
|
634
637
|
ApprovalCheckPlugin = __decorateClass([
|
|
635
|
-
(0,
|
|
638
|
+
(0, import_sdk2.Plugin)({
|
|
636
639
|
name: "approval:check",
|
|
637
640
|
description: "Checks tool approval state before execution",
|
|
638
641
|
// A server where a tool or agent declares `approval` and this gate does not reach it refuses to start.
|
|
@@ -641,7 +644,7 @@ ApprovalCheckPlugin = __decorateClass([
|
|
|
641
644
|
], ApprovalCheckPlugin);
|
|
642
645
|
|
|
643
646
|
// plugins/plugin-approval/src/services/approval.service.ts
|
|
644
|
-
var
|
|
647
|
+
var import_sdk3 = require("@frontmcp/sdk");
|
|
645
648
|
var ApprovalService = class {
|
|
646
649
|
constructor(store, sessionId, userId, requirementOf = () => void 0) {
|
|
647
650
|
this.store = store;
|
|
@@ -657,6 +660,24 @@ var ApprovalService = class {
|
|
|
657
660
|
checkGrant(toolId, scope, ttlMs) {
|
|
658
661
|
return checkGrantAgainstPolicy(toolId, scope, ttlMs, this.requirementOf(toolId));
|
|
659
662
|
}
|
|
663
|
+
/** The caller's user id, when it names a principal: an anonymous subject (`anon:…`) names no one. */
|
|
664
|
+
get signedInUserId() {
|
|
665
|
+
return this.userId && !(0, import_sdk3.isAnonymousSubject)(this.userId) ? this.userId : void 0;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Who a grant is recorded as when the call names no one: the signed-in user whose tool call made it.
|
|
669
|
+
* It used to be `'policy'`, so an audit trail showed every approval a user gave as a policy's.
|
|
670
|
+
* Without a signed-in user it is the store's default, a user with no identifier.
|
|
671
|
+
*/
|
|
672
|
+
defaultGrantor() {
|
|
673
|
+
const userId = this.signedInUserId;
|
|
674
|
+
return userId ? userGrantor(userId) : { source: "user" };
|
|
675
|
+
}
|
|
676
|
+
/** Who a revocation is recorded as when the call names no one, by the same rule as a grant. */
|
|
677
|
+
defaultRevoker() {
|
|
678
|
+
const userId = this.signedInUserId;
|
|
679
|
+
return userId ? userRevoker(userId) : { source: "user" };
|
|
680
|
+
}
|
|
660
681
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
661
682
|
// Query Methods
|
|
662
683
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -725,7 +746,7 @@ var ApprovalService = class {
|
|
|
725
746
|
scope: "session" /* SESSION */,
|
|
726
747
|
ttlMs,
|
|
727
748
|
sessionId: this.sessionId,
|
|
728
|
-
grantedBy: options.grantedBy ??
|
|
749
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
729
750
|
reason: options.reason,
|
|
730
751
|
metadata: options.metadata
|
|
731
752
|
});
|
|
@@ -743,7 +764,7 @@ var ApprovalService = class {
|
|
|
743
764
|
scope: "user" /* USER */,
|
|
744
765
|
ttlMs,
|
|
745
766
|
userId: this.userId,
|
|
746
|
-
grantedBy: options.grantedBy ??
|
|
767
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
747
768
|
reason: options.reason,
|
|
748
769
|
metadata: options.metadata
|
|
749
770
|
});
|
|
@@ -759,7 +780,7 @@ var ApprovalService = class {
|
|
|
759
780
|
ttlMs,
|
|
760
781
|
sessionId: this.sessionId,
|
|
761
782
|
userId: this.userId,
|
|
762
|
-
grantedBy: options.grantedBy ??
|
|
783
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
763
784
|
reason: options.reason,
|
|
764
785
|
metadata: options.metadata
|
|
765
786
|
});
|
|
@@ -776,7 +797,7 @@ var ApprovalService = class {
|
|
|
776
797
|
context,
|
|
777
798
|
sessionId: this.sessionId,
|
|
778
799
|
userId: this.userId,
|
|
779
|
-
grantedBy: options.grantedBy ??
|
|
800
|
+
grantedBy: options.grantedBy ?? this.defaultGrantor(),
|
|
780
801
|
reason: options.reason,
|
|
781
802
|
metadata: options.metadata
|
|
782
803
|
});
|
|
@@ -793,7 +814,7 @@ var ApprovalService = class {
|
|
|
793
814
|
toolId,
|
|
794
815
|
sessionId: this.sessionId,
|
|
795
816
|
userId: this.userId,
|
|
796
|
-
revokedBy: options.revokedBy ??
|
|
817
|
+
revokedBy: options.revokedBy ?? this.defaultRevoker(),
|
|
797
818
|
reason: options.reason
|
|
798
819
|
});
|
|
799
820
|
}
|
|
@@ -805,10 +826,10 @@ var ApprovalService = class {
|
|
|
805
826
|
}
|
|
806
827
|
};
|
|
807
828
|
ApprovalService = __decorateClass([
|
|
808
|
-
(0,
|
|
829
|
+
(0, import_sdk3.Provider)({
|
|
809
830
|
name: "provider:approval:service",
|
|
810
831
|
description: "Service for managing tool approvals",
|
|
811
|
-
scope:
|
|
832
|
+
scope: import_sdk3.ProviderScope.CONTEXT
|
|
812
833
|
})
|
|
813
834
|
], ApprovalService);
|
|
814
835
|
function createApprovalService(store, sessionId, userId, requirementOf) {
|
|
@@ -816,7 +837,7 @@ function createApprovalService(store, sessionId, userId, requirementOf) {
|
|
|
816
837
|
}
|
|
817
838
|
|
|
818
839
|
// plugins/plugin-approval/src/services/challenge.service.ts
|
|
819
|
-
var
|
|
840
|
+
var import_sdk4 = require("@frontmcp/sdk");
|
|
820
841
|
var import_utils2 = require("@frontmcp/utils");
|
|
821
842
|
var ChallengeService = class {
|
|
822
843
|
storage;
|
|
@@ -950,10 +971,10 @@ var ChallengeService = class {
|
|
|
950
971
|
}
|
|
951
972
|
};
|
|
952
973
|
ChallengeService = __decorateClass([
|
|
953
|
-
(0,
|
|
974
|
+
(0, import_sdk4.Provider)({
|
|
954
975
|
name: "provider:approval:challenge-service",
|
|
955
976
|
description: "PKCE challenge service for webhook approval flows",
|
|
956
|
-
scope:
|
|
977
|
+
scope: import_sdk4.ProviderScope.GLOBAL
|
|
957
978
|
})
|
|
958
979
|
], ChallengeService);
|
|
959
980
|
function generatePkcePairFromVerifier(codeVerifier) {
|
|
@@ -971,7 +992,7 @@ function createMemoryChallengeService(options = {}) {
|
|
|
971
992
|
}
|
|
972
993
|
|
|
973
994
|
// plugins/plugin-approval/src/stores/approval-storage.store.ts
|
|
974
|
-
var
|
|
995
|
+
var import_sdk5 = require("@frontmcp/sdk");
|
|
975
996
|
var import_utils3 = require("@frontmcp/utils");
|
|
976
997
|
function escapePattern(str) {
|
|
977
998
|
return str.replace(/[*?[\]\\]/g, "\\$&");
|
|
@@ -1275,10 +1296,10 @@ var ApprovalStorageStore = class {
|
|
|
1275
1296
|
}
|
|
1276
1297
|
};
|
|
1277
1298
|
ApprovalStorageStore = __decorateClass([
|
|
1278
|
-
(0,
|
|
1299
|
+
(0, import_sdk5.Provider)({
|
|
1279
1300
|
name: "provider:approval:store:storage",
|
|
1280
1301
|
description: "Storage-backed approval store (supports Memory, Redis, Vercel KV, Upstash)",
|
|
1281
|
-
scope:
|
|
1302
|
+
scope: import_sdk5.ProviderScope.GLOBAL
|
|
1282
1303
|
})
|
|
1283
1304
|
], ApprovalStorageStore);
|
|
1284
1305
|
function createApprovalMemoryStore(options = {}) {
|
|
@@ -1290,7 +1311,7 @@ function createApprovalMemoryStore(options = {}) {
|
|
|
1290
1311
|
}
|
|
1291
1312
|
|
|
1292
1313
|
// plugins/plugin-approval/src/approval.plugin.ts
|
|
1293
|
-
var ApprovalPlugin = class extends
|
|
1314
|
+
var ApprovalPlugin = class extends import_sdk6.DynamicPlugin {
|
|
1294
1315
|
options;
|
|
1295
1316
|
constructor(options = {}) {
|
|
1296
1317
|
super();
|
|
@@ -1351,8 +1372,8 @@ __publicField(ApprovalPlugin, "dynamicProviders", (options) => {
|
|
|
1351
1372
|
providers.push({
|
|
1352
1373
|
name: "approval:service",
|
|
1353
1374
|
provide: ApprovalServiceToken,
|
|
1354
|
-
scope:
|
|
1355
|
-
inject: () => [ApprovalStoreToken,
|
|
1375
|
+
scope: import_sdk6.ProviderScope.CONTEXT,
|
|
1376
|
+
inject: () => [ApprovalStoreToken, import_sdk6.FRONTMCP_CONTEXT, import_sdk6.ScopeEntry],
|
|
1356
1377
|
useFactory: (store, ctx, scope) => {
|
|
1357
1378
|
const { sessionId, userId } = resolveApprovalIdentity(ctx);
|
|
1358
1379
|
const requirementOf = (toolId) => {
|
|
@@ -1367,7 +1388,7 @@ __publicField(ApprovalPlugin, "dynamicProviders", (options) => {
|
|
|
1367
1388
|
return providers;
|
|
1368
1389
|
});
|
|
1369
1390
|
ApprovalPlugin = __decorateClass([
|
|
1370
|
-
(0,
|
|
1391
|
+
(0, import_sdk6.Plugin)({
|
|
1371
1392
|
name: "approval",
|
|
1372
1393
|
description: "Tool authorization workflow with PKCE webhook security",
|
|
1373
1394
|
plugins: [ApprovalCheckPlugin],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-approval",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "Approval plugin for FrontMCP - tool authorization workflow with PKCE webhook security",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.8.
|
|
53
|
-
"@frontmcp/utils": "1.8.
|
|
52
|
+
"@frontmcp/sdk": "1.8.6",
|
|
53
|
+
"@frontmcp/utils": "1.8.6",
|
|
54
54
|
"ioredis": "^5.8.0",
|
|
55
55
|
"reflect-metadata": "^0.2.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
58
|
+
"@frontmcp/lazy-zod": "1.8.6",
|
|
59
59
|
"@vercel/kv": "^2.0.0 || ^3.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
@@ -14,7 +14,7 @@ export type ApprovalRequirementLookup = (toolId: string) => ToolApprovalRequirem
|
|
|
14
14
|
* Options for granting approvals via the service.
|
|
15
15
|
*/
|
|
16
16
|
export interface GrantOptions {
|
|
17
|
-
/** Who/what is granting the approval (defaults to '
|
|
17
|
+
/** Who/what is granting the approval (defaults to the signed-in user, or `{ source: 'user' }` without one) */
|
|
18
18
|
grantedBy?: ApprovalGrantor | ApprovalSourceType;
|
|
19
19
|
/** Optional reason for the approval */
|
|
20
20
|
reason?: string;
|
|
@@ -25,7 +25,7 @@ export interface GrantOptions {
|
|
|
25
25
|
* Options for revoking approvals via the service.
|
|
26
26
|
*/
|
|
27
27
|
export interface RevokeOptions {
|
|
28
|
-
/** Who/what is revoking the approval (defaults to '
|
|
28
|
+
/** Who/what is revoking the approval (defaults to the signed-in user, or `{ source: 'user' }` without one) */
|
|
29
29
|
revokedBy?: ApprovalRevoker | RevocationSourceType;
|
|
30
30
|
/** Optional reason for revocation */
|
|
31
31
|
reason?: string;
|
|
@@ -41,6 +41,16 @@ export declare class ApprovalService {
|
|
|
41
41
|
constructor(store: ApprovalStore, sessionId: string, userId?: string | undefined, requirementOf?: ApprovalRequirementLookup);
|
|
42
42
|
/** Checks a grant against the tool's `allowedScopes` and `maxTtlMs`; returns the ttl to store. */
|
|
43
43
|
private checkGrant;
|
|
44
|
+
/** The caller's user id, when it names a principal: an anonymous subject (`anon:…`) names no one. */
|
|
45
|
+
private get signedInUserId();
|
|
46
|
+
/**
|
|
47
|
+
* Who a grant is recorded as when the call names no one: the signed-in user whose tool call made it.
|
|
48
|
+
* It used to be `'policy'`, so an audit trail showed every approval a user gave as a policy's.
|
|
49
|
+
* Without a signed-in user it is the store's default, a user with no identifier.
|
|
50
|
+
*/
|
|
51
|
+
private defaultGrantor;
|
|
52
|
+
/** Who a revocation is recorded as when the call names no one, by the same rule as a grant. */
|
|
53
|
+
private defaultRevoker;
|
|
44
54
|
/**
|
|
45
55
|
* Check if a tool is approved for current session/user.
|
|
46
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.service.d.ts","sourceRoot":"","sources":["../../src/services/approval.service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"approval.service.d.ts","sourceRoot":"","sources":["../../src/services/approval.service.ts"],"names":[],"mappings":"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"}
|