@frontmcp/skills 1.3.0 → 1.4.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 (116) 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 +9 -2
  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-http.md +149 -15
  83. package/catalog/frontmcp-deployment/SKILL.md +15 -13
  84. package/catalog/frontmcp-deployment/references/deploy-manifest-yaml.md +308 -0
  85. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +174 -0
  86. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +38 -2
  87. package/catalog/frontmcp-development/SKILL.md +30 -44
  88. package/catalog/frontmcp-development/references/decorators-guide.md +15 -15
  89. package/catalog/frontmcp-extensibility/SKILL.md +1 -1
  90. package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +8 -6
  91. package/catalog/frontmcp-extensibility/references/skill-audit-log.md +7 -2
  92. package/catalog/frontmcp-guides/SKILL.md +1 -1
  93. package/catalog/frontmcp-observability/SKILL.md +1 -1
  94. package/catalog/frontmcp-production-readiness/SKILL.md +1 -1
  95. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -2
  96. package/catalog/frontmcp-setup/SKILL.md +1 -1
  97. package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
  98. package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
  99. package/catalog/frontmcp-testing/SKILL.md +9 -1
  100. package/catalog/frontmcp-testing/references/test-auth.md +24 -0
  101. package/catalog/skills-manifest.json +653 -149
  102. package/package.json +1 -1
  103. package/src/manifest.d.ts +72 -1
  104. package/src/manifest.js +4 -1
  105. package/src/manifest.js.map +1 -1
  106. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +0 -80
  107. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -132
  108. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -110
  109. package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +0 -92
  110. package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +0 -59
  111. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +0 -101
  112. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +0 -62
  113. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +0 -101
  114. package/catalog/frontmcp-development/references/create-tool-annotations.md +0 -48
  115. package/catalog/frontmcp-development/references/create-tool-output-schema-types.md +0 -71
  116. package/catalog/frontmcp-development/references/create-tool.md +0 -806
@@ -2,18 +2,25 @@
2
2
  name: remote-enterprise-oauth
3
3
  reference: configure-auth-modes
4
4
  level: advanced
5
- description: 'Delegate authentication to an external OAuth orchestrator with Redis-backed token storage.'
5
+ description: 'Proxy authentication to one mandatory upstream IdP, mint a FrontMCP session, and read the upstream token in tools.'
6
6
  tags: [config, oauth, auth, redis, remote, auth-modes]
7
7
  features:
8
- - "Using `mode: 'remote'` to delegate to an external OAuth 2.1 authorization server"
8
+ - "Using `mode: 'remote'` to proxy authentication to a single mandatory upstream IdP"
9
9
  - 'Loading `clientId` and `clientSecret` from environment variables (never hardcoded)'
10
10
  - 'Configuring Redis-backed token storage for production persistence'
11
- - 'Full OAuth flow: clients are redirected to the provider and return with an authorization code'
11
+ - '`GET /oauth/authorize` redirects straight to the upstream IdP (no in-tree login page)'
12
+ - 'Session identity (sub/email/name) is derived from the upstream user'
13
+ - 'Tools read the upstream token via `this.orchestration.getToken(providerId)`'
12
14
  ---
13
15
 
14
16
  # Remote Enterprise OAuth
15
17
 
16
- Delegate authentication to an external OAuth orchestrator with Redis-backed token storage.
18
+ Proxy authentication to one mandatory upstream IdP, mint a FrontMCP session, and read the upstream token in tools.
19
+
20
+ FrontMCP runs a local OAuth 2.1 server: `GET /oauth/authorize` redirects straight
21
+ to the IdP (no FrontMCP login page), exchanges the returned code, stores the
22
+ upstream tokens encrypted in Redis, derives the session identity from the upstream
23
+ user, and mints its own HS256 session token.
17
24
 
18
25
  ## Code
19
26
 
@@ -23,23 +30,34 @@ import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
23
30
 
24
31
  @Tool({
25
32
  name: 'query_data',
26
- description: 'Query enterprise data warehouse',
33
+ description: 'Query the enterprise data warehouse on behalf of the user',
27
34
  inputSchema: { sql: z.string() },
28
35
  outputSchema: { rows: z.array(z.record(z.string(), z.unknown())), rowCount: z.number() },
29
36
  })
30
37
  class QueryDataTool extends ToolContext {
31
38
  async execute(input: { sql: string }) {
39
+ // The upstream IdP token is stored server-side (encrypted) and read by id.
40
+ // 'enterprise-idp' is providerConfig.id below (defaults to the provider host).
41
+ const token = await this.orchestration.tryGetToken('enterprise-idp');
42
+ if (!token) {
43
+ // Once the upstream token expires the user must re-authenticate
44
+ // (upstream auto-refresh is not yet wired).
45
+ throw new Error('Upstream token unavailable — please re-authenticate');
46
+ }
47
+ // A real tool would call the warehouse API with `token`.
32
48
  return { rows: [{ id: 1, name: 'example' }], rowCount: 1 };
33
49
  }
34
50
  }
35
51
 
36
- @App({
37
- name: 'enterprise-api',
52
+ @FrontMcp({
53
+ info: { name: 'enterprise-server', version: '1.0.0' },
54
+ apps: [{ name: 'enterprise-api', tools: [QueryDataTool] }],
38
55
  auth: {
39
56
  mode: 'remote',
40
57
  provider: 'https://auth.example.com',
41
- clientId: process.env['OAUTH_CLIENT_ID']!,
58
+ clientId: process.env['OAUTH_CLIENT_ID']!, // pre-registered (DCR not yet wired)
42
59
  clientSecret: process.env['OAUTH_CLIENT_SECRET'],
60
+ scopes: ['openid', 'profile', 'email'],
43
61
  tokenStorage: {
44
62
  redis: {
45
63
  host: process.env['REDIS_HOST'] ?? 'redis.internal',
@@ -47,19 +65,8 @@ class QueryDataTool extends ToolContext {
47
65
  password: process.env['REDIS_PASSWORD'],
48
66
  },
49
67
  },
50
- },
51
- tools: [QueryDataTool],
52
- })
53
- class EnterpriseApi {}
54
-
55
- @FrontMcp({
56
- info: { name: 'enterprise-server', version: '1.0.0' },
57
- apps: [EnterpriseApi],
58
- redis: {
59
- provider: 'redis',
60
- host: process.env['REDIS_HOST'] ?? 'redis.internal',
61
- port: Number(process.env['REDIS_PORT'] ?? 6379),
62
- password: process.env['REDIS_PASSWORD'],
68
+ // Pin a stable provider id (and override endpoints for non-standard IdPs).
69
+ providerConfig: { id: 'enterprise-idp' },
63
70
  },
64
71
  })
65
72
  class Server {}
@@ -67,10 +74,17 @@ class Server {}
67
74
 
68
75
  ## What This Demonstrates
69
76
 
70
- - Using `mode: 'remote'` to delegate to an external OAuth 2.1 authorization server
77
+ - Using `mode: 'remote'` to proxy authentication to a single mandatory upstream IdP
71
78
  - Loading `clientId` and `clientSecret` from environment variables (never hardcoded)
72
79
  - Configuring Redis-backed token storage for production persistence
73
- - Full OAuth flow: clients are redirected to the provider and return with an authorization code
80
+ - `GET /oauth/authorize` redirects straight to the upstream IdP (no in-tree login page)
81
+ - Session identity (sub/email/name) is derived from the upstream user
82
+ - Tools read the upstream token via `this.orchestration.getToken(providerId)`
83
+
84
+ ## Not Yet Wired
85
+
86
+ - **Dynamic Client Registration** (`providerConfig.dcrEnabled`): a pre-registered `clientId` is required.
87
+ - **Upstream token auto-refresh**: when the upstream access token expires the user must re-authenticate (FrontMCP's own session token still refreshes via the `refresh_token` grant).
74
88
 
75
89
  ## Related
76
90
 
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: custom-http-routes
3
+ reference: configure-http
4
+ level: intermediate
5
+ description: 'Mount first-class custom HTTP routes (download, secret-validation POST, auth-gated webhook) on the MCP listener via http.routes.'
6
+ tags: [config, http, routes, custom, webhook, download, auth]
7
+ features:
8
+ - 'Registering custom HTTP endpoints with `http.routes` — no tool/resource/prompt needed'
9
+ - 'A POST endpoint that validates a user-entered secret server-side (the `/connect-env` pattern)'
10
+ - 'Overriding the default `application/json` Content-Type for binary/HTML delivery'
11
+ - 'Gating a route behind the MCP `session:verify` flow with `auth: true`'
12
+ - 'Avoiding reserved paths (`/sse`, `/message`, `/oauth/*`, `/.well-known/*`, `/health`, `/metrics`)'
13
+ ---
14
+
15
+ # Custom HTTP Routes
16
+
17
+ Mount first-class custom HTTP routes (download, secret-validation POST, auth-gated webhook) on the MCP listener via http.routes.
18
+
19
+ ## Code
20
+
21
+ ```typescript
22
+ // src/server.ts
23
+ import { App, FrontMcp, type ServerRequest, type ServerResponse } from '@frontmcp/sdk';
24
+
25
+ @App({ name: 'my-app' })
26
+ class MyApp {}
27
+
28
+ @FrontMcp({
29
+ info: { name: 'routes-server', version: '1.0.0' },
30
+ apps: [MyApp],
31
+ auth: { mode: 'public', sessionTtl: 3600, anonymousScopes: ['anonymous'] },
32
+ http: {
33
+ port: Number(process.env['PORT']) || 3000,
34
+ routes: [
35
+ // 1. Public binary download. The adapter defaults responses to JSON, so a
36
+ // binary/HTML handler MUST set its own Content-Type before sending.
37
+ {
38
+ method: 'GET',
39
+ path: '/download/:id',
40
+ handler: (req: ServerRequest, res: ServerResponse) => {
41
+ res.setHeader('Content-Type', 'application/pdf');
42
+ res.status(200).send(loadPdfBytes(req.params['id']));
43
+ },
44
+ },
45
+
46
+ // 2. The `/connect-env` pattern: validate a user-entered secret against a
47
+ // backend WITHOUT finalizing an OAuth exchange. The shared
48
+ // express.json() middleware parses req.body (subject to http.bodyLimit).
49
+ {
50
+ method: 'POST',
51
+ path: '/connect-env',
52
+ handler: async (req: ServerRequest, res: ServerResponse) => {
53
+ const secret = (req.body as { secret?: string } | undefined)?.secret;
54
+ if (!secret || !(await isValidSecret(secret))) {
55
+ res.status(400).json({ ok: false, error: 'invalid secret' });
56
+ return;
57
+ }
58
+ res.status(200).json({ ok: true, connected: true });
59
+ },
60
+ },
61
+
62
+ // 3. Auth-gated webhook. With auth: true the request runs the MCP
63
+ // session:verify flow first; req.authSession is populated on success.
64
+ {
65
+ method: 'POST',
66
+ path: '/webhooks/billing',
67
+ auth: true,
68
+ handler: (req: ServerRequest, res: ServerResponse) => {
69
+ res.status(202).json({ accepted: true, user: req.authSession?.user?.sub });
70
+ },
71
+ },
72
+ ],
73
+ },
74
+ })
75
+ class Server {}
76
+
77
+ declare function loadPdfBytes(id: string): Buffer;
78
+ declare function isValidSecret(secret: string): Promise<boolean>;
79
+ ```
80
+
81
+ ## What This Demonstrates
82
+
83
+ - Registering custom HTTP endpoints with `http.routes` — no tool/resource/prompt needed
84
+ - A POST endpoint that validates a user-entered secret server-side (the `/connect-env` pattern)
85
+ - Overriding the default `application/json` Content-Type for binary/HTML delivery
86
+ - Gating a route behind the MCP `session:verify` flow with `auth: true`
87
+ - Avoiding reserved paths (`/sse`, `/message`, `/oauth/*`, `/.well-known/*`, `/health`, `/metrics`)
88
+
89
+ ## Gotchas
90
+
91
+ - Reserved paths fail-fast at startup: the resolved MCP entry path and its `/sse` + `/message` siblings, anything under `/oauth/*` and `/.well-known/*`, and `/health` + `/metrics`.
92
+ - For large payloads, prefer a custom GET route + a `resource_link` over a `@Resource` — a resource rides the JSON-RPC channel and is not out-of-band.
93
+ - A custom `hostFactory` owns its Express app (body limits, CORS, Content-Type defaults); `http.routes` and `bodyLimit` are consumed only by the built-in `ExpressHostAdapter`.
94
+
95
+ ## Related
96
+
97
+ - See `configure-http` for the full HTTP configuration reference
98
+ - See `configure-auth-modes` for how `auth: true` behaves under each auth mode
@@ -19,7 +19,7 @@ Production-grade audit log with the Redis-backed StorageAdapterAuditStore and th
19
19
 
20
20
  ```typescript
21
21
  // src/server.ts — must be an ES module (`"type": "module"` in package.json,
22
- // or `.mts` extension) so the top-level `await createStorageAdapter(...)`
22
+ // or `.mts` extension) so the top-level `await createStorage(...)`
23
23
  // below is allowed. CommonJS consumers should wrap the bootstrap inside an
24
24
  // `async function init() { ... }` and await it before constructing the
25
25
  // FrontMcp class.
@@ -33,7 +33,7 @@ import {
33
33
  StorageAdapterAuditStore,
34
34
  } from '@frontmcp/adapters/skills';
35
35
  import { FrontMcp } from '@frontmcp/sdk';
36
- import { createStorageAdapter } from '@frontmcp/utils';
36
+ import { createStorage } from '@frontmcp/utils';
37
37
 
38
38
  import { MainApp } from './main.app';
39
39
 
@@ -48,11 +48,16 @@ setSkillAuditFactory(() => ({
48
48
  MemoryAuditStore,
49
49
  }));
50
50
 
51
- const auditStorage = await createStorageAdapter({
52
- provider: 'redis',
53
- host: process.env.REDIS_HOST!,
54
- port: 6379,
55
- keyPrefix: 'mcp:skill-audit:',
51
+ // createStorage() returns a RootStorage, which is a StorageAdapter. Note this
52
+ // is the @frontmcp/utils storage-adapter config shape (`redis.config`), which
53
+ // is distinct from the SDK `RedisOptions` (`{ provider, host, port }`) used by
54
+ // `@FrontMcp({ redis })` and `skillsConfig.cache.redis` below.
55
+ const auditStorage = await createStorage({
56
+ type: 'redis',
57
+ redis: {
58
+ config: { host: process.env.REDIS_HOST!, port: 6379 },
59
+ keyPrefix: 'mcp:skill-audit:',
60
+ },
56
61
  });
57
62
 
58
63
  // Constructor signature: new Rs256AuditSigner(privateJwk, keyId).
@@ -86,9 +91,12 @@ export default class ProductionServer {}
86
91
  ```typescript
87
92
  // scripts/verify-audit-chain.ts — run in CI
88
93
  import { defaultAuditSignatureVerifier, StorageAdapterAuditStore, verifyChain } from '@frontmcp/adapters/skills';
89
- import { createStorageAdapter } from '@frontmcp/utils';
94
+ import { createStorage } from '@frontmcp/utils';
90
95
 
91
- const storage = await createStorageAdapter({ provider: 'redis', host: process.env.REDIS_HOST!, port: 6379 });
96
+ const storage = await createStorage({
97
+ type: 'redis',
98
+ redis: { config: { host: process.env.REDIS_HOST!, port: 6379 } },
99
+ });
92
100
  const store = new StorageAdapterAuditStore(storage);
93
101
  const records = await store.iterate();
94
102
 
@@ -29,64 +29,127 @@ auth: {
29
29
  mode: 'transparent',
30
30
  provider: 'https://auth.example.com',
31
31
  expectedAudience: 'my-api',
32
- clientId: 'my-client-id',
32
+ // clientId is OPTIONAL and unused for pure JWT validation — transparent mode
33
+ // never runs an OAuth code exchange, it only verifies tokens against the
34
+ // provider's JWKS. Omit it unless you have a specific reason to set it.
33
35
  }
34
36
  ```
35
37
 
36
38
  **Use when:** Behind an API gateway or reverse proxy that handles auth.
37
39
 
40
+ > Transparent also accepts `allowAnonymous` (default `false`) + `anonymousScopes` (default `['anonymous']`) to admit tokenless requests as anonymous, and `requiredScopes` to reject tokens missing a scope. `expectedAudience` is shared across transparent/local/remote, not transparent-only.
41
+
38
42
  ## Local Mode
39
43
 
40
- Server signs its own JWT tokens. Full control over token lifecycle.
44
+ Built-in OAuth 2.1 server that signs its own JWT tokens. Full control over token lifecycle.
41
45
 
42
46
  ```typescript
43
47
  auth: {
44
48
  mode: 'local',
45
49
  local: {
46
- issuer: 'my-server',
50
+ issuer: 'https://mcp.example.com', // optional; request-host-derived otherwise
47
51
  },
48
52
  expectedAudience: 'my-api',
49
- tokenStorage: { redis: { host: process.env['REDIS_HOST'] ?? 'localhost', port: 6379 } },
50
- consent: { enabled: true },
51
- incrementalAuth: { enabled: true },
53
+ // 'memory' (default, lost on restart) | { sqlite: { path } } | { redis: { ... } }
54
+ tokenStorage: { sqlite: { path: './data/auth.sqlite' } },
55
+ consent: { enabled: true }, // tool-selection screen at login + call-time enforcement
56
+ incrementalAuth: { enabled: true }, // app-level gating + progressive expansion (opt-in)
52
57
  }
53
58
  ```
54
59
 
55
- **Use when:** Standalone servers with full auth control, development with local OAuth.
60
+ Signing is **HS256 with a symmetric `JWT_SECRET`** (no key pair). Set a stable `JWT_SECRET` or tokens are invalidated on every restart. For a single operator (e.g. Claude Code), add `requireEmail: false` to skip the email prompt (a stable `sub` is derived from `anonymousSubject`, default `'local-operator'`).
61
+
62
+ Local mode also accepts `allowDefaultPublic` (default `false` — set `true` to admit tokenless requests as anonymous instead of returning 401), `anonymousScopes` (default `['anonymous']` — scopes for those anonymous sessions), and `expectedAudience` (reject tokens minted for a different `aud`).
63
+
64
+ **Progressive / incremental authorization** (opt-in via `incrementalAuth`): when enabled, the minted token carries an `authorized_apps` claim and a `tools/call` for an app NOT in that claim resolves to a `CallToolResult` with `isError: true` and `_meta.code === 'AUTHORIZATION_REQUIRED'` (fields: `authorization_required: true`, `app`, `tool`, `auth_url`, `required_scopes`, `session_mode`, `supports_incremental`). The client declares the initial grant on `/oauth/authorize?…&apps=crm` (omit `apps` to grant all apps) and expands it later via an incremental authorize `…&mode=incremental&app=slack&apps=crm` — the new token's claim is the **union** of the prior apps plus the target (the user identity and already-granted apps are preserved; upstream tokens stay server-side). Without an `incrementalAuth` block, no claim is minted and there is **no** app-level gating (allow-all preserved). `consent` (tool-level) and `incrementalAuth` (app-level) are independent.
65
+
66
+ To collect and verify your own credentials, add a declarative `login` (custom page fields / title / subject strategy) and an `authenticate(input, ctx)` verifier that returns `{ ok: true, sub?, claims? }` (custom claims are embedded in the token; reserved claims are stripped) or `{ ok: false, message }` (re-renders the login page; no code issued). Both are optional and default to the built-in email login. See `configure-auth.md` for a full example.
67
+
68
+ To orchestrate **multiple upstream OAuth providers** (GitHub, Slack, Jira, …) declare a `providers` array — FrontMCP federates them at `/oauth/authorize`, refuses to mint a JWT until `federatedAuth.minProviders` (default `1`) are linked, stores each provider's tokens encrypted server-side, and exposes them to tools via `this.orchestration.getToken(id)`:
69
+
70
+ ```typescript
71
+ auth: {
72
+ mode: 'local',
73
+ providers: [
74
+ { id: 'github', authorizeUrl: '…', tokenUrl: '…', clientId: '…', scopes: ['repo'] },
75
+ { id: 'slack', authorizeUrl: '…', tokenUrl: '…', clientId: '…' },
76
+ ],
77
+ federatedAuth: { minProviders: 1, requiredProviders: ['github'] }, // no JWT until linked
78
+ }
79
+ ```
80
+
81
+ See `configure-auth.md` → "Multi-provider orchestration" for the full provider schema and the `this.orchestration` tool API.
82
+
83
+ **Use when:** Standalone servers with full auth control, development with local OAuth, orchestrating several upstream OAuth providers behind one MCP server.
56
84
 
57
85
  ## Remote Mode
58
86
 
59
- Server delegates to an upstream auth orchestrator for token management.
87
+ FrontMCP runs a local OAuth 2.1 server that **proxies user authentication to one
88
+ mandatory upstream IdP**. `GET /oauth/authorize` redirects **straight to the
89
+ upstream IdP** — there is no FrontMCP login page and no provider-selection page.
90
+ After the IdP returns to `/oauth/provider/{id}/callback`, FrontMCP exchanges the
91
+ code, stores the upstream tokens encrypted (server-side), derives the session
92
+ identity (`sub`/`email`/`name`) from the **upstream user**, and mints its own
93
+ HS256 session token. Tools read the upstream token via
94
+ `this.orchestration.getToken('<provider-id>')`.
60
95
 
61
96
  ```typescript
62
97
  auth: {
63
98
  mode: 'remote',
64
99
  provider: 'https://auth.example.com',
65
- clientId: 'my-client-id',
100
+ clientId: 'my-client-id', // pre-registered (DCR not yet wired)
66
101
  clientSecret: process.env.AUTH_SECRET,
102
+ scopes: ['openid', 'profile', 'email'],
67
103
  tokenStorage: { redis: { host: process.env['REDIS_HOST'] ?? 'localhost', port: 6379 } },
104
+ // The provider id defaults to the `provider` hostname; pin a stable id (and/or
105
+ // override non-standard endpoints) with providerConfig:
106
+ providerConfig: { id: 'idp' },
68
107
  }
69
108
  ```
70
109
 
71
- **Use when:** Enterprise deployments with centralized identity management.
110
+ Endpoints are derived from `provider` using standard OIDC paths
111
+ (`/authorize`, `/token`, `/userinfo`, `/.well-known/jwks.json`). For
112
+ non-standard IdPs, override them with
113
+ `providerConfig.{authEndpoint,tokenEndpoint,userInfoEndpoint,jwksUri}`.
114
+
115
+ **Deferred (not yet wired):** upstream **Dynamic Client Registration**
116
+ (`providerConfig.dcrEnabled` / `registrationEndpoint`) — a pre-registered
117
+ `clientId` is required; and upstream **token auto-refresh** — once the upstream
118
+ access token expires the user must re-authenticate (FrontMCP's own session token
119
+ still refreshes via the `refresh_token` grant).
120
+
121
+ **Use when:** Enterprise deployments delegating user authentication to a single
122
+ centralized IdP that may not support DCR, while keeping FrontMCP-issued sessions,
123
+ upstream-token access in tools, and an optional consent layer.
72
124
 
73
125
  ## Comparison Table
74
126
 
75
- | Feature | Public | Transparent | Local | Remote |
76
- | ---------------- | ------------- | --------------- | ----------- | ------------ |
77
- | Token issuance | Anonymous JWT | None (upstream) | Self-signed | Orchestrator |
78
- | Token refresh | No | No | Yes | Yes |
79
- | PKCE support | No | No | Yes | Yes |
80
- | Credential vault | No | No | Yes | Yes |
81
- | Consent flow | No | No | Optional | Optional |
82
- | Federated auth | No | No | Optional | Optional |
127
+ | Feature | Public | Transparent | Local | Remote |
128
+ | ------------------------ | ------------- | --------------- | ------------------------------- | --------------------------------- |
129
+ | Token issuance | Anonymous JWT | None (upstream) | Self-signed (HS256) | Self-signed (HS256) |
130
+ | Signing | HS256 secret | Upstream JWKS | HS256 secret (`JWT_SECRET`) | HS256 secret (`JWT_SECRET`) |
131
+ | Session-token refresh | No | No | Yes | Yes |
132
+ | Upstream-token refresh | n/a | n/a | On-demand (when wired) | Not yet wired (re-auth on expiry) |
133
+ | Identity source | Anonymous | Upstream token | Login form / `authenticate()` | Upstream IdP user |
134
+ | PKCE support | No | No | Yes | Yes |
135
+ | Token persistence | n/a | n/a | memory / sqlite / redis | memory / sqlite / redis |
136
+ | Consent (tool selection) | No | No | Optional (screen + enforcement) | Optional (screen + enforcement) |
137
+ | Upstream OAuth providers | No | No | 0..N (declared `providers[]`) | Exactly 1 (mandatory) |
138
+
139
+ > "Remote" still issues its own HS256 session token to the MCP client; it delegates **user authentication** to a single upstream IdP rather than delegating token signing. `GET /oauth/authorize` redirects straight to that IdP (no in-tree login page), and tools read the upstream token via `this.orchestration.getToken(id)`.
83
140
 
84
141
  ## Examples
85
142
 
86
- | Example | Level | Description |
87
- | ---------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------- |
88
- | [`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. |
89
- | [`remote-enterprise-oauth`](../examples/configure-auth-modes/remote-enterprise-oauth.md) | Advanced | Delegate authentication to an external OAuth orchestrator with Redis-backed token storage. |
90
- | [`transparent-jwt-validation`](../examples/configure-auth-modes/transparent-jwt-validation.md) | Basic | Validate externally-issued JWTs without managing token lifecycle on the server. |
143
+ | Example | Level | Description |
144
+ | -------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
+ | [`local-minimal`](../examples/configure-auth-modes/local-minimal.md) | Basic | Stand up the built-in local OAuth 2.1 server with the minimum configuration: HS256 signing via JWT_SECRET and in-memory token storage. |
146
+ | [`local-self-signed-tokens`](../examples/configure-auth-modes/local-self-signed-tokens.md) | Intermediate | Configure a local-mode server that signs its own HS256 JWTs and persists auth state across restarts with SQLite or Redis. |
147
+ | [`local-single-operator`](../examples/configure-auth-modes/local-single-operator.md) | Basic | Run local mode for a single operator (e.g. Claude Code) by skipping the email prompt and minting a stable anonymous subject. |
148
+ | [`local-consent-enforcement`](../examples/configure-auth-modes/local-consent-enforcement.md) | Intermediate | Enable consent in local mode to render a tool-selection screen at login and enforce the chosen tools at call time, keeping essential tools always available via excludedTools. |
149
+ | [`local-behind-tunnel`](../examples/configure-auth-modes/local-behind-tunnel.md) | Intermediate | Expose a local-mode server through a tunnel or TLS proxy by aligning the token issuer with the public URL clients actually reach. |
150
+ | [`local-multi-provider-orchestration`](../examples/configure-auth-modes/local-multi-provider-orchestration.md) | Advanced | Orchestrate multiple upstream OAuth providers (GitHub + Slack) in local mode, gate the JWT until they are linked, and read downstream tokens in tools via this.orchestration. |
151
+ | [`local-dcr-control`](../examples/configure-auth-modes/local-dcr-control.md) | Intermediate | Lock down the built-in Dynamic Client Registration endpoint with the auth.dcr control surface: disable open registration, allowlist redirect URIs and client ids, and seed a pre-registered trusted client. |
152
+ | [`remote-enterprise-oauth`](../examples/configure-auth-modes/remote-enterprise-oauth.md) | Advanced | Proxy authentication to one mandatory upstream IdP, mint a FrontMCP session, and read the upstream token in tools. |
153
+ | [`transparent-jwt-validation`](../examples/configure-auth-modes/transparent-jwt-validation.md) | Basic | Validate externally-issued JWTs without managing token lifecycle on the server. |
91
154
 
92
155
  > See all examples in [`examples/configure-auth-modes/`](../examples/configure-auth-modes/)