@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
|
@@ -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
|
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
name: remote-enterprise-oauth
|
|
3
3
|
reference: configure-auth-modes
|
|
4
4
|
level: advanced
|
|
5
|
-
description: '
|
|
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
|
|
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
|
-
- '
|
|
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
|
-
|
|
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
|
-
@
|
|
37
|
-
name: 'enterprise-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 {
|
|
94
|
+
import { createStorage } from '@frontmcp/utils';
|
|
90
95
|
|
|
91
|
-
const storage = await
|
|
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
|
|
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
|
-
|
|
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: '
|
|
50
|
+
issuer: 'https://mcp.example.com', // optional; request-host-derived otherwise
|
|
47
51
|
},
|
|
48
52
|
expectedAudience: 'my-api',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
|
77
|
-
| Token issuance
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
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
|
|
87
|
-
|
|
|
88
|
-
| [`local-
|
|
89
|
-
| [`
|
|
90
|
-
| [`
|
|
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/)
|