@frontmcp/skills 1.8.7 → 1.9.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.
Files changed (62) hide show
  1. package/README.md +107 -155
  2. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
  3. package/catalog/create-tool/references/availability.md +10 -10
  4. package/catalog/create-tool/references/ui-widgets.md +30 -8
  5. package/catalog/frontmcp-authorities/SKILL.md +5 -0
  6. package/catalog/frontmcp-channels/SKILL.md +17 -16
  7. package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
  8. package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
  9. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
  10. package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
  11. package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
  12. package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
  13. package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
  14. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
  15. package/catalog/frontmcp-config/references/configure-auth.md +1 -1
  16. package/catalog/frontmcp-config/references/configure-deployment-targets.md +54 -20
  17. package/catalog/frontmcp-config/references/configure-http.md +5 -2
  18. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +1 -1
  19. package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
  20. package/catalog/frontmcp-config/references/configure-transport.md +4 -5
  21. package/catalog/frontmcp-deployment/SKILL.md +19 -19
  22. package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
  23. package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
  24. package/catalog/frontmcp-deployment/references/build-for-browser.md +38 -9
  25. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -9
  26. package/catalog/frontmcp-deployment/references/build-for-sdk.md +2 -1
  27. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +3 -1
  28. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +2 -2
  29. package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
  30. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
  31. package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
  32. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
  33. package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
  34. package/catalog/frontmcp-development/references/create-adapter.md +14 -0
  35. package/catalog/frontmcp-development/references/create-agent.md +82 -49
  36. package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
  37. package/catalog/frontmcp-development/references/create-plugin.md +8 -4
  38. package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
  39. package/catalog/frontmcp-development/references/create-skill.md +4 -0
  40. package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
  41. package/catalog/frontmcp-development/references/official-adapters.md +1 -1
  42. package/catalog/frontmcp-development/references/official-plugins.md +127 -24
  43. package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
  44. package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
  45. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +12 -1
  46. package/catalog/frontmcp-production-readiness/references/common-checklist.md +2 -1
  47. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +48 -28
  48. package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
  49. package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
  50. package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
  51. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +1 -1
  52. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
  53. package/catalog/frontmcp-setup/references/multi-app-composition.md +11 -8
  54. package/catalog/frontmcp-setup/references/nx-workflow.md +53 -21
  55. package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
  56. package/catalog/frontmcp-setup/references/setup-project.md +15 -0
  57. package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
  58. package/catalog/frontmcp-setup/references/setup-sqlite.md +12 -8
  59. package/catalog/frontmcp-testing/SKILL.md +16 -12
  60. package/catalog/frontmcp-testing/references/setup-testing.md +14 -1
  61. package/catalog/skills-manifest.json +10 -8
  62. package/package.json +1 -1
@@ -62,6 +62,9 @@ These are the flow names with pre-built hook decorator exports in `@frontmcp/sdk
62
62
  | `resources:read-resource` | Resource reading | `ResourceHook` |
63
63
  | `resources:list-resources` | Resource listing | `ListResourcesHook` |
64
64
  | `resources:list-resource-templates` | Resource template listing | `ListResourceTemplatesHook` |
65
+ | `prompts:get-prompt` | Prompt retrieval | `PromptHook` |
66
+ | `prompts:list-prompts` | Prompt listing | `ListPromptsHook` |
67
+ | `completion:complete` | Argument completion | `CompletionHook` |
65
68
  | `agents:call-agent` | Agent invocation | `AgentCallHook` |
66
69
  | `channels:send-notification` | Channel notification send | `ChannelSendHook` |
67
70
  | `channels:list` | Channel listing | `ChannelListHook` |
@@ -152,10 +155,13 @@ import {
152
155
  AgentCallHook, // FlowHooksOf('agents:call-agent')
153
156
  ChannelListHook, // FlowHooksOf('channels:list')
154
157
  ChannelSendHook, // FlowHooksOf('channels:send-notification')
158
+ CompletionHook, // FlowHooksOf('completion:complete')
155
159
  HttpHook, // FlowHooksOf('http:request')
160
+ ListPromptsHook, // FlowHooksOf('prompts:list-prompts')
156
161
  ListResourcesHook, // FlowHooksOf('resources:list-resources')
157
162
  ListResourceTemplatesHook, // FlowHooksOf('resources:list-resource-templates')
158
163
  ListToolsHook, // FlowHooksOf('tools:list-tools')
164
+ PromptHook, // FlowHooksOf('prompts:get-prompt')
159
165
  ResourceHook, // FlowHooksOf('resources:read-resource')
160
166
  ToolHook, // FlowHooksOf('tools:call-tool')
161
167
  } from '@frontmcp/sdk';
@@ -167,7 +173,7 @@ Usage:
167
173
  const { Will, Did, Around, Stage } = ToolHook;
168
174
  ```
169
175
 
170
- > **Note:** Other internal flows (e.g., `prompts:get-prompt`, `prompts:list-prompts`, `skills:search`, `completion:complete`, transport flows) exist at runtime and can be hooked by passing the flow name to `FlowHooksOf<'flow:name'>('flow:name')`, but they do not currently ship with a pre-built typed export. Prefer the pre-built exports above when one is available.
176
+ > **Note:** Other flows (e.g., `skills:filter`, transport flows) can be hooked by passing the flow name to `FlowHooksOf('flow:name')`. Prefer the pre-built exports above when one is available: exporting them is also what puts a flow's types in the published package, which is why `FlowHooksOf('prompts:get-prompt')`, `'prompts:list-prompts'` and `'completion:complete'` did not typecheck in consumer projects up to 1.8.7.
171
177
 
172
178
  ## call-tool Flow Stages
173
179
 
@@ -310,7 +316,9 @@ export class MyApp {}
310
316
 
311
317
  Plugins are initialized in array order. Hook priority determines execution order within the same stage.
312
318
 
313
- Hooks declared on an app's providers, on its plugins (including plugins nested inside them), and on those plugins' providers run only for that app's tools, resources and prompts (`tools:call-tool`, `resources:read-resource`, `prompts:get-prompt`, `completion:complete`), including the ones its adapters and plugins provide, such as the tools an OpenAPI adapter generates. Plugins registered on the server (`@FrontMcp({ plugins })`) apply to every app. Resources and prompts the server serves outside every app, such as the SEP-2640 `skill://` resources, run every app's hooks.
319
+ Hooks declared on an app's providers, on its plugins (including plugins nested inside them), and on those plugins' providers run only for that app's tools, resources and prompts (`tools:call-tool`, `resources:read-resource`, `prompts:get-prompt`, `completion:complete`), including the ones its adapters and plugins provide, such as the tools an OpenAPI adapter generates. Plugins and providers registered on the server (`@FrontMcp({ plugins, providers })`) apply to every app. Resources and prompts the server serves outside every app, such as the SEP-2640 `skill://` resources, run every app's hooks.
320
+
321
+ A hook declared on a `CONTEXT`-scoped provider (`@Provider({ scope: ProviderScope.CONTEXT })`, a class provider) runs on the instance built for the request or session -- the same instance the request's tools get from `this.get()`. Up to 1.8.7, hooks on server-level and `CONTEXT`-scoped providers were never registered.
314
322
 
315
323
  ## Using Hooks Inside a @Tool Class
316
324
 
@@ -377,11 +385,12 @@ class ProcessOrderTool extends ToolContext {
377
385
  ### Available Stages for Tool Hooks
378
386
 
379
387
  ```
380
- parseInput → findTool → checkToolAuthorization → createToolCallContext
381
- → validateInput → execute → validateOutput → finalize
388
+ parseInput → ensureRemoteCapabilities → findTool → checkToolAuthorization → checkEntryAuthorities
389
+ → createTaskIfRequested → createToolCallContext → checkToolCredentials → acquireQuota → acquireSemaphore
390
+ → validateInput → execute → validateOutput → releaseSemaphore → releaseQuota → applyUI → finalize
382
391
  ```
383
392
 
384
- Any stage can have `@Will`, `@Did`, `@Stage`, or `@Around` hooks.
393
+ A tool-class hook runs on the tool instance, which `createToolCallContext` builds. So it can hook `Did`/`Stage` on `createToolCallContext` and any hook on a later stage. A hook on an earlier stage, `Will`/`Around` on `createToolCallContext`, or a list-flow hook (`ListToolsHook`; listing builds no instance) fails startup with `InvalidHookFlowError` -- put those on a plugin or a provider. The same holds for `@Resource` (`createResourceContext`), `@Prompt` (`createPromptContext`) and `@Agent` (`createAgentContext`) classes. `@Job` classes cannot declare hooks at all (jobs do not run through a hookable flow); hook `tools:call-tool` for the `execute_job` tool instead. Up to 1.8.7 these hooks were accepted and silently never ran.
385
394
 
386
395
  ## Common Patterns
387
396
 
@@ -415,6 +424,7 @@ Any stage can have `@Will`, `@Did`, `@Stage`, or `@Around` hooks.
415
424
  | Problem | Cause | Solution |
416
425
  | --------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------- |
417
426
  | Hook never fires | Plugin not registered in `plugins` array | Add plugin class to `@App` or `@FrontMcp` `plugins` array |
427
+ | `InvalidHookFlowError` at startup | Entry-class hook that could never run | Move early-stage, list-flow and `@Job` hooks to a plugin or a provider |
418
428
  | Hook fires for wrong flow | Used wrong flow name in `FlowHooksOf` | Verify flow name matches (e.g., `'tools:call-tool'` not `'tool:call'`) |
419
429
  | `@Around` skips the stage entirely | `next()` not called inside the around handler | Always `await next()` to execute the wrapped stage |
420
430
  | Multiple hooks execute in wrong order | Priorities not set or conflicting | Set explicit `priority` values; lower numbers execute first |
@@ -5,7 +5,7 @@ description: Build plugins with providers, context extensions, lifecycle hooks,
5
5
 
6
6
  # Create a FrontMCP Plugin
7
7
 
8
- This skill covers building custom plugins for FrontMCP and using all 6 official plugins. Plugins are modular units that extend server behavior through providers, context extensions, lifecycle hooks, and contributed tools/resources/prompts.
8
+ This skill covers building custom plugins for FrontMCP and using all 7 official plugins. Plugins are modular units that extend server behavior through providers, context extensions, lifecycle hooks, and contributed tools/resources/prompts.
9
9
 
10
10
  ## When to Use This Skill
11
11
 
@@ -332,7 +332,9 @@ The reverse does not work: an option-derived provider cannot inject a provider t
332
332
 
333
333
  ### Options named like plugin metadata, and option-derived tools
334
334
 
335
- `init(options)` spreads the options into the plugin's metadata, so an option named like a list-valued metadata key (`tools`, `resources`, `prompts`, `skills`, `adapters`, `plugins`, `exports`) used to be read as that list: `RememberPlugin.init({ tools: { enabled: true } })` crashed at startup. A non-array value under one of those keys is now an option and stays out of the metadata; an array still contributes.
335
+ `init(options)` spreads the options into the plugin's metadata, so an option named like a list-valued metadata key (`tools`, `resources`, `prompts`, `skills`, `adapters`, `plugins`, `exports`, `contextExtensions`, `enforcesMetadata`) used to be read as that list: `RememberPlugin.init({ tools: { enabled: true } })` crashed at startup. A non-array value under one of those keys is now an option and stays out of the metadata; an array still contributes.
336
+
337
+ `providers` follows the same rule: an array adds providers to the plugin; any other value (`MyPlugin.init({ providers: { region: 'eu' } })`) is the plugin's own option and reaches the instance instead of throwing `(extraProviders ?? []) is not iterable` at module load. When the options type declares `providers`, `init()` types the key as that option.
336
338
 
337
339
  To register tools only when an option asks for it, declare `static dynamicTools(options)`, the counterpart of `dynamicProviders`. Its tools are added to those from `@Plugin({ tools })` and from an array `tools` option:
338
340
 
@@ -343,7 +345,7 @@ export default class MemoryPlugin extends DynamicPlugin<MemoryOptions, MemoryOpt
343
345
  }
344
346
  ```
345
347
 
346
- `dynamicTools` runs for `init(options)`; `init({ inject, useFactory })` takes its tools from the `@Plugin` metadata, since the options are unknown until the factory runs.
348
+ `dynamicTools`, like `dynamicProviders`, runs on the options the plugin is built with: those given to `init(options)`, or the ones an `init({ inject, useFactory })` factory returns at startup. `RememberPlugin.init({ inject, useFactory: () => ({ type: 'memory', tools: { enabled: true } }) })` therefore registers the memory tools.
347
349
 
348
350
  ### Installing the same plugin in several apps
349
351
 
@@ -357,6 +359,8 @@ class BillingApp {}
357
359
  class OpsApp {}
358
360
  ```
359
361
 
362
+ An app that does not install the plugin does not get its providers: there, `this.get(Token)` throws and `this.tryGet(Token)` returns `undefined`. Install the plugin on the server (`@FrontMcp({ plugins })`) to share it with every app. Up to 1.8.7 the providers a plugin derives from its options (`dynamicProviders(options)`, `init({ providers })`) leaked to every other app on the server.
363
+
360
364
  ## Step 5: Extend Metadata and Execution Context
361
365
 
362
366
  FrontMCP provides two extension mechanisms for plugins: **metadata augmentation** (add fields to decorators) and **context extensions** (add properties to `this` in tools/resources/prompts).
@@ -475,7 +479,7 @@ export { MyServiceToken } from './my-plugin.symbols';
475
479
 
476
480
  ## Official Plugins
477
481
 
478
- For official plugin installation, configuration, and examples, see the **official-plugins** skill. FrontMCP provides 6 official plugins: CodeCall, Remember, Approval, Cache, Feature Flags, and Dashboard. Install individually or via `@frontmcp/plugins` (meta-package).
482
+ For official plugin installation, configuration, and examples, see the **official-plugins** skill. FrontMCP provides 7 official plugins: CodeCall, Remember, Approval, Cache, Feature Flags, Dashboard, and WebMCP. Install individually or via `@frontmcp/plugins` (meta-package).
479
483
 
480
484
  ## Recommended Folder Structure
481
485
 
@@ -82,7 +82,9 @@ The `tools` array in `@Skill` metadata supports three ways to reference tools th
82
82
 
83
83
  ### 1. Class Reference
84
84
 
85
- Pass the tool class directly. The framework resolves the tool name and validates it exists in the registry.
85
+ Pass the tool class directly (or `{ tool: ToolClass, purpose?, required? }`). The framework resolves the tool name (the tool's `id` when it declares one, else its `name`) and validates it exists in the registry. The class is only a reference: register the tool in the `tools` of the `@App` or `@FrontMcp` too. A class that isn't a `@Tool` stops the server from starting with `Invalid tool class '<ClassName>'`.
86
+
87
+ Up to 1.8.7 a class reference itself stopped the server from starting (the class lost its `@Tool` name while the skill's options were validated); on those versions, reference the tool by name.
86
88
 
87
89
  ```typescript
88
90
  @Skill({
@@ -170,6 +172,8 @@ class StrictWorkflowSkill extends SkillContext {}
170
172
  | `'warn'` | Logs a warning for missing tools but continues. Use during development when tools may not all be available yet. |
171
173
  | `'ignore'` | Silently ignores missing tools. Use for optional tool references or cross-server skills. |
172
174
 
175
+ In `'strict'` mode the server refuses to start, with `SkillValidationError: Skill '<name>' failed tool validation: missing tools [...]`. Up to 1.8.7 it started anyway, silently.
176
+
173
177
  When a caller loads the skill (`skills/load`, the `skills:load` flow, `GET /skills/{id}`, `/llm_full.txt`), a referenced tool that `availableWhen.surface` doesn't offer that caller (an agent-only tool, for an MCP client) is reported as missing, without its input schema, just as `tools/list` leaves it out. The same skill loaded by an agent lists it as available.
174
178
 
175
179
  ## Instruction Sources
@@ -652,7 +656,7 @@ class AuditServer {}
652
656
 
653
657
  ## CodeCall Compatibility
654
658
 
655
- When the `CodeCallPlugin` is active in `codecall_only` mode, all tools registered on the server are hidden from `list_tools`. The AI client only sees the three CodeCall meta-tools (`codecall:search`, `codecall:describe`, `codecall:execute`). This means skill instructions that reference tool names directly (e.g., "Use the `build_project` tool") become misleading -- the AI cannot call those tools because they do not appear in the tool listing.
659
+ When the `CodeCallPlugin` is active in `codecall_only` mode, all tools registered on the server are hidden from `list_tools`. The AI client only sees the three CodeCall meta-tools (`codecall:search`, `codecall:describe`, `codecall:execute`). This means skill instructions that reference tool names directly (e.g., "Use the `build_project` tool") become misleading -- the AI cannot call those tools: they do not appear in the tool listing, and a direct `tools/call` of a hidden tool is refused as for an unknown tool.
656
660
 
657
661
  ### When This Matters
658
662
 
@@ -755,6 +759,7 @@ class DeployServiceSkill extends SkillContext {}
755
759
  | -------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------- |
756
760
  | Skill not appearing in `/llm.txt` | `visibility` is set to `'mcp'` | Change to `'both'` or `'http'` to include HTTP discovery |
757
761
  | `toolValidation: 'strict'` throws at startup | A referenced tool is not registered in the scope | Register all referenced tools in the `tools` array of `@App` or `@FrontMcp` |
762
+ | `Invalid tool class` at startup | A class in `tools` isn't decorated with `@Tool` | Reference a `@Tool` class, or the tool's name |
758
763
  | `skillDir()` fails to load | `SKILL.md` file missing or frontmatter is invalid YAML | Ensure the directory contains a `SKILL.md` with valid `---` delimited YAML frontmatter |
759
764
  | Instructions are empty at runtime | `{ file: './path.md' }` path is relative to wrong directory | Use a path relative to the skill file's location, not the project root |
760
765
  | Parameters not visible to AI client | `parameters` defined as a plain object instead of an array | Use array format: `[{ name, description, type, required }]` |
@@ -53,6 +53,8 @@ Create a class extending `SkillContext` and decorate it with `@Skill`. The decor
53
53
  | `allowedTools` | `string` | No | Space-delimited pre-approved tool names (Agent Skills spec) |
54
54
  | `resources` | `SkillResources` | No | Bundled dirs: `{ scripts?, references?, assets? }` |
55
55
 
56
+ With `toolValidation: 'strict'`, the server refuses to start when a referenced tool isn't registered (`SkillValidationError`).
57
+
56
58
  ### Basic Example
57
59
 
58
60
  ```typescript
@@ -158,6 +160,8 @@ class ApiStandardsSkill extends SkillContext {}
158
160
 
159
161
  > **When file and URL instructions are read:** when the server starts, for every skill — the server indexes each skill for `skills/search` and checks its tools then, so a URL is fetched at every start whether or not a client reads the skill. A read that fails is logged (`Failed to load skill <name>: …`) and tried again the first time the skill is loaded; the content is kept once a read succeeds.
160
162
 
163
+ > **Search index:** `skills/search` ranks with TF-IDF from the optional peer `vectoriadb`, loaded the first time a search runs — not at server start. Listing and loading skills (`skills/list`, `skills/load`, `skill://` resources) never need it, so a server whose skills are never searched runs without it installed, and a server started inside a Jest test needs no `--experimental-vm-modules`. The first search without the package fails with an error naming it.
164
+
161
165
  ## SkillContext: loadInstructions() and build()
162
166
 
163
167
  The `SkillContext` class resolves instructions regardless of the source type. When the framework serves a skill, it calls `build()` which internally calls `loadInstructions()`.
@@ -87,7 +87,7 @@ FrontMCP uses a hierarchical decorator system. The nesting order is:
87
87
  | `pagination?` | List operation pagination (`tools/list` endpoint) |
88
88
  | `fetch?` | What `this.fetch()` adds upstream: `forwardCallerTokenTo` / `forwardCustomHeadersTo` origin allow-lists (default: none), `autoInjectTracingHeaders`, `requestTimeout` |
89
89
  | `ui?` | UI rendering config (CDN overrides for widget imports) |
90
- | `extApps?` | Widget-to-host MCP Apps communication (host capabilities, session validation) |
90
+ | `extApps?` | Widget-to-host MCP Apps communication (host capabilities, session validation). `ui/callServerTool` returns the tool's data without its page; `ui/log` answers `result: {}` |
91
91
  | `loader?` | Default npm/ESM package loader for `App.esm()` / `App.remote()` apps |
92
92
 
93
93
  > **Throttle vs per-tool guards:** Server-level `throttle` is a `GuardConfig` object with `global`, `defaultRateLimit`, `defaultConcurrency`, `defaultTimeout` sub-fields that set server-wide defaults. Tool-level `rateLimit`, `concurrency`, `timeout` fields (on `@Tool`) override these defaults per tool.
@@ -123,7 +123,7 @@ class MyServer {}
123
123
  | `tools?` | Array of tool classes or function-built tools |
124
124
  | `resources?` | Array of resource classes or function-built resources |
125
125
  | `prompts?` | Array of prompt classes or function-built prompts |
126
- | `agents?` | Array of agent classes (each exposed as `use-agent:<name>` tool) |
126
+ | `agents?` | Array of agent classes (each exposed as an `invoke_<id>` tool) |
127
127
  | `skills?` | Array of skill definitions |
128
128
  | `plugins?` | App-scoped plugins |
129
129
  | `providers?` | App-scoped DI providers |
@@ -331,10 +331,11 @@ class UserProfileResource extends ResourceContext {
331
331
  | `llm` | LLM configuration (model, provider, temperature, etc.) |
332
332
  | `inputSchema?` | Zod raw shape for agent input |
333
333
  | `outputSchema?` | Zod schema for structured output |
334
- | `tools?` | Tools available to this agent |
335
- | `agents?` | Sub-agents for delegation |
336
- | `exports?` | What capabilities to expose externally |
337
- | `swarm?` | Multi-agent swarm configuration |
334
+ | `tools?` | Tools offered to this agent's model |
335
+ | `agents?` | Nested agents, offered to its model as `invoke_<id>` |
336
+ | `exports?` | `{ resources?, prompts?, providers? }` shared with app |
337
+ | `swarm?` | Which other agents it can call, and how deep |
338
+ | `execution?` | Loop limits, `inheritParentTools`, `inheritPlugins` |
338
339
 
339
340
  ```typescript
340
341
  import { Agent, AgentContext, z } from '@frontmcp/sdk';
@@ -348,11 +349,7 @@ import { Agent, AgentContext, z } from '@frontmcp/sdk';
348
349
  },
349
350
  tools: [WebSearchTool, SummarizeTool],
350
351
  })
351
- class ResearchAgent extends AgentContext {
352
- async execute(input: { topic: string }) {
353
- return this.run(`Research and summarize: ${input.topic}`);
354
- }
355
- }
352
+ class ResearchAgent extends AgentContext {} // the default execute() runs the LLM loop
356
353
  ```
357
354
 
358
355
  ---
@@ -384,6 +381,8 @@ class ResearchAgent extends AgentContext {
384
381
  | `allowedTools?` | Space-delimited pre-approved tool names (Agent Skills spec) |
385
382
  | `resources?` | Bundled dirs: `{ scripts?, references?, assets? }` (Agent Skills spec) |
386
383
 
384
+ `toolValidation: 'strict'` makes the server refuse to start when a referenced tool isn't registered.
385
+
387
386
  ```typescript
388
387
  import { Skill } from '@frontmcp/sdk';
389
388
 
@@ -5,7 +5,7 @@ description: Overview of all official FrontMCP adapters that convert external de
5
5
 
6
6
  # Official Adapters
7
7
 
8
- Adapters convert external definitions (OpenAPI specs, Lambda functions, etc.) into MCP tools, resources, and prompts automatically. They are registered in the `adapters` array of `@App`.
8
+ Adapters convert external definitions (OpenAPI specs, Lambda functions, etc.) into MCP tools, resources, and prompts automatically. They are registered in the `adapters` array of `@App` (that app's entries) or of `@FrontMcp` (entries every app serves).
9
9
 
10
10
  ## When to Use This Skill
11
11
 
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: official-plugins
3
- description: Guide to the 6 official plugins for discovery, memory, auth, caching, flags, and monitoring
3
+ description: Guide to the 7 official plugins for discovery, memory, auth, caching, flags, monitoring, and WebMCP
4
4
  ---
5
5
 
6
6
  # Official FrontMCP Plugins
7
7
 
8
- FrontMCP ships 6 official plugins that extend server behavior with cross-cutting concerns: semantic tool discovery, session memory, authorization workflows, result caching, feature gating, and visual monitoring. Install individually or via `@frontmcp/plugins` (meta-package re-exporting cache, codecall, and remember).
8
+ FrontMCP ships 7 official plugins that extend server behavior with cross-cutting concerns: semantic tool discovery, session memory, authorization workflows, result caching, feature gating, visual monitoring, and exposing in-browser tools to browser agents (WebMCP). Install individually or via `@frontmcp/plugins` (meta-package re-exporting cache, codecall, and remember).
9
9
 
10
10
  > **Note:** The Dashboard plugin (`@frontmcp/plugin-dashboard`) is currently in **beta** and may not work correctly in all environments. It is not recommended for production use at this time.
11
11
 
@@ -16,6 +16,7 @@ FrontMCP ships 6 official plugins that extend server behavior with cross-cutting
16
16
  - Installing and configuring any official FrontMCP plugin (CodeCall, Remember, Approval, Cache, Feature Flags)
17
17
  - Adding session memory, tool caching, or authorization workflows to an existing server
18
18
  - Integrating feature flag services (LaunchDarkly, Split.io, Unleash) to gate tools at runtime
19
+ - Exposing the tools of a FrontMCP server running in the browser to in-browser agents through WebMCP (`document.modelContext`)
19
20
 
20
21
  ### Recommended
21
22
 
@@ -98,7 +99,7 @@ class MyServer {}
98
99
 
99
100
  ### Modes
100
101
 
101
- - `codecall_only` -- Hides all tools from `list_tools` except CodeCall meta-tools. All other tools are discovered only via `codecall:search`. Best when the server has a large number of tools and you want the AI to search-then-execute. When `appIds` is set, only tools from those apps are hidden — tools from other apps remain visible.
102
+ - `codecall_only` -- Hides all tools from `list_tools` except CodeCall meta-tools. All other tools are discovered only via `codecall:search` and reached only through CodeCall: a client's direct `tools/call` of a hidden tool is refused. Best when the server has a large number of tools and you want the AI to search-then-execute. When `appIds` is set, only tools from those apps are hidden — tools from other apps remain visible.
102
103
  - `codecall_opt_in` -- Shows all tools in `list_tools` normally. Tools opt-in to CodeCall execution via metadata. Useful when only some tools benefit from orchestrated execution.
103
104
  - `metadata_driven` -- Per-tool `metadata.codecall` controls visibility and CodeCall availability independently. Most granular control.
104
105
 
@@ -115,7 +116,19 @@ CodeCallPlugin.init({
115
116
  });
116
117
  ```
117
118
 
118
- Without `appIds`, `codecall_only` mode hides ALL tools in the server. With `appIds`, only tools from the specified apps are hidden — tools from other apps remain directly callable.
119
+ Without `appIds`, `codecall_only` mode hides every tool the plugin judges: all tools of the server when it is installed on the server, or its own app's tools and those of apps without a CodeCall plugin of their own when it is installed on an app. With `appIds`, only tools from the specified apps are hidden — tools from other apps remain directly callable. An app with its own CodeCall plugin is judged by that plugin alone, in `list_tools` and on a direct `tools/call` alike; up to 1.8.7 another app's `codecall_only` plugin hid its tools from `list_tools` while its own plugin still let clients call them.
120
+
121
+ ### Hidden Tools Are Not Directly Callable
122
+
123
+ A tool CodeCall hides from `list_tools` (in any mode: every non-meta tool in `codecall_only` unless it sets
124
+ `visibleInListTools: true`, any tool with `visibleInListTools: false` otherwise) is reachable only through CodeCall. A
125
+ client's direct `tools/call` of it -- MCP, an MCP Apps widget, an in-page WebMCP agent, `DirectMcpServer.callTool()` --
126
+ is answered exactly like a call of an unknown tool (`Tool "<name>" not found`), before its input is validated. Still
127
+ allowed: CodeCall's own calls (`codecall:execute`, `codecall:invoke`), server-side composition (`this.callTool()` from
128
+ a tool, agent or job), and the server's own system tools (such as `sendElicitationResult`). Give a tool that clients
129
+ or widgets call directly `codecall: { visibleInListTools: true }`. Up to 1.8.7 the tool was only missing from the
130
+ listing, and a client that knew its name ran it directly, past `includeTools`, `enabledInCodeCall`, the blocked
131
+ namespaces and `directCalls`.
119
132
 
120
133
  ### VM Presets
121
134
 
@@ -143,7 +156,7 @@ Control how individual tools interact with CodeCall:
143
156
  @Tool({
144
157
  name: 'my_tool',
145
158
  codecall: {
146
- visibleInListTools: false, // Hide from list_tools (only discoverable via codecall:search)
159
+ visibleInListTools: false, // Hide from list_tools; reached only through CodeCall (direct tools/call refused)
147
160
  enabledInCodeCall: true, // Available for execution via codecall:execute
148
161
  tags: ['data', 'query'], // Extra indexing hints for semantic search
149
162
  },
@@ -174,7 +187,7 @@ CodeCallPlugin.init({
174
187
  - `tool.appId` names the owning app for the tools its adapters and plugins provide too, so `includeTools: (tool) => tool.appId !== 'admin'` withholds every tool of app `admin`.
175
188
  - `codecall:searchSkills` and `codecall:searchKnowledge` run the SDK's `skills:filter` flow, so a skill a plugin withholds there (a flag-disabled skill, for one) is absent from both.
176
189
  - `directCalls.allowedTools` and `directCalls.filter` only narrow the base policy; listing a withheld tool does not make it callable. Unlisted tools are refused.
177
- - Hiding a tool from search is not the control; the refusal at execution is. Do not rely on `visibleInListTools` or search ranking to protect a tool.
190
+ - Hiding a tool from search is not the control; the refusal at execution is. Do not rely on search ranking to protect a tool. Hiding it from `list_tools` (`visibleInListTools: false`) does also refuse a client's direct `tools/call` of it, but CodeCall's own surfaces then apply this policy.
178
191
  - `includeTools` and `directCalls.filter` receive the same object, with the tool's `annotations` and declared `metadata` (`tool.metadata?.annotations` is the same object as `tool.annotations`). It is a deep read-only copy, so a filter cannot change what the next decision reads.
179
192
  - Namespace bindings (`mail.send({...})` for a tool named `mail.send`) are AgentScript wrappers over `callTool()` inside the sandbox: they count toward `vm.maxSteps` and pass the rate limit and suspicious-sequence checks exactly like `callTool('mail.send', {...})`. A binding with no argument sends `{}`.
180
193
  - `codecall:execute` results never include a `stack`, in any environment. In `runtime_error`, `syntax_error` and `tool_error` messages, stack frames are dropped and absolute paths (POSIX, Windows, UNC, `file:` URLs, quoted paths) become `[path]`; other URLs are kept.
@@ -277,11 +290,20 @@ class MyTool extends ToolContext {
277
290
  // List keys matching pattern
278
291
  const keys = await this.remember.list({ pattern: 'user:*' });
279
292
 
293
+ // Update a value, keeping its metadata (and its expiry, unless a new `ttl` is given)
294
+ await this.remember.update('theme', 'light');
295
+
280
296
  return { content: [{ type: 'text', text: `Theme: ${theme}` }] };
281
297
  }
282
298
  }
283
299
  ```
284
300
 
301
+ `update(key, value, { ttl? })` returns `false` for a key that does not exist. Without a `ttl` the entry keeps its
302
+ current expiry, and `knows()` and `list()` stop reporting it once that passes, the same as `get()`; up to 1.8.7 an
303
+ entry updated without a `ttl` stayed in `knows()` and `list()` after it expired. `knows()` and `list()` read each entry
304
+ and check its own expiry, so they report exactly the keys `get()` returns a value for, even while the store still holds
305
+ an expired key for up to a second.
306
+
285
307
  ### Memory Scopes
286
308
 
287
309
  - `session` -- Default scope. With a verified session, valid only for that session and cleared
@@ -348,6 +370,7 @@ clear the legacy prefixes manually if you want the storage back.
348
370
 
349
371
  `tools.prefix` renames them (`prefix: 'memory_'` gives `memory_recall`, ...; each description names the prefixed siblings) and
350
372
  `tools.allowedScopes` rejects any other `scope` (including the default `session` when a call omits it) with a public `REMEMBER_SCOPE_NOT_ALLOWED` error that lists the allowed scopes. With `enabled` unset or `false` none is registered.
373
+ With `RememberPlugin.init({ inject, useFactory })`, `tools` is read from the options the factory returns, at startup.
351
374
 
352
375
  All four take an optional `scope` (default `session`) and describe it to the model the same way:
353
376
  `session` is this session, or without one (stateless HTTP, MCP 2026-07-28) the signed-in caller
@@ -434,12 +457,20 @@ authInfo.extra.approvalContext = { type: 'project', identifier: resolvedProjectI
434
457
  2. A recorded **denial** for the caller (session, user, time-limited or context scope): refused
435
458
  with state `denied`. A denial outranks pre-approved contexts and any approval.
436
459
  3. The session context is one of `preApprovedContexts`: the tool runs.
437
- 4. `alwaysPrompt: true`: refused with state `pending`.
438
- 5. An approval for the caller that the tool's policy accepts: the tool runs. The caller's session,
460
+ 4. An approval for the caller that the tool's policy accepts: the tool runs. The caller's session,
439
461
  user, time-limited and context approvals all count (a context approval only when the session
440
462
  carries that context); its scope must be in `allowedScopes`, and it must be younger than
441
- `maxTtlMs`, however it was stored.
442
- 6. Otherwise refused with state `pending` (or `expired`).
463
+ `maxTtlMs`, however it was stored. With `alwaysPrompt: true` the approval is used up by this
464
+ call (only one of two concurrent calls gets it), so the next call needs a new one.
465
+ 5. Otherwise refused with state `pending` (or `expired`).
466
+
467
+ Releases up to 1.8.7 refused every call of an `alwaysPrompt` tool, approved or not. The built-in store
468
+ uses up an approval with the storage's atomic `deleteIfEquals()` (memory, Redis, Upstash, Vercel KV), so
469
+ a denial or new approval recorded in the meantime is kept; a backend without it (Cloudflare KV, the
470
+ filesystem, SQLite) has the approval deleted directly. A custom `ApprovalStore` should implement
471
+ `consumeApproval()` (delete exactly that record, and only while it is still stored, in one step; resolve
472
+ `true` only for the call that deleted it); without it the gate revokes the caller's approvals of the tool
473
+ instead.
443
474
 
444
475
  A refused call throws `ApprovalRequiredError`; the client receives an error result whose text is
445
476
  exactly the tool's `approvalMessage` (or the default `Tool "<full name>" requires approval to
@@ -465,8 +496,10 @@ Installed on an app, `ApprovalPlugin` gates that app's tools (including those it
465
496
  plugins provide) against its own store, so two apps can each install it with separate stores. It
466
497
  also gates, against its store, the `approval` tools of apps with no approval plugin of their own,
467
498
  so such a tool never runs ungated because the plugin sits on another app (releases up to 1.8.2 ran
468
- them for anyone). Installed on the server, it gates every tool; a tool several plugins gate must
469
- pass each store's check, and a denial in any of them refuses the call.
499
+ them for anyone). Installed on the server, it gates every tool. A tool several plugins gate (one on
500
+ the server, one on its app) is decided once over all their stores: an approval in any of them lets
501
+ it run -- one approval is enough, e.g. a grant through `this.approval` in the tool's app -- and a
502
+ denial in any of them refuses the call. Releases up to 1.8.7 required an approval in each store.
470
503
  `this.approval` resolves the `ApprovalService` of the nearest `ApprovalPlugin` -- the one the
471
504
  tool's own app installed, otherwise the server's -- so with two apps each installing it, a grant
472
505
  or check in one app's tool uses that app's store. Releases up to 1.8.1 resolved the store of the
@@ -632,7 +665,7 @@ class GlobalCacheServer {}
632
665
  Enable caching on individual tools via the `cache` metadata field:
633
666
 
634
667
  ```typescript
635
- // Enable caching with default TTL
668
+ // Enable caching with default TTL (no sliding window)
636
669
  @Tool({ name: 'get_weather', cache: true })
637
670
  class GetWeatherTool extends ToolContext {
638
671
  /* ... */
@@ -669,6 +702,13 @@ CachePlugin.init({
669
702
 
670
703
  A tool is cached if it matches any pattern OR has `cache: true` (or a cache object) in its metadata. `cache: { ttl: 0 }` (or a negative TTL) turns caching off for the tool, even when it matches a pattern; up to 1.8.5 it cached the first result with no expiry.
671
704
 
705
+ Only `slideWindow: true` refreshes the TTL on a hit (`ttl`, or the plugin's `defaultTTL` when the tool sets none).
706
+ `cache: true` means the plugin defaults and never slides: an entry expires its TTL after it was written, however often it
707
+ is read. Up to 1.8.7, `cache: true` slid on every hit and `{ slideWindow: true }` without a `ttl` never slid.
708
+
709
+ A result the tool returns with `isError: true` (a `CallToolResult` reporting a failure) is never cached, so the next
710
+ call runs the tool again; up to 1.8.7 the failure was served from the cache until the TTL ran out.
711
+
672
712
  ### Cache Bypass
673
713
 
674
714
  Send the bypass header to skip caching for a specific request:
@@ -678,6 +718,13 @@ x-frontmcp-disable-cache: true
678
718
  ```
679
719
 
680
720
  The header name is configurable via `bypassHeader` in the plugin options. Default: `'x-frontmcp-disable-cache'`.
721
+ It must start with `x-frontmcp-` (case-insensitive): the plugin reads it from the request context, which keeps only a
722
+ request's `x-frontmcp-*` headers. Any other name (`'x-no-cache'`) throws `CachePluginConfigurationError` when the
723
+ plugin is created; up to 1.8.7 it was accepted and silently ignored.
724
+
725
+ ```typescript
726
+ CachePlugin.init({ type: 'memory', bypassHeader: 'x-frontmcp-no-cache' }); // client sends `x-frontmcp-no-cache: 1`
727
+ ```
681
728
 
682
729
  ### Cache Key
683
730
 
@@ -718,7 +765,8 @@ That matters because a listing is not an access control. Clients cache listings
718
765
  resource URIs and prompt names from earlier sessions, so anything gated only at list time
719
766
  stays reachable by name. The refusal is a public `FeatureFlagDisabledError` (`FEATURE_FLAG_DISABLED`, 403)
720
767
  that names the capability and the flag. `FeatureFlagPlugin.init()` with no (or an unknown) `adapter` throws a
721
- `FeatureFlagConfigurationError` at startup. If the adapter is unavailable the gate uses the ref's
768
+ `FeatureFlagConfigurationError` at startup, and so does `adapter: 'custom'` without an `adapterInstance` that has
769
+ `isEnabled()`, `getVariant()` and `evaluateFlags()` (it used to start and answer every request with a 500). If the adapter is unavailable the gate uses the ref's
722
770
  `defaultValue`, and a bare string ref (no default) fails closed.
723
771
 
724
772
  ### Installation
@@ -796,7 +844,7 @@ class CustomFlagServer {}
796
844
  - `splitio` -- Split.io integration. Requires `@splitsoftware/splitio` package.
797
845
  - `launchdarkly` -- LaunchDarkly integration. Requires `launchdarkly-node-server-sdk` package.
798
846
  - `unleash` -- Unleash integration. Requires `unleash-client` package.
799
- - `custom` -- Provide your own adapter instance implementing the `FeatureFlagAdapter` interface.
847
+ - `custom` -- Provide your own adapter instance (`adapterInstance`, required) implementing the `FeatureFlagAdapter` interface.
800
848
 
801
849
  ### Using `this.featureFlags` in Tools
802
850
 
@@ -810,15 +858,24 @@ class BetaFeatureTool extends ToolContext {
810
858
  return { content: [{ type: 'text', text: 'Feature not available' }] };
811
859
  }
812
860
 
861
+ // Fail open: `true` when the adapter throws or has no answer for the flag
862
+ const engine = (await this.featureFlags.isEnabled('search-v2', true)) ? 'v2' : 'v1';
863
+
813
864
  // Get variant value (for multivariate flags)
814
865
  const variant = await this.featureFlags.getVariant('experiment-flag');
815
866
  // variant may be 'control', 'treatment-a', 'treatment-b', etc.
816
867
 
817
- return { content: [{ type: 'text', text: `Running variant: ${variant}` }] };
868
+ return { content: [{ type: 'text', text: `Running variant: ${variant} on search ${engine}` }] };
818
869
  }
819
870
  }
820
871
  ```
821
872
 
873
+ `isEnabled(key, defaultValue?)` answers `defaultValue` (else the plugin's `defaultValue`, else `false`) when the
874
+ adapter throws or has no answer for the flag -- a key the `static` adapter was not given, or one a custom adapter's
875
+ `evaluateFlags()` omits -- the same rule the gates apply to a ref's `defaultValue`. A flag the adapter answers keeps
876
+ its answer, `false` included. Split.io, LaunchDarkly and Unleash answer every key with the service's own default. Up to
877
+ 1.8.7 the default applied only when the adapter threw, so `isEnabled('unknown-flag', true)` was `false`.
878
+
822
879
  ### Per-Tool Feature Flag Gating
823
880
 
824
881
  Tools gated by a feature flag are automatically hidden from `list_tools` and blocked from execution when the flag is off:
@@ -830,7 +887,7 @@ class BetaTool extends ToolContext {
830
887
  /* ... */
831
888
  }
832
889
 
833
- // Object with default value -- if flag evaluation fails, use the default
890
+ // Object with default value -- if flag evaluation fails or the flag is unknown, use the default
834
891
  @Tool({
835
892
  name: 'experimental_tool',
836
893
  featureFlag: { key: 'experimental-flag', defaultValue: false },
@@ -960,6 +1017,51 @@ All official plugins use the static `init()` pattern inherited from `DynamicPlug
960
1017
  class ProductionServer {}
961
1018
  ```
962
1019
 
1020
+ ## 7. WebMCP Plugin (`@frontmcp/plugin-webmcp`)
1021
+
1022
+ Exposes the tools of a FrontMCP server that runs **in the page** (`create()` from `@frontmcp/sdk` / `@frontmcp/react`) to in-browser agents through [WebMCP](https://webmachinelearning.github.io/webmcp/) — `document.modelContext`, which Gemini in Chrome, the Model Context Tool Inspector extension and DevTools' WebMCP pane use. WebMCP is in origin trial in Chrome/Edge (149–162); for local development enable `chrome://flags/#enable-webmcp-testing`.
1023
+
1024
+ ### Installation
1025
+
1026
+ ```typescript
1027
+ import { WebMcpPlugin } from '@frontmcp/plugin-webmcp';
1028
+ import { create } from '@frontmcp/sdk';
1029
+
1030
+ const server = await create({
1031
+ info: { name: 'shop', version: '1.0.0' },
1032
+ tools: [SearchProducts, AddToCart],
1033
+ plugins: [WebMcpPlugin.init({ prefix: 'shop.' })], // always .init(), with or without options
1034
+ });
1035
+ ```
1036
+
1037
+ The plugin is a transport adapter: it lists tools through the `tools:list-tools` flow and runs every agent call through `tools:call-tool`, both on the `'webmcp'` call surface — so hooks, authorities, quota and `availableWhen` apply. It keeps the registrations in sync with the tool registry (including `server.registerTool()` and React `useDynamicTool` tools) and unregisters everything on `server.dispose()`. Without `document.modelContext` (Node, unsupported browsers) it does nothing.
1038
+
1039
+ ### Options
1040
+
1041
+ | Option | Default | Description |
1042
+ | -------------- | ------------------------- | -------------------------------------------------------------------------- |
1043
+ | `prefix` | `''` | Prepended to every exposed name |
1044
+ | `include` | all | `(tool) => boolean`, runs after `availableWhen` and authorities |
1045
+ | `exposedTo` | — | Other origins (e.g. an iframe's parent) the tools are offered to |
1046
+ | `authContext` | anonymous `webmcp` caller | `DirectAuthContext` or a function returning it, resolved per list and call |
1047
+ | `modelContext` | `document.modelContext` | A polyfill or test double |
1048
+
1049
+ ### Choosing what agents see
1050
+
1051
+ ```typescript
1052
+ @Tool({ name: 'fill_checkout_form', availableWhen: { surface: ['webmcp'] } }) // browser agents only
1053
+ @Tool({ name: 'admin_reset', availableWhen: { surface: ['mcp'] } }) // never exposed through WebMCP
1054
+ ```
1055
+
1056
+ ### Translation rules
1057
+
1058
+ - Names: `prefix + name`, characters outside `[A-Za-z0-9_.-]` become `_` (`app:tool` → `app_tool`), max 128, collisions get `_2`, `_3`, …
1059
+ - Annotations: `readOnlyHint` → `readOnlyHint`; explicit `destructiveHint: true` → `consequentialHint`; explicit `openWorldHint: true` → `untrustedContentHint`.
1060
+ - Results: `{ content, structuredContent? }` without `_meta`; an `isError` result or server error rejects with its message.
1061
+ - Tools only — resources and prompts stay MCP-only; elicitation is unavailable to WebMCP callers.
1062
+
1063
+ For other browsers, load a polyfill that installs `document.modelContext` (e.g. `@mcp-b/global`) before `create()`, or pass one as `modelContext`.
1064
+
963
1065
  ## Common Patterns
964
1066
 
965
1067
  | Pattern | Correct | Incorrect | Why |
@@ -994,13 +1096,14 @@ class ProductionServer {}
994
1096
 
995
1097
  ## Troubleshooting
996
1098
 
997
- | Problem | Cause | Solution |
998
- | --------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
999
- | `this.remember` is undefined | RememberPlugin not registered or missing `.init()` | Add `RememberPlugin.init({ type: 'memory' })` to `plugins` array |
1000
- | Cache not working for a tool | Tool name does not match any `toolPatterns` glob and `cache` metadata is not set | Add `cache: true` to `@Tool` decorator or add matching pattern to `toolPatterns` |
1001
- | Feature flag always returns false | Using `'static'` adapter with flag not in the `flags` map | Add the flag key to `flags: { 'my-flag': true }` or check adapter connection |
1002
- | Dashboard returns 404 | Plugin is in beta and auto-disabled in production (`NODE_ENV=production`) | Dashboard is unstable — avoid in production. For dev: set `enabled: true` explicitly |
1003
- | Approval webhook times out | Callback URL not reachable from the external approval service | Verify `callbackPath` is publicly accessible and matches the webhook configuration |
1099
+ | Problem | Cause | Solution |
1100
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
1101
+ | `this.remember` is undefined | RememberPlugin not registered or missing `.init()` | Add `RememberPlugin.init({ type: 'memory' })` to `plugins` array |
1102
+ | Cache not working for a tool | Tool name does not match any `toolPatterns` glob and `cache` metadata is not set | Add `cache: true` to `@Tool` decorator or add matching pattern to `toolPatterns` |
1103
+ | Feature flag always returns false | Using `'static'` adapter with flag not in the `flags` map | Add the flag key to `flags: { 'my-flag': true }` or check adapter connection |
1104
+ | Dashboard returns 404 | Plugin is in beta and auto-disabled in production (`NODE_ENV=production`) | Dashboard is unstable — avoid in production. For dev: set `enabled: true` explicitly |
1105
+ | Approval webhook times out | Callback URL not reachable from the external approval service | Verify `callbackPath` is publicly accessible and matches the webhook configuration |
1106
+ | WebMCP tools never appear | No `document.modelContext` (flag off, no origin-trial token, non-HTTPS, no polyfill), or `WebMcpPlugin` without `.init()` | Enable `chrome://flags/#enable-webmcp-testing` or load `@mcp-b/global`; check `isWebMcpSupported()`; use `WebMcpPlugin.init()` |
1004
1107
 
1005
1108
  ## Examples
1006
1109
 
@@ -201,12 +201,60 @@ class IntegrationHub {}
201
201
  // Tools: github:createIssue, jira:createTicket, slack:postMessage, etc.
202
202
  ```
203
203
 
204
+ ## Options From Providers (`useFactory`)
205
+
206
+ Build the options at startup from injected providers. The factory returns `OpenApiAdapterOptions` (or a promise of them); the adapter is built from them under the `name` given to `init()`:
207
+
208
+ ```typescript
209
+ OpenapiAdapter.init({
210
+ name: 'billing',
211
+ inject: () => [BillingConfig] as const,
212
+ useFactory: (config: BillingConfig) => ({ name: 'billing', url: config.specUrl, baseUrl: config.baseUrl }),
213
+ });
214
+ ```
215
+
216
+ Until 1.8.7 this form failed startup with `Cannot read properties of undefined (reading 'name')` plus an unhandled rejection that could end the Node process.
217
+
204
218
  ## Filtering Operations
205
219
 
206
- Control which API operations become MCP tools:
220
+ Control which API operations become MCP tools. Every `generateOptions` field is passed to `mcp-from-openapi`'s generator, and an operation becomes a tool only when it passes every filter you set:
207
221
 
208
222
  ```typescript
209
- // Filter by path prefix
223
+ // Filter by OpenAPI tag
224
+ OpenapiAdapter.init({
225
+ name: 'billing-api',
226
+ url: 'https://api.example.com/openapi.json',
227
+ generateOptions: {
228
+ includeTags: ['invoices', 'customers'], // carries one of these tags
229
+ excludeTags: ['internal'], // carries none of these
230
+ },
231
+ });
232
+
233
+ // Filter by path glob (`*` = within a segment, `**` = across segments, `?` = one character)
234
+ OpenapiAdapter.init({
235
+ name: 'billing-api',
236
+ url: 'https://api.example.com/openapi.json',
237
+ generateOptions: {
238
+ includePaths: ['/invoices/**', '/customers/*'],
239
+ excludePaths: ['/invoices/*/admin/**'],
240
+ },
241
+ });
242
+
243
+ // Read-only tools only (annotations say readOnlyHint: true — GET/HEAD/OPTIONS/TRACE unless overridden)
244
+ OpenapiAdapter.init({
245
+ name: 'billing-api',
246
+ url: 'https://api.example.com/openapi.json',
247
+ generateOptions: { readOnlyOnly: true },
248
+ });
249
+
250
+ // Filter by HTTP method (lower-case names)
251
+ OpenapiAdapter.init({
252
+ name: 'billing-api',
253
+ url: 'https://api.example.com/openapi.json',
254
+ generateOptions: { excludeMethods: ['delete', 'put'] }, // or includeMethods: ['get']
255
+ });
256
+
257
+ // Custom filter (runs after every other filter)
210
258
  OpenapiAdapter.init({
211
259
  name: 'billing-api',
212
260
  url: 'https://api.example.com/openapi.json',
@@ -234,6 +282,8 @@ OpenapiAdapter.init({
234
282
  });
235
283
  ```
236
284
 
285
+ The adapter's own defaults are `preferredStatusCodes: [200, 201, 202, 204]`, `includeDeprecated: false` and `includeAllResponses: true`; every other generator option (`maxToolNameLength`, `descriptionStrategy`, `target`, …) takes the value you set.
286
+
237
287
  ## Input Transforms
238
288
 
239
289
  Hide inputs from AI/users and inject values server-side: