@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.
- package/README.md +38 -29
- package/catalog/TEMPLATE.md +26 -0
- package/catalog/create-tool/SKILL.md +318 -0
- package/catalog/create-tool/examples/01-basic-class-tool.md +112 -0
- package/catalog/create-tool/examples/02-basic-function-tool.md +80 -0
- package/catalog/create-tool/examples/03-tool-with-zod-shape-output.md +78 -0
- package/catalog/create-tool/examples/04-tool-with-zod-schema-output.md +97 -0
- package/catalog/create-tool/examples/05-tool-with-primitive-output.md +93 -0
- package/catalog/create-tool/examples/06-tool-with-media-output.md +109 -0
- package/catalog/create-tool/examples/08-tool-with-provider-injection.md +110 -0
- package/catalog/create-tool/examples/09-tool-with-multiple-providers.md +107 -0
- package/catalog/create-tool/examples/11-tool-with-fetch.md +94 -0
- package/catalog/create-tool/examples/12-tool-with-fetch-and-retries.md +115 -0
- package/catalog/create-tool/examples/13-tool-with-single-auth-provider.md +85 -0
- package/catalog/create-tool/examples/14-tool-with-multiple-auth-providers.md +105 -0
- package/catalog/create-tool/examples/15-tool-with-credential-vault.md +115 -0
- package/catalog/create-tool/examples/16-tool-with-rate-limit.md +71 -0
- package/catalog/create-tool/examples/17-tool-with-concurrency-and-timeout.md +101 -0
- package/catalog/create-tool/examples/18-tool-with-progress-and-notify.md +96 -0
- package/catalog/create-tool/examples/19-tool-with-elicitation.md +102 -0
- package/catalog/create-tool/examples/20-tool-with-annotations.md +125 -0
- package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +107 -0
- package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +93 -0
- package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +112 -0
- package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +127 -0
- package/catalog/create-tool/examples/25-tool-handing-off-to-job.md +143 -0
- package/catalog/create-tool/examples/26-tool-with-resource-link-output.md +94 -0
- package/catalog/create-tool/examples/27-tool-with-examples-metadata.md +90 -0
- package/catalog/create-tool/references/annotations.md +96 -0
- package/catalog/create-tool/references/auth-providers.md +167 -0
- package/catalog/create-tool/references/availability.md +106 -0
- package/catalog/create-tool/references/decorator-options.md +95 -0
- package/catalog/create-tool/references/derived-types.md +102 -0
- package/catalog/create-tool/references/elicitation.md +128 -0
- package/catalog/create-tool/references/error-handling.md +128 -0
- package/catalog/create-tool/references/execution-context.md +158 -0
- package/catalog/create-tool/references/file-layout.md +96 -0
- package/catalog/create-tool/references/function-style-builder.md +118 -0
- package/catalog/create-tool/references/input-schema.md +141 -0
- package/catalog/create-tool/references/output-schema.md +175 -0
- package/catalog/create-tool/references/quick-start.md +124 -0
- package/catalog/create-tool/references/registration.md +132 -0
- package/catalog/create-tool/references/remote-and-esm.md +68 -0
- package/catalog/create-tool/references/testing.md +59 -0
- package/catalog/create-tool/references/throttling.md +109 -0
- package/catalog/create-tool/references/ui-widgets.md +198 -0
- package/catalog/create-tool/rules/always-define-output-schema.md +77 -0
- package/catalog/create-tool/rules/derive-execute-types.md +57 -0
- package/catalog/create-tool/rules/input-schema-is-raw-shape.md +76 -0
- package/catalog/create-tool/rules/no-toolcontext-generics.md +50 -0
- package/catalog/create-tool/rules/no-try-catch-around-execute.md +79 -0
- package/catalog/create-tool/rules/register-in-app.md +76 -0
- package/catalog/create-tool/rules/snake-case-tool-names.md +45 -0
- package/catalog/create-tool/rules/use-this-fail-for-business-errors.md +75 -0
- package/catalog/create-tool/rules/widget-paths-anchor-with-import-meta-url.md +76 -0
- package/catalog/create-tool/rules/widget-resource-mode-host-detect.md +61 -0
- package/catalog/frontmcp-auth-ui/SKILL.md +146 -0
- package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/login-slot.md +97 -0
- package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/multi-step-auth-extra.md +133 -0
- package/catalog/frontmcp-auth-ui/references/custom-auth-ui.md +162 -0
- package/catalog/frontmcp-authorities/SKILL.md +55 -18
- package/catalog/frontmcp-authorities/references/authority-profiles.md +25 -1
- package/catalog/frontmcp-authorities/references/custom-evaluators.md +1 -1
- package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +9 -0
- package/catalog/frontmcp-channels/SKILL.md +7 -1
- package/catalog/frontmcp-config/SKILL.md +14 -7
- package/catalog/frontmcp-config/examples/configure-auth/local-credential-vault.md +94 -0
- package/catalog/frontmcp-config/examples/configure-auth/local-secure-store.md +138 -0
- package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +45 -23
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-behind-tunnel.md +73 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-consent-enforcement.md +87 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-dcr-control.md +67 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-minimal.md +62 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-multi-provider-orchestration.md +93 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +18 -20
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-single-operator.md +66 -0
- package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +37 -23
- package/catalog/frontmcp-config/examples/configure-http/custom-http-routes.md +98 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +17 -9
- package/catalog/frontmcp-config/references/configure-auth-modes.md +86 -23
- package/catalog/frontmcp-config/references/configure-auth.md +296 -50
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
- package/catalog/frontmcp-config/references/configure-http.md +203 -14
- package/catalog/frontmcp-config/references/configure-session.md +14 -7
- package/catalog/frontmcp-deployment/SKILL.md +17 -15
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
- package/catalog/frontmcp-deployment/references/deploy-manifest-yaml.md +308 -0
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +174 -0
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +145 -2
- package/catalog/frontmcp-development/SKILL.md +36 -50
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
- package/catalog/frontmcp-development/references/create-job.md +45 -11
- package/catalog/frontmcp-development/references/create-provider.md +80 -8
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
- package/catalog/frontmcp-development/references/create-skill.md +45 -0
- package/catalog/frontmcp-development/references/decorators-guide.md +15 -15
- package/catalog/frontmcp-extensibility/SKILL.md +1 -1
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +8 -6
- package/catalog/frontmcp-extensibility/references/skill-audit-log.md +7 -2
- package/catalog/frontmcp-guides/SKILL.md +8 -8
- package/catalog/frontmcp-observability/SKILL.md +16 -8
- package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
- package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
- package/catalog/frontmcp-production-readiness/SKILL.md +1 -1
- package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -2
- package/catalog/frontmcp-setup/SKILL.md +12 -12
- package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
- package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
- package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
- package/catalog/frontmcp-setup/references/setup-project.md +29 -0
- package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
- package/catalog/frontmcp-testing/SKILL.md +26 -18
- package/catalog/frontmcp-testing/references/test-auth.md +24 -0
- package/catalog/skills-manifest.json +676 -146
- package/package.json +1 -1
- package/src/manifest.d.ts +72 -1
- package/src/manifest.js +4 -1
- package/src/manifest.js.map +1 -1
- package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +0 -61
- package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -84
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -92
- package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +0 -92
- package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +0 -59
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +0 -101
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +0 -62
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +0 -101
- package/catalog/frontmcp-development/references/create-tool-annotations.md +0 -48
- package/catalog/frontmcp-development/references/create-tool-output-schema-types.md +0 -71
- package/catalog/frontmcp-development/references/create-tool.md +0 -728
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-secure-store
|
|
3
|
+
reference: configure-auth
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: 'Configure the general session secure-secret store (this.secureStore) with a pluggable backing (memory / sqlite / redis / custom OS-keychain) and read/write user-typed secrets from a tool.'
|
|
6
|
+
tags: [config, auth, local, secure-store, secrets, this-secure-store, keychain]
|
|
7
|
+
features:
|
|
8
|
+
- 'Selecting the secure-store backing via `auth.secureStore` (memory / sqlite / redis / custom backend) plus a namespace `scope`'
|
|
9
|
+
- 'Reading/writing arbitrary user-typed secrets from a tool via `this.secureStore.set/get/list/delete` (JSON-serialized, scoped to the session/subject)'
|
|
10
|
+
- 'Backing the store with an OS keychain by supplying a `SecureStoreBackend` — no native dependency is bundled by the framework'
|
|
11
|
+
- 'Understanding scope: `user` (keyed by sub, default), `session` (keyed by sessionId), `global` (server-wide)'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Local Mode with the Session Secure-Secret Store (`this.secureStore`)
|
|
15
|
+
|
|
16
|
+
Configure the general session secure-secret store (this.secureStore) with a pluggable backing (memory / sqlite / redis / custom OS-keychain) and read/write user-typed secrets from a tool.
|
|
17
|
+
|
|
18
|
+
The store is distinct from `this.credentials` (which is OAuth-credential-centric):
|
|
19
|
+
use `this.secureStore` for arbitrary secrets like an API key a tool prompts for.
|
|
20
|
+
The built-in backings encrypt at rest with AES-256-GCM, and an OS keychain can be
|
|
21
|
+
plugged in without the framework bundling any native dependency.
|
|
22
|
+
|
|
23
|
+
## Code
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// src/server.ts
|
|
27
|
+
import { App, FrontMcp, Tool, ToolContext, z, type SecureStoreConfig } from '@frontmcp/sdk';
|
|
28
|
+
|
|
29
|
+
@Tool({
|
|
30
|
+
name: 'set_api_key',
|
|
31
|
+
description: 'Store a user-typed API key in the session secure store',
|
|
32
|
+
inputSchema: { apiKey: z.string() },
|
|
33
|
+
outputSchema: { saved: z.boolean(), keys: z.array(z.string()) },
|
|
34
|
+
})
|
|
35
|
+
class SetApiKeyTool extends ToolContext {
|
|
36
|
+
async execute(input: { apiKey: string }) {
|
|
37
|
+
// Values are JSON-serialized and encrypted at rest (built-in backings),
|
|
38
|
+
// scoped to the current `sub` (user scope, the default).
|
|
39
|
+
await this.secureStore.set('stg.api-key', input.apiKey);
|
|
40
|
+
const keys = await this.secureStore.list();
|
|
41
|
+
return { saved: true, keys };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Tool({
|
|
46
|
+
name: 'read_api_key',
|
|
47
|
+
description: 'Read the stored API key (presence only — never returns the raw secret)',
|
|
48
|
+
inputSchema: {},
|
|
49
|
+
outputSchema: { present: z.boolean() },
|
|
50
|
+
})
|
|
51
|
+
class ReadApiKeyTool extends ToolContext {
|
|
52
|
+
async execute() {
|
|
53
|
+
const apiKey = await this.secureStore.get<string>('stg.api-key');
|
|
54
|
+
// Never return the raw secret to the model — only presence.
|
|
55
|
+
return { present: apiKey !== undefined };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@App({ name: 'Secrets', tools: [SetApiKeyTool, ReadApiKeyTool] })
|
|
60
|
+
class SecretsApp {}
|
|
61
|
+
|
|
62
|
+
// Pick the backing per deployment. Default ('memory') is encrypted in-process.
|
|
63
|
+
const secureStore: SecureStoreConfig = {
|
|
64
|
+
sqlite: { path: './.frontmcp/secrets.sqlite' }, // survives restart
|
|
65
|
+
scope: 'user', // 'user' (default) | 'session' | 'global'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
@FrontMcp({
|
|
69
|
+
info: { name: 'secure-store-demo', version: '0.1.0' },
|
|
70
|
+
apps: [SecretsApp],
|
|
71
|
+
auth: {
|
|
72
|
+
mode: 'local',
|
|
73
|
+
secureStore,
|
|
74
|
+
login: {
|
|
75
|
+
title: 'Sign in',
|
|
76
|
+
fields: { apiKey: { type: 'password', label: 'API Key', required: true } },
|
|
77
|
+
subject: { fromField: 'apiKey', strategy: 'per-account' },
|
|
78
|
+
},
|
|
79
|
+
authenticate: async (input) => {
|
|
80
|
+
if (!input.fields['apiKey']) return { ok: false, message: 'API key required', retryField: 'apiKey' };
|
|
81
|
+
return { ok: true };
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
export default class Server {}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## What This Demonstrates
|
|
89
|
+
|
|
90
|
+
- Selecting the secure-store backing via `auth.secureStore` (memory / sqlite / redis / custom backend) plus a namespace `scope`
|
|
91
|
+
- Reading/writing arbitrary user-typed secrets from a tool via `this.secureStore.set/get/list/delete` (JSON-serialized, scoped to the session/subject)
|
|
92
|
+
- Backing the store with an OS keychain by supplying a `SecureStoreBackend` — no native dependency is bundled by the framework
|
|
93
|
+
- Understanding scope: `user` (keyed by sub, default), `session` (keyed by sessionId), `global` (server-wide)
|
|
94
|
+
|
|
95
|
+
## Optional: back the store with an OS keychain (pluggable, not bundled)
|
|
96
|
+
|
|
97
|
+
FrontMCP does **not** ship `keytar`/`wincred`/`libsecret`. Supply an object
|
|
98
|
+
implementing `SecureStoreBackend` and the framework uses it as-is (no framework
|
|
99
|
+
crypto — an OS keychain is encrypted by the OS):
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import type { SecureStoreBackend } from '@frontmcp/sdk';
|
|
103
|
+
|
|
104
|
+
// import keytar from 'keytar'; // YOU add the native peer-dep
|
|
105
|
+
|
|
106
|
+
const keychainBackend: SecureStoreBackend = {
|
|
107
|
+
async get(namespace, key) {
|
|
108
|
+
return (await keytar.getPassword(`frontmcp:${namespace}`, key)) ?? null;
|
|
109
|
+
},
|
|
110
|
+
async set(namespace, key, value /*, ttlMs */) {
|
|
111
|
+
await keytar.setPassword(`frontmcp:${namespace}`, key, value); // ttlMs ignored
|
|
112
|
+
},
|
|
113
|
+
async delete(namespace, key) {
|
|
114
|
+
return keytar.deletePassword(`frontmcp:${namespace}`, key);
|
|
115
|
+
},
|
|
116
|
+
async list(/* namespace */) {
|
|
117
|
+
const creds = await keytar.findCredentials('frontmcp');
|
|
118
|
+
return creds.map((c) => c.account);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// auth: { mode: 'local', secureStore: { backend: keychainBackend, scope: 'global' } }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Notes
|
|
126
|
+
|
|
127
|
+
- **Backings**: `'memory'` (default, encrypted in-process), `{ sqlite: { path } }`,
|
|
128
|
+
`{ redis: { ... } }`, or `{ backend }` (custom). The persistent built-ins reuse
|
|
129
|
+
the same `StorageAdapter`/`VaultEncryption` as `tokenStorage`; when the backing
|
|
130
|
+
matches `tokenStorage` the same connection is shared.
|
|
131
|
+
- **Scope**: `user` keys by `sub` (anonymous requests read empty / skip writes),
|
|
132
|
+
`session` keys by `sessionId`, `global` shares one server-wide namespace. The
|
|
133
|
+
identity is hashed into the namespace — never stored raw.
|
|
134
|
+
- **API**: `get<T>(key)`, `set<T>(key, value, { ttlMs? })`, `delete(key)`,
|
|
135
|
+
`list()`. Object configs also accept `ttlMs` and `encryption.pepper`
|
|
136
|
+
(overrides `VAULT_SECRET ?? JWT_SECRET`).
|
|
137
|
+
- **Never return raw secrets to the model** — expose presence/redacted previews
|
|
138
|
+
only, as `read_api_key` does above.
|
|
@@ -2,17 +2,26 @@
|
|
|
2
2
|
name: remote-oauth-with-vault
|
|
3
3
|
reference: configure-auth
|
|
4
4
|
level: intermediate
|
|
5
|
-
description: 'Configure a FrontMCP server with
|
|
6
|
-
tags: [config, oauth, auth,
|
|
5
|
+
description: 'Configure a FrontMCP server with local OAuth orchestration of an upstream provider and read the downstream provider token in a tool via this.orchestration.getToken.'
|
|
6
|
+
tags: [config, oauth, auth, orchestration, providers]
|
|
7
7
|
features:
|
|
8
|
-
-
|
|
9
|
-
- 'Loading `clientId` from environment variables instead of hardcoding'
|
|
10
|
-
- "
|
|
8
|
+
- 'Declaring an upstream provider in top-level `auth.providers` so FrontMCP orchestrates its OAuth flow'
|
|
9
|
+
- 'Loading `clientId`/`clientSecret` from environment variables instead of hardcoding'
|
|
10
|
+
- "Reading the downstream provider token in a tool via `this.orchestration.getToken('github')`"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# Local OAuth Orchestration with a Downstream Provider Token
|
|
14
14
|
|
|
15
|
-
Configure a FrontMCP server with
|
|
15
|
+
Configure a FrontMCP server with local OAuth orchestration of an upstream provider and read the downstream provider token in a tool via this.orchestration.getToken.
|
|
16
|
+
|
|
17
|
+
> **Two distinct subsystems — do not conflate them.** `this.orchestration` reads
|
|
18
|
+
> tokens for providers declared in top-level `auth.providers` (the local-mode
|
|
19
|
+
> multi-provider OAuth orchestrator). `this.authProviders` is a _separate_
|
|
20
|
+
> downstream-OAuth-provider accessor whose providers are registered via
|
|
21
|
+
> `@App`/`@Tool({ authProviders: [...] })`. Neither is the per-session credential
|
|
22
|
+
> vault — that is `this.credentials` (see `local-credential-vault`). This example
|
|
23
|
+
> uses the orchestrator, so the provider MUST be declared in `auth.providers` or
|
|
24
|
+
> `getToken('github')` has nothing to return.
|
|
16
25
|
|
|
17
26
|
## Code
|
|
18
27
|
|
|
@@ -32,12 +41,14 @@ import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
|
32
41
|
})
|
|
33
42
|
class CreateGithubIssueTool extends ToolContext {
|
|
34
43
|
async execute(input: { repo: string; title: string; body: string }) {
|
|
35
|
-
//
|
|
36
|
-
|
|
44
|
+
// Read the orchestrated GitHub token (declared in auth.providers below).
|
|
45
|
+
// Throws if GitHub is not linked for this session; use tryGetToken() for a
|
|
46
|
+
// null-on-missing variant. The raw token is never exposed to the LLM.
|
|
47
|
+
const token = await this.orchestration.getToken('github');
|
|
37
48
|
|
|
38
49
|
const response = await fetch(`https://api.github.com/repos/${input.repo}/issues`, {
|
|
39
50
|
method: 'POST',
|
|
40
|
-
headers: {
|
|
51
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
41
52
|
body: JSON.stringify({ title: input.title, body: input.body }),
|
|
42
53
|
});
|
|
43
54
|
const issue = await response.json();
|
|
@@ -45,31 +56,42 @@ class CreateGithubIssueTool extends ToolContext {
|
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
@App({
|
|
49
|
-
name: 'dev-tools',
|
|
50
|
-
auth: {
|
|
51
|
-
mode: 'remote',
|
|
52
|
-
provider: 'https://auth.example.com',
|
|
53
|
-
clientId: process.env['OAUTH_CLIENT_ID'] ?? 'mcp-client-id',
|
|
54
|
-
},
|
|
55
|
-
tools: [CreateGithubIssueTool],
|
|
56
|
-
})
|
|
59
|
+
@App({ name: 'dev-tools', tools: [CreateGithubIssueTool] })
|
|
57
60
|
class DevToolsApp {}
|
|
58
61
|
|
|
59
62
|
@FrontMcp({
|
|
60
63
|
info: { name: 'dev-tools-server', version: '1.0.0' },
|
|
61
64
|
apps: [DevToolsApp],
|
|
65
|
+
// Server-level auth so it applies to every app (no `splitByApp` needed).
|
|
66
|
+
auth: {
|
|
67
|
+
mode: 'local',
|
|
68
|
+
// Declare the upstream provider FrontMCP should orchestrate. FrontMCP
|
|
69
|
+
// federates it at /oauth/authorize, stores its tokens encrypted
|
|
70
|
+
// server-side, and exposes them via this.orchestration.getToken('github').
|
|
71
|
+
providers: [
|
|
72
|
+
{
|
|
73
|
+
id: 'github',
|
|
74
|
+
authorizeUrl: 'https://github.com/login/oauth/authorize',
|
|
75
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
76
|
+
clientId: process.env['GITHUB_CLIENT_ID']!,
|
|
77
|
+
clientSecret: process.env['GITHUB_CLIENT_SECRET'],
|
|
78
|
+
scopes: ['repo'],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
federatedAuth: { minProviders: 1, requiredProviders: ['github'] },
|
|
82
|
+
},
|
|
62
83
|
})
|
|
63
84
|
class Server {}
|
|
64
85
|
```
|
|
65
86
|
|
|
66
87
|
## What This Demonstrates
|
|
67
88
|
|
|
68
|
-
-
|
|
69
|
-
- Loading `clientId` from environment variables instead of hardcoding
|
|
70
|
-
-
|
|
89
|
+
- Declaring an upstream provider in top-level `auth.providers` so FrontMCP orchestrates its OAuth flow
|
|
90
|
+
- Loading `clientId`/`clientSecret` from environment variables instead of hardcoding
|
|
91
|
+
- Reading the downstream provider token in a tool via `this.orchestration.getToken('github')`
|
|
71
92
|
|
|
72
93
|
## Related
|
|
73
94
|
|
|
74
|
-
- See `configure-auth` for
|
|
95
|
+
- See `configure-auth` for the multi-provider orchestration config and the `this.orchestration` API (`getToken`, `tryGetToken`, `isAuthenticated`)
|
|
96
|
+
- See `local-credential-vault` for the separate per-session vault (`this.credentials`)
|
|
75
97
|
- See `configure-session` for setting up Redis-based session storage in production
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-behind-tunnel
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: 'Expose a local-mode server through a tunnel or TLS proxy by aligning the token issuer with the public URL clients actually reach.'
|
|
6
|
+
tags: [config, auth, local, tunnel, proxy, issuer, auth-modes]
|
|
7
|
+
features:
|
|
8
|
+
- 'Relying on request-host-derived OAuth discovery, which works behind a tunnel or under an http.entryPath without extra config'
|
|
9
|
+
- 'Setting `local.issuer` to a full public HTTPS URL so the token `iss` matches what clients reach through the proxy'
|
|
10
|
+
- 'Knowing `FRONTMCP_PUBLIC_HOST` overrides only the discovery host (scheme/port still come from the HTTP config or local.issuer)'
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Local Mode Behind a Tunnel
|
|
14
|
+
|
|
15
|
+
Expose a local-mode server through a tunnel or TLS proxy by aligning the token issuer with the public URL clients actually reach.
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// src/server.ts
|
|
21
|
+
// OAuth discovery (.well-known/*) is derived from the incoming request host at
|
|
22
|
+
// runtime and advertises /oauth/* at the root, so it works behind a tunnel or
|
|
23
|
+
// reverse proxy with no extra config. `local.issuer` only aligns the boot-time
|
|
24
|
+
// `iss` claim with the public HTTPS URL clients reach.
|
|
25
|
+
//
|
|
26
|
+
// `FRONTMCP_PUBLIC_HOST=mcp.example.com` would set only the discovery HOST
|
|
27
|
+
// (scheme stays http, port stays the HTTP port) — use `local.issuer` when you
|
|
28
|
+
// need a different scheme/port, as below.
|
|
29
|
+
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
30
|
+
|
|
31
|
+
@Tool({
|
|
32
|
+
name: 'ping',
|
|
33
|
+
description: 'Ping the server',
|
|
34
|
+
inputSchema: {},
|
|
35
|
+
outputSchema: { pong: z.boolean() },
|
|
36
|
+
})
|
|
37
|
+
class PingTool extends ToolContext {
|
|
38
|
+
async execute() {
|
|
39
|
+
return { pong: true };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@App({
|
|
44
|
+
name: 'tunneled-api',
|
|
45
|
+
auth: {
|
|
46
|
+
mode: 'local',
|
|
47
|
+
local: {
|
|
48
|
+
// Public URL exposed by the tunnel / TLS proxy.
|
|
49
|
+
issuer: 'https://mcp.example.com',
|
|
50
|
+
},
|
|
51
|
+
tokenStorage: { sqlite: { path: './data/auth.sqlite' } },
|
|
52
|
+
},
|
|
53
|
+
tools: [PingTool],
|
|
54
|
+
})
|
|
55
|
+
class TunneledApi {}
|
|
56
|
+
|
|
57
|
+
@FrontMcp({
|
|
58
|
+
info: { name: 'tunneled-server', version: '1.0.0' },
|
|
59
|
+
apps: [TunneledApi],
|
|
60
|
+
})
|
|
61
|
+
class Server {}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What This Demonstrates
|
|
65
|
+
|
|
66
|
+
- Relying on request-host-derived OAuth discovery, which works behind a tunnel or under an http.entryPath without extra config
|
|
67
|
+
- Setting `local.issuer` to a full public HTTPS URL so the token `iss` matches what clients reach through the proxy
|
|
68
|
+
- Knowing `FRONTMCP_PUBLIC_HOST` overrides only the discovery host (scheme/port still come from the HTTP config or local.issuer)
|
|
69
|
+
|
|
70
|
+
## Related
|
|
71
|
+
|
|
72
|
+
- See `configure-auth-modes` for a comparison of all auth modes
|
|
73
|
+
- See `local-self-signed-tokens` for token persistence options
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-consent-enforcement
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: '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.'
|
|
6
|
+
tags: [config, auth, local, consent, tool-authorization, auth-modes]
|
|
7
|
+
features:
|
|
8
|
+
- 'Setting `consent.enabled` so login renders a tool-selection screen and the chosen tools are enforced on every tools/call'
|
|
9
|
+
- 'Listing `excludedTools` so essential tools are never offered and are always available'
|
|
10
|
+
- 'Honoring `requireSelection` / `defaultSelectedTools` to require a non-empty selection and pre-check tools'
|
|
11
|
+
- 'Pinning `rememberConsent: false` to re-show the screen on every login (the default `true` reuses a prior per-(user, client) selection and only re-prompts when a NEW tool appears)'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Local Consent Enforcement
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/server.ts
|
|
22
|
+
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
23
|
+
|
|
24
|
+
@Tool({
|
|
25
|
+
name: 'health',
|
|
26
|
+
description: 'Health check',
|
|
27
|
+
inputSchema: {},
|
|
28
|
+
outputSchema: { ok: z.boolean() },
|
|
29
|
+
})
|
|
30
|
+
class HealthTool extends ToolContext {
|
|
31
|
+
async execute() {
|
|
32
|
+
return { ok: true };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Tool({
|
|
37
|
+
name: 'delete_account',
|
|
38
|
+
description: 'Delete a user account',
|
|
39
|
+
inputSchema: { userId: z.string() },
|
|
40
|
+
outputSchema: { deleted: z.boolean() },
|
|
41
|
+
})
|
|
42
|
+
class DeleteAccountTool extends ToolContext {
|
|
43
|
+
async execute(input: { userId: string }) {
|
|
44
|
+
return { deleted: true };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@App({
|
|
49
|
+
name: 'admin-api',
|
|
50
|
+
auth: {
|
|
51
|
+
mode: 'local',
|
|
52
|
+
consent: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
requireSelection: true, // reject an empty submit (default)
|
|
55
|
+
// Re-show the screen on every login so the user must explicitly opt into
|
|
56
|
+
// delete_account each time. `rememberConsent` defaults to `true` (reuse a
|
|
57
|
+
// prior per-(user, client) selection); pin it `false` for an opt-in-every-time
|
|
58
|
+
// screen like this one.
|
|
59
|
+
rememberConsent: false,
|
|
60
|
+
// `health` is never offered on the consent screen and is always callable.
|
|
61
|
+
excludedTools: ['health'],
|
|
62
|
+
// Pre-check nothing dangerous: the user must explicitly opt into delete_account.
|
|
63
|
+
defaultSelectedTools: [],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
tools: [HealthTool, DeleteAccountTool],
|
|
67
|
+
})
|
|
68
|
+
class AdminApi {}
|
|
69
|
+
|
|
70
|
+
@FrontMcp({
|
|
71
|
+
info: { name: 'consent-server', version: '1.0.0' },
|
|
72
|
+
apps: [AdminApi],
|
|
73
|
+
})
|
|
74
|
+
class Server {}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## What This Demonstrates
|
|
78
|
+
|
|
79
|
+
- Setting `consent.enabled` so login renders a tool-selection screen and the chosen tools are enforced on every tools/call
|
|
80
|
+
- Listing `excludedTools` so essential tools are never offered and are always available
|
|
81
|
+
- Honoring `requireSelection` / `defaultSelectedTools` to require a non-empty selection and pre-check tools
|
|
82
|
+
- Pinning `rememberConsent: false` to re-show the screen on every login (the default `true` reuses a prior per-(user, client) selection and only re-prompts when a NEW tool appears)
|
|
83
|
+
|
|
84
|
+
## Related
|
|
85
|
+
|
|
86
|
+
- See `configure-auth-modes` for a comparison of all auth modes
|
|
87
|
+
- See `local-self-signed-tokens` for persisting tokens across restarts
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-dcr-control
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: '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.'
|
|
6
|
+
tags: [config, auth, local, dcr, dynamic-client-registration, security, auth-modes]
|
|
7
|
+
features:
|
|
8
|
+
- 'Setting `dcr.enabled: false` so `POST /oauth/register` returns 404 and `registration_endpoint` is dropped from AS metadata'
|
|
9
|
+
- 'Constraining `dcr.allowedRedirectUris` (exact or `*` glob) so unlisted redirect URIs are rejected at both register and authorize'
|
|
10
|
+
- 'Constraining `dcr.allowedClientIds` so only known client ids may use `/oauth/authorize`'
|
|
11
|
+
- 'Seeding `dcr.clients` so a trusted client is accepted without any DCR round-trip'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Lock Down Dynamic Client Registration
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/server.ts
|
|
22
|
+
// JWT_SECRET still signs the HS256 tokens — set a stable value.
|
|
23
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
24
|
+
|
|
25
|
+
@FrontMcp({
|
|
26
|
+
info: { name: 'internal-api', version: '1.0.0' },
|
|
27
|
+
auth: {
|
|
28
|
+
mode: 'local',
|
|
29
|
+
dcr: {
|
|
30
|
+
// Close open self-registration entirely (regardless of NODE_ENV).
|
|
31
|
+
// /oauth/register now responds 404 and AS metadata omits registration_endpoint.
|
|
32
|
+
enabled: false,
|
|
33
|
+
// Only these redirect URIs are ever accepted (exact match or simple `*` glob),
|
|
34
|
+
// enforced at BOTH /oauth/register and /oauth/authorize.
|
|
35
|
+
allowedRedirectUris: ['https://app.example.com/callback', 'http://localhost:*/callback'],
|
|
36
|
+
// Only these client ids may use /oauth/authorize.
|
|
37
|
+
allowedClientIds: ['dashboard'],
|
|
38
|
+
// Seed the trusted client so authorize accepts it WITHOUT a DCR round-trip.
|
|
39
|
+
clients: [
|
|
40
|
+
{
|
|
41
|
+
clientId: 'dashboard',
|
|
42
|
+
redirectUris: ['https://app.example.com/callback'],
|
|
43
|
+
clientName: 'Internal Dashboard',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
class Server {}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To keep DCR open but authenticated instead of closed, drop `enabled: false` and set
|
|
53
|
+
`initialAccessToken` — then `POST /oauth/register` requires an
|
|
54
|
+
`Authorization: Bearer <token>` header that matches it (constant-time compared), and
|
|
55
|
+
requests without it get `401 invalid_token`.
|
|
56
|
+
|
|
57
|
+
## What This Demonstrates
|
|
58
|
+
|
|
59
|
+
- Setting `dcr.enabled: false` so `POST /oauth/register` returns 404 and `registration_endpoint` is dropped from AS metadata
|
|
60
|
+
- Constraining `dcr.allowedRedirectUris` (exact or `*` glob) so unlisted redirect URIs are rejected at both register and authorize
|
|
61
|
+
- Constraining `dcr.allowedClientIds` so only known client ids may use `/oauth/authorize`
|
|
62
|
+
- Seeding `dcr.clients` so a trusted client is accepted without any DCR round-trip
|
|
63
|
+
|
|
64
|
+
## Related
|
|
65
|
+
|
|
66
|
+
- See `configure-auth-modes` for the full local-mode option reference, including the `dcr` block
|
|
67
|
+
- See `local-single-operator` for the smallest single-operator local-mode configuration
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-minimal
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: basic
|
|
5
|
+
description: 'Stand up the built-in local OAuth 2.1 server with the minimum configuration: HS256 signing via JWT_SECRET and in-memory token storage.'
|
|
6
|
+
tags: [config, auth, local, hs256, auth-modes, modes]
|
|
7
|
+
features:
|
|
8
|
+
- "Using `mode: 'local'` with no other auth options to run the built-in OAuth 2.1 server"
|
|
9
|
+
- 'Relying on `JWT_SECRET` for HS256 token signing (symmetric secret, no key pair)'
|
|
10
|
+
- 'Accepting the default in-memory `tokenStorage` for local development (state is lost on restart)'
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Minimal Local Mode
|
|
14
|
+
|
|
15
|
+
Stand up the built-in local OAuth 2.1 server with the minimum configuration: HS256 signing via JWT_SECRET and in-memory token storage.
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// src/server.ts
|
|
21
|
+
// Set JWT_SECRET in the environment, e.g. `export JWT_SECRET=$(openssl rand -hex 32)`.
|
|
22
|
+
// If unset, FrontMCP uses a random per-process secret and tokens are invalidated on restart.
|
|
23
|
+
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
24
|
+
|
|
25
|
+
@Tool({
|
|
26
|
+
name: 'whoami',
|
|
27
|
+
description: 'Return the authenticated subject',
|
|
28
|
+
inputSchema: {},
|
|
29
|
+
outputSchema: { sub: z.string() },
|
|
30
|
+
})
|
|
31
|
+
class WhoAmITool extends ToolContext {
|
|
32
|
+
async execute() {
|
|
33
|
+
return { sub: String(this.auth.claims['sub'] ?? 'unknown') };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@App({
|
|
38
|
+
name: 'internal-api',
|
|
39
|
+
auth: {
|
|
40
|
+
mode: 'local',
|
|
41
|
+
},
|
|
42
|
+
tools: [WhoAmITool],
|
|
43
|
+
})
|
|
44
|
+
class InternalApi {}
|
|
45
|
+
|
|
46
|
+
@FrontMcp({
|
|
47
|
+
info: { name: 'local-minimal', version: '1.0.0' },
|
|
48
|
+
apps: [InternalApi],
|
|
49
|
+
})
|
|
50
|
+
class Server {}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What This Demonstrates
|
|
54
|
+
|
|
55
|
+
- Using `mode: 'local'` with no other auth options to run the built-in OAuth 2.1 server
|
|
56
|
+
- Relying on `JWT_SECRET` for HS256 token signing (symmetric secret, no key pair)
|
|
57
|
+
- Accepting the default in-memory `tokenStorage` for local development (state is lost on restart)
|
|
58
|
+
|
|
59
|
+
## Related
|
|
60
|
+
|
|
61
|
+
- See `configure-auth-modes` for a comparison of all auth modes
|
|
62
|
+
- See `local-self-signed-tokens` for persisting tokens with SQLite or Redis
|
package/catalog/frontmcp-config/examples/configure-auth-modes/local-multi-provider-orchestration.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-multi-provider-orchestration
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: advanced
|
|
5
|
+
description: '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.'
|
|
6
|
+
tags: [config, auth, local, orchestration, federated, providers, multi-provider, auth-modes]
|
|
7
|
+
features:
|
|
8
|
+
- 'Declaring an `auth.providers` array so FrontMCP federates GitHub + Slack at /oauth/authorize and stores their tokens encrypted server-side'
|
|
9
|
+
- 'Using `authorizeUrl`/`tokenUrl` aliases and letting the per-provider callback URL be auto-computed as ${issuer}/oauth/provider/${id}/callback'
|
|
10
|
+
- 'Gating JWT issuance with `federatedAuth.minProviders` / `requiredProviders` so no token is minted until the linked-provider threshold is met'
|
|
11
|
+
- 'Reading downstream provider tokens in a tool via `this.orchestration.getToken(id)` / `tryGetToken(id)` without exposing them to the LLM'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Multi-Provider Orchestration (Local Mode)
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/server.ts
|
|
22
|
+
// JWT_SECRET signs the HS256 tokens — set a stable value.
|
|
23
|
+
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
24
|
+
|
|
25
|
+
@Tool({
|
|
26
|
+
name: 'github_repos',
|
|
27
|
+
description: 'List the operator GitHub repositories using the linked upstream token',
|
|
28
|
+
inputSchema: {},
|
|
29
|
+
outputSchema: { repos: z.array(z.string()) },
|
|
30
|
+
})
|
|
31
|
+
class GitHubReposTool extends ToolContext {
|
|
32
|
+
async execute() {
|
|
33
|
+
// `this.orchestration` is bound for authenticated orchestrated requests.
|
|
34
|
+
const token = await this.orchestration.getToken('github'); // throws if not linked
|
|
35
|
+
const res = await fetch('https://api.github.com/user/repos', {
|
|
36
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
37
|
+
});
|
|
38
|
+
const repos = (await res.json()) as Array<{ full_name: string }>;
|
|
39
|
+
return { repos: repos.map((r) => r.full_name) };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@App({
|
|
44
|
+
name: 'orchestrated-api',
|
|
45
|
+
auth: {
|
|
46
|
+
mode: 'local',
|
|
47
|
+
// Declare the upstream providers to orchestrate. `authorizeUrl`/`tokenUrl`
|
|
48
|
+
// are accepted aliases for `authorizationEndpoint`/`tokenEndpoint`.
|
|
49
|
+
providers: [
|
|
50
|
+
{
|
|
51
|
+
id: 'github',
|
|
52
|
+
authorizeUrl: 'https://github.com/login/oauth/authorize',
|
|
53
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
54
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
55
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
56
|
+
scopes: ['read:user', 'repo'],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'slack',
|
|
60
|
+
authorizeUrl: 'https://slack.com/oauth/v2/authorize',
|
|
61
|
+
tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
62
|
+
clientId: process.env.SLACK_CLIENT_ID!,
|
|
63
|
+
clientSecret: process.env.SLACK_CLIENT_SECRET,
|
|
64
|
+
scopes: ['users:read'],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
// No JWT is minted until GitHub (required) is linked.
|
|
68
|
+
federatedAuth: { minProviders: 1, requiredProviders: ['github'] },
|
|
69
|
+
tokenStorage: { sqlite: { path: './data/auth.sqlite' } },
|
|
70
|
+
},
|
|
71
|
+
tools: [GitHubReposTool],
|
|
72
|
+
})
|
|
73
|
+
class OrchestratedApi {}
|
|
74
|
+
|
|
75
|
+
@FrontMcp({
|
|
76
|
+
info: { name: 'orchestrated-server', version: '1.0.0' },
|
|
77
|
+
apps: [OrchestratedApi],
|
|
78
|
+
})
|
|
79
|
+
class Server {}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## What This Demonstrates
|
|
83
|
+
|
|
84
|
+
- Declaring an `auth.providers` array so FrontMCP federates GitHub + Slack at /oauth/authorize and stores their tokens encrypted server-side
|
|
85
|
+
- Using `authorizeUrl`/`tokenUrl` aliases and letting the per-provider callback URL be auto-computed as ${issuer}/oauth/provider/${id}/callback
|
|
86
|
+
- Gating JWT issuance with `federatedAuth.minProviders` / `requiredProviders` so no token is minted until the linked-provider threshold is met
|
|
87
|
+
- Reading downstream provider tokens in a tool via `this.orchestration.getToken(id)` / `tryGetToken(id)` without exposing them to the LLM
|
|
88
|
+
|
|
89
|
+
## Related
|
|
90
|
+
|
|
91
|
+
- See `configure-auth` → "Multi-provider orchestration" for the full provider schema and the `this.orchestration` API
|
|
92
|
+
- See `configure-auth-modes` for a comparison of all auth modes
|
|
93
|
+
- See `local-single-operator` for the no-provider single-operator local setup
|