@frontmcp/skills 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +38 -29
  2. package/catalog/TEMPLATE.md +26 -0
  3. package/catalog/create-tool/SKILL.md +318 -0
  4. package/catalog/create-tool/examples/01-basic-class-tool.md +112 -0
  5. package/catalog/create-tool/examples/02-basic-function-tool.md +80 -0
  6. package/catalog/create-tool/examples/03-tool-with-zod-shape-output.md +78 -0
  7. package/catalog/create-tool/examples/04-tool-with-zod-schema-output.md +97 -0
  8. package/catalog/create-tool/examples/05-tool-with-primitive-output.md +93 -0
  9. package/catalog/create-tool/examples/06-tool-with-media-output.md +109 -0
  10. package/catalog/create-tool/examples/08-tool-with-provider-injection.md +110 -0
  11. package/catalog/create-tool/examples/09-tool-with-multiple-providers.md +107 -0
  12. package/catalog/create-tool/examples/11-tool-with-fetch.md +94 -0
  13. package/catalog/create-tool/examples/12-tool-with-fetch-and-retries.md +115 -0
  14. package/catalog/create-tool/examples/13-tool-with-single-auth-provider.md +85 -0
  15. package/catalog/create-tool/examples/14-tool-with-multiple-auth-providers.md +105 -0
  16. package/catalog/create-tool/examples/15-tool-with-credential-vault.md +115 -0
  17. package/catalog/create-tool/examples/16-tool-with-rate-limit.md +71 -0
  18. package/catalog/create-tool/examples/17-tool-with-concurrency-and-timeout.md +101 -0
  19. package/catalog/create-tool/examples/18-tool-with-progress-and-notify.md +96 -0
  20. package/catalog/create-tool/examples/19-tool-with-elicitation.md +102 -0
  21. package/catalog/create-tool/examples/20-tool-with-annotations.md +125 -0
  22. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +107 -0
  23. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +93 -0
  24. package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +112 -0
  25. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +127 -0
  26. package/catalog/create-tool/examples/25-tool-handing-off-to-job.md +143 -0
  27. package/catalog/create-tool/examples/26-tool-with-resource-link-output.md +94 -0
  28. package/catalog/create-tool/examples/27-tool-with-examples-metadata.md +90 -0
  29. package/catalog/create-tool/references/annotations.md +96 -0
  30. package/catalog/create-tool/references/auth-providers.md +167 -0
  31. package/catalog/create-tool/references/availability.md +106 -0
  32. package/catalog/create-tool/references/decorator-options.md +95 -0
  33. package/catalog/create-tool/references/derived-types.md +102 -0
  34. package/catalog/create-tool/references/elicitation.md +128 -0
  35. package/catalog/create-tool/references/error-handling.md +128 -0
  36. package/catalog/create-tool/references/execution-context.md +158 -0
  37. package/catalog/create-tool/references/file-layout.md +96 -0
  38. package/catalog/create-tool/references/function-style-builder.md +118 -0
  39. package/catalog/create-tool/references/input-schema.md +141 -0
  40. package/catalog/create-tool/references/output-schema.md +175 -0
  41. package/catalog/create-tool/references/quick-start.md +124 -0
  42. package/catalog/create-tool/references/registration.md +132 -0
  43. package/catalog/create-tool/references/remote-and-esm.md +68 -0
  44. package/catalog/create-tool/references/testing.md +59 -0
  45. package/catalog/create-tool/references/throttling.md +109 -0
  46. package/catalog/create-tool/references/ui-widgets.md +198 -0
  47. package/catalog/create-tool/rules/always-define-output-schema.md +77 -0
  48. package/catalog/create-tool/rules/derive-execute-types.md +57 -0
  49. package/catalog/create-tool/rules/input-schema-is-raw-shape.md +76 -0
  50. package/catalog/create-tool/rules/no-toolcontext-generics.md +50 -0
  51. package/catalog/create-tool/rules/no-try-catch-around-execute.md +79 -0
  52. package/catalog/create-tool/rules/register-in-app.md +76 -0
  53. package/catalog/create-tool/rules/snake-case-tool-names.md +45 -0
  54. package/catalog/create-tool/rules/use-this-fail-for-business-errors.md +75 -0
  55. package/catalog/create-tool/rules/widget-paths-anchor-with-import-meta-url.md +76 -0
  56. package/catalog/create-tool/rules/widget-resource-mode-host-detect.md +61 -0
  57. package/catalog/frontmcp-auth-ui/SKILL.md +146 -0
  58. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/login-slot.md +97 -0
  59. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/multi-step-auth-extra.md +133 -0
  60. package/catalog/frontmcp-auth-ui/references/custom-auth-ui.md +162 -0
  61. package/catalog/frontmcp-authorities/SKILL.md +55 -18
  62. package/catalog/frontmcp-authorities/references/authority-profiles.md +25 -1
  63. package/catalog/frontmcp-authorities/references/custom-evaluators.md +1 -1
  64. package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +9 -0
  65. package/catalog/frontmcp-channels/SKILL.md +7 -1
  66. package/catalog/frontmcp-config/SKILL.md +14 -7
  67. package/catalog/frontmcp-config/examples/configure-auth/local-credential-vault.md +94 -0
  68. package/catalog/frontmcp-config/examples/configure-auth/local-secure-store.md +138 -0
  69. package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +45 -23
  70. package/catalog/frontmcp-config/examples/configure-auth-modes/local-behind-tunnel.md +73 -0
  71. package/catalog/frontmcp-config/examples/configure-auth-modes/local-consent-enforcement.md +87 -0
  72. package/catalog/frontmcp-config/examples/configure-auth-modes/local-dcr-control.md +67 -0
  73. package/catalog/frontmcp-config/examples/configure-auth-modes/local-minimal.md +62 -0
  74. package/catalog/frontmcp-config/examples/configure-auth-modes/local-multi-provider-orchestration.md +93 -0
  75. package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +18 -20
  76. package/catalog/frontmcp-config/examples/configure-auth-modes/local-single-operator.md +66 -0
  77. package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +37 -23
  78. package/catalog/frontmcp-config/examples/configure-http/custom-http-routes.md +98 -0
  79. package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +17 -9
  80. package/catalog/frontmcp-config/references/configure-auth-modes.md +86 -23
  81. package/catalog/frontmcp-config/references/configure-auth.md +296 -50
  82. package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
  83. package/catalog/frontmcp-config/references/configure-http.md +203 -14
  84. package/catalog/frontmcp-config/references/configure-session.md +14 -7
  85. package/catalog/frontmcp-deployment/SKILL.md +17 -15
  86. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
  87. package/catalog/frontmcp-deployment/references/deploy-manifest-yaml.md +308 -0
  88. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +174 -0
  89. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +145 -2
  90. package/catalog/frontmcp-development/SKILL.md +36 -50
  91. package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
  92. package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
  93. package/catalog/frontmcp-development/references/create-job.md +45 -11
  94. package/catalog/frontmcp-development/references/create-provider.md +80 -8
  95. package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
  96. package/catalog/frontmcp-development/references/create-skill.md +45 -0
  97. package/catalog/frontmcp-development/references/decorators-guide.md +15 -15
  98. package/catalog/frontmcp-extensibility/SKILL.md +1 -1
  99. package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +8 -6
  100. package/catalog/frontmcp-extensibility/references/skill-audit-log.md +7 -2
  101. package/catalog/frontmcp-guides/SKILL.md +8 -8
  102. package/catalog/frontmcp-observability/SKILL.md +16 -8
  103. package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
  104. package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
  105. package/catalog/frontmcp-production-readiness/SKILL.md +1 -1
  106. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -2
  107. package/catalog/frontmcp-setup/SKILL.md +12 -12
  108. package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
  109. package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
  110. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
  111. package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
  112. package/catalog/frontmcp-setup/references/setup-project.md +29 -0
  113. package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
  114. package/catalog/frontmcp-testing/SKILL.md +26 -18
  115. package/catalog/frontmcp-testing/references/test-auth.md +24 -0
  116. package/catalog/skills-manifest.json +676 -146
  117. package/package.json +1 -1
  118. package/src/manifest.d.ts +72 -1
  119. package/src/manifest.js +4 -1
  120. package/src/manifest.js.map +1 -1
  121. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +0 -61
  122. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -84
  123. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -92
  124. package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +0 -92
  125. package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +0 -59
  126. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +0 -101
  127. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +0 -62
  128. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +0 -101
  129. package/catalog/frontmcp-development/references/create-tool-annotations.md +0 -48
  130. package/catalog/frontmcp-development/references/create-tool-output-schema-types.md +0 -71
  131. package/catalog/frontmcp-development/references/create-tool.md +0 -728
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: custom-auth-ui
3
+ description: Replace FrontMCP's built-in OAuth pages with custom React components using the auth.ui slot→file map and auth.extras name→handler map (no decorator, no class) plus the @frontmcp/ui/auth hooks.
4
+ ---
5
+
6
+ # Custom Authorization UI (`auth.ui` / `auth.extras`)
7
+
8
+ FrontMCP's `local` and `remote` OAuth modes serve built-in HTML for the login, consent, incremental, federated, and error steps. The `auth.ui` **slot→file map** replaces any of those slots with **your own React component** while the framework keeps owning everything security-sensitive — CSRF, the Content-Security-Policy, anti-clickjacking headers, the pending-authorization state, the submit target, and the OAuth redirect. **You write only the UI.**
9
+
10
+ There is **no decorator and no class** — a slot is just a `.tsx` path, and an extra is just a handler function. If no `auth.ui` slot is configured, the built-in pages are served unchanged — the feature is purely additive and opt-in per slot.
11
+
12
+ ## Two halves
13
+
14
+ | Half | Package | Responsibility |
15
+ | ---------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | **Server** | `@frontmcp/sdk` (`auth.ui` / `auth.extras`) | Transpiles your component's `.tsx` once (server-side, single-file transform — deps stay external), inlines it as an ES module + an import-map, injects the flow state, mints/verifies CSRF, sets CSP. It never bundles or renders your component server-side. |
17
+ | **Client** | `@frontmcp/ui/auth` | The framework-free contract (`AuthFlowState`, wire constants) **plus** hooks (`useAuthFlow`, …), `<AuthPageWrapper>`, and `mountAuthPage` that read the state, render the component in the browser, and drive submits. Loaded in the browser **from esm.sh** via the import-map. |
18
+
19
+ At request time the server serializes an `AuthFlowState` into `window.__FRONTMCP_AUTH__`, serves a page with an **empty** `#frontmcp-auth-root` mount node, an **`<script type="importmap">`** mapping `react` / `react-dom` / `@frontmcp/ui/auth` to **esm.sh** (the `@frontmcp/ui/auth` entry gets `?external=react,react-dom` for a single React), and an inline **`<script type="module">`** with your transpiled component + a `mountAuthPage(<YourComponent>)` tail. The browser loads the deps from esm.sh, runs the module, and `mountAuthPage` renders your component **client-side** into the empty node — there is **no bundling and no server-side React**, exactly how a `@Tool({ ui: { file } })` widget loads. `@frontmcp/ui` is browser-only — the SDK passes `@frontmcp/ui/auth` to the renderer only as an import-map specifier string and never imports it. Your component reads the state via the hooks and submits back to the OAuth callback.
20
+
21
+ ## Server: `auth.ui` — a slot→file map
22
+
23
+ Map each slot to a sibling `.tsx`/`.jsx` source (default export); the SDK transpiles it once with esbuild and inlines it as an ES module (deps loaded from esm.sh via an import-map; `mountAuthPage` appended for you), exactly like a `@Tool({ ui: { file } })` widget. The path is **relative and auto-anchored to the config file's directory** — no `fileURLToPath`:
24
+
25
+ ```ts
26
+ import { App } from '@frontmcp/sdk'; // or @FrontMcp
27
+
28
+ @App({
29
+ auth: {
30
+ mode: 'local',
31
+ // slot → RELATIVE .tsx, auto-anchored to THIS config file's directory.
32
+ ui: { login: './auth/login.tsx' },
33
+ },
34
+ })
35
+ export default class Server {}
36
+ ```
37
+
38
+ Absolute paths pass through unchanged. If the declaring file's directory can't be captured (exotic loader), the framework falls back to `process.cwd()` with a warning — use an absolute path if that's wrong.
39
+
40
+ ### Slots
41
+
42
+ | Slot | Replaces the built-in… | Key fields the component receives |
43
+ | ------------- | ------------------------------------------- | ----------------------------------------- |
44
+ | `login` | Local sign-in page | `clientName`, `scopes`, `redirectUri` |
45
+ | `consent` | Tool-consent screen | `tools[]` (selectable tool cards) |
46
+ | `incremental` | Single-app incremental authorization screen | `extras.appId` / `extras.appName` |
47
+ | `federated` | Multi-provider selection | `providers[]` (selectable provider cards) |
48
+ | `error` | OAuth error page | `error` text |
49
+
50
+ Map only the slots you want to customize — the rest keep their built-in pages.
51
+
52
+ ### Per-app scoping (`splitByApp`)
53
+
54
+ `auth.ui` / `auth.extras` are **scoped to the auth config they live on** (like `consent`). Under `splitByApp`, each `@App({ auth: { mode, ui, extras } })` gets its OWN custom auth UI (paths anchored to that `@App` file); the parent multi-app scope uses the top-level `@FrontMcp({ auth })` (paths anchored to the server file).
55
+
56
+ ## Server: `auth.extras` — validated extra fields
57
+
58
+ An `auth.extras[name]` entry is a **server handler function** that adds a server-validated side endpoint your page can POST to **mid-flow**, without finishing the authorization. Each accepted submission is appended to a per-`(pending-auth, extra)` accumulator the framework keeps; the response carries the full accumulator map back so the page refreshes without a reload.
59
+
60
+ ```ts
61
+ import { type AuthExtraContext } from '@frontmcp/sdk';
62
+
63
+ export async function addEnv(input: Record<string, unknown>, ctx: AuthExtraContext) {
64
+ const key = typeof input['key'] === 'string' ? input['key'].trim() : '';
65
+ if (!key) return { ok: false as const, error: 'key is required' };
66
+ if (ctx.current.some((it) => (it as { key?: string }).key === key)) {
67
+ return { ok: false as const, error: `"${key}" was already added` };
68
+ }
69
+ const value = typeof input['value'] === 'string' ? input['value'] : '';
70
+ // `addedItems` here is the list of NEW items to APPEND on success.
71
+ return { ok: true as const, addedItems: [{ key, value }] };
72
+ }
73
+ ```
74
+
75
+ The handler returns `{ ok, error?, addedItems?, sideEffects? }` (sync or async). The `ctx` is minimal and PII-free: `{ name, pendingAuthId?, current }` (`current` is what's already accepted for this extra). Declare it under `auth`: `@FrontMcp({ auth: { mode: 'local', extras: { 'envs:add': addEnv } } })`.
76
+
77
+ ## The `AuthFlowState` a component receives
78
+
79
+ | Field | Type | Notes |
80
+ | --------------- | ------------------------- | ------------------------------------------------------------------------ |
81
+ | `slot` | `AuthSlot` | Which page slot is rendering. |
82
+ | `pendingAuthId` | `string?` | Correlation id; round-tripped on every submit. Absent only for `error`. |
83
+ | `clientName` | `string?` | OAuth client display name (**not** an end user). |
84
+ | `clientId` | `string?` | OAuth `client_id`. |
85
+ | `scopes` | `string[]` | Requested OAuth scopes. |
86
+ | `redirectUri` | `string?` | Validated `redirect_uri` the code is sent to. |
87
+ | `resource` | `string?` | RFC 8707 resource indicator, when supplied. |
88
+ | `error` | `string?` | Error text (error slot, or a re-rendered failed submit). |
89
+ | `csrfToken` | `string?` | Server-minted anti-CSRF token. The hooks echo it for you. |
90
+ | `providers` | `AuthProvider[]` | Selectable providers on the `federated` slot. |
91
+ | `tools` | `AuthTool[]` | Selectable tools on the `consent` slot. |
92
+ | `extras` | `Record<string, unknown>` | Free-form, slot-specific extras (e.g. logo URI, incremental target app). |
93
+
94
+ **No PII is in the contract.** Anything the user types (email, name, …) travels in your own form fields, never in `AuthFlowState`.
95
+
96
+ ## Client: `@frontmcp/ui/auth`
97
+
98
+ ```bash
99
+ npm install @frontmcp/ui react react-dom
100
+ ```
101
+
102
+ `react` / `react-dom` are peer dependencies of `@frontmcp/ui`.
103
+
104
+ | Import | Use |
105
+ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
+ | `@frontmcp/ui/auth` | The framework-free contract types + wire constants (no React) **and** React hooks (`useAuthFlow`, …) + `<AuthPageWrapper>` + `mountAuthPage`. Single source of truth for the client API; imports only `react`/`react-dom` — no MUI. |
107
+ | `@frontmcp/ui/auth/vanilla` | Framework-free (browser) helpers: `getAuthFlow`, `submitFinish`, `submitExtra`, `getAddedItems` (plus the contract types). |
108
+
109
+ ### React hooks + wrapper + client mount
110
+
111
+ - **`useAuthFlow()`** — the flow-state fields above plus a `submitFinish` handler. `<form onSubmit={submitFinish}>` `preventDefault`s, serializes the form, attaches `pending_auth_id` + `csrf` + the slot marker, posts to the callback, and follows the OAuth redirect.
112
+ - **`useExtraField(name)`** — `{ onSubmit, result, pending }` for an `auth.extras` form. On success it merges the returned `addedItems` back into context.
113
+ - **`useAddedItems(name)`** — the server-side accumulator for a named extra, reactively.
114
+ - **`<AuthPageWrapper>`** — outer chrome that reads the injected state once, provides it via context, and (by default) renders the enclosing `<form>` with the `pending_auth_id` + `csrf` hidden fields so a no-JS submit still works. Pass `renderForm={false}` to supply your own forms.
115
+ - **`mountAuthPage(Component, options?)`** — the client entrypoint; wraps `Component` in `<AuthPageWrapper>` and renders it (`createRoot(...).render(...)`) into the empty `#frontmcp-auth-root` node in the browser. Pure client render — no server markup to hydrate. **The SDK appends `mountAuthPage(<your default export>)` to the inlined module for you**, so a `file`-based component just needs a default export (loaded in the browser from esm.sh via the import-map).
116
+
117
+ ### Vanilla (no framework)
118
+
119
+ `@frontmcp/ui/auth/vanilla` exposes the same flow without React: `getAuthFlow()` (read `window.__FRONTMCP_AUTH__`; `tryGetAuthFlow()` is tolerant), `getAddedItems(name)`, `submitFinish(formOrData?)`, and `submitExtra(name, formOrData?)`.
120
+
121
+ ## Routes the server adds
122
+
123
+ When at least one `auth.extras` handler is configured (otherwise it 404s / falls through, so defaults are untouched):
124
+
125
+ | Route | Method | Purpose |
126
+ | ----------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | `/oauth/ui/extra` | `POST` | Routes an `auth.extras[name]` submission (the `action` field names the extra) to its handler; returns `{ ok, error?, addedItems?, sideEffects? }`. CSRF-verified (400 on mismatch). |
128
+
129
+ There is **no `/oauth/ui/:slot.js` route** — the component is transpiled server-side and **inlined** into the authorize/callback page as a `<script type="module">` (deps from esm.sh via the import-map), so there is no separately-served bundle. The pages themselves are served by the existing `/oauth/authorize` + `/oauth/callback` flows; an `auth.ui` slot swaps the served page body (import-map + injected state + the inline transpiled module + empty mount) — the component renders client-side. The finish submit still posts to `/oauth/callback`.
130
+
131
+ ## Security — the framework owns it
132
+
133
+ - **CSRF**: the server mints a per-pending-authorization token, stores it (echoed into `csrfToken`), and verifies it on the finish submit and every `auth.extras` POST with a constant-time compare. Your component never generates or checks it.
134
+ - **CSP + anti-clickjacking**: the auth-UI HTML ships with a strict CSP — `default-src 'self'; script-src 'self' 'unsafe-inline' https://esm.sh; connect-src 'self' https://esm.sh; style-src 'self' 'unsafe-inline' https://esm.sh; img-src 'self' data: https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'` — plus `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`. It allows `https://esm.sh` (deps) + `'unsafe-inline'` (the JSON-escaped state script) but **NOT `'unsafe-eval'`** — the transform is server-side, never in the browser.
135
+ - **No PII**: the injected state carries OAuth client identifiers + control fields only.
136
+ - **Fail-safe**: a component that can't be transpiled (missing / invalid `.tsx`) is logged (error cached so a broken file isn't retried each request) and **falls back to the built-in page** — a broken custom page can't take the server down.
137
+
138
+ ## Local dev / offline (esm.sh caveat)
139
+
140
+ The browser loads `react`, `react-dom`, and `@frontmcp/ui/auth` from **esm.sh**. `react`/`react-dom` are always there, but `@frontmcp/ui/auth` resolves only once **published to npm**. In an unpublished monorepo, an in-browser render can't fetch it — though the SERVER still emits the full page (import-map + injected state + inline transpiled module), so HTTP-level checks pass; only the browser DOM render is affected. To render before publishing, map the specifier to a locally-served ESM URL via `@FrontMcp({ ui: { cdnOverrides } })`:
141
+
142
+ ```ts
143
+ @FrontMcp({
144
+ ui: { cdnOverrides: { '@frontmcp/ui/auth': 'http://localhost:5173/ui-auth.mjs' } },
145
+ auth: { mode: 'local', ui: { login: './auth/login.tsx' } },
146
+ })
147
+ ```
148
+
149
+ A non-esm.sh override URL is left as-is (no `?external=react`). Once published, no overrides are needed.
150
+
151
+ ## Examples
152
+
153
+ | Example | Level | Description |
154
+ | ------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
+ | [`login-slot`](../examples/custom-auth-ui/login-slot.md) | Intermediate | Replace the built-in login page with a custom React component via auth.ui: { login: './login.tsx' } and useAuthFlow, while the framework keeps owning CSRF and CSP. |
156
+ | [`multi-step-auth-extra`](../examples/custom-auth-ui/multi-step-auth-extra.md) | Advanced | Add a server-validated multi-step field to a custom login page with auth.extras: { 'envs:add': fn }, useExtraField, and useAddedItems — accepted rows accumulate server-side and reflect back without a reload. |
157
+
158
+ ## See also
159
+
160
+ - Docs: [Custom Authorization UI (`auth.ui`)](https://docs.agentfront.dev/frontmcp/authentication/custom-ui)
161
+ - `frontmcp-config` → `configure-auth` — the declarative `login` / `authenticate` config (tweak the built-in page's fields without React)
162
+ - `create-tool` → `ui-widgets` — the `@Tool({ ui: { file } })` widget pipeline this mirrors
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: frontmcp-authorities
3
- description: 'Use when implementing authorization, access control, RBAC, ABAC, or ReBAC for tools, resources, or prompts. Covers JWT claims mapping, authority profiles, and policy enforcement.'
3
+ description: 'Use when implementing authorization and access control for FrontMCP tools, resources, prompts, or skills, deciding who may invoke what. Covers the RBAC, ABAC, and ReBAC models and when to choose each; JWT claims mapping per identity provider (Auth0, Keycloak, Okta, Cognito, Frontegg); reusable named authority profiles; and custom authority evaluators for domain-specific policy. This is about who-can-do-what (permissions, roles, scopes), distinct from configuring auth modes and login (see frontmcp-config) and custom login UI (see frontmcp-auth-ui). Triggers: authorization, access control, RBAC, ABAC, ReBAC, permissions, roles, scopes, policy enforcement, JWT claims, restrict who can call a tool.'
4
4
  tags: [authorization, rbac, abac, rebac, security, permissions, roles, access-control, authorities, jwt]
5
5
  category: development
6
6
  targets: [all]
@@ -20,9 +20,10 @@ Built-in RBAC/ABAC/ReBAC authorization system for FrontMCP entry types. Each flo
20
20
 
21
21
  ### Must Use
22
22
 
23
- - Adding role-based or permission-based access control to tools, resources, or prompts
23
+ - Adding role-based or permission-based access control to tools, resources, prompts, or skills
24
24
  - Restricting MCP entry visibility based on the caller's JWT claims
25
25
  - Enforcing tenant isolation (ABAC) or relationship checks (ReBAC) on entries
26
+ - Gating which skills a caller can discover and load (`@Skill({ authorities })`)
26
27
 
27
28
  ### Recommended
28
29
 
@@ -73,7 +74,7 @@ Add the `authorities` field to your `@FrontMcp` decorator. No plugin import need
73
74
  import { FrontMcp } from '@frontmcp/sdk';
74
75
 
75
76
  @FrontMcp({
76
- name: 'my-server',
77
+ info: { name: 'my-server', version: '1.0.0' },
77
78
  authorities: {
78
79
  // configured in next steps
79
80
  },
@@ -284,23 +285,59 @@ authorities: {
284
285
  }
285
286
  ```
286
287
 
288
+ ### Skill Authorities (`@Skill({ authorities })`)
289
+
290
+ `authorities` on `@Skill` is enforced exactly like the other entry types, across
291
+ **every** surface a skill is served from:
292
+
293
+ - **Deny on load/read** — loading a gated skill the caller can't access throws
294
+ `AuthorityDeniedError` (MCP code `-32003`), the same as a denied `tools/call`.
295
+ Covers `skills/load` (MCP), `skill://<path>/SKILL.md` and `skill://<path>/<file>`
296
+ reads (SEP-2640), and `GET /skills/{id}` (HTTP).
297
+ - **Filter on discovery** — gated skills the caller can't access are removed from
298
+ `skills/search` / `skills/list` (MCP), the `skill://index.json` discovery index and
299
+ skill-path autocomplete (SEP-2640), and `GET /skills` (HTTP).
300
+
301
+ ```typescript
302
+ @Skill({ name: 'review-pr', description: '…', instructions: '…' }) // open to all
303
+ @Skill({ name: 'internal-runbook', description: '…', instructions: '…',
304
+ authorities: 'admin' }) // admins only
305
+ ```
306
+
307
+ Two limitations to design around:
308
+
309
+ - **List-time filtering is role/permission/claims-only.** Discovery runs without
310
+ request input, so `{ fromInput: '…' }` ABAC/ReBAC policies can't be evaluated when
311
+ filtering and will hide the skill from discovery. Use role/permission/claims
312
+ authorities for discoverable skills; input-dependent policies still enforce at
313
+ load time. (Same limitation applies to tools/resources/prompts.)
314
+ - **HTTP skills discovery is fail-closed.** The Skills HTTP API uses a binary
315
+ api-key/bearer gate with no claims, so gated skills are hidden from `GET /skills`
316
+ and denied on `GET /skills/{id}` regardless of the bearer. Serve gated skills over
317
+ an MCP transport for claims-based access. Ungated skills are unaffected.
318
+
319
+ Boot-time fail-fast covers skills too: a `@Skill` with `authorities` but no configured
320
+ authorities engine fails server startup with `AuthConfigurationError`, exactly like a
321
+ tool/resource/prompt/agent.
322
+
287
323
  ## Scenario Routing Table
288
324
 
289
- | Scenario | Approach | Reference |
290
- | ------------------------------------- | ---------------------------------------------------------------- | ---------------------------------- |
291
- | Simple role gate (admin-only tool) | `authorities: 'admin'` profile | `references/authority-profiles.md` |
292
- | Permission-based access | `authorities: { permissions: { all: ['x'] } }` | `references/rbac-abac-rebac.md` |
293
- | Tenant isolation | ABAC with `{ fromInput: 'tenantId' }` | `references/rbac-abac-rebac.md` |
294
- | Resource ownership check | ReBAC with relationship resolver | `references/rbac-abac-rebac.md` |
295
- | IP allowlist or custom logic | Custom evaluator via `custom.*` | `references/custom-evaluators.md` |
296
- | Different IdP (Auth0/Keycloak/Okta) | Configure `claimsMapping` | `references/claims-mapping.md` |
297
- | Admin OR (editor AND same-tenant) | `anyOf` / `allOf` combinators | `references/authority-profiles.md` |
298
- | Custom pre/post authority logic | Hook with `Will`/`Did`/`Around` on `checkEntryAuthorities` stage | `references/custom-evaluators.md` |
299
- | Replace built-in check with OPA/Cedar | `Around('checkEntryAuthorities')` hook | `references/custom-evaluators.md` |
300
- | Audit authority decisions | `Did('checkEntryAuthorities')` hook for logging/metrics | `references/custom-evaluators.md` |
301
- | Tenant allowlist in Redis/DB | Async custom evaluator with `custom.*` field | `references/custom-evaluators.md` |
302
- | Subscription check before tool runs | Async custom evaluator or `Will('checkEntryAuthorities')` hook | `references/custom-evaluators.md` |
303
- | Feature flag gate on a tool | Async custom evaluator checking flag service | `references/custom-evaluators.md` |
325
+ | Scenario | Approach | Reference |
326
+ | ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------- |
327
+ | Simple role gate (admin-only tool) | `authorities: 'admin'` profile | `references/authority-profiles.md` |
328
+ | Gate skill discovery + load | `@Skill({ authorities: 'admin' })` (role/permission/claims-based) | `references/authority-profiles.md` |
329
+ | Permission-based access | `authorities: { permissions: { all: ['x'] } }` | `references/rbac-abac-rebac.md` |
330
+ | Tenant isolation | ABAC with `{ fromInput: 'tenantId' }` | `references/rbac-abac-rebac.md` |
331
+ | Resource ownership check | ReBAC with relationship resolver | `references/rbac-abac-rebac.md` |
332
+ | IP allowlist or custom logic | Custom evaluator via `custom.*` | `references/custom-evaluators.md` |
333
+ | Different IdP (Auth0/Keycloak/Okta) | Configure `claimsMapping` | `references/claims-mapping.md` |
334
+ | Admin OR (editor AND same-tenant) | `anyOf` / `allOf` combinators | `references/authority-profiles.md` |
335
+ | Custom pre/post authority logic | Hook with `Will`/`Did`/`Around` on `checkEntryAuthorities` stage | `references/custom-evaluators.md` |
336
+ | Replace built-in check with OPA/Cedar | `Around('checkEntryAuthorities')` hook | `references/custom-evaluators.md` |
337
+ | Audit authority decisions | `Did('checkEntryAuthorities')` hook for logging/metrics | `references/custom-evaluators.md` |
338
+ | Tenant allowlist in Redis/DB | Async custom evaluator with `custom.*` field | `references/custom-evaluators.md` |
339
+ | Subscription check before tool runs | Async custom evaluator or `Will('checkEntryAuthorities')` hook | `references/custom-evaluators.md` |
340
+ | Feature flag gate on a tool | Async custom evaluator checking flag service | `references/custom-evaluators.md` |
304
341
 
305
342
  ## Common Patterns
306
343
 
@@ -15,7 +15,7 @@ Profiles are registered in the `profiles` field of the `authorities` config. Eac
15
15
  import { FrontMcp } from '@frontmcp/sdk';
16
16
 
17
17
  @FrontMcp({
18
- name: 'my-server',
18
+ info: { name: 'my-server', version: '1.0.0' },
19
19
  authorities: {
20
20
  claimsMapping: {
21
21
  roles: 'realm_access.roles',
@@ -95,6 +95,15 @@ export default class AdminReportPrompt extends PromptContext {
95
95
  // only admin can use this prompt
96
96
  }
97
97
  }
98
+
99
+ // Skills are gated the same way. Non-admins can neither discover nor load this:
100
+ @Skill({
101
+ name: 'internal-runbook',
102
+ description: 'Restricted operational runbook',
103
+ instructions: { file: './internal-runbook.md' },
104
+ authorities: 'admin',
105
+ })
106
+ export class InternalRunbookSkill {}
98
107
  ```
99
108
 
100
109
  ### Multiple Profiles (String Array)
@@ -244,9 +253,24 @@ The authorities system does not only enforce on execution. The built-in `filterB
244
253
  - `tools/list` only returns tools the current user is authorized to call
245
254
  - `resources/list` only returns resources the current user can read
246
255
  - `prompts/list` only returns prompts the current user can get
256
+ - Skills are filtered on every discovery surface: `skills/search` / `skills/list`, the SEP-2640 `skill://index.json` index + skill-path autocomplete, and `GET /skills`. Loading a gated skill the caller can't access (via `skills/load`, a `skill://…` read, or `GET /skills/{id}`) is denied with `AuthorityDeniedError` (`-32003`).
247
257
 
248
258
  This filtering happens automatically. No additional configuration is needed. Entries without an `authorities` field are always visible.
249
259
 
260
+ **List-time evaluation has no request input.** Because discovery runs before any
261
+ arguments exist, input-dependent policies — ABAC conditions using `{ fromInput: '…' }`
262
+ or ReBAC `resourceId: { fromInput: '…' }` — cannot be evaluated when filtering and
263
+ will exclude the entry from the list (it is still enforced correctly at execution/load
264
+ time, where input is available). For entries (especially **skills**) that must remain
265
+ discoverable, gate them with role/permission/claims-based authorities such as
266
+ `authorities: 'admin'` or `{ roles: { any: ['admin'] } }`.
267
+
268
+ **HTTP skills discovery is fail-closed.** The Skills HTTP API authenticates with a
269
+ binary api-key/bearer gate that surfaces no JWT claims, so authority-gated skills are
270
+ hidden from `GET /skills` and denied on `GET /skills/{id}` regardless of the bearer.
271
+ Serve gated skills over an MCP transport (full claims context) for claims-based access;
272
+ skills without `authorities` are served over HTTP unchanged.
273
+
250
274
  ## Profile Design Guidelines
251
275
 
252
276
  | Guideline | Example |
@@ -66,7 +66,7 @@ import { ipAllowListEvaluator } from './evaluators/ip-allow-list';
66
66
  import { timeWindowEvaluator } from './evaluators/time-window';
67
67
 
68
68
  @FrontMcp({
69
- name: 'my-server',
69
+ info: { name: 'my-server', version: '1.0.0' },
70
70
  authorities: {
71
71
  claimsMapping: { roles: 'roles', permissions: 'permissions' },
72
72
  profiles: { admin: { roles: { any: ['admin'] } } },
@@ -173,6 +173,15 @@ Instead of hardcoding values, reference runtime data from tool input or JWT clai
173
173
  { path: 'claims.department', op: 'eq', value: { fromClaims: 'manager.department' } }
174
174
  ```
175
175
 
176
+ > **`fromInput` is for tools and agents only.** Only **tools** and **agents**
177
+ > pass request input into the authorities evaluation. `@Resource` and `@Prompt`
178
+ > do not receive tool-style input, so a `fromInput` reference (in an ABAC
179
+ > condition or a ReBAC `resourceId`) has nothing to resolve against on those
180
+ > entries — gate resources and prompts with role / permission / `fromClaims`
181
+ > policies instead. (Separately, list-time discovery filtering runs without any
182
+ > input at all, so `fromInput` policies are also excluded from list results for
183
+ > everyone — reserve them for call-time enforcement.)
184
+
176
185
  ### ABAC Examples
177
186
 
178
187
  **Tenant isolation:**
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  name: frontmcp-channels
3
- description: 'Use when you want to push real-time notifications into Claude Code sessions. Build webhook channels, chat bridges (WhatsApp, Telegram, Slack), agent completion alerts, job status notifications, or error forwarding. The skill for CHANNELS and NOTIFICATIONS.'
3
+ description: 'Use when pushing real-time notifications or events into Claude Code (or another MCP client) sessions, or building two-way chat bridges. Covers channel source types: incoming webhooks (such as GitHub), app error events, agent-completion and job-completion alerts, service connectors, file watchers, and replay buffers; plus two-way conversational bridges connecting WhatsApp, Telegram, Slack, and Discord to a Claude Code session. Triggers: push notifications, real-time alerts, webhook channel, chat bridge, WhatsApp / Telegram / Slack / Discord, agent completion alert, job status notification, error forwarding, server-to-client messaging. The skill for CHANNELS and NOTIFICATIONS.'
4
+ when_to_use: |
5
+ Trigger when creating or editing a *.channel.ts file, or building a channel
6
+ that pushes real-time notifications into a Claude Code session: webhook
7
+ sources, app / agent / job event alerts, service connectors, file watchers,
8
+ or a two-way chat bridge (WhatsApp, Telegram, Slack, Discord).
9
+ paths: '**/*.channel.ts'
4
10
  tags: [channels, notifications, claude-code, webhooks, messaging, real-time, two-way]
5
11
  category: development
6
12
  targets: [all]
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  name: frontmcp-config
3
- description: 'Use when you want to configure auth, set up CORS, add rate limiting, throttle requests, manage sessions, choose transport, set HTTP options, add authentication, configure JWT, or set up OAuth. The skill for server CONFIGURATION.'
3
+ description: 'Use when configuring a FrontMCP server through frontmcp.config or the @FrontMcp options. Covers auth modes (public, transparent, local, remote), OAuth plus credential vault and secureStore, CORS, HTTP port / entry-path prefix / unix socket, security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options), rate limiting / throttling / concurrency / timeout / IP filtering (GuardConfig), session storage (Redis, Vercel KV), client transport protocols (SSE, Streamable HTTP, stateless, protocol presets), elicitation, multi-target build config, and skillsConfig (HTTP catalog, caching, audit log, instruction injection). Triggers: configure auth, set up CORS, add rate limiting, throttle requests, manage sessions, choose transport, set HTTP options, configure JWT or OAuth. The skill for server CONFIGURATION.'
4
+ when_to_use: |
5
+ Trigger when creating or editing frontmcp.config.ts/js or the @FrontMcp({...})
6
+ options: configuring auth modes, OAuth + credential vault, CORS, HTTP port /
7
+ entry path / unix socket, security headers, rate limiting / throttling /
8
+ GuardConfig, session storage (Redis, Vercel KV), client transport / protocol
9
+ presets, elicitation, multi-target builds, or skillsConfig.
10
+ paths: '**/frontmcp.config.*'
4
11
  tags: [router, config, transport, http, auth, session, redis, sqlite, throttle, guide]
5
12
  category: config
6
13
  targets: [all]
@@ -14,7 +21,7 @@ metadata:
14
21
 
15
22
  # FrontMCP Configuration Router
16
23
 
17
- Entry point for configuring FrontMCP servers. This skill helps you find the right configuration skill based on what aspect of your server you need to set up.
24
+ Entry point for configuring FrontMCP servers. This skill helps you find the right configuration reference (under `references/`) based on what aspect of your server you need to set up.
18
25
 
19
26
  ## When to Use This Skill
20
27
 
@@ -26,7 +33,7 @@ Entry point for configuring FrontMCP servers. This skill helps you find the righ
26
33
 
27
34
  ### Recommended
28
35
 
29
- - Looking up which skill covers a specific config option (CORS, rate limits, session TTL, etc.)
36
+ - Looking up which reference covers a specific config option (CORS, rate limits, session TTL, etc.)
30
37
  - Understanding how configuration layers work (server-level vs app-level vs tool-level)
31
38
  - Reviewing the full configuration surface area before production deployment
32
39
 
@@ -36,7 +43,7 @@ Entry point for configuring FrontMCP servers. This skill helps you find the righ
36
43
  - You need to build components, not configure the server (see `frontmcp-development`)
37
44
  - You need to deploy, not configure (see `frontmcp-deployment`)
38
45
 
39
- > **Decision:** Use this skill when you need to figure out WHAT to configure. Use the specific skill when you already know.
46
+ > **Decision:** Use this skill when you need to figure out WHAT to configure. Open the matching reference under `references/` directly when you already know.
40
47
 
41
48
  ## Prerequisites
42
49
 
@@ -46,13 +53,13 @@ Entry point for configuring FrontMCP servers. This skill helps you find the righ
46
53
  ## Steps
47
54
 
48
55
  1. Identify the configuration area you need using the Scenario Routing Table below
49
- 2. Navigate to the specific configuration skill (e.g., `configure-transport`, `configure-auth`) for detailed instructions
56
+ 2. Navigate to the specific configuration reference (e.g., `references/configure-transport.md`, `references/configure-auth.md`) for detailed instructions
50
57
  3. Apply the configuration in your `@FrontMcp` or `@App` decorator
51
58
  4. Verify using the Verification Checklist at the end of this skill
52
59
 
53
60
  ## Scenario Routing Table
54
61
 
55
- | Scenario | Skill | Description |
62
+ | Scenario | Reference | Description |
56
63
  | -------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------- |
57
64
  | Choose between SSE, Streamable HTTP, or stdio | `configure-transport` | Transport protocol selection with distributed session options |
58
65
  | Set up CORS, port, base path, or request limits | `configure-http` | HTTP server options for Streamable HTTP and SSE transports |
@@ -162,7 +169,7 @@ Each reference has matching examples under [`examples/<reference>/`](./examples/
162
169
  | Example | Level | Description |
163
170
  | --------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------- |
164
171
  | [`local-self-signed-tokens`](./examples/configure-auth-modes/local-self-signed-tokens.md) | Intermediate | Configure a server that signs its own JWT tokens with consent and incremental auth enabled. |
165
- | [`remote-enterprise-oauth`](./examples/configure-auth-modes/remote-enterprise-oauth.md) | Advanced | Delegate authentication to an external OAuth orchestrator with Redis-backed token storage. |
172
+ | [`remote-enterprise-oauth`](./examples/configure-auth-modes/remote-enterprise-oauth.md) | Advanced | Proxy auth to one mandatory upstream IdP, mint a FrontMCP session, read the upstream token. |
166
173
  | [`transparent-jwt-validation`](./examples/configure-auth-modes/transparent-jwt-validation.md) | Basic | Validate externally-issued JWTs without managing token lifecycle on the server. |
167
174
 
168
175
  ### `configure-auth`
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: local-credential-vault
3
+ reference: configure-auth
4
+ level: intermediate
5
+ description: 'Persist a per-session credential from a local authenticate() verifier into the built-in encrypted vault and read it from a tool via this.credentials.'
6
+ tags: [config, auth, local, vault, credentials, this-credentials]
7
+ features:
8
+ - 'Returning `credentials: [{ key, secret, metadata? }]` from `authenticate()` so FrontMCP persists them encrypted, keyed by the minted `sub`'
9
+ - 'Reading a per-session credential from a tool via `this.credentials.get(key)` (no `this.authProviders` wiring needed)'
10
+ - 'Prompting the agent to connect a missing credential mid-session with `this.credentials.requireConnect({ key })` (framework-signed `/oauth/connect` URL)'
11
+ - 'Understanding per-session rotation: a reconnect yields an empty vault and old ciphertext is undecryptable'
12
+ ---
13
+
14
+ # Local Mode with the Per-Session Credential Vault (`this.credentials`)
15
+
16
+ Persist a per-session credential from a local authenticate() verifier into the built-in encrypted vault and read it from a tool via this.credentials.
17
+
18
+ The vault is AES-256-GCM-encrypted, keyed by the authenticated `sub`, and enabled
19
+ automatically in `local` (and `remote`) modes — there is nothing to register.
20
+
21
+ ## Code
22
+
23
+ ```typescript
24
+ // src/server.ts
25
+ import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
26
+
27
+ @Tool({
28
+ name: 'call_acme',
29
+ description: 'Call the Acme API using the session credential',
30
+ inputSchema: {},
31
+ outputSchema: { connected: z.boolean(), connectUrl: z.string().optional() },
32
+ })
33
+ class CallAcmeTool extends ToolContext {
34
+ async execute() {
35
+ // Read the credential the verifier persisted at login.
36
+ const cred = await this.credentials.get('acme'); // { secret, metadata } | undefined
37
+ if (!cred) {
38
+ // Not connected — hand the agent a framework-signed resume URL so the
39
+ // user can connect it mid-session (verified server-side, short-lived).
40
+ const res = await this.credentials.requireConnect({ key: 'acme' });
41
+ if (!res.connected) return { connected: false, connectUrl: res.resumeUrl };
42
+ }
43
+ // const headers = { Authorization: `Bearer ${cred!.secret}` };
44
+ // … call Acme with cred.secret / cred.metadata …
45
+ return { connected: true };
46
+ }
47
+ }
48
+
49
+ @App({ name: 'acme-tools', tools: [CallAcmeTool] })
50
+ class AcmeApp {}
51
+
52
+ @FrontMcp({
53
+ info: { name: 'acme-server', version: '1.0.0' },
54
+ apps: [AcmeApp],
55
+ auth: {
56
+ mode: 'local',
57
+ login: {
58
+ title: 'Connect Acme',
59
+ fields: { apiKey: { type: 'password', label: 'Acme API Key', required: true } },
60
+ subject: { fromField: 'apiKey', strategy: 'per-account' },
61
+ },
62
+ authenticate: async (input) => {
63
+ // Mid-session connect re-invokes authenticate() with a `resume` context.
64
+ if (input.resume) {
65
+ const value = input.fields['apiKey'];
66
+ if (!value) return { ok: false, message: 'A value is required', retryField: 'apiKey' };
67
+ return { ok: true, credentials: [{ key: input.resume.key, secret: value }] };
68
+ }
69
+ // Initial login: validate and persist the credential into the vault.
70
+ const apiKey = input.fields['apiKey'];
71
+ if (!apiKey?.startsWith('sk-')) return { ok: false, message: 'Invalid API key', retryField: 'apiKey' };
72
+ return {
73
+ ok: true,
74
+ credentials: [{ key: 'acme', secret: apiKey, metadata: { baseUrl: 'https://acme.example' } }],
75
+ };
76
+ },
77
+ },
78
+ })
79
+ class Server {}
80
+ ```
81
+
82
+ ## What This Demonstrates
83
+
84
+ - Returning `credentials: [{ key, secret, metadata? }]` from `authenticate()` so FrontMCP persists them encrypted, keyed by the minted `sub`
85
+ - Reading a per-session credential from a tool via `this.credentials.get(key)` (no `this.authProviders` wiring needed)
86
+ - Prompting the agent to connect a missing credential mid-session with `this.credentials.requireConnect({ key })` (framework-signed `/oauth/connect` URL)
87
+ - Understanding per-session rotation: a reconnect yields an empty vault and old ciphertext is undecryptable
88
+
89
+ ## Related
90
+
91
+ - See `configure-auth` for the full `this.credentials` API and the
92
+ `authenticate()` verifier contract.
93
+ - See `remote-oauth-with-vault` for the separate `this.authProviders` downstream
94
+ OAuth-provider vault.