@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
|
@@ -71,47 +71,301 @@ class MyApp {}
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
- `provider` -- the authorization server URL. FrontMCP fetches JWKS from `{provider}/.well-known/jwks.json`.
|
|
74
|
-
- `expectedAudience` -- the `aud` claim value that tokens must contain.
|
|
74
|
+
- `expectedAudience` -- the `aud` claim value that tokens must contain. Supported in **transparent, local, AND remote** modes (it lives on the shared/orchestrated auth options), so set it wherever FrontMCP verifies bearer tokens — not transparent-only.
|
|
75
|
+
- `allowAnonymous` (default `false`) -- when `true`, requests without a token get an anonymous session (scoped by `anonymousScopes`, default `['anonymous']`) instead of a 401.
|
|
75
76
|
|
|
76
77
|
Use transparent mode when clients already have tokens from your identity provider and the server only needs to verify them.
|
|
77
78
|
|
|
78
79
|
## Mode 3: Local
|
|
79
80
|
|
|
80
|
-
Local mode
|
|
81
|
+
Local mode runs a built-in OAuth 2.1 authorization server and signs its own JWT tokens. This is useful for internal services or environments where an external identity provider is not available.
|
|
81
82
|
|
|
82
83
|
```typescript
|
|
83
|
-
@
|
|
84
|
+
@FrontMcp({
|
|
85
|
+
info: { name: 'internal-api', version: '1.0.0' },
|
|
84
86
|
auth: {
|
|
85
87
|
mode: 'local',
|
|
86
88
|
local: {
|
|
87
|
-
issuer: '
|
|
89
|
+
issuer: 'https://mcp.internal.example.com', // optional; auto-derived from request host otherwise
|
|
88
90
|
},
|
|
89
91
|
},
|
|
90
92
|
})
|
|
91
|
-
class
|
|
93
|
+
class Server {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `local.issuer` -- the `iss` claim set in generated tokens (defaults to a request-host-derived URL if omitted).
|
|
97
|
+
|
|
98
|
+
Token signing uses **HS256, a symmetric secret** read from the `JWT_SECRET` environment variable -- there is **no RSA/EC key pair** and no key store. Generate a stable secret (`JWT_SECRET=$(openssl rand -hex 32)`); if it is unset, FrontMCP falls back to a random per-process secret and all tokens are invalidated on restart.
|
|
99
|
+
|
|
100
|
+
Key local-mode options:
|
|
101
|
+
|
|
102
|
+
- `tokenStorage` -- where authorization codes / refresh tokens / federated sessions persist. Defaults to `'memory'` (lost on restart). Use `{ sqlite: { path } }` for single-node persistence or `{ redis: { ... } }` for multi-instance. This is honored in local mode.
|
|
103
|
+
- `requireEmail` (default `true`) -- when `false`, the login callback mints a code without prompting for an email, deriving a stable `sub` from `anonymousSubject` (default `'local-operator'`). Use for single-operator setups (e.g. Claude Code).
|
|
104
|
+
- `consent` -- enables an **interactive tool-selection screen** during login AND **call-time enforcement**. When `consent.enabled` is `true`, `/oauth/callback` renders a consent screen (after authentication) listing the available tools; the user's checked tools are GET-submitted back to `/oauth/callback`, embedded in the token's `consent` claim, and enforced on every `tools/call` — a call to an unselected tool is rejected with `TOOL_NOT_CONSENTED` (JSON-RPC `-32003`). Honored flags: `groupByApp` (default `true`), `showDescriptions` (default `true`), `allowSelectAll` (default `true`), `requireSelection` (default `true` — rejects an empty submit), `customMessage`, `rememberConsent` (default `true`), `excludedTools` (never offered, always available), `defaultSelectedTools` (pre-checked). Federated logins show the screen after the last provider links. Tokens minted without consent (disabled, or via the test factory) carry no claim and stay all-tools-allowed. `rememberConsent` persists each user's per-client selection (keyed by `consent:{userSub}:{clientId}`, sharing the configured `tokenStorage` backend) and reuses it on the next login: the screen is skipped when no new tool appeared, or re-shown pre-filled when a NEW tool was added (a newly-added tool is never silently granted). Set `rememberConsent: false` to always re-show the screen.
|
|
105
|
+
- `login` -- customize the built-in login page: `title` / `subtitle` / `logoUri`, declarative `fields` (each `{ type: 'text'|'password'|'email'|'select'|'hidden'; label?; required?; placeholder?; options? }`), a full HTML `render(ctx)` override, and a `subject` strategy (`{ fromField, strategy: 'per-session'|'per-account' }`). Omitting `login` keeps the default email/name page.
|
|
106
|
+
- `authenticate(input, ctx)` -- custom verification run at the login callback **before** a token is minted. `input.fields` carries the submitted login fields (reserved OAuth params excluded); `ctx` is `{ get, fetch, logger, clientId?, clientName? }`. Return `{ ok: true, sub?, claims? }` to mint a token (custom `claims` are embedded in the JWT; reserved claims like `sub`/`iss`/`exp`/`scope` are stripped) or `{ ok: false, message, retryField? }` to re-render the login page with the error (no code issued). When set, the email requirement no longer applies.
|
|
107
|
+
- `providers` -- declarative upstream OAuth providers (GitHub, Slack, Jira, …) to orchestrate. When set, FrontMCP federates them at `/oauth/authorize`, stores each provider's tokens **encrypted server-side**, and exposes them to tools via `this.orchestration.getToken(id)`. See [Multi-provider orchestration](#multi-provider-orchestration-providers--federatedauth) below.
|
|
108
|
+
- `federatedAuth` -- gates JWT issuance during federated login: `minProviders` (default `1` when `providers` are set — "no JWT until ≥1 linked"), `requiredProviders` (ids that must all be linked), and `stateValidation` (`'strict'` default).
|
|
109
|
+
- `dcr` -- declarative control surface for the built-in Dynamic Client Registration endpoint (`POST /oauth/register`) and the `/oauth/authorize` client allowlist. All fields optional; omitting `dcr` preserves today's behavior (DCR on in dev, off in prod). See [Dynamic Client Registration](#dynamic-client-registration-dcr) below.
|
|
110
|
+
- `anonymousScopes` (default `['anonymous']`) -- scopes assigned to anonymous sessions (used when `allowDefaultPublic` lets an unauthenticated request through).
|
|
111
|
+
- `allowDefaultPublic` (default `false`) -- when `true`, requests without a token are allowed through as anonymous instead of being rejected with a 401. Keep `false` to require auth on every request.
|
|
112
|
+
- `expectedAudience` -- the `aud` claim value tokens must carry. Also valid in local/remote mode (not just transparent); set it when FrontMCP must reject tokens minted for a different audience.
|
|
113
|
+
|
|
114
|
+
### Multi-provider orchestration (`providers` + `federatedAuth`)
|
|
115
|
+
|
|
116
|
+
Declare upstream providers to make multi-provider orchestration a turnkey local default. No JWT is minted until the `minProviders` threshold (and every `requiredProviders` id) is met; linked providers' tokens are read by tools via `this.orchestration`.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
@FrontMcp({
|
|
120
|
+
info: { name: 'internal-api', version: '1.0.0' },
|
|
121
|
+
auth: {
|
|
122
|
+
mode: 'local',
|
|
123
|
+
providers: [
|
|
124
|
+
{
|
|
125
|
+
id: 'github',
|
|
126
|
+
// `authorizeUrl`/`tokenUrl` are accepted aliases for
|
|
127
|
+
// `authorizationEndpoint`/`tokenEndpoint`.
|
|
128
|
+
authorizeUrl: 'https://github.com/login/oauth/authorize',
|
|
129
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
130
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
131
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
132
|
+
scopes: ['read:user', 'repo'],
|
|
133
|
+
},
|
|
134
|
+
{ id: 'slack', authorizeUrl: '…', tokenUrl: '…', clientId: '…', scopes: ['users:read'] },
|
|
135
|
+
],
|
|
136
|
+
federatedAuth: { minProviders: 1, requiredProviders: ['github'] },
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
class Server {}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`UpstreamProviderOptions` fields: `id` (required; used in `getToken(id)`), `authorizationEndpoint`/`authorizeUrl` (required), `tokenEndpoint`/`tokenUrl` (required), `clientId` (required), `clientSecret?`, `scopes?`, `name?`, `userInfoEndpoint?`, `jwksUri?`. The per-provider callback URL is auto-computed as `${issuer}/oauth/provider/${id}/callback` — register that URL with each provider.
|
|
143
|
+
|
|
144
|
+
Tools read downstream tokens through the `this.orchestration` context extension (available in `local`/`remote` mode):
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@Tool({ name: 'github_repos' })
|
|
148
|
+
class GitHubReposTool extends ToolContext {
|
|
149
|
+
async execute() {
|
|
150
|
+
if (!this.orchestration.isAuthenticated) return { error: 'not authenticated' };
|
|
151
|
+
const token = await this.orchestration.getToken('github'); // throws if not linked
|
|
152
|
+
const maybe = await this.orchestration.tryGetToken('slack'); // null if skipped
|
|
153
|
+
const res = await fetch('https://api.github.com/user/repos', {
|
|
154
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
155
|
+
});
|
|
156
|
+
return { repos: await res.json() };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Tokens stay server-side and AES-256-GCM-encrypted at rest — never placed in the JWT or exposed to the model. No login PII is stored (only provider tokens + non-PII provider ids).
|
|
162
|
+
|
|
163
|
+
### Dynamic Client Registration (`dcr`)
|
|
164
|
+
|
|
165
|
+
The built-in `POST /oauth/register` (RFC 7591) endpoint lets clients self-register. By default it is **enabled in development and disabled in production** (`NODE_ENV=production` short-circuits it), holds clients in an in-memory map, and enforces no allowlist. The optional `dcr` block makes that a declarative control surface. **Omitting `dcr` preserves the default behavior exactly.**
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
@FrontMcp({
|
|
169
|
+
info: { name: 'internal-api', version: '1.0.0' },
|
|
170
|
+
auth: {
|
|
171
|
+
mode: 'local',
|
|
172
|
+
dcr: {
|
|
173
|
+
enabled: false, // force off regardless of NODE_ENV (register → 404; metadata omits registration_endpoint)
|
|
174
|
+
allowedRedirectUris: ['https://app.example.com/callback', 'http://localhost:*/callback'], // exact or `*` glob
|
|
175
|
+
allowedClientIds: ['dashboard'], // only these client_ids may use /oauth/authorize
|
|
176
|
+
initialAccessToken: process.env.DCR_INITIAL_ACCESS_TOKEN, // require `Authorization: Bearer <token>` on register
|
|
177
|
+
clients: [
|
|
178
|
+
// pre-registered trusted clients — accepted WITHOUT a DCR round-trip
|
|
179
|
+
{ clientId: 'dashboard', redirectUris: ['https://app.example.com/callback'], clientName: 'Internal Dashboard' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
class Server {}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`LocalDcrConfig` fields:
|
|
188
|
+
|
|
189
|
+
- `enabled` -- when `false`, `/oauth/register` responds `404` and `registration_endpoint` is omitted from `/.well-known/oauth-authorization-server`. When unset, defaults to on in dev / off in prod.
|
|
190
|
+
- `allowedRedirectUris` -- exact or simple-`*`-glob allowlist. A `redirect_uri` not on it is rejected at **register** (`400 invalid_redirect_uri`) and at **authorize** (error page — the unlisted URI is never redirected to, an open-redirect guard).
|
|
191
|
+
- `allowedClientIds` -- only these `client_id`s may be used at `/oauth/authorize`. CIMD URL client ids are validated by the CIMD layer and are exempt.
|
|
192
|
+
- `initialAccessToken` -- when set, `/oauth/register` requires a matching `Authorization: Bearer <token>` (constant-time compared); otherwise `401 invalid_token`.
|
|
193
|
+
- `clients` -- pre-registered trusted clients (`{ clientId, redirectUris, clientName?, clientSecret?, tokenEndpointAuthMethod?, grantTypes?, responseTypes?, scope? }`) seeded at startup; accepted by the authorize/token flows **without** a DCR round-trip. Lets you disable DCR and still ship known clients.
|
|
194
|
+
|
|
195
|
+
A successful registration responds `201 Created`. This `dcr` block governs the **local Authorization Server only** — it is unrelated to the upstream-provider `providerConfig.dcrEnabled` / `registrationEndpoint` fields (which register THIS server against an upstream IdP).
|
|
196
|
+
|
|
197
|
+
### Custom login + verification (`login` / `authenticate`)
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
@FrontMcp({
|
|
201
|
+
info: { name: 'internal-api', version: '1.0.0' },
|
|
202
|
+
auth: {
|
|
203
|
+
mode: 'local',
|
|
204
|
+
login: {
|
|
205
|
+
title: 'Sign in with your API key',
|
|
206
|
+
fields: { apiKey: { type: 'password', label: 'API Key', required: true } },
|
|
207
|
+
subject: { fromField: 'apiKey', strategy: 'per-account' }, // stable sub per key
|
|
208
|
+
},
|
|
209
|
+
authenticate: async (input, ctx) => {
|
|
210
|
+
const res = await ctx.fetch('https://api.example.com/verify', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: { authorization: `Bearer ${input.fields.apiKey}` },
|
|
213
|
+
});
|
|
214
|
+
if (!res.ok) return { ok: false, message: 'Invalid API key', retryField: 'apiKey' };
|
|
215
|
+
const { accountId, tier } = (await res.json()) as { accountId: string; tier: string };
|
|
216
|
+
return { ok: true, sub: accountId, claims: { tenantId: accountId, plan: tier } };
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
class Server {}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Per-session credential vault (`this.credentials`)
|
|
224
|
+
|
|
225
|
+
An `{ ok: true }` result may return `credentials: Array<{ key; secret; metadata? }>`. FrontMCP **persists** these into a built-in, **AES-256-GCM-encrypted**, per-session vault keyed by the authenticated `sub`, and exposes them to tools via `this.credentials`. The vault is enabled automatically in `local` (and `remote`) modes — no wiring required. (This is distinct from `this.authProviders`, which is opt-in scaffolding for downstream OAuth providers — see below.)
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
auth: {
|
|
229
|
+
mode: 'local',
|
|
230
|
+
login: { fields: { apiKey: { type: 'password', label: 'API Key', required: true } } },
|
|
231
|
+
authenticate: async (input) => {
|
|
232
|
+
const apiKey = input.fields['apiKey'];
|
|
233
|
+
if (!(await isValid(apiKey))) return { ok: false, message: 'Invalid API key' };
|
|
234
|
+
// Persisted, encrypted, keyed by the minted `sub`:
|
|
235
|
+
return { ok: true, credentials: [{ key: 'acme', secret: apiKey, metadata: { baseUrl: 'https://acme.example' } }] };
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Read them from any tool:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
@Tool({ name: 'call_acme' })
|
|
244
|
+
class CallAcmeTool extends ToolContext {
|
|
245
|
+
async execute(input: Input): Promise<Output> {
|
|
246
|
+
const cred = await this.credentials.get('acme'); // { secret, metadata } | undefined
|
|
247
|
+
if (!cred) {
|
|
248
|
+
const res = await this.credentials.requireConnect({ key: 'acme' });
|
|
249
|
+
if (!res.connected) return { connectUrl: res.resumeUrl }; // framework-signed /oauth/connect URL
|
|
250
|
+
}
|
|
251
|
+
const keys = await this.credentials.list();
|
|
252
|
+
// use cred.secret / cred.metadata …
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`this.credentials` API:
|
|
258
|
+
|
|
259
|
+
- `get(key)` -> `Promise<{ secret, metadata } | undefined>` — decrypt a credential for the request's subject.
|
|
260
|
+
- `list()` -> `Promise<string[]>` — credential keys in the current session vault.
|
|
261
|
+
- `requireConnect({ key, context? })` -> returns the credential when connected, else a **framework-signed**, short-lived `/oauth/connect?token=…` resume URL. Opening it renders a single-field add-credential page (reusing `login.fields`); on submit FrontMCP re-invokes `authenticate()` with a `resume: { sub, key, context }` context and **additively** stores the returned credential into the existing session vault. A rotated-away (dead) session is refused.
|
|
262
|
+
|
|
263
|
+
Security: per-record AES key derived (HKDF) from a fresh per-session `vaultId` + a pepper from `VAULT_SECRET ?? JWT_SECRET` (random per-process fallback, logged with a warning — credentials then do not survive a restart, never plaintext). A disconnect + reconnect rotates the `vaultId`, so the reconnected session sees an **empty vault** and old ciphertext is undecryptable. The vault is backed by the same `auth.tokenStorage` backend (memory / Redis / SQLite). No login PII is stored.
|
|
264
|
+
|
|
265
|
+
### Session secure-secret store (`this.secureStore`)
|
|
266
|
+
|
|
267
|
+
`this.credentials` is OAuth-credential-centric (resume URLs, per-authorize vault rotation). For **arbitrary user-typed secrets** (an API key a tool prompts for, a webhook signing secret, a per-session draft token) use `this.secureStore` — a general, typed, session-scoped key→secret store whose backing is **pluggable per deployment**. It is enabled automatically in `local` (and `remote`) modes. Stop re-implementing AES-GCM + HKDF + scope + persistence by hand.
|
|
268
|
+
|
|
269
|
+
Select the backing with `auth.secureStore` (mirrors `tokenStorage`, plus a namespace `scope`):
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
@FrontMcp({
|
|
273
|
+
apps: [MyApp],
|
|
274
|
+
auth: {
|
|
275
|
+
mode: 'local',
|
|
276
|
+
// 'memory' | { sqlite } | { redis } | { backend } | { scope?, ttlMs?, encryption? }
|
|
277
|
+
secureStore: { sqlite: { path: './.frontmcp/secrets.sqlite' }, scope: 'user' },
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
export default class Server {}
|
|
92
281
|
```
|
|
93
282
|
|
|
94
|
-
|
|
283
|
+
Read/write from any tool (or the auth UI):
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
@Tool({ name: 'save_api_key' })
|
|
287
|
+
class SaveApiKeyTool extends ToolContext {
|
|
288
|
+
async execute(input: { apiKey: string }) {
|
|
289
|
+
await this.secureStore.set('stg.api-key', input.apiKey); // JSON-serialized
|
|
290
|
+
const key = await this.secureStore.get<string>('stg.api-key');
|
|
291
|
+
const all = await this.secureStore.list();
|
|
292
|
+
// await this.secureStore.delete('stg.api-key');
|
|
293
|
+
return { saved: key !== undefined, keys: all };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
95
297
|
|
|
96
|
-
|
|
298
|
+
Backings:
|
|
299
|
+
|
|
300
|
+
- `'memory'` (default) -- in-memory, **AES-256-GCM**-encrypted via `VaultEncryption` (lost on restart).
|
|
301
|
+
- `{ sqlite: { path } }` / `{ redis: { ... } }` -- persistent; same `VaultEncryption` over the shared `StorageAdapter` (reuses `createTokenStorageAdapter`). When the backing matches `tokenStorage`, the same adapter/connection is shared.
|
|
302
|
+
- `{ backend }` -- a **custom** backing implementing the four-method `SecureStoreBackend` (`get`/`set`/`delete`/`list`), e.g. an OS keychain. Used as-is — **no native dependency is bundled** by the framework (see below).
|
|
303
|
+
|
|
304
|
+
Scope (`scope`, identity is hashed into the namespace, never stored raw):
|
|
305
|
+
|
|
306
|
+
- `user` (default) -- keyed by the authenticated `sub`; anonymous requests read empty / skip writes.
|
|
307
|
+
- `session` -- keyed by the transport `sessionId`.
|
|
308
|
+
- `global` -- one server-wide namespace.
|
|
309
|
+
|
|
310
|
+
`this.secureStore` API: `get<T>(key)` -> `Promise<T | undefined>`, `set<T>(key, value, { ttlMs? })`, `delete(key)` -> `Promise<boolean>`, `list()` -> `Promise<string[]>`. Object configs also accept `ttlMs` (default write TTL) and `encryption.pepper` (overrides `VAULT_SECRET ?? JWT_SECRET`).
|
|
311
|
+
|
|
312
|
+
**OS keychain is pluggable, not bundled.** FrontMCP does not ship `keytar`/`wincred`/`libsecret`. To back the store with an OS keychain, pass `secureStore: { backend: myKeychainBackend }` where `myKeychainBackend` implements `SecureStoreBackend` (you load the native peer-dep). A backend deals only in `(namespace, key) → string`; the accessor resolves the namespace and JSON-serializes values. Backends that cannot honor a TTL (an OS keychain) ignore the `ttlMs` argument; OS keychains rely on the OS for at-rest encryption rather than `VaultEncryption`.
|
|
97
313
|
|
|
98
314
|
## Mode 4: Remote
|
|
99
315
|
|
|
100
|
-
Remote mode
|
|
316
|
+
Remote mode runs a local OAuth 2.1 server that **proxies user authentication to a
|
|
317
|
+
single mandatory upstream IdP**. `GET /oauth/authorize` redirects **straight to
|
|
318
|
+
the upstream IdP** — there is no FrontMCP login page and no provider-selection
|
|
319
|
+
page. The IdP returns to `/oauth/provider/{id}/callback`; FrontMCP exchanges the
|
|
320
|
+
code, stores the upstream tokens encrypted (server-side), derives the session
|
|
321
|
+
identity (`sub`/`email`/`name`) from the **upstream user**, and mints its own
|
|
322
|
+
HS256 session token for the MCP client.
|
|
101
323
|
|
|
102
324
|
```typescript
|
|
103
|
-
@
|
|
325
|
+
@FrontMcp({
|
|
326
|
+
info: { name: 'srv', version: '1.0.0' },
|
|
327
|
+
apps: [MyApp],
|
|
104
328
|
auth: {
|
|
105
329
|
mode: 'remote',
|
|
106
330
|
provider: 'https://auth.example.com',
|
|
107
|
-
clientId: 'xxx',
|
|
331
|
+
clientId: 'xxx', // pre-registered (DCR not yet wired)
|
|
332
|
+
clientSecret: process.env['OAUTH_CLIENT_SECRET'],
|
|
333
|
+
scopes: ['openid', 'profile', 'email'],
|
|
334
|
+
providerConfig: { id: 'idp' }, // stable provider id (defaults to provider host)
|
|
108
335
|
},
|
|
109
336
|
})
|
|
110
|
-
class
|
|
337
|
+
class Server {}
|
|
111
338
|
```
|
|
112
339
|
|
|
113
|
-
- `provider` -- the OAuth 2.1 authorization server URL.
|
|
114
|
-
|
|
340
|
+
- `provider` -- the upstream OAuth 2.1 authorization server base URL. Endpoints are
|
|
341
|
+
derived from it (`/authorize`, `/token`, `/userinfo`, `/.well-known/jwks.json`)
|
|
342
|
+
unless overridden via `providerConfig.{authEndpoint,tokenEndpoint,userInfoEndpoint,jwksUri}`.
|
|
343
|
+
- `clientId` -- the OAuth client identifier **pre-registered** with the provider.
|
|
344
|
+
- `providerConfig.id` -- stable id used by tools: `this.orchestration.getToken('idp')`.
|
|
345
|
+
|
|
346
|
+
Tools read the upstream IdP token (never exposed to the LLM) via the orchestration
|
|
347
|
+
accessor:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
@Tool({ name: 'whoami' })
|
|
351
|
+
class Whoami extends ToolContext {
|
|
352
|
+
async execute() {
|
|
353
|
+
const token = await this.orchestration.tryGetToken('idp');
|
|
354
|
+
if (!token) return { error: 'Re-authenticate' }; // upstream auto-refresh not yet wired
|
|
355
|
+
return await (
|
|
356
|
+
await this.fetch('https://auth.example.com/userinfo', {
|
|
357
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
358
|
+
})
|
|
359
|
+
).json();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Deferred (not yet wired):** upstream **Dynamic Client Registration**
|
|
365
|
+
(`providerConfig.dcrEnabled` / `registrationEndpoint`) — provide a pre-registered
|
|
366
|
+
`clientId`; and upstream **token auto-refresh** — once the upstream access token
|
|
367
|
+
expires the user must re-authenticate (FrontMCP's own session token still
|
|
368
|
+
refreshes via the `refresh_token` grant).
|
|
115
369
|
|
|
116
370
|
## OAuth Local Dev Flow
|
|
117
371
|
|
|
@@ -158,29 +412,17 @@ class PublicApi {}
|
|
|
158
412
|
class AdminApi {}
|
|
159
413
|
```
|
|
160
414
|
|
|
161
|
-
## Credential Vault
|
|
162
|
-
|
|
163
|
-
The credential vault stores downstream API tokens obtained during the OAuth flow. Use it when your MCP tools need to call external APIs on behalf of the authenticated user. Credential vault is managed through the auth provider's OAuth flow — downstream tokens are stored automatically when users authorize external services.
|
|
415
|
+
## Credential Vault (`this.authProviders`)
|
|
164
416
|
|
|
165
|
-
|
|
166
|
-
@App({
|
|
167
|
-
name: 'MyApp',
|
|
168
|
-
auth: {
|
|
169
|
-
mode: 'remote',
|
|
170
|
-
provider: 'https://auth.example.com',
|
|
171
|
-
clientId: 'mcp-client-id',
|
|
172
|
-
},
|
|
173
|
-
})
|
|
174
|
-
class MyApp {}
|
|
175
|
-
```
|
|
417
|
+
The credential vault and its `this.authProviders` accessor are intended to surface downstream API tokens to tools. The accessor and the underlying vault primitives exist in `@frontmcp/auth`, but they are **scaffolding** — the vault must be explicitly configured/registered with providers before `this.authProviders` resolves. It is **not** turnkey-wired for `local` mode (or auto-populated by simply setting `auth.mode`), so do not assume credentials appear automatically from configuring an auth mode.
|
|
176
418
|
|
|
177
|
-
|
|
419
|
+
When the vault is configured with registered providers, tools read credentials via the `this.authProviders` context extension:
|
|
178
420
|
|
|
179
421
|
```typescript
|
|
180
422
|
@Tool({ name: 'create_github_issue' })
|
|
181
423
|
class CreateGithubIssueTool extends ToolContext {
|
|
182
424
|
async execute(input: { title: string; body: string }) {
|
|
183
|
-
//
|
|
425
|
+
// Resolves only when the AuthProviders vault is configured with providers.
|
|
184
426
|
const github = await this.authProviders.get('github');
|
|
185
427
|
const headers = await this.authProviders.headers('github');
|
|
186
428
|
// Use headers to call GitHub API
|
|
@@ -188,22 +430,24 @@ class CreateGithubIssueTool extends ToolContext {
|
|
|
188
430
|
}
|
|
189
431
|
```
|
|
190
432
|
|
|
191
|
-
The `authProviders` accessor (from `@frontmcp/auth`)
|
|
433
|
+
The `authProviders` accessor (from `@frontmcp/auth`) exposes:
|
|
192
434
|
|
|
193
435
|
- `get(provider)` -- get the credential/token for a provider.
|
|
194
436
|
- `headers(provider)` -- get pre-formatted auth headers for HTTP requests.
|
|
195
437
|
- `has(provider)` -- check if a provider is configured.
|
|
196
438
|
- `refresh(provider)` -- force refresh the credential.
|
|
197
439
|
|
|
440
|
+
If the vault is not configured, accessing `this.authProviders` throws (`AuthProvidersNotConfiguredError`).
|
|
441
|
+
|
|
198
442
|
## Common Patterns
|
|
199
443
|
|
|
200
|
-
| Pattern | Correct | Incorrect | Why
|
|
201
|
-
| --------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
202
|
-
| Session store in production | Use Redis or Vercel KV session store | Use default in-memory session store | Sessions are lost on restart; in-memory does not survive process recycling
|
|
203
|
-
| Secret management | Load `clientId`, vault secrets, and Redis passwords from environment variables | Hardcode secrets in source code | Hardcoded secrets leak into version control and are difficult to rotate
|
|
204
|
-
| Audience validation | Always set `expectedAudience` in transparent/remote mode | Omit the audience field | Without audience validation, tokens issued for any audience would be accepted
|
|
205
|
-
| Auth mode for development | Use `public` mode or local OAuth mock for dev environments | Use `remote` mode pointing at production IdP during development | Avoids accidental production token usage and simplifies local iteration
|
|
206
|
-
|
|
|
444
|
+
| Pattern | Correct | Incorrect | Why |
|
|
445
|
+
| --------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
446
|
+
| Session store in production | Use Redis or Vercel KV session store | Use default in-memory session store | Sessions are lost on restart; in-memory does not survive process recycling |
|
|
447
|
+
| Secret management | Load `clientId`, vault secrets, and Redis passwords from environment variables | Hardcode secrets in source code | Hardcoded secrets leak into version control and are difficult to rotate |
|
|
448
|
+
| Audience validation | Always set `expectedAudience` in transparent/remote mode | Omit the audience field | Without audience validation, tokens issued for any audience would be accepted |
|
|
449
|
+
| Auth mode for development | Use `public` mode or local OAuth mock for dev environments | Use `remote` mode pointing at production IdP during development | Avoids accidental production token usage and simplifies local iteration |
|
|
450
|
+
| Local-mode signing secret | Set a stable `JWT_SECRET` (e.g. `openssl rand -hex 32`) in env | Leave `JWT_SECRET` unset in a long-lived deployment | Unset means a random per-process HS256 secret; every restart invalidates all issued tokens |
|
|
207
451
|
|
|
208
452
|
## Verification Checklist
|
|
209
453
|
|
|
@@ -217,7 +461,7 @@ The `authProviders` accessor (from `@frontmcp/auth`) provides:
|
|
|
217
461
|
**Security**
|
|
218
462
|
|
|
219
463
|
- [ ] No secrets are hardcoded in source files -- all loaded from environment variables
|
|
220
|
-
- [ ]
|
|
464
|
+
- [ ] `JWT_SECRET` is set to a stable strong random value when using `local`/`remote` mode (otherwise tokens are invalidated on every restart)
|
|
221
465
|
- [ ] Production deployments use Redis or Vercel KV for session storage, not in-memory
|
|
222
466
|
|
|
223
467
|
**Runtime**
|
|
@@ -229,21 +473,23 @@ The `authProviders` accessor (from `@frontmcp/auth`) provides:
|
|
|
229
473
|
|
|
230
474
|
## Troubleshooting
|
|
231
475
|
|
|
232
|
-
| Problem | Cause
|
|
233
|
-
| --------------------------------------- |
|
|
234
|
-
| `JWKS fetch failed` error on startup | The `provider` URL is unreachable or does not serve `/.well-known/jwks.json`
|
|
235
|
-
| Tokens rejected with `invalid audience` | The `expectedAudience` value does not match the `aud` claim in the token
|
|
236
|
-
| Sessions lost after server restart | Using the default in-memory session store in production
|
|
237
|
-
|
|
|
238
|
-
| OAuth redirect fails in local dev | `remote` mode requires HTTPS and reachable callback URLs
|
|
476
|
+
| Problem | Cause | Solution |
|
|
477
|
+
| --------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
|
478
|
+
| `JWKS fetch failed` error on startup | The `provider` URL is unreachable or does not serve `/.well-known/jwks.json` | Verify the provider URL is correct and accessible from the server; check network/firewall rules |
|
|
479
|
+
| Tokens rejected with `invalid audience` | The `expectedAudience` value does not match the `aud` claim in the token | Align the `expectedAudience` config with the audience value your identity provider sets in tokens |
|
|
480
|
+
| Sessions lost after server restart | Using the default in-memory session store in production | Switch to Redis or Vercel KV session store via `configure-session` reference |
|
|
481
|
+
| Local-mode tokens invalid after restart | `JWT_SECRET` unset (random per-process secret) and/or `tokenStorage: 'memory'` | Set a stable `JWT_SECRET`; use `tokenStorage: { sqlite: { path } }` or `{ redis }` to persist codes/refresh tokens |
|
|
482
|
+
| OAuth redirect fails in local dev | `remote` mode requires HTTPS and reachable callback URLs | Set `NODE_ENV=development` to relax HTTPS requirements, or use a local OAuth mock server |
|
|
239
483
|
|
|
240
484
|
## Examples
|
|
241
485
|
|
|
242
|
-
| Example | Level | Description
|
|
243
|
-
| ---------------------------------------------------------------------------------- | ------------ |
|
|
244
|
-
| [`multi-app-auth`](../examples/configure-auth/multi-app-auth.md) | Advanced | Configure a single FrontMCP server with multiple apps, each using a different auth mode -- public for open endpoints and remote for admin endpoints.
|
|
245
|
-
| [`public-mode-setup`](../examples/configure-auth/public-mode-setup.md) | Basic | Set up a FrontMCP server with public (unauthenticated) access and anonymous scopes.
|
|
246
|
-
| [`remote-oauth-with-vault`](../examples/configure-auth/remote-oauth-with-vault.md) | Intermediate | Configure a FrontMCP server with
|
|
486
|
+
| Example | Level | Description |
|
|
487
|
+
| ---------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
488
|
+
| [`multi-app-auth`](../examples/configure-auth/multi-app-auth.md) | Advanced | Configure a single FrontMCP server with multiple apps, each using a different auth mode -- public for open endpoints and remote for admin endpoints. |
|
|
489
|
+
| [`public-mode-setup`](../examples/configure-auth/public-mode-setup.md) | Basic | Set up a FrontMCP server with public (unauthenticated) access and anonymous scopes. |
|
|
490
|
+
| [`remote-oauth-with-vault`](../examples/configure-auth/remote-oauth-with-vault.md) | Intermediate | 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. |
|
|
491
|
+
| [`local-credential-vault`](../examples/configure-auth/local-credential-vault.md) | Intermediate | Persist a per-session credential from a local authenticate() verifier into the built-in encrypted vault and read it from a tool via this.credentials. |
|
|
492
|
+
| [`local-secure-store`](../examples/configure-auth/local-secure-store.md) | Intermediate | 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. |
|
|
247
493
|
|
|
248
494
|
> See all examples in [`examples/configure-auth/`](../examples/configure-auth/)
|
|
249
495
|
|
|
@@ -157,14 +157,97 @@ frontmcp build --target vercel
|
|
|
157
157
|
| `takeoverGracePeriodMs` | number | 5000 | Grace period before takeover |
|
|
158
158
|
| `redisKeyPrefix` | string | `mcp:ha:` | Redis key prefix |
|
|
159
159
|
|
|
160
|
+
### Project-Defined CLI Commands (`cli.commands`)
|
|
161
|
+
|
|
162
|
+
Register project-specific verbs that ship alongside the built-in
|
|
163
|
+
`frontmcp` commands. Each verb spawns a runner module (TS or JS) as a
|
|
164
|
+
child process — the project's own code never runs in the CLI process.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
export default defineConfig({
|
|
168
|
+
name: 'my-server',
|
|
169
|
+
deployments: [{ target: 'node' }],
|
|
170
|
+
cli: {
|
|
171
|
+
commands: {
|
|
172
|
+
deploy: {
|
|
173
|
+
entry: './scripts/deploy.ts',
|
|
174
|
+
description: 'Push the current build to staging',
|
|
175
|
+
arguments: [{ name: 'env', required: true }],
|
|
176
|
+
options: [{ flags: '-n, --dry-run' }, { flags: '-c, --concurrency <num>', default: 4 }],
|
|
177
|
+
},
|
|
178
|
+
'db-migrate': { entry: './scripts/migrate.ts' },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Field | Type | Description |
|
|
185
|
+
| ------------- | -------------------------- | --------------------------------------------------- |
|
|
186
|
+
| `entry` | string | Path to runner (TS/JS), relative to project root |
|
|
187
|
+
| `description` | string | One-line description (shown in `--help`) |
|
|
188
|
+
| `arguments` | `ProjectCommandArgument[]` | Positional args (`{ name, required?, variadic? }`) |
|
|
189
|
+
| `options` | `ProjectCommandOption[]` | Named options (`{ flags, description?, default? }`) |
|
|
190
|
+
| `hidden` | boolean | Hide from `--help` (verb still invokable) |
|
|
191
|
+
|
|
192
|
+
Verb names must match `/^[a-zA-Z][a-zA-Z0-9:_-]*$/` and may not collide
|
|
193
|
+
with a built-in (`dev`, `build`, `test`, `start`, `skills`, etc.). Use a
|
|
194
|
+
namespaced prefix to avoid collisions: `project:init`, `db-migrate`.
|
|
195
|
+
|
|
196
|
+
Runner selection:
|
|
197
|
+
|
|
198
|
+
- `.ts` / `.tsx` / `.mts` / `.cts` → `node --import tsx <entry>`
|
|
199
|
+
- `.js` / `.mjs` / `.cjs` → `node <entry>`
|
|
200
|
+
|
|
201
|
+
The runner gets the parsed positionals as argv plus a
|
|
202
|
+
`FRONTMCP_PROJECT_COMMAND` env var holding a JSON payload of
|
|
203
|
+
`{ verb, positionals, options, cwd }`.
|
|
204
|
+
|
|
205
|
+
List every registered verb (built-in + project) with:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
frontmcp --list-commands
|
|
209
|
+
```
|
|
210
|
+
|
|
160
211
|
## File Resolution Order
|
|
161
212
|
|
|
213
|
+
Per-invocation precedence (issue #400):
|
|
214
|
+
|
|
215
|
+
1. Explicit `--config <path>` flag.
|
|
216
|
+
2. `FRONTMCP_CONFIG` env var.
|
|
217
|
+
3. Upward walk from `cwd` to the nearest ancestor containing a `frontmcp.config.*` (caps at 10 levels — monorepo nested apps work without `cd <repo-root>`).
|
|
218
|
+
4. Fallback: `package.json` (derives name, default node target).
|
|
219
|
+
|
|
220
|
+
Within a directory:
|
|
221
|
+
|
|
162
222
|
1. `frontmcp.config.ts`
|
|
163
223
|
2. `frontmcp.config.js`
|
|
164
224
|
3. `frontmcp.config.json`
|
|
165
225
|
4. `frontmcp.config.mjs`
|
|
166
226
|
5. `frontmcp.config.cjs`
|
|
167
|
-
|
|
227
|
+
|
|
228
|
+
## Override precedence (issue #400)
|
|
229
|
+
|
|
230
|
+
For every CLI option that's also expressible in the config:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
explicit CLI flag > FRONTMCP_<NAME> env var > frontmcp.config field > built-in default
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Per-command consumption (issue #400)
|
|
237
|
+
|
|
238
|
+
The config is consumed by every `frontmcp` command, not just `build`:
|
|
239
|
+
|
|
240
|
+
| Command | Config fields consumed |
|
|
241
|
+
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `build` | `name`, `version`, `entry`, `deployments`, `build`, `nodeVersion` |
|
|
243
|
+
| `dev` | `entry`, `transport.http.port`, `env.shared` ⊕ `env.dev` |
|
|
244
|
+
| `test` | `test.timeoutMs` / `test.runInBand` / `test.coverage` / `test.testMatch`, `env.shared` ⊕ `env.test` |
|
|
245
|
+
| `inspector` | `transport.default`, `transport.http.port`, `transport.stdio` |
|
|
246
|
+
| `pm start` / `socket` / `service` | `name`, `entry`, `transport.http.port`, `transport.http.socketPath`, `env.shared` ⊕ `env.ship` |
|
|
247
|
+
| `skills install` / `export` | `skills.provider`, `skills.bundle`, `skills.install`, `skills.exportTarget` |
|
|
248
|
+
| `eject-mcp-config <client>` | `clients.<client>`, `name`, `transport`, `env.ship` |
|
|
249
|
+
|
|
250
|
+
See `transport`, `env`, `clients`, `test`, `skills` field reference in [docs/frontmcp/deployment/frontmcp-config](https://docs.agentfront.dev/frontmcp/deployment/frontmcp-config).
|
|
168
251
|
|
|
169
252
|
## JSON Schema for IDE Support
|
|
170
253
|
|