@frontmcp/skills 1.8.3 → 1.8.4
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/catalog/create-tool/references/availability.md +10 -10
- package/catalog/frontmcp-authorities/SKILL.md +3 -2
- package/catalog/frontmcp-authorities/references/authority-profiles.md +1 -1
- package/catalog/frontmcp-config/references/configure-auth-modes.md +1 -1
- package/catalog/frontmcp-deployment/references/build-for-browser.md +19 -0
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +8 -0
- package/catalog/frontmcp-development/references/official-plugins.md +1 -1
- package/catalog/frontmcp-development/references/openapi-adapter.md +7 -7
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +1 -1
- package/catalog/frontmcp-production-readiness/references/production-browser.md +1 -0
- package/package.json +1 -1
|
@@ -26,15 +26,15 @@ On Linux / Windows servers, this tool simply doesn't exist — it's not in `tool
|
|
|
26
26
|
|
|
27
27
|
## Axes
|
|
28
28
|
|
|
29
|
-
| Axis | Values | Source
|
|
30
|
-
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
-
| `os` | `'darwin'`, `'linux'`, `'win32'` | `process.platform` (since #417 — was previously `platform`)
|
|
32
|
-
| `runtime` | `'node'`, `'browser'`, `'edge'`, `'bun'`, `'deno'` | Detected at boot
|
|
33
|
-
| `deployment` | `'serverless'`, `'standalone'`, `'distributed'`, `'browser'` | Detected from `frontmcp.config` / env
|
|
34
|
-
| `provider` | `'bare'`, `'docker'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'netlify'`, `'azure'`, `'gcp'`, `'fly'`, `'render'`, `'railway'` | Auto-detected; override with `FRONTMCP_PROVIDER=<name>`
|
|
35
|
-
| `target` | `'cli'`, `'node'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'browser'`, `'sdk'`, `'mcpb'`, `'distributed'` | Set by `frontmcp build --target <x>`; `'unknown'` in dev
|
|
36
|
-
| `surface` | `'mcp'`, `'cli'`, `'agent'`, `'job'`, `'http-trigger'` | Per-call axis — which entry point is invoking the tool
|
|
37
|
-
| `env` | `'production'`, `'development'`, `'test'` | `process.env.NODE_ENV`
|
|
29
|
+
| Axis | Values | Source |
|
|
30
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
31
|
+
| `os` | `'darwin'`, `'linux'`, `'win32'` | `process.platform` (since #417 — was previously `platform`) |
|
|
32
|
+
| `runtime` | `'node'`, `'browser'`, `'edge'`, `'bun'`, `'deno'` | Detected at boot |
|
|
33
|
+
| `deployment` | `'serverless'`, `'standalone'`, `'distributed'`, `'browser'` | Detected from `frontmcp.config` / env |
|
|
34
|
+
| `provider` | `'bare'`, `'docker'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'netlify'`, `'azure'`, `'gcp'`, `'fly'`, `'render'`, `'railway'` | Auto-detected; override with `FRONTMCP_PROVIDER=<name>` |
|
|
35
|
+
| `target` | `'cli'`, `'node'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'browser'`, `'sdk'`, `'mcpb'`, `'distributed'` | Set by `frontmcp build --target <x>`; `'unknown'` in dev |
|
|
36
|
+
| `surface` | `'mcp'`, `'cli'`, `'agent'`, `'job'`, `'http-trigger'` | Per-call axis — which entry point is invoking the tool |
|
|
37
|
+
| `env` | `'production'`, `'development'`, `'test'` | `process.env.NODE_ENV` |
|
|
38
38
|
|
|
39
39
|
## Semantics
|
|
40
40
|
|
|
@@ -100,7 +100,7 @@ These are fine for ergonomic branching. For tools that **shouldn't exist at all*
|
|
|
100
100
|
|
|
101
101
|
This is the safest way to expose internal-only tools that you want an agent / job to call but don't want a user to invoke from a chat UI.
|
|
102
102
|
|
|
103
|
-
An MCP client (and the in-process client of a CLI build, surface `'cli'`) never sees such a tool: it is absent from `tools/list`, and `tools/call` answers `Tool "rotate_secrets" not found`, exactly as for a tool that doesn't exist. Resources, resource templates, prompts, agents and skills (including the skills HTTP endpoints, which count as `'mcp'`) follow the same rule, and CodeCall applies its caller's surface to the tools it reaches.
|
|
103
|
+
An MCP client (and the in-process client of a CLI build, surface `'cli'`) never sees such a tool: it is absent from `tools/list`, and `tools/call` answers `Tool "rotate_secrets" not found`, exactly as for a tool that doesn't exist. Resources, resource templates, prompts, agents and skills (including the skills HTTP endpoints, which count as `'mcp'`) follow the same rule, and CodeCall applies its caller's surface to the tools it reaches. An agent's model calls its tools on `'agent'` (and is only offered those its `surface` allows), a job's or workflow step's `this.callTool()` on `'job'`, and a `@Channel` handling a webhook on `'http-trigger'`. A tool, resource or prompt calling `this.callTool()` is in-process dispatch: that call carries no surface and is not restricted. Code reads its call's surface with `getCallSurface()`. The process-wide axes (`os`, `runtime`, ...) answer `EntryUnavailableError` instead.
|
|
104
104
|
|
|
105
105
|
## See also
|
|
106
106
|
|
|
@@ -332,7 +332,8 @@ profile's rule checks nothing or isn't what it looks like, and `AuthoritiesEngin
|
|
|
332
332
|
denies such a rule if it runs anyway (even under `not`):
|
|
333
333
|
|
|
334
334
|
- `{}`, `{ roles: {} }`, `{ roles: { all: [] } }`, `allOf: []`, `guards: []`
|
|
335
|
-
- a misspelled field (`role:`), a profile name
|
|
335
|
+
- a misspelled field (`role:`), a profile name no profile has (`authorities: 'admn'`), a profile name inside
|
|
336
|
+
`allOf`/`anyOf`, an `operator` other than `'AND'`/`'OR'`
|
|
336
337
|
- an ABAC condition with no `value`, or one the operator can't use: `exists` needs `true`/`false`;
|
|
337
338
|
`in`/`notIn` a non-empty list; `gt`/`gte`/`lt`/`lte` a number; `startsWith`/`endsWith`/`matches`
|
|
338
339
|
a string (a `{ fromInput }` / `{ fromClaims }` reference works for all but `exists`)
|
|
@@ -401,7 +402,7 @@ the caller the agent runs for, also with `execution.useToolFlow: false`.
|
|
|
401
402
|
|
|
402
403
|
| Problem | Cause | Solution |
|
|
403
404
|
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
404
|
-
| `
|
|
405
|
+
| Startup: `Invalid authorities rule: … names an unknown profile "admin"` | Profile used in decorator but not in `authorities.profiles` config | Fix the name, or add the profile to the `profiles` field in `@FrontMcp({ authorities })` |
|
|
405
406
|
| All users denied despite correct roles | `claimsMapping.roles` path does not match the actual JWT claim path | Decode a real JWT and verify the dot-path resolves to the roles array |
|
|
406
407
|
| `authorities` field not recognized on decorator | `@frontmcp/auth` not imported (metadata augmentation not active) | Add `import '@frontmcp/auth'` (or `import type ... from '@frontmcp/auth'`) anywhere in your project to activate the metadata augmentation. There is no `@frontmcp/auth/authorities` subpath. |
|
|
407
408
|
| ABAC condition always fails | `{ fromInput: 'tenantId' }` but tool input field is named `tenant_id` | The `fromInput` key must exactly match the tool's input schema field name |
|
|
@@ -62,7 +62,7 @@ export class MyServer {}
|
|
|
62
62
|
|
|
63
63
|
### Single Profile (String)
|
|
64
64
|
|
|
65
|
-
The simplest form. The named profile's policy is evaluated.
|
|
65
|
+
The simplest form. The named profile's policy is evaluated. A name no registered profile has stops the server at startup with `Invalid authorities rule: … names an unknown profile "name"`, alone or in a list of profiles.
|
|
66
66
|
|
|
67
67
|
```typescript
|
|
68
68
|
@Tool({ name: 'delete_user', authorities: 'admin' })
|
|
@@ -88,7 +88,7 @@ Local mode also accepts `allowDefaultPublic` (default `false` — set `true` to
|
|
|
88
88
|
|
|
89
89
|
> **Client registration (security):** `requireRegisteredClients` defaults to `true` (local/remote): every client must be registered (DCR / `dcr.clients`) or a CIMD client-id URL, so `redirect_uri` is exact-matched (OAuth 2.1) — this prevents auth-code interception via an attacker-chosen redirect. Set it to `false` only for local development. The server grants only the scopes in `allowedScopes` (default: the OpenID scopes). Confidential clients (`token_endpoint_auth_method: client_secret_basic`/`client_secret_post`) are authenticated with a constant-time `client_secret` check on both the code-exchange and refresh grants (Basic header or body param).
|
|
90
90
|
|
|
91
|
-
> **Public origin (security):** pin `FRONTMCP_PUBLIC_URL` in production. The issuer / resource / OAuth-discovery URLs and the transparent expected audience derive from it rather than from request headers; `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored unless `FRONTMCP_TRUST_PROXY=1` (a trusted proxy that strips client-supplied forwarded headers).
|
|
91
|
+
> **Public origin (security):** pin `FRONTMCP_PUBLIC_URL` in production. The issuer / resource / OAuth-discovery URLs and the transparent expected audience derive from it rather than from request headers; `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored unless `FRONTMCP_TRUST_PROXY=1` (a trusted proxy that strips client-supplied forwarded headers). The Web fetch handler (`createFetchHandler`, Workers) takes the address from the request URL, never from a `Host` header that disagrees with it.
|
|
92
92
|
|
|
93
93
|
**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 by **following the `auth_url` from the failed call** — that URL carries a framework-signed, single-use `ticket` naming the target app and the prior grant, and 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). Do NOT hand-assemble `…&mode=incremental&app=slack`: since 1.7.2 (GHSA-2c4g-9c8x-6m8g) an authorize without a valid ticket is an ordinary login and runs the full credential gate, and `/oauth/callback` ignores an `incremental=true` parameter entirely. Single use is enforced with a conditional write against the configured session storage, so a **multi-instance deployment needs shared storage** (Redis, or any adapter supporting `ifNotExists`) for that guarantee to hold across instances; a backend without conditional writes (Cloudflare KV) degrades to an in-memory guard that holds within one instance only and warns at first use. 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.
|
|
94
94
|
|
|
@@ -57,6 +57,23 @@ Not all FrontMCP features are available in browser environments:
|
|
|
57
57
|
| Crypto (`@frontmcp/utils`) | Yes | Uses WebCrypto API |
|
|
58
58
|
| Direct client (`connect()`) | Yes | In-memory connection |
|
|
59
59
|
|
|
60
|
+
### Request context in the browser
|
|
61
|
+
|
|
62
|
+
A browser has no `AsyncLocalStorage`. Unless the runtime provides TC39 `AsyncContext`
|
|
63
|
+
(`getAsyncContextMode()` from `@frontmcp/utils` is then `'native'`), the browser build runs
|
|
64
|
+
requests one at a time (`'serialized'`) so a request never reads another request's session, auth
|
|
65
|
+
info or running tool:
|
|
66
|
+
|
|
67
|
+
- `DirectMcpServer` calls, `connect()` clients and `createFetchHandler` requests take turns. A
|
|
68
|
+
request keeps its turn until it returns and everything it started has unwound; background jobs,
|
|
69
|
+
workflows and tasks take their own turn afterwards.
|
|
70
|
+
- A request waiting on its client (elicitation, `roots/list`) steps aside while it waits.
|
|
71
|
+
- Concurrent tool calls inside one request (`Promise.all`) are refused with
|
|
72
|
+
`AsyncContextOverlapError` once they overlap. Run them one after another.
|
|
73
|
+
- A tool must not call its own server through a `DirectClient`/`DirectMcpServer` (it waits for its
|
|
74
|
+
own turn); use `this.scope` flows. A request that waits more than 10s for its turn logs why.
|
|
75
|
+
- Timers and un-awaited promises must not read request context.
|
|
76
|
+
|
|
60
77
|
## Usage with @frontmcp/react
|
|
61
78
|
|
|
62
79
|
The browser build is commonly paired with `@frontmcp/react` for React applications. `FrontMcpProvider` takes a pre-created `DirectMcpServer` (via the SDK's `create()` factory) — not a `serverUrl`. Hooks for listing/invoking are `useListTools` / `useCallTool`:
|
|
@@ -159,6 +176,8 @@ ls dist/browser/
|
|
|
159
176
|
| CORS errors on tool calls | MCP server missing CORS headers | Configure CORS middleware on the MCP server |
|
|
160
177
|
| Bundle too large | All server-side code included | Use `--target browser` and a dedicated client entry file |
|
|
161
178
|
| `@frontmcp/utils` fs throws | File system ops called in browser | Remove fs calls; use API endpoints or in-memory alternatives |
|
|
179
|
+
| `AsyncContextOverlapError` | Concurrent tool calls inside one request | Await the calls one after another (no `AsyncContext` in browser) |
|
|
180
|
+
| A call never returns | A tool calls its own server via a client | Call other tools through `this.scope` flows |
|
|
162
181
|
|
|
163
182
|
## Examples
|
|
164
183
|
|
|
@@ -178,6 +178,10 @@ Because `[vars]` reach `process.env`, `npx wrangler dev` sees the same `NODE_ENV
|
|
|
178
178
|
|
|
179
179
|
Background tasks need a store that outlives a single request and is shared between isolates, which an edge runtime cannot provide in-process. FrontMCP disables them automatically when no distributed store is configured, and the worker serves normally without them — no `tasks: { enabled: false }` opt-out is needed. `tasks: { enabled: true }` without `tasks.redis` fails the build rather than the deployed worker.
|
|
180
180
|
|
|
181
|
+
### Startup checks
|
|
182
|
+
|
|
183
|
+
The server is built on its first request, but the checks the config's metadata settles run when the module evaluates, so `createEdgeMcp` throws and the worker fails to deploy: an `approval` or `featureFlag` field no plugin that reaches the entry enforces (`UnenforcedMetadataError`), or `authorities` without the `authorities` option (`AuthConfigurationError`). A plugin installed on an app reaches only that app's entries, unless one of its hooks is `appliesTo: 'uncovered-apps'` as the built-in approval and feature-flag plugins' are. A tool declared inside an `@Agent` is reached only by that agent's plugins (none with `execution.useToolFlow: false`), and an agent's plugins reach nothing else. The remaining checks run when the first request builds the server.
|
|
184
|
+
|
|
181
185
|
## Step 5: Deploy
|
|
182
186
|
|
|
183
187
|
```bash
|
|
@@ -269,6 +273,10 @@ new_classes = ["FrontMcpSession"]
|
|
|
269
273
|
|
|
270
274
|
One DO per session holds a persistent transport so the `GET` notification stream stays open and `tools/call` notifications reach it. It runs the **same `http:request` flow** (auth/session:verify/router/audit/metrics + hooks) as the stateless path — so transparent auth returns `401` + `WWW-Authenticate` on the worker too.
|
|
271
275
|
|
|
276
|
+
A session belongs to the caller that opened it: the `Mcp-Session-Id` only addresses the DO, so the flow's `checkPersistentSessionOwner` stage binds the session to the caller of its first request (verified issuer + subject, else its token) and answers anyone else with `404 Session not found` on `POST`, `GET` and `DELETE`, before every protocol handler (2026-07-28 included). The owner is kept in the DO's `state.storage`, so a DO rebuilt after eviction still refuses strangers. On a public server (anonymous callers, no token) the unguessable session id is the only credential. The owner ends its session with `DELETE`.
|
|
277
|
+
|
|
278
|
+
OAuth sign-in works on the worker too, including `/oauth/provider/:providerId/callback` (a federated provider's redirect and the federated consent submission): path parameters are matched like Express.
|
|
279
|
+
|
|
272
280
|
## Storage Options
|
|
273
281
|
|
|
274
282
|
| Storage | Use Case | Notes |
|
|
@@ -795,7 +795,7 @@ The `toolName` completion of `ui://widget/{toolName}.html` runs the caller's `to
|
|
|
795
795
|
|
|
796
796
|
The skill catalog in the `initialize` instructions (and the SEP-2640 `skill://` hints under `skillsConfig.sep2640InInstructions`) is filtered for the initializing client too, so a flag-disabled skill's name and description never appear there ([#603](https://github.com/agentfront/frontmcp/issues/603)). A skill's `skill://<path>/SKILL.md` entry in `resources/list` is gated by the skill that path serves now -- replace a skill at the same path and the entry takes the new skill's flag ([#606](https://github.com/agentfront/frontmcp/issues/606)).
|
|
797
797
|
|
|
798
|
-
Installed on an `@App`, the gates cover every capability that app provides, including tools, resources and prompts contributed by its adapters (e.g. an OpenAPI adapter) and plugins. Its tool, resource, prompt and completion gates also cover the flagged capabilities of apps with no feature-flag plugin of their own
|
|
798
|
+
Installed on an `@App`, the gates cover every capability that app provides, including tools, resources and prompts contributed by its adapters (e.g. an OpenAPI adapter) and plugins. Its tool, resource, prompt and completion gates also cover the flagged capabilities of apps with no feature-flag plugin of their own, but not those of an app that installs its own -- install it in `@FrontMcp({ plugins })` to gate every app with one adapter. Listings follow the same copy of the plugin as the gates (a plugin on each of two apps decides each app's entries, listed and served, with that app's flags); up to 1.8.3 every copy filtered every app's listings. Releases up to 1.8.2 hid another app's flagged-off tool from `tools/list` but still ran it when called by name. Resources and prompts served outside every app (the SEP-2640 `skill://` resources) are gated by every installed copy. Skills are gated through the `skills:filter` flow, which every skill surface runs -- as the calling user on every transport, stdio and in-memory included; custom plugins can hook `Did('filterSkills')` on it the same way, and reuse `filterServableSkills(scope, skills)` from `@frontmcp/sdk` to serve skills from a surface of their own.
|
|
799
799
|
|
|
800
800
|
---
|
|
801
801
|
|
|
@@ -128,16 +128,16 @@ OpenapiAdapter.init({
|
|
|
128
128
|
|
|
129
129
|
With no `authProviderMapper`, `securityResolver` or `staticAuth`, the adapter sends **no** credentials: operations that require auth fail with `Authentication required for tool '…'` and a `SECURITY WARNING` is logged at startup. The caller's MCP token (`ctx.authInfo.token`) is never forwarded implicitly — not by default, and not when an `authProviderMapper` function returns `undefined` — because passing it to another API is token passthrough, which the MCP specification forbids. `passthroughCallerToken: true` is the explicit opt-in, used only after every other credential source came up empty.
|
|
130
130
|
|
|
131
|
-
| Risk Level | Strategy
|
|
132
|
-
| ---------- |
|
|
133
|
-
| LOW | `authProviderMapper` or `securityResolver`
|
|
134
|
-
| MEDIUM | `staticAuth`, `additionalHeaders`, or none
|
|
135
|
-
| HIGH | `includeSecurityInInput: true`
|
|
136
|
-
| HIGH | `passthroughCallerToken: true`
|
|
131
|
+
| Risk Level | Strategy | Description |
|
|
132
|
+
| ---------- | ----------------------------------------------------------- | ---------------------------------------------------- |
|
|
133
|
+
| LOW | `authProviderMapper` or `securityResolver` | Auth from user context, not exposed to clients |
|
|
134
|
+
| MEDIUM | `staticAuth`, `additionalHeaders`, or none | Static credentials, or no credentials at all |
|
|
135
|
+
| HIGH | `includeSecurityInInput: true`, or `securitySchemesInInput` | Auth fields exposed to MCP clients (not recommended) |
|
|
136
|
+
| HIGH | `passthroughCallerToken: true` | The MCP client's own token is sent to the API |
|
|
137
137
|
|
|
138
138
|
`passthroughCallerToken: true` scores HIGH alongside an `authProviderMapper` too (the token is sent when no mapper function returns a credential); only a `securityResolver` or a non-empty `staticAuth` leaves it unused.
|
|
139
139
|
|
|
140
|
-
Resolution order: `securityResolver` → `authProviderMapper` → `staticAuth` (fills every credential no mapper function returned; a mapped value wins) → `passthroughCallerToken`. A security scheme with no `authProviderMapper` entry is refused at startup unless `staticAuth` or `passthroughCallerToken`
|
|
140
|
+
Resolution order: `securityResolver` → `authProviderMapper` → `staticAuth` (fills every credential no mapper function returned; a mapped value wins) → `passthroughCallerToken`. A security scheme with no `authProviderMapper` entry is refused at startup unless `staticAuth` covers it, `additionalHeaders` carries its credential, `headersMapper` may set it (a header or cookie scheme, checked on each request), or `passthroughCallerToken` does for an HTTP bearer scheme (it sends the caller's token for it and logs a `SECURITY WARNING`; the caller's token never fills an API key, basic, OAuth2 or OpenID Connect scheme). An operation that requires auth is sent only with a credential for one of its own schemes, from a credential option, the tool input (`securitySchemesInInput`, `includeSecurityInInput`), `additionalHeaders` or `headersMapper`; otherwise it fails with `Authentication required for tool '…'`. A tool-input credential is used for a scheme only when no other source supplies one (a server credential always wins).
|
|
141
141
|
|
|
142
142
|
## Spec Polling
|
|
143
143
|
|
|
@@ -18,7 +18,7 @@ These checks apply to ALL deployment targets. Run them first, then proceed to yo
|
|
|
18
18
|
- [ ] Session TTL is configured appropriately (not infinite)
|
|
19
19
|
- [ ] Tool-level authorization is enforced where needed (ApprovalPlugin or custom)
|
|
20
20
|
- [ ] OAuth redirect URIs are restricted to known domains
|
|
21
|
-
- [ ] `FRONTMCP_PUBLIC_URL` is pinned to the canonical origin — issuer / resource / OAuth-discovery URLs and the transparent-mode expected audience derive from it, not from request headers. `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored by default; only set `FRONTMCP_TRUST_PROXY=1` behind a proxy that strips client-supplied forwarded headers
|
|
21
|
+
- [ ] `FRONTMCP_PUBLIC_URL` is pinned to the canonical origin — issuer / resource / OAuth-discovery URLs and the transparent-mode expected audience derive from it, not from request headers. `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored by default; only set `FRONTMCP_TRUST_PROXY=1` behind a proxy that strips client-supplied forwarded headers. The Web fetch handler (`createFetchHandler`, Workers) takes the address from the request URL, never from a `Host` header that disagrees with it
|
|
22
22
|
- [ ] `auth.requireRegisteredClients` left at its default `true` (local/remote) so unknown clients can't present an attacker-chosen `redirect_uri` (auth-code interception). Clients register via DCR / `dcr.clients` / CIMD; a production remote-mode server has neither DCR nor `dcr.clients`, so its MCP clients must use CIMD
|
|
23
23
|
- [ ] `auth.allowedScopes` lists the scopes the server may grant (local/remote); anything else a client asks for is dropped
|
|
24
24
|
- [ ] Transparent mode sets `auth.expectedAudience` (bind tokens to this resource) and validates issuer (`providerConfig.verifyIssuer`, default on)
|
|
@@ -23,6 +23,7 @@ Target-specific checklist for publishing FrontMCP as a browser-compatible SDK.
|
|
|
23
23
|
- [ ] All file operations removed or polyfilled
|
|
24
24
|
- [ ] Fetch API used instead of Node http/https modules
|
|
25
25
|
- [ ] Works in major browsers (Chrome, Firefox, Safari, Edge)
|
|
26
|
+
- [ ] Without `AsyncContext` requests run one at a time: tools don't start concurrent tool calls inside one request, don't call their own server through a client, and don't read request context from timers
|
|
26
27
|
|
|
27
28
|
## Security
|
|
28
29
|
|