@frontmcp/skills 1.0.3 → 1.1.0-beta.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 (141) hide show
  1. package/catalog/frontmcp-authorities/SKILL.md +272 -0
  2. package/catalog/frontmcp-authorities/references/authority-profiles.md +262 -0
  3. package/catalog/frontmcp-authorities/references/claims-mapping.md +266 -0
  4. package/catalog/frontmcp-authorities/references/custom-evaluators.md +420 -0
  5. package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +391 -0
  6. package/catalog/frontmcp-channels/SKILL.md +122 -0
  7. package/catalog/frontmcp-channels/examples/channel-sources/agent-notify.md +70 -0
  8. package/catalog/frontmcp-channels/examples/channel-sources/app-errors.md +71 -0
  9. package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +102 -0
  10. package/catalog/frontmcp-channels/examples/channel-sources/job-completion.md +79 -0
  11. package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +106 -0
  12. package/catalog/frontmcp-channels/examples/channel-sources/service-connector.md +136 -0
  13. package/catalog/frontmcp-channels/examples/channel-sources/webhook-github.md +85 -0
  14. package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +133 -0
  15. package/catalog/frontmcp-channels/references/channel-sources.md +214 -0
  16. package/catalog/frontmcp-channels/references/channel-two-way.md +195 -0
  17. package/catalog/frontmcp-config/SKILL.md +20 -18
  18. package/catalog/frontmcp-config/examples/configure-auth/multi-app-auth.md +1 -2
  19. package/catalog/frontmcp-config/examples/configure-auth/public-mode-setup.md +1 -2
  20. package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +1 -2
  21. package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +1 -2
  22. package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +1 -2
  23. package/catalog/frontmcp-config/examples/configure-auth-modes/transparent-jwt-validation.md +1 -2
  24. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +121 -0
  25. package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +64 -0
  26. package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +113 -0
  27. package/catalog/frontmcp-config/examples/configure-elicitation/basic-confirmation-gate.md +1 -2
  28. package/catalog/frontmcp-config/examples/configure-elicitation/distributed-elicitation-redis.md +1 -2
  29. package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -2
  30. package/catalog/frontmcp-config/examples/configure-http/unix-socket-local.md +1 -2
  31. package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +69 -0
  32. package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +91 -0
  33. package/catalog/frontmcp-config/examples/configure-throttle/distributed-redis-throttle.md +1 -2
  34. package/catalog/frontmcp-config/examples/configure-throttle/per-tool-rate-limit.md +1 -2
  35. package/catalog/frontmcp-config/examples/configure-throttle/server-level-rate-limit.md +1 -2
  36. package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +1 -2
  37. package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +1 -2
  38. package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +1 -2
  39. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/legacy-preset-nodejs.md +1 -2
  40. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +1 -2
  41. package/catalog/frontmcp-config/references/configure-deployment-targets.md +214 -0
  42. package/catalog/frontmcp-config/references/configure-elicitation.md +1 -2
  43. package/catalog/frontmcp-config/references/configure-security-headers.md +198 -0
  44. package/catalog/frontmcp-deployment/SKILL.md +1 -0
  45. package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -2
  46. package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -2
  47. package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +117 -0
  48. package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -3
  49. package/catalog/frontmcp-deployment/examples/build-for-sdk/create-flat-config.md +1 -2
  50. package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +3 -3
  51. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +1 -2
  52. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +1 -2
  53. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +1 -2
  54. package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +1 -2
  55. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +1 -2
  56. package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +106 -0
  57. package/catalog/frontmcp-deployment/examples/mcp-client-integration/stdio-binary-with-env.md +107 -0
  58. package/catalog/frontmcp-deployment/examples/mcp-client-integration/stdio-npx.md +89 -0
  59. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +209 -0
  60. package/catalog/frontmcp-deployment/references/build-for-sdk.md +1 -2
  61. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +225 -0
  62. package/catalog/frontmcp-development/SKILL.md +4 -3
  63. package/catalog/frontmcp-development/examples/create-agent/basic-agent-with-tools.md +3 -6
  64. package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -2
  65. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +2 -4
  66. package/catalog/frontmcp-development/examples/create-agent-llm-config/anthropic-config.md +1 -2
  67. package/catalog/frontmcp-development/examples/create-agent-llm-config/openai-config.md +1 -2
  68. package/catalog/frontmcp-development/examples/create-job/basic-report-job.md +1 -2
  69. package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +2 -3
  70. package/catalog/frontmcp-development/examples/create-job/job-with-retry.md +1 -2
  71. package/catalog/frontmcp-development/examples/create-plugin-hooks/tool-level-hooks-and-stage-replacement.md +2 -5
  72. package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +4 -3
  73. package/catalog/frontmcp-development/examples/create-skill-with-tools/directory-skill-with-tools.md +2 -3
  74. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +1 -2
  75. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +2 -2
  76. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +1 -2
  77. package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +2 -4
  78. package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +1 -2
  79. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +3 -6
  80. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +1 -2
  81. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +2 -4
  82. package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +3 -5
  83. package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +5 -5
  84. package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +4 -6
  85. package/catalog/frontmcp-development/examples/official-plugins/cache-and-feature-flags.md +3 -5
  86. package/catalog/frontmcp-development/examples/official-plugins/production-multi-plugin-setup.md +4 -5
  87. package/catalog/frontmcp-development/examples/official-plugins/remember-plugin-session-memory.md +3 -5
  88. package/catalog/frontmcp-development/examples/{official-adapters → openapi-adapter}/authenticated-adapter-with-polling.md +2 -2
  89. package/catalog/frontmcp-development/examples/{official-adapters → openapi-adapter}/basic-openapi-adapter.md +2 -2
  90. package/catalog/frontmcp-development/examples/openapi-adapter/format-resolution-and-custom-resolvers.md +108 -0
  91. package/catalog/frontmcp-development/examples/{official-adapters → openapi-adapter}/multi-api-hub-with-inline-spec.md +2 -2
  92. package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +111 -0
  93. package/catalog/frontmcp-development/references/create-agent.md +4 -7
  94. package/catalog/frontmcp-development/references/create-job.md +3 -6
  95. package/catalog/frontmcp-development/references/create-plugin-hooks.md +12 -16
  96. package/catalog/frontmcp-development/references/create-skill-with-tools.md +2 -3
  97. package/catalog/frontmcp-development/references/create-tool.md +93 -23
  98. package/catalog/frontmcp-development/references/create-workflow.md +2 -3
  99. package/catalog/frontmcp-development/references/decorators-guide.md +32 -36
  100. package/catalog/frontmcp-development/references/official-adapters.md +24 -153
  101. package/catalog/frontmcp-development/references/openapi-adapter.md +431 -0
  102. package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +4 -4
  103. package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -4
  104. package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +4 -3
  105. package/catalog/frontmcp-guides/SKILL.md +3 -3
  106. package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +4 -5
  107. package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +4 -3
  108. package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +4 -4
  109. package/catalog/frontmcp-guides/examples/example-weather-api/weather-tool-with-schemas.md +1 -2
  110. package/catalog/frontmcp-guides/references/example-knowledge-base.md +22 -17
  111. package/catalog/frontmcp-guides/references/example-task-manager.md +16 -11
  112. package/catalog/frontmcp-guides/references/example-weather-api.md +6 -3
  113. package/catalog/frontmcp-observability/examples/telemetry-api/tool-custom-spans.md +2 -3
  114. package/catalog/frontmcp-observability/examples/tracing-setup/basic-tracing.md +4 -3
  115. package/catalog/frontmcp-observability/references/telemetry-api.md +2 -3
  116. package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -2
  117. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -4
  118. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +229 -0
  119. package/catalog/frontmcp-production-readiness/examples/production-browser/cross-platform-crypto.md +2 -3
  120. package/catalog/frontmcp-production-readiness/examples/production-cli-binary/stdio-transport-error-handling.md +1 -2
  121. package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -4
  122. package/catalog/frontmcp-production-readiness/examples/production-cloudflare/workers-runtime-constraints.md +2 -3
  123. package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +3 -2
  124. package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +2 -2
  125. package/catalog/frontmcp-production-readiness/examples/production-vercel/stateless-serverless-design.md +3 -3
  126. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +194 -0
  127. package/catalog/frontmcp-setup/SKILL.md +11 -11
  128. package/catalog/frontmcp-setup/examples/project-structure-standalone/feature-folder-organization.md +5 -3
  129. package/catalog/frontmcp-setup/examples/project-structure-standalone/minimal-standalone-layout.md +4 -2
  130. package/catalog/frontmcp-setup/examples/setup-project/basic-node-server.md +4 -2
  131. package/catalog/frontmcp-setup/examples/setup-project/vercel-serverless-server.md +4 -2
  132. package/catalog/frontmcp-setup/examples/setup-redis/hybrid-vercel-kv-with-pubsub.md +8 -7
  133. package/catalog/frontmcp-setup/references/setup-project.md +10 -9
  134. package/catalog/frontmcp-setup/references/setup-redis.md +19 -16
  135. package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +1 -3
  136. package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +1 -3
  137. package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +2 -2
  138. package/catalog/frontmcp-testing/references/test-direct-client.md +1 -3
  139. package/catalog/frontmcp-testing/references/test-tool-unit.md +2 -2
  140. package/catalog/skills-manifest.json +364 -12
  141. package/package.json +1 -1
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: claims-mapping
3
+ description: JWT claims mapping configuration per identity provider (Auth0, Keycloak, Okta, Cognito, Frontegg) for the authorities system
4
+ ---
5
+
6
+ # JWT Claims Mapping
7
+
8
+ The `claimsMapping` field in `@FrontMcp({ authorities: { claimsMapping } })` tells the authorities engine where to find roles, permissions, tenant ID, and user ID within the decoded JWT payload. Every identity provider structures its tokens differently, so this mapping is required for correct policy evaluation.
9
+
10
+ ## How Claims Mapping Works
11
+
12
+ The engine receives `authInfo` on each request, which contains the decoded JWT claims. The `claimsMapping` fields are dot-paths resolved against these claims using `resolveDotPath()`. The resolver first tries a direct key lookup (for keys containing dots, such as Auth0 namespaced claims), then falls back to dot-separated path traversal.
13
+
14
+ ```typescript
15
+ interface AuthoritiesClaimsMapping {
16
+ /** Dot-path to roles array in JWT claims */
17
+ roles?: string;
18
+ /** Dot-path to permissions array/string in JWT claims */
19
+ permissions?: string;
20
+ /** Dot-path to tenant/org ID in JWT claims */
21
+ tenantId?: string;
22
+ /** Dot-path to user ID in JWT claims (default: 'sub') */
23
+ userId?: string;
24
+ /** Extensible: additional custom claim mappings */
25
+ [key: string]: string | undefined;
26
+ }
27
+ ```
28
+
29
+ ## Auth0
30
+
31
+ Auth0 stores roles in a custom namespaced claim (you define the namespace) and permissions in a flat `permissions` array. The org ID (if using Auth0 Organizations) is in `org_id`.
32
+
33
+ **Sample JWT payload:**
34
+
35
+ ```json
36
+ {
37
+ "sub": "auth0|abc123",
38
+ "https://myapp.com/roles": ["admin", "editor"],
39
+ "permissions": ["users:read", "users:write", "content:publish"],
40
+ "org_id": "org_def456",
41
+ "aud": "https://api.myapp.com",
42
+ "iss": "https://myapp.auth0.com/"
43
+ }
44
+ ```
45
+
46
+ **Claims mapping:**
47
+
48
+ ```typescript
49
+ // In @FrontMcp({ authorities: { ... } })
50
+ authorities: {
51
+ claimsMapping: {
52
+ roles: 'https://myapp.com/roles',
53
+ permissions: 'permissions',
54
+ tenantId: 'org_id',
55
+ },
56
+ }
57
+ ```
58
+
59
+ **Notes:**
60
+
61
+ - The roles claim namespace (`https://myapp.com/roles`) is configured in an Auth0 Action or Rule. It must be a fully qualified URL.
62
+ - `resolveDotPath` handles the dotted namespace key via direct key lookup, so `https://myapp.com/roles` works correctly.
63
+ - Permissions appear only if the API has RBAC enabled in Auth0 dashboard.
64
+
65
+ ## Keycloak
66
+
67
+ Keycloak nests realm roles under `realm_access.roles` and client-specific roles under `resource_access.<client-id>.roles`. Permissions are typically modeled as client roles or fine-grained UMA permissions.
68
+
69
+ **Sample JWT payload:**
70
+
71
+ ```json
72
+ {
73
+ "sub": "f1b2c3d4-e5f6-7890-abcd-ef1234567890",
74
+ "realm_access": {
75
+ "roles": ["admin", "user", "offline_access"]
76
+ },
77
+ "resource_access": {
78
+ "my-client": {
79
+ "roles": ["content:write", "content:publish"]
80
+ },
81
+ "account": {
82
+ "roles": ["manage-account"]
83
+ }
84
+ },
85
+ "preferred_username": "jane.doe",
86
+ "azp": "my-client",
87
+ "iss": "https://keycloak.example.com/realms/myrealm"
88
+ }
89
+ ```
90
+
91
+ **Claims mapping:**
92
+
93
+ ```typescript
94
+ // In @FrontMcp({ authorities: { ... } })
95
+ authorities: {
96
+ claimsMapping: {
97
+ roles: 'realm_access.roles',
98
+ permissions: 'resource_access.my-client.roles',
99
+ },
100
+ }
101
+ ```
102
+
103
+ **Notes:**
104
+
105
+ - Replace `my-client` with your actual Keycloak client ID.
106
+ - Keycloak adds system roles like `offline_access` and `uma_authorization` to `realm_access.roles`. You may want to use `all` instead of `any` in RBAC policies to avoid matching on these.
107
+ - For multi-tenant Keycloak setups, tenant ID is often a custom claim added via a protocol mapper.
108
+
109
+ ## Okta
110
+
111
+ Okta uses `groups` for role-like claims and `scp` (or `scope`) for permission-like claims. Groups must be explicitly included in the token via an Authorization Server claim configuration.
112
+
113
+ **Sample JWT payload:**
114
+
115
+ ```json
116
+ {
117
+ "sub": "00u1a2b3c4d5e6f7g8h9",
118
+ "groups": ["Admin", "Engineering", "Everyone"],
119
+ "scp": ["openid", "profile", "users.read", "users.write"],
120
+ "cid": "0oa1b2c3d4e5f6g7h8",
121
+ "uid": "00u1a2b3c4d5e6f7g8h9",
122
+ "iss": "https://myorg.okta.com/oauth2/default"
123
+ }
124
+ ```
125
+
126
+ **Claims mapping:**
127
+
128
+ ```typescript
129
+ // In @FrontMcp({ authorities: { ... } })
130
+ authorities: {
131
+ claimsMapping: {
132
+ roles: 'groups',
133
+ permissions: 'scp',
134
+ },
135
+ }
136
+ ```
137
+
138
+ **Notes:**
139
+
140
+ - Groups must be added as a claim to the authorization server. Go to Security > API > Authorization Servers > Claims > Add Claim with value type "Groups" and filter "Matches regex `.*`".
141
+ - `scp` is an array of scope strings. If your Okta config uses `scope` (singular), adjust the path accordingly.
142
+ - Okta does not have a built-in org/tenant claim. For multi-tenant, add a custom claim via a token hook.
143
+
144
+ ## Amazon Cognito
145
+
146
+ Cognito uses `cognito:groups` for groups and `scope` (space-separated string) for OAuth scopes. Custom attributes use the `custom:` prefix.
147
+
148
+ **Sample JWT payload:**
149
+
150
+ ```json
151
+ {
152
+ "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
153
+ "cognito:groups": ["Admins", "PowerUsers"],
154
+ "scope": "openid profile aws.cognito.signin.user.admin",
155
+ "custom:tenantId": "tenant-abc",
156
+ "client_id": "1a2b3c4d5e6f7g8h9i0j",
157
+ "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_AbCdEfG"
158
+ }
159
+ ```
160
+
161
+ **Claims mapping:**
162
+
163
+ ```typescript
164
+ // In @FrontMcp({ authorities: { ... } })
165
+ authorities: {
166
+ claimsMapping: {
167
+ roles: 'cognito:groups',
168
+ permissions: 'scope',
169
+ tenantId: 'custom:tenantId',
170
+ },
171
+ }
172
+ ```
173
+
174
+ **Notes:**
175
+
176
+ - `scope` in Cognito is a space-separated string, not an array. The authorities engine handles this automatically via `toStringArray()`, which splits space-separated strings.
177
+ - `cognito:groups` contains dots in the key name. `resolveDotPath` handles this via direct key lookup.
178
+ - Custom attributes require the `custom:` prefix in both the claim path and the User Pool schema.
179
+
180
+ ## Frontegg
181
+
182
+ Frontegg places roles and permissions as flat arrays at the top level. Tenant ID is in `tenantId`. The token also includes `tenantIds` (array of all tenant memberships) for multi-tenant scenarios.
183
+
184
+ **Sample JWT payload:**
185
+
186
+ ```json
187
+ {
188
+ "sub": "user-uuid-123",
189
+ "roles": ["Admin", "ReadOnly"],
190
+ "permissions": ["fe.users.read", "fe.users.write", "fe.tenant.admin"],
191
+ "tenantId": "tenant-uuid-456",
192
+ "tenantIds": ["tenant-uuid-456", "tenant-uuid-789"],
193
+ "email": "jane@example.com",
194
+ "iss": "https://app-abc.frontegg.com"
195
+ }
196
+ ```
197
+
198
+ **Claims mapping:**
199
+
200
+ ```typescript
201
+ // In @FrontMcp({ authorities: { ... } })
202
+ authorities: {
203
+ claimsMapping: {
204
+ roles: 'roles',
205
+ permissions: 'permissions',
206
+ tenantId: 'tenantId',
207
+ },
208
+ }
209
+ ```
210
+
211
+ **Notes:**
212
+
213
+ - Frontegg tokens have the simplest structure since roles and permissions are flat top-level arrays.
214
+ - The default fallback behavior (no `claimsMapping`) already looks for `roles` and `permissions` at the top level, so Frontegg often works without explicit mapping.
215
+ - For cross-tenant operations, use `tenantIds` (array) instead of `tenantId` (single active tenant).
216
+
217
+ ## Custom IdP / claimsResolver
218
+
219
+ If your JWT structure does not fit the dot-path model, use `claimsResolver` instead of `claimsMapping`. This gives full programmatic control over claim extraction.
220
+
221
+ ```typescript
222
+ // In @FrontMcp({ authorities: { ... } })
223
+ authorities: {
224
+ claimsResolver: (authInfo) => {
225
+ const claims = authInfo?.extra?.authorization?.claims ?? {};
226
+ const user = authInfo?.user ?? {};
227
+
228
+ // Custom extraction logic
229
+ const roles = (claims['x-custom-roles'] as string || '').split(',').filter(Boolean);
230
+ const permissions = Array.isArray(claims['x-permissions'])
231
+ ? claims['x-permissions']
232
+ : [];
233
+
234
+ return {
235
+ roles,
236
+ permissions,
237
+ claims: { ...claims, ...user },
238
+ };
239
+ },
240
+ }
241
+ ```
242
+
243
+ **When to use `claimsResolver`:**
244
+
245
+ - The token has roles encoded as a comma-separated string or bitmask
246
+ - Roles need to be derived from multiple claim fields
247
+ - Permissions require runtime transformation (e.g., wildcard expansion)
248
+ - The token structure is deeply nested or unconventional
249
+
250
+ `claimsResolver` takes precedence over `claimsMapping` when both are provided.
251
+
252
+ ## Quick Reference Table
253
+
254
+ | IdP | Roles Path | Permissions Path | Tenant Path | Notes |
255
+ | -------- | ------------------------- | -------------------------------- | ----------------- | --------------------------------- |
256
+ | Auth0 | `https://myapp.com/roles` | `permissions` | `org_id` | Namespace is developer-defined |
257
+ | Keycloak | `realm_access.roles` | `resource_access.<client>.roles` | Custom claim | Replace `<client>` with client ID |
258
+ | Okta | `groups` | `scp` | Custom claim | Groups must be added as a claim |
259
+ | Cognito | `cognito:groups` | `scope` | `custom:tenantId` | `scope` is space-separated |
260
+ | Frontegg | `roles` | `permissions` | `tenantId` | Works with default fallback |
261
+
262
+ ## Reference
263
+
264
+ - Type: `AuthoritiesClaimsMapping` in `libs/auth/src/authorities/authorities.profiles.ts`
265
+ - Context builder: `AuthoritiesContextBuilder` in `libs/auth/src/authorities/authorities.context.ts`
266
+ - Related: `references/authority-profiles.md`, `references/rbac-abac-rebac.md`
@@ -0,0 +1,420 @@
1
+ ---
2
+ name: custom-evaluators
3
+ description: Creating and registering custom authority evaluators for domain-specific authorization logic beyond RBAC, ABAC, and ReBAC
4
+ ---
5
+
6
+ # Custom Evaluators
7
+
8
+ When the built-in RBAC, ABAC, and ReBAC models do not cover your authorization requirements, you can create custom evaluators. Custom evaluators are registered via the `authorities.evaluators` config in `@FrontMcp()` and invoked via the `custom.*` field in policies.
9
+
10
+ ## AuthoritiesEvaluator Interface
11
+
12
+ Every custom evaluator must implement the `AuthoritiesEvaluator` interface from `@frontmcp/auth`.
13
+
14
+ ```typescript
15
+ import type { AuthoritiesEvaluator, AuthoritiesEvaluationContext, AuthoritiesResult } from '@frontmcp/auth';
16
+
17
+ interface AuthoritiesEvaluator {
18
+ /** Evaluator name (must match the key under `custom.*` in policies) */
19
+ name: string;
20
+ /** Evaluate the policy against the context */
21
+ evaluate(policy: unknown, ctx: AuthoritiesEvaluationContext): Promise<AuthoritiesResult>;
22
+ }
23
+ ```
24
+
25
+ The `policy` parameter is whatever value is passed under the evaluator's key in the `custom` field. The `ctx` parameter provides the full evaluation context including user info, input, environment, and the relationship resolver.
26
+
27
+ The return value must be an `AuthoritiesResult`:
28
+
29
+ ```typescript
30
+ interface AuthoritiesResult {
31
+ /** Whether access was granted */
32
+ granted: boolean;
33
+ /** Human-readable reason for denial */
34
+ deniedBy?: string;
35
+ /** List of policy types that were evaluated (for audit) */
36
+ evaluatedPolicies: string[];
37
+ /** Optional detailed message */
38
+ message?: string;
39
+ }
40
+ ```
41
+
42
+ ## Registering Custom Evaluators
43
+
44
+ Custom evaluators are registered in the `evaluators` field of the `authorities` config. The key must match the evaluator's `name` and the key used in `custom.*` policies.
45
+
46
+ ```typescript
47
+ import { FrontMcp } from '@frontmcp/sdk';
48
+ import { ipAllowListEvaluator } from './evaluators/ip-allow-list';
49
+ import { featureFlagEvaluator } from './evaluators/feature-flag';
50
+ import { timeWindowEvaluator } from './evaluators/time-window';
51
+
52
+ @FrontMcp({
53
+ name: 'my-server',
54
+ authorities: {
55
+ claimsMapping: { roles: 'roles', permissions: 'permissions' },
56
+ profiles: { admin: { roles: { any: ['admin'] } } },
57
+ evaluators: {
58
+ ipAllowList: ipAllowListEvaluator,
59
+ featureFlag: featureFlagEvaluator,
60
+ timeWindow: timeWindowEvaluator,
61
+ },
62
+ },
63
+ })
64
+ export class MyServer {}
65
+ ```
66
+
67
+ ## Async Guards: DB/Redis Lookups
68
+
69
+ Custom evaluators are **fully async** — use them for database queries, Redis checks, feature flags, or any I/O-bound authorization logic.
70
+
71
+ ### Redis Set Membership
72
+
73
+ ```typescript
74
+ const tenantAllowlistGuard: AuthoritiesEvaluator = {
75
+ name: 'tenantAllowlist',
76
+ evaluate: async (policy, ctx) => {
77
+ const { redisKey } = policy as { redisKey: string };
78
+ const tenantId = ctx.input['tenantId'] as string;
79
+ const isAllowed = await redis.sismember(redisKey, tenantId);
80
+ return {
81
+ granted: isAllowed,
82
+ deniedBy: isAllowed ? undefined : `tenant '${tenantId}' not in allowlist`,
83
+ denial: isAllowed ? undefined : { kind: 'custom', path: 'custom.tenantAllowlist' },
84
+ evaluatedPolicies: ['custom.tenantAllowlist'],
85
+ };
86
+ },
87
+ };
88
+ ```
89
+
90
+ ### Database Query
91
+
92
+ ```typescript
93
+ const activeSubscriptionGuard: AuthoritiesEvaluator = {
94
+ name: 'activeSubscription',
95
+ evaluate: async (_policy, ctx) => {
96
+ const row = await db.query('SELECT active FROM subscriptions WHERE user_id = $1', [ctx.user.sub]);
97
+ const active = row?.active === true;
98
+ return {
99
+ granted: active,
100
+ deniedBy: active ? undefined : 'no active subscription',
101
+ denial: active ? undefined : { kind: 'custom', path: 'custom.activeSubscription' },
102
+ evaluatedPolicies: ['custom.activeSubscription'],
103
+ };
104
+ },
105
+ };
106
+ ```
107
+
108
+ ### Feature Flag
109
+
110
+ ```typescript
111
+ const featureFlagGuard: AuthoritiesEvaluator = {
112
+ name: 'featureFlag',
113
+ evaluate: async (policy, ctx) => {
114
+ const { flag } = policy as { flag: string };
115
+ const enabled = await featureFlagService.isEnabled(flag, { userId: ctx.user.sub });
116
+ return {
117
+ granted: enabled,
118
+ deniedBy: enabled ? undefined : `feature '${flag}' not enabled`,
119
+ denial: enabled ? undefined : { kind: 'custom', path: `custom.featureFlag.${flag}` },
120
+ evaluatedPolicies: [`custom.featureFlag`],
121
+ };
122
+ },
123
+ };
124
+ ```
125
+
126
+ ### When to Use Custom Evaluators vs Hooks
127
+
128
+ | Approach | When to Use |
129
+ | ---------------------------------------- | ------------------------------------------------------------------------------------ |
130
+ | **Custom evaluator** | Reusable async check across many tools — register once, reference via `custom` field |
131
+ | **`Will('checkEntryAuthorities')` hook** | One-off async check for a specific plugin/app, not tied to a specific tool |
132
+ | **Static `authorities` policy** | Roles, permissions, attributes — no I/O needed |
133
+
134
+ ## Using Custom Evaluators in Policies
135
+
136
+ Reference custom evaluators via the `custom` field on any `AuthoritiesPolicyMetadata`. The key under `custom` must match the registered evaluator name.
137
+
138
+ ```typescript
139
+ @Tool({
140
+ name: 'admin_panel',
141
+ authorities: {
142
+ roles: { any: ['admin'] },
143
+ custom: {
144
+ ipAllowList: { cidr: ['10.0.0.0/8', '172.16.0.0/12'] },
145
+ },
146
+ },
147
+ })
148
+ export default class AdminPanelTool extends ToolContext { ... }
149
+ ```
150
+
151
+ The engine evaluates `custom` evaluators alongside RBAC, ABAC, and ReBAC. They follow the same `operator` semantics (default `'AND'`), so in the example above the user must have the `admin` role AND pass the IP allowlist check.
152
+
153
+ ## Example: IP Allowlist Evaluator
154
+
155
+ Restricts access to requests from specific CIDR ranges.
156
+
157
+ ```typescript
158
+ // evaluators/ip-allow-list.ts
159
+ import type { AuthoritiesEvaluator, AuthoritiesEvaluationContext, AuthoritiesResult } from '@frontmcp/auth';
160
+
161
+ interface IpAllowListPolicy {
162
+ cidr: string[];
163
+ }
164
+
165
+ function isInCidr(ip: string, cidr: string): boolean {
166
+ // Simplified -- use a library like 'ip-cidr' in production
167
+ const [subnet, bits] = cidr.split('/');
168
+ if (!bits) return ip === subnet;
169
+ // ... full CIDR matching logic
170
+ return false;
171
+ }
172
+
173
+ export const ipAllowListEvaluator: AuthoritiesEvaluator = {
174
+ name: 'ipAllowList',
175
+ async evaluate(policy: unknown, ctx: AuthoritiesEvaluationContext): Promise<AuthoritiesResult> {
176
+ const { cidr } = policy as IpAllowListPolicy;
177
+ const remoteIp = ctx.env['remoteIp'] as string | undefined;
178
+
179
+ if (!remoteIp) {
180
+ return {
181
+ granted: false,
182
+ deniedBy: 'custom.ipAllowList: remoteIp not available in env',
183
+ evaluatedPolicies: ['custom.ipAllowList'],
184
+ };
185
+ }
186
+
187
+ const allowed = cidr.some((c) => isInCidr(remoteIp, c));
188
+
189
+ return {
190
+ granted: allowed,
191
+ deniedBy: allowed ? undefined : `custom.ipAllowList: ${remoteIp} not in allowed CIDR ranges`,
192
+ evaluatedPolicies: ['custom.ipAllowList'],
193
+ };
194
+ },
195
+ };
196
+ ```
197
+
198
+ **Usage:**
199
+
200
+ ```typescript
201
+ @Tool({
202
+ name: 'internal_admin',
203
+ authorities: {
204
+ custom: {
205
+ ipAllowList: { cidr: ['10.0.0.0/8', '192.168.0.0/16'] },
206
+ },
207
+ },
208
+ })
209
+ export default class InternalAdminTool extends ToolContext { ... }
210
+ ```
211
+
212
+ ## Example: Feature Flag Evaluator
213
+
214
+ Gates access behind a feature flag service.
215
+
216
+ ```typescript
217
+ // evaluators/feature-flag.ts
218
+ import type { AuthoritiesEvaluator, AuthoritiesEvaluationContext, AuthoritiesResult } from '@frontmcp/auth';
219
+
220
+ interface FeatureFlagPolicy {
221
+ flag: string;
222
+ /** If true, the flag must be disabled for access (inverse gate) */
223
+ inverse?: boolean;
224
+ }
225
+
226
+ // Assume a global feature flag client is available
227
+ declare const featureFlags: { isEnabled(flag: string, userId: string): Promise<boolean> };
228
+
229
+ export const featureFlagEvaluator: AuthoritiesEvaluator = {
230
+ name: 'featureFlag',
231
+ async evaluate(policy: unknown, ctx: AuthoritiesEvaluationContext): Promise<AuthoritiesResult> {
232
+ const { flag, inverse } = policy as FeatureFlagPolicy;
233
+ const enabled = await featureFlags.isEnabled(flag, ctx.user.sub);
234
+ const granted = inverse ? !enabled : enabled;
235
+
236
+ return {
237
+ granted,
238
+ deniedBy: granted
239
+ ? undefined
240
+ : `custom.featureFlag: '${flag}' is ${enabled ? 'enabled' : 'disabled'} (inverse=${!!inverse})`,
241
+ evaluatedPolicies: ['custom.featureFlag'],
242
+ };
243
+ },
244
+ };
245
+ ```
246
+
247
+ **Usage:**
248
+
249
+ ```typescript
250
+ @Tool({
251
+ name: 'beta_feature',
252
+ authorities: {
253
+ custom: {
254
+ featureFlag: { flag: 'beta-tools-v2' },
255
+ },
256
+ },
257
+ })
258
+ export default class BetaFeatureTool extends ToolContext { ... }
259
+ ```
260
+
261
+ ## Example: Time Window Evaluator
262
+
263
+ Restricts access to specific time windows (e.g., business hours only).
264
+
265
+ ```typescript
266
+ // evaluators/time-window.ts
267
+ import type { AuthoritiesEvaluator, AuthoritiesEvaluationContext, AuthoritiesResult } from '@frontmcp/auth';
268
+
269
+ interface TimeWindowPolicy {
270
+ /** Allowed days (0=Sunday, 6=Saturday) */
271
+ days: number[];
272
+ /** Start hour (0-23, inclusive) */
273
+ startHour: number;
274
+ /** End hour (0-23, exclusive) */
275
+ endHour: number;
276
+ /** IANA timezone (default: UTC) */
277
+ timezone?: string;
278
+ }
279
+
280
+ export const timeWindowEvaluator: AuthoritiesEvaluator = {
281
+ name: 'timeWindow',
282
+ async evaluate(policy: unknown, ctx: AuthoritiesEvaluationContext): Promise<AuthoritiesResult> {
283
+ const { days, startHour, endHour, timezone } = policy as TimeWindowPolicy;
284
+ const now = new Date();
285
+ const formatter = new Intl.DateTimeFormat('en-US', {
286
+ timeZone: timezone ?? 'UTC',
287
+ hour: 'numeric',
288
+ hour12: false,
289
+ weekday: 'short',
290
+ });
291
+
292
+ const parts = formatter.formatToParts(now);
293
+ const hour = parseInt(parts.find((p) => p.type === 'hour')?.value ?? '0', 10);
294
+ // Get day of week in the configured timezone (not local system timezone)
295
+ const dayName = new Intl.DateTimeFormat('en-US', { timeZone: timezone ?? 'UTC', weekday: 'long' }).format(now);
296
+ const dayIndex = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].indexOf(dayName);
297
+
298
+ const dayAllowed = days.includes(dayIndex);
299
+ const hourAllowed = hour >= startHour && hour < endHour;
300
+ const granted = dayAllowed && hourAllowed;
301
+
302
+ return {
303
+ granted,
304
+ deniedBy: granted ? undefined : `custom.timeWindow: current time outside allowed window`,
305
+ evaluatedPolicies: ['custom.timeWindow'],
306
+ };
307
+ },
308
+ };
309
+ ```
310
+
311
+ **Usage:**
312
+
313
+ ```typescript
314
+ @Tool({
315
+ name: 'payroll_sync',
316
+ authorities: {
317
+ roles: { any: ['finance'] },
318
+ custom: {
319
+ timeWindow: {
320
+ days: [1, 2, 3, 4, 5], // Monday through Friday
321
+ startHour: 9,
322
+ endHour: 17,
323
+ timezone: 'America/New_York',
324
+ },
325
+ },
326
+ },
327
+ })
328
+ export default class PayrollSyncTool extends ToolContext { ... }
329
+ ```
330
+
331
+ ## Example: Rate Limit Evaluator
332
+
333
+ Denies access when a per-user rate limit is exceeded.
334
+
335
+ ```typescript
336
+ // evaluators/rate-limit.ts
337
+ import type { AuthoritiesEvaluator, AuthoritiesEvaluationContext, AuthoritiesResult } from '@frontmcp/auth';
338
+
339
+ interface RateLimitPolicy {
340
+ /** Maximum calls allowed */
341
+ max: number;
342
+ /** Time window in seconds */
343
+ windowSeconds: number;
344
+ }
345
+
346
+ // In-memory counter (use Redis in production)
347
+ const counters = new Map<string, { count: number; resetAt: number }>();
348
+
349
+ export const rateLimitEvaluator: AuthoritiesEvaluator = {
350
+ name: 'rateLimit',
351
+ async evaluate(policy: unknown, ctx: AuthoritiesEvaluationContext): Promise<AuthoritiesResult> {
352
+ const { max, windowSeconds } = policy as RateLimitPolicy;
353
+ const key = `${ctx.user.sub}`;
354
+ const now = Date.now();
355
+
356
+ let entry = counters.get(key);
357
+ if (!entry || now > entry.resetAt) {
358
+ entry = { count: 0, resetAt: now + windowSeconds * 1000 };
359
+ counters.set(key, entry);
360
+ }
361
+
362
+ entry.count++;
363
+
364
+ if (entry.count > max) {
365
+ return {
366
+ granted: false,
367
+ deniedBy: `custom.rateLimit: ${entry.count}/${max} calls in window`,
368
+ evaluatedPolicies: ['custom.rateLimit'],
369
+ };
370
+ }
371
+
372
+ return {
373
+ granted: true,
374
+ evaluatedPolicies: ['custom.rateLimit'],
375
+ };
376
+ },
377
+ };
378
+ ```
379
+
380
+ ## Combining Custom Evaluators with Built-in Models
381
+
382
+ Custom evaluators participate in the same combinator system as RBAC, ABAC, and ReBAC.
383
+
384
+ ```typescript
385
+ // Admin from allowed IP during business hours
386
+ authorities: {
387
+ allOf: [
388
+ { roles: { any: ['admin'] } },
389
+ { custom: { ipAllowList: { cidr: ['10.0.0.0/8'] } } },
390
+ { custom: { timeWindow: { days: [1, 2, 3, 4, 5], startHour: 9, endHour: 17 } } },
391
+ ],
392
+ }
393
+
394
+ // Feature flag OR admin bypass
395
+ authorities: {
396
+ anyOf: [
397
+ { roles: { any: ['admin'] } },
398
+ { custom: { featureFlag: { flag: 'new-dashboard' } } },
399
+ ],
400
+ }
401
+ ```
402
+
403
+ ## Best Practices
404
+
405
+ | Practice | Description |
406
+ | --------------------------------------- | ----------------------------------------------------------------------------------- |
407
+ | Always return `evaluatedPolicies` | Include `custom.<name>` in the array for audit trail |
408
+ | Provide descriptive `deniedBy` | Include the evaluator name, the failing condition, and relevant values |
409
+ | Keep evaluators stateless when possible | Use external stores (Redis, database) for state; avoid in-memory maps in production |
410
+ | Type the policy parameter | Cast `policy as YourPolicyType` at the top of `evaluate()` |
411
+ | Handle missing context gracefully | Return denial with a clear message if expected env/input values are absent |
412
+ | Test evaluators in isolation | Each evaluator is a plain object; test `evaluate()` directly with mock contexts |
413
+ | Name evaluators with camelCase | The name must match both the registered key and the `custom.*` policy key |
414
+
415
+ ## Reference
416
+
417
+ - Type: `AuthoritiesEvaluator` in `libs/auth/src/authorities/authorities.types.ts`
418
+ - Registry: `AuthoritiesEvaluatorRegistry` in `libs/auth/src/authorities/authorities.registry.ts`
419
+ - Engine dispatch: `evaluateCustom()` in `libs/auth/src/authorities/authorities.engine.ts`
420
+ - Related: `references/authority-profiles.md`, `references/rbac-abac-rebac.md`