@frontmcp/skills 1.3.0 → 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 +9 -2
- 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-http.md +149 -15
- package/catalog/frontmcp-deployment/SKILL.md +15 -13
- 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 +38 -2
- package/catalog/frontmcp-development/SKILL.md +30 -44
- 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 +1 -1
- package/catalog/frontmcp-observability/SKILL.md +1 -1
- 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 +1 -1
- package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
- package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
- package/catalog/frontmcp-testing/SKILL.md +9 -1
- package/catalog/frontmcp-testing/references/test-auth.md +24 -0
- package/catalog/skills-manifest.json +653 -149
- 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 -80
- package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -132
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -110
- 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 -806
|
@@ -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
|
|
@@ -2,24 +2,26 @@
|
|
|
2
2
|
name: local-self-signed-tokens
|
|
3
3
|
reference: configure-auth-modes
|
|
4
4
|
level: intermediate
|
|
5
|
-
description: 'Configure a server that signs its own
|
|
6
|
-
tags: [config, auth,
|
|
5
|
+
description: 'Configure a local-mode server that signs its own HS256 JWTs and persists auth state across restarts with SQLite or Redis.'
|
|
6
|
+
tags: [config, auth, local, sqlite, redis, auth-modes, modes]
|
|
7
7
|
features:
|
|
8
|
-
- "Using `mode: 'local'` so the server signs its own JWTs"
|
|
8
|
+
- "Using `mode: 'local'` so the server signs its own HS256 JWTs (symmetric `JWT_SECRET`, no key pair)"
|
|
9
9
|
- 'Setting `local.issuer` and `expectedAudience` to control token claims'
|
|
10
|
-
- '
|
|
11
|
-
- '
|
|
12
|
-
- '
|
|
10
|
+
- 'Persisting authorization codes and refresh tokens with `tokenStorage: { sqlite: { path } }` so they survive restart'
|
|
11
|
+
- 'Switching the same `tokenStorage` to `{ redis }` for multi-instance deployments'
|
|
12
|
+
- 'Enabling `consent` so login renders a tool-selection screen and the chosen tools are enforced at call time'
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# Local Self-Signed Tokens
|
|
16
16
|
|
|
17
|
-
Configure a server that signs its own
|
|
17
|
+
Configure a local-mode server that signs its own HS256 JWTs and persists auth state across restarts with SQLite or Redis.
|
|
18
18
|
|
|
19
19
|
## Code
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
// src/server.ts
|
|
23
|
+
// Set a stable JWT_SECRET (e.g. `openssl rand -hex 32`) so HS256-signed
|
|
24
|
+
// tokens survive restart. If unset, a random per-process secret is used.
|
|
23
25
|
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
24
26
|
|
|
25
27
|
@Tool({
|
|
@@ -39,12 +41,13 @@ class ManageUsersTool extends ToolContext {
|
|
|
39
41
|
auth: {
|
|
40
42
|
mode: 'local',
|
|
41
43
|
local: {
|
|
42
|
-
issuer: '
|
|
44
|
+
issuer: 'https://mcp.internal.example.com',
|
|
43
45
|
},
|
|
44
46
|
expectedAudience: 'internal-api',
|
|
45
|
-
|
|
47
|
+
// Single-node persistence (survives restart, no Redis required).
|
|
48
|
+
// For multiple instances, swap for: { redis: { host: ..., port: 6379 } }
|
|
49
|
+
tokenStorage: { sqlite: { path: './data/auth.sqlite' } },
|
|
46
50
|
consent: { enabled: true },
|
|
47
|
-
incrementalAuth: { enabled: true },
|
|
48
51
|
},
|
|
49
52
|
tools: [ManageUsersTool],
|
|
50
53
|
})
|
|
@@ -53,24 +56,19 @@ class InternalApi {}
|
|
|
53
56
|
@FrontMcp({
|
|
54
57
|
info: { name: 'local-auth-server', version: '1.0.0' },
|
|
55
58
|
apps: [InternalApi],
|
|
56
|
-
redis: {
|
|
57
|
-
provider: 'redis',
|
|
58
|
-
host: process.env['REDIS_HOST'] ?? 'localhost',
|
|
59
|
-
port: 6379,
|
|
60
|
-
},
|
|
61
59
|
})
|
|
62
60
|
class Server {}
|
|
63
61
|
```
|
|
64
62
|
|
|
65
63
|
## What This Demonstrates
|
|
66
64
|
|
|
67
|
-
- Using `mode: 'local'` so the server signs its own JWTs
|
|
65
|
+
- Using `mode: 'local'` so the server signs its own HS256 JWTs (symmetric `JWT_SECRET`, no key pair)
|
|
68
66
|
- Setting `local.issuer` and `expectedAudience` to control token claims
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
67
|
+
- Persisting authorization codes and refresh tokens with `tokenStorage: { sqlite: { path } }` so they survive restart
|
|
68
|
+
- Switching the same `tokenStorage` to `{ redis }` for multi-instance deployments
|
|
69
|
+
- Enabling `consent` so login renders a tool-selection screen and the chosen tools are enforced at call time
|
|
72
70
|
|
|
73
71
|
## Related
|
|
74
72
|
|
|
75
73
|
- See `configure-auth-modes` for a comparison of all auth modes
|
|
76
|
-
- See `configure-session` for session storage configuration
|
|
74
|
+
- See `configure-session` for transport/session storage configuration
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-single-operator
|
|
3
|
+
reference: configure-auth-modes
|
|
4
|
+
level: basic
|
|
5
|
+
description: 'Run local mode for a single operator (e.g. Claude Code) by skipping the email prompt and minting a stable anonymous subject.'
|
|
6
|
+
tags: [config, auth, local, single-operator, claude-code, auth-modes]
|
|
7
|
+
features:
|
|
8
|
+
- 'Setting `requireEmail: false` so the login callback mints a code without prompting for an email'
|
|
9
|
+
- 'Setting `anonymousSubject` so the single operator gets a stable `sub` across logins'
|
|
10
|
+
- 'Persisting tokens with SQLite so the operator stays logged in across restarts'
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Single-Operator Local Mode
|
|
14
|
+
|
|
15
|
+
Run local mode for a single operator (e.g. Claude Code) by skipping the email prompt and minting a stable anonymous subject.
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// src/server.ts
|
|
21
|
+
// JWT_SECRET still signs the HS256 tokens — set a stable value.
|
|
22
|
+
import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
23
|
+
|
|
24
|
+
@Tool({
|
|
25
|
+
name: 'list_notes',
|
|
26
|
+
description: 'List the operator notes',
|
|
27
|
+
inputSchema: {},
|
|
28
|
+
outputSchema: { notes: z.array(z.string()) },
|
|
29
|
+
})
|
|
30
|
+
class ListNotesTool extends ToolContext {
|
|
31
|
+
async execute() {
|
|
32
|
+
return { notes: [] };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@App({
|
|
37
|
+
name: 'operator-api',
|
|
38
|
+
auth: {
|
|
39
|
+
mode: 'local',
|
|
40
|
+
// Single operator: do not prompt for an email at /oauth/callback.
|
|
41
|
+
requireEmail: false,
|
|
42
|
+
// Stable subject minted when no email is supplied (this is the default value).
|
|
43
|
+
anonymousSubject: 'local-operator',
|
|
44
|
+
tokenStorage: { sqlite: { path: './data/auth.sqlite' } },
|
|
45
|
+
},
|
|
46
|
+
tools: [ListNotesTool],
|
|
47
|
+
})
|
|
48
|
+
class OperatorApi {}
|
|
49
|
+
|
|
50
|
+
@FrontMcp({
|
|
51
|
+
info: { name: 'single-operator-server', version: '1.0.0' },
|
|
52
|
+
apps: [OperatorApi],
|
|
53
|
+
})
|
|
54
|
+
class Server {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What This Demonstrates
|
|
58
|
+
|
|
59
|
+
- Setting `requireEmail: false` so the login callback mints a code without prompting for an email
|
|
60
|
+
- Setting `anonymousSubject` so the single operator gets a stable `sub` across logins
|
|
61
|
+
- Persisting tokens with SQLite so the operator stays logged in across restarts
|
|
62
|
+
|
|
63
|
+
## Related
|
|
64
|
+
|
|
65
|
+
- See `configure-auth-modes` for a comparison of all auth modes
|
|
66
|
+
- See `local-minimal` for the smallest local-mode configuration
|