@frontmcp/skills 0.0.1

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 (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +135 -0
  3. package/catalog/TEMPLATE.md +49 -0
  4. package/catalog/adapters/create-adapter/SKILL.md +127 -0
  5. package/catalog/adapters/official-adapters/SKILL.md +136 -0
  6. package/catalog/auth/configure-auth/SKILL.md +250 -0
  7. package/catalog/auth/configure-auth/references/auth-modes.md +77 -0
  8. package/catalog/auth/configure-session/SKILL.md +201 -0
  9. package/catalog/config/configure-elicitation/SKILL.md +136 -0
  10. package/catalog/config/configure-http/SKILL.md +167 -0
  11. package/catalog/config/configure-throttle/SKILL.md +189 -0
  12. package/catalog/config/configure-throttle/references/guard-config.md +68 -0
  13. package/catalog/config/configure-transport/SKILL.md +151 -0
  14. package/catalog/config/configure-transport/references/protocol-presets.md +57 -0
  15. package/catalog/deployment/build-for-browser/SKILL.md +95 -0
  16. package/catalog/deployment/build-for-cli/SKILL.md +100 -0
  17. package/catalog/deployment/build-for-sdk/SKILL.md +218 -0
  18. package/catalog/deployment/deploy-to-cloudflare/SKILL.md +192 -0
  19. package/catalog/deployment/deploy-to-lambda/SKILL.md +304 -0
  20. package/catalog/deployment/deploy-to-node/SKILL.md +229 -0
  21. package/catalog/deployment/deploy-to-node/references/Dockerfile.example +45 -0
  22. package/catalog/deployment/deploy-to-vercel/SKILL.md +196 -0
  23. package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +60 -0
  24. package/catalog/development/create-agent/SKILL.md +563 -0
  25. package/catalog/development/create-agent/references/llm-config.md +46 -0
  26. package/catalog/development/create-job/SKILL.md +566 -0
  27. package/catalog/development/create-prompt/SKILL.md +400 -0
  28. package/catalog/development/create-provider/SKILL.md +233 -0
  29. package/catalog/development/create-resource/SKILL.md +437 -0
  30. package/catalog/development/create-skill/SKILL.md +526 -0
  31. package/catalog/development/create-skill-with-tools/SKILL.md +579 -0
  32. package/catalog/development/create-tool/SKILL.md +418 -0
  33. package/catalog/development/create-tool/references/output-schema-types.md +56 -0
  34. package/catalog/development/create-tool/references/tool-annotations.md +34 -0
  35. package/catalog/development/create-workflow/SKILL.md +709 -0
  36. package/catalog/development/decorators-guide/SKILL.md +598 -0
  37. package/catalog/plugins/create-plugin/SKILL.md +336 -0
  38. package/catalog/plugins/create-plugin-hooks/SKILL.md +282 -0
  39. package/catalog/plugins/official-plugins/SKILL.md +667 -0
  40. package/catalog/setup/frontmcp-skills-usage/SKILL.md +200 -0
  41. package/catalog/setup/multi-app-composition/SKILL.md +358 -0
  42. package/catalog/setup/nx-workflow/SKILL.md +357 -0
  43. package/catalog/setup/project-structure-nx/SKILL.md +186 -0
  44. package/catalog/setup/project-structure-standalone/SKILL.md +153 -0
  45. package/catalog/setup/setup-project/SKILL.md +493 -0
  46. package/catalog/setup/setup-redis/SKILL.md +385 -0
  47. package/catalog/setup/setup-sqlite/SKILL.md +359 -0
  48. package/catalog/skills-manifest.json +414 -0
  49. package/catalog/testing/setup-testing/SKILL.md +539 -0
  50. package/catalog/testing/setup-testing/references/test-auth.md +88 -0
  51. package/catalog/testing/setup-testing/references/test-browser-build.md +57 -0
  52. package/catalog/testing/setup-testing/references/test-cli-binary.md +48 -0
  53. package/catalog/testing/setup-testing/references/test-direct-client.md +62 -0
  54. package/catalog/testing/setup-testing/references/test-e2e-handler.md +51 -0
  55. package/catalog/testing/setup-testing/references/test-tool-unit.md +41 -0
  56. package/package.json +34 -0
  57. package/src/index.d.ts +3 -0
  58. package/src/index.js +16 -0
  59. package/src/index.js.map +1 -0
  60. package/src/loader.d.ts +46 -0
  61. package/src/loader.js +75 -0
  62. package/src/loader.js.map +1 -0
  63. package/src/manifest.d.ts +81 -0
  64. package/src/manifest.js +26 -0
  65. package/src/manifest.js.map +1 -0
@@ -0,0 +1,336 @@
1
+ ---
2
+ name: create-plugin
3
+ description: Build a FrontMCP plugin with lifecycle hooks and context extensions. Use when creating custom plugins, extending tool context, or adding cross-cutting concerns.
4
+ tags:
5
+ - plugins
6
+ - extensibility
7
+ - hooks
8
+ - context
9
+ bundle:
10
+ - full
11
+ visibility: both
12
+ priority: 5
13
+ parameters:
14
+ - name: plugin-name
15
+ description: Name for the new plugin (kebab-case)
16
+ type: string
17
+ required: true
18
+ - name: with-context-extension
19
+ description: Whether the plugin adds properties to ExecutionContextBase
20
+ type: boolean
21
+ required: false
22
+ default: false
23
+ - name: with-dynamic-options
24
+ description: Whether the plugin accepts runtime configuration options
25
+ type: boolean
26
+ required: false
27
+ default: false
28
+ examples:
29
+ - scenario: Create a simple logging plugin with no context extensions
30
+ parameters:
31
+ plugin-name: audit-log
32
+ with-context-extension: false
33
+ expected-outcome: A plugin that hooks into tool execution to log audit events
34
+ - scenario: Create an advanced plugin that extends ToolContext with a new property
35
+ parameters:
36
+ plugin-name: feature-flags
37
+ with-context-extension: true
38
+ with-dynamic-options: true
39
+ expected-outcome: A configurable plugin that adds this.featureFlags to all tool contexts
40
+ license: MIT
41
+ compatibility: Requires Node.js 18+ and @frontmcp/sdk
42
+ metadata:
43
+ category: plugins
44
+ difficulty: advanced
45
+ docs: https://docs.agentfront.dev/frontmcp/plugins/creating-plugins
46
+ ---
47
+
48
+ # Create a FrontMCP Plugin
49
+
50
+ 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.
51
+
52
+ ## Plugin Decorator Signature
53
+
54
+ ```typescript
55
+ function Plugin(metadata: PluginMetadata): ClassDecorator;
56
+ ```
57
+
58
+ The `PluginMetadata` interface:
59
+
60
+ ```typescript
61
+ interface PluginMetadata {
62
+ name: string;
63
+ id?: string;
64
+ description?: string;
65
+ providers?: ProviderType[];
66
+ exports?: ProviderType[];
67
+ plugins?: PluginType[];
68
+ adapters?: AdapterType[];
69
+ tools?: ToolType[];
70
+ resources?: ResourceType[];
71
+ prompts?: PromptType[];
72
+ skills?: SkillType[];
73
+ scope?: 'app' | 'server'; // default: 'app'
74
+ contextExtensions?: ContextExtension[];
75
+ }
76
+
77
+ interface ContextExtension {
78
+ property: string;
79
+ token: Token<unknown>;
80
+ errorMessage?: string;
81
+ }
82
+ ```
83
+
84
+ ## DynamicPlugin Base Class
85
+
86
+ For plugins that accept runtime configuration, extend `DynamicPlugin<TOptions, TInput>`:
87
+
88
+ ```typescript
89
+ abstract class DynamicPlugin<TOptions extends object, TInput extends object = TOptions> {
90
+ static dynamicProviders?(options: any): readonly ProviderType[];
91
+ static init<TThis>(options: InitOptions<TInput>): PluginReturn<TOptions>;
92
+ get<T>(token: Reference<T>): T;
93
+ }
94
+ ```
95
+
96
+ - `TOptions` -- the resolved options type (after parsing/defaults)
97
+ - `TInput` -- the input type users provide to `init()` (may have optional fields)
98
+ - `init()` creates a provider entry for use in `plugins: [...]` arrays
99
+ - `dynamicProviders()` returns providers computed from the input options
100
+
101
+ ## Step 1: Create a Simple Plugin
102
+
103
+ The minimal plugin only needs a name:
104
+
105
+ ```typescript
106
+ import { Plugin } from '@frontmcp/sdk';
107
+
108
+ @Plugin({
109
+ name: 'audit-log',
110
+ description: 'Logs tool executions for audit compliance',
111
+ })
112
+ export default class AuditLogPlugin {}
113
+ ```
114
+
115
+ Register it in your server:
116
+
117
+ ```typescript
118
+ import { FrontMcp, App } from '@frontmcp/sdk';
119
+ import AuditLogPlugin from './plugins/audit-log.plugin';
120
+
121
+ @App()
122
+ class MyApp {}
123
+
124
+ @FrontMcp({
125
+ info: { name: 'my-server', version: '1.0.0' },
126
+ apps: [MyApp],
127
+ plugins: [AuditLogPlugin],
128
+ tools: [
129
+ /* your tools */
130
+ ],
131
+ })
132
+ class MyServer {}
133
+ ```
134
+
135
+ ## Step 2: Add Providers
136
+
137
+ Plugins contribute injectable services via `providers`:
138
+
139
+ ```typescript
140
+ import { Plugin, Provider } from '@frontmcp/sdk';
141
+ import type { Token } from '@frontmcp/sdk';
142
+
143
+ export const AuditLoggerToken: Token<AuditLogger> = Symbol('AuditLogger');
144
+
145
+ @Provider()
146
+ class AuditLogger {
147
+ async logToolCall(toolName: string, userId: string, input: unknown): Promise<void> {
148
+ console.log(`[AUDIT] ${userId} called ${toolName}`, input);
149
+ }
150
+ }
151
+
152
+ @Plugin({
153
+ name: 'audit-log',
154
+ description: 'Logs tool executions for audit compliance',
155
+ providers: [{ provide: AuditLoggerToken, useClass: AuditLogger }],
156
+ exports: [AuditLogger],
157
+ })
158
+ export default class AuditLogPlugin {}
159
+ ```
160
+
161
+ ## Step 3: Add Context Extensions
162
+
163
+ Context extensions add properties to `ExecutionContextBase` so tools access plugin services via `this.propertyName`. Two parts are required:
164
+
165
+ ### Part A: TypeScript Type Declaration (Module Augmentation)
166
+
167
+ ```typescript
168
+ // audit-log.context-extension.ts
169
+ import type { AuditLogger } from './audit-logger';
170
+
171
+ declare module '@frontmcp/sdk' {
172
+ interface ExecutionContextBase {
173
+ /** Audit logger provided by AuditLogPlugin */
174
+ readonly auditLog: AuditLogger;
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Part B: Register via Plugin Metadata
180
+
181
+ The SDK handles runtime installation when you declare `contextExtensions` in plugin metadata. Do not modify `ExecutionContextBase.prototype` directly.
182
+
183
+ ```typescript
184
+ import { Plugin } from '@frontmcp/sdk';
185
+ import type { Token } from '@frontmcp/sdk';
186
+ import './audit-log.context-extension'; // Import for type augmentation side effect
187
+
188
+ export const AuditLoggerToken: Token<AuditLogger> = Symbol('AuditLogger');
189
+
190
+ @Plugin({
191
+ name: 'audit-log',
192
+ description: 'Logs tool executions for audit compliance',
193
+ providers: [{ provide: AuditLoggerToken, useClass: AuditLogger }],
194
+ contextExtensions: [
195
+ {
196
+ property: 'auditLog',
197
+ token: AuditLoggerToken,
198
+ errorMessage: 'AuditLogPlugin is not installed. Add it to your @FrontMcp plugins array.',
199
+ },
200
+ ],
201
+ })
202
+ export default class AuditLogPlugin {}
203
+ ```
204
+
205
+ Now tools can use `this.auditLog`:
206
+
207
+ ```typescript
208
+ import { Tool, ToolContext } from '@frontmcp/sdk';
209
+
210
+ @Tool({ name: 'delete_record' })
211
+ class DeleteRecordTool extends ToolContext {
212
+ async execute(input: { recordId: string }) {
213
+ await this.auditLog.logToolCall('delete_record', this.scope.userId, input);
214
+ return { deleted: true };
215
+ }
216
+ }
217
+ ```
218
+
219
+ ## Step 4: Create a Configurable Plugin with DynamicPlugin
220
+
221
+ For plugins that accept runtime options, extend `DynamicPlugin`:
222
+
223
+ ```typescript
224
+ import { Plugin, DynamicPlugin, ProviderType } from '@frontmcp/sdk';
225
+ import type { Token } from '@frontmcp/sdk';
226
+
227
+ export interface MyPluginOptions {
228
+ endpoint: string;
229
+ refreshIntervalMs: number;
230
+ }
231
+
232
+ export type MyPluginOptionsInput = Omit<MyPluginOptions, 'refreshIntervalMs'> & {
233
+ refreshIntervalMs?: number;
234
+ };
235
+
236
+ export const MyServiceToken: Token<MyService> = Symbol('MyService');
237
+
238
+ @Plugin({
239
+ name: 'my-plugin',
240
+ description: 'A configurable plugin',
241
+ contextExtensions: [
242
+ {
243
+ property: 'myService',
244
+ token: MyServiceToken,
245
+ errorMessage: 'MyPlugin is not installed.',
246
+ },
247
+ ],
248
+ })
249
+ export default class MyPlugin extends DynamicPlugin<MyPluginOptions, MyPluginOptionsInput> {
250
+ options: MyPluginOptions;
251
+
252
+ constructor(options: MyPluginOptionsInput = { endpoint: '' }) {
253
+ super();
254
+ this.options = { refreshIntervalMs: 30_000, ...options };
255
+ }
256
+
257
+ static override dynamicProviders(options: MyPluginOptionsInput): ProviderType[] {
258
+ return [
259
+ {
260
+ provide: MyServiceToken,
261
+ useFactory: () =>
262
+ new MyService({
263
+ refreshIntervalMs: 30_000,
264
+ ...options,
265
+ }),
266
+ },
267
+ ];
268
+ }
269
+ }
270
+ ```
271
+
272
+ Register with `init()`:
273
+
274
+ ```typescript
275
+ @FrontMcp({
276
+ info: { name: 'my-server', version: '1.0.0' },
277
+ apps: [MyApp],
278
+ plugins: [
279
+ MyPlugin.init({
280
+ endpoint: 'https://api.example.com',
281
+ refreshIntervalMs: 60_000,
282
+ }),
283
+ ],
284
+ })
285
+ class MyServer {}
286
+ ```
287
+
288
+ ## Step 5: Extend Tool Metadata
289
+
290
+ Plugins can add fields to the `@Tool` decorator via global augmentation:
291
+
292
+ ```typescript
293
+ declare global {
294
+ interface ExtendFrontMcpToolMetadata {
295
+ audit?: {
296
+ enabled: boolean;
297
+ level: 'info' | 'warn' | 'critical';
298
+ };
299
+ }
300
+ }
301
+ ```
302
+
303
+ Tools then use it:
304
+
305
+ ```typescript
306
+ @Tool({
307
+ name: 'delete_user',
308
+ audit: { enabled: true, level: 'critical' },
309
+ })
310
+ class DeleteUserTool extends ToolContext {
311
+ /* ... */
312
+ }
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Official Plugins
318
+
319
+ 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).
320
+
321
+ ## Common Mistakes
322
+
323
+ - **Module-level side effects for context extension** -- do not call `installExtension()` at the top level of a module. This causes circular dependencies. The SDK handles installation via `contextExtensions` metadata.
324
+ - **Forgetting the type augmentation** -- without `declare module '@frontmcp/sdk'`, TypeScript will not recognize `this.auditLog` in tools.
325
+ - **Using `any` types in providers** -- use `unknown` for generic defaults.
326
+ - **Scope confusion** -- `scope: 'server'` makes hooks fire for all apps in a gateway. Default to `scope: 'app'`.
327
+ - **Direct prototype modification** -- use the `contextExtensions` metadata array instead of directly modifying `ExecutionContextBase.prototype`.
328
+
329
+ ## Reference
330
+
331
+ - Plugin system docs: [docs.agentfront.dev/frontmcp/plugins/creating-plugins](https://docs.agentfront.dev/frontmcp/plugins/creating-plugins)
332
+ - `@Plugin` decorator: import from `@frontmcp/sdk` — [source](https://github.com/agentfront/frontmcp/tree/main/libs/sdk/src/common/decorators/plugin.decorator.ts)
333
+ - `DynamicPlugin` base class: import from `@frontmcp/sdk` — [source](https://github.com/agentfront/frontmcp/tree/main/libs/sdk/src/common/dynamic/dynamic.plugin.ts)
334
+ - `PluginMetadata` interface (contextExtensions): import from `@frontmcp/sdk` — [source](https://github.com/agentfront/frontmcp/tree/main/libs/sdk/src/common/metadata/plugin.metadata.ts)
335
+ - Official plugins: `@frontmcp/plugin-cache`, `@frontmcp/plugin-codecall`, `@frontmcp/plugin-remember`, `@frontmcp/plugin-approval`, `@frontmcp/plugin-feature-flags`, `@frontmcp/plugin-dashboard`
336
+ - Meta-package: `@frontmcp/plugins` (re-exports cache, codecall, dashboard, remember)
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: create-plugin-hooks
3
+ description: Create plugins with flow lifecycle hooks using @Will, @Did, @Stage, and @Around decorators. Use when intercepting tool calls, adding logging, modifying request/response, or implementing cross-cutting middleware.
4
+ tags: [plugin, hooks, will, did, stage, around, flow, middleware]
5
+ priority: 7
6
+ visibility: both
7
+ license: Apache-2.0
8
+ metadata:
9
+ docs: https://docs.agentfront.dev/frontmcp/plugins/creating-plugins
10
+ ---
11
+
12
+ # Creating Plugins with Flow Lifecycle Hooks
13
+
14
+ Plugins intercept and extend FrontMCP flows using lifecycle hook decorators. Every flow (tool calls, resource reads, prompt gets, etc.) is composed of **stages**, and hooks let you run logic before, after, around, or instead of any stage.
15
+
16
+ ## Hook Decorator Types
17
+
18
+ FrontMCP provides four hook decorators obtained via `FlowHooksOf(flowName)`:
19
+
20
+ | Decorator | Timing | Use Case |
21
+ | --------- | ---------------------------------- | ------------------------------------------- |
22
+ | `@Will` | **Before** a stage runs | Validate input, inject headers, check auth |
23
+ | `@Did` | **After** a stage completes | Log results, emit metrics, transform output |
24
+ | `@Stage` | **Replaces** a stage entirely | Custom execution, mock responses |
25
+ | `@Around` | **Wraps** a stage (before + after) | Caching, timing, retry logic |
26
+
27
+ ### FlowHooksOf API
28
+
29
+ ```typescript
30
+ import { FlowHooksOf } from '@frontmcp/sdk';
31
+
32
+ const { Stage, Will, Did, Around } = FlowHooksOf('tools:call-tool');
33
+ ```
34
+
35
+ `FlowHooksOf(flowName)` returns an object with all four decorator factories bound to the specified flow.
36
+
37
+ ## Available Flow Names
38
+
39
+ | Flow Name | Description |
40
+ | -------------------------- | ------------------------ |
41
+ | `tools:call-tool` | Tool execution |
42
+ | `tools:list-tools` | Tool listing / discovery |
43
+ | `resources:read-resource` | Resource reading |
44
+ | `resources:list-resources` | Resource listing |
45
+ | `prompts:get-prompt` | Prompt retrieval |
46
+ | `prompts:list-prompts` | Prompt listing |
47
+ | `http:request` | HTTP request handling |
48
+ | `agents:call-agent` | Agent invocation |
49
+
50
+ ## Pre-Built Hook Type Exports
51
+
52
+ For convenience, FrontMCP exports typed aliases so you do not need to call `FlowHooksOf` directly:
53
+
54
+ ```typescript
55
+ import {
56
+ ToolHook, // FlowHooksOf('tools:call-tool')
57
+ ListToolsHook, // FlowHooksOf('tools:list-tools')
58
+ ResourceHook, // FlowHooksOf('resources:read-resource')
59
+ ListResourcesHook, // FlowHooksOf('resources:list-resources')
60
+ AgentCallHook, // FlowHooksOf('agents:call-agent')
61
+ HttpHook, // FlowHooksOf('http:request')
62
+ } from '@frontmcp/sdk';
63
+ ```
64
+
65
+ Usage:
66
+
67
+ ```typescript
68
+ const { Will, Did, Around, Stage } = ToolHook;
69
+ ```
70
+
71
+ ## call-tool Flow Stages
72
+
73
+ The `tools:call-tool` flow proceeds through these stages in order:
74
+
75
+ 1. **parseInput** - Parse raw input from the MCP request
76
+ 2. **findTool** - Look up the tool in the registry
77
+ 3. **checkToolAuthorization** - Verify the caller is authorized
78
+ 4. **createToolCallContext** - Build the ToolContext instance
79
+ 5. **validateInput** - Validate input against the Zod schema
80
+ 6. **execute** - Run the tool's `execute()` method
81
+ 7. **validateOutput** - Validate output against the output schema
82
+ 8. **finalize** - Format and return the MCP response
83
+
84
+ ## HookOptions
85
+
86
+ Both `@Will` and `@Did` (and `@Around`) accept an optional options object:
87
+
88
+ ```typescript
89
+ @Will('execute', {
90
+ priority: 10, // Higher runs first (default: 0)
91
+ filter: (ctx) => ctx.toolName !== 'health_check', // Predicate to skip
92
+ })
93
+ ```
94
+
95
+ - **priority** (`number`) - Execution order when multiple hooks target the same stage. Higher values run first. Default: `0`.
96
+ - **filter** (`(ctx) => boolean`) - A predicate that receives the flow context. Return `false` to skip this hook for the current invocation.
97
+
98
+ ## Examples
99
+
100
+ ### Logging Plugin
101
+
102
+ ```typescript
103
+ import { Plugin } from '@frontmcp/sdk';
104
+ import { ToolHook } from '@frontmcp/sdk';
105
+
106
+ const { Will, Did } = ToolHook;
107
+
108
+ @Plugin({ name: 'logging-plugin' })
109
+ export class LoggingPlugin {
110
+ @Will('execute', { priority: 100 })
111
+ logBefore(ctx) {
112
+ console.log(`[LOG] Tool "${ctx.toolName}" called with`, ctx.input);
113
+ }
114
+
115
+ @Did('execute')
116
+ logAfter(ctx) {
117
+ console.log(`[LOG] Tool "${ctx.toolName}" completed in ${ctx.elapsed}ms`);
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### Authorization Check Plugin
123
+
124
+ ```typescript
125
+ import { Plugin } from '@frontmcp/sdk';
126
+ import { ToolHook } from '@frontmcp/sdk';
127
+
128
+ const { Will } = ToolHook;
129
+
130
+ @Plugin({ name: 'auth-check-plugin' })
131
+ export class AuthCheckPlugin {
132
+ @Will('checkToolAuthorization', { priority: 50 })
133
+ enforceRole(ctx) {
134
+ const user = ctx.tryGet(UserToken);
135
+ if (!user || !user.roles.includes('admin')) {
136
+ ctx.fail('Unauthorized: admin role required');
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ ### Caching Plugin with @Around
143
+
144
+ ```typescript
145
+ import { Plugin } from '@frontmcp/sdk';
146
+ import { ToolHook } from '@frontmcp/sdk';
147
+
148
+ const { Around } = ToolHook;
149
+
150
+ @Plugin({ name: 'cache-plugin' })
151
+ export class CachePlugin {
152
+ private cache = new Map<string, { data: unknown; expiry: number }>();
153
+
154
+ @Around('execute', { priority: 90 })
155
+ async cacheResults(ctx, next) {
156
+ const key = `${ctx.toolName}:${JSON.stringify(ctx.input)}`;
157
+ const cached = this.cache.get(key);
158
+
159
+ if (cached && cached.expiry > Date.now()) {
160
+ return cached.data;
161
+ }
162
+
163
+ const result = await next();
164
+
165
+ this.cache.set(key, {
166
+ data: result,
167
+ expiry: Date.now() + 60_000,
168
+ });
169
+
170
+ return result;
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### Stage Replacement
176
+
177
+ ```typescript
178
+ import { Plugin } from '@frontmcp/sdk';
179
+ import { ToolHook } from '@frontmcp/sdk';
180
+
181
+ const { Stage } = ToolHook;
182
+
183
+ @Plugin({ name: 'mock-plugin' })
184
+ export class MockPlugin {
185
+ @Stage('execute', {
186
+ filter: (ctx) => ctx.toolName === 'fetch_weather',
187
+ })
188
+ mockWeather(ctx) {
189
+ return { content: [{ type: 'text', text: '72F and sunny' }] };
190
+ }
191
+ }
192
+ ```
193
+
194
+ ## Registering Plugins
195
+
196
+ Register plugins in your `@App` decorator:
197
+
198
+ ```typescript
199
+ import { App } from '@frontmcp/sdk';
200
+ import { LoggingPlugin } from './plugins/logging.plugin';
201
+ import { CachePlugin } from './plugins/cache.plugin';
202
+
203
+ @App({
204
+ name: 'my-app',
205
+ plugins: [LoggingPlugin, CachePlugin],
206
+ })
207
+ export class MyApp {}
208
+ ```
209
+
210
+ Plugins are initialized in array order. Hook priority determines execution order within the same stage.
211
+
212
+ ## Using Hooks Inside a @Tool Class
213
+
214
+ You can add hook methods directly on a `@Tool` class to intercept its own execution flow. The hooks apply only when **this tool** is called:
215
+
216
+ ```typescript
217
+ import { Tool, ToolContext } from '@frontmcp/sdk';
218
+ import { z } from 'zod';
219
+
220
+ const { Will, Did } = ToolHook;
221
+
222
+ @Tool({
223
+ name: 'process_order',
224
+ description: 'Process a customer order',
225
+ inputSchema: {
226
+ orderId: z.string(),
227
+ amount: z.number(),
228
+ },
229
+ outputSchema: { status: z.string(), receipt: z.string() },
230
+ })
231
+ class ProcessOrderTool extends ToolContext {
232
+ // Runs BEFORE execute — validate, enrich input, check preconditions
233
+ @Will('execute', { priority: 10 })
234
+ async beforeExecute() {
235
+ const db = this.get(DB_TOKEN);
236
+ const order = await db.findOrder(this.input.orderId);
237
+ if (!order) {
238
+ this.fail(new Error(`Order ${this.input.orderId} not found`));
239
+ }
240
+ if (order.status === 'completed') {
241
+ this.fail(new Error('Order already processed'));
242
+ }
243
+ this.mark('validated');
244
+ }
245
+
246
+ // Main execution
247
+ async execute(input: { orderId: string; amount: number }) {
248
+ const payment = this.get(PAYMENT_TOKEN);
249
+ const receipt = await payment.charge(input.orderId, input.amount);
250
+ return { status: 'completed', receipt: receipt.id };
251
+ }
252
+
253
+ // Runs AFTER execute — log, notify, cleanup
254
+ @Did('execute')
255
+ async afterExecute() {
256
+ const analytics = this.tryGet(ANALYTICS_TOKEN);
257
+ if (analytics) {
258
+ await analytics.track('order_processed', {
259
+ orderId: this.input.orderId,
260
+ amount: this.input.amount,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### How Tool-Level Hooks Work
268
+
269
+ - `@Will('execute')` on a tool class runs **before** the `execute()` method of that specific tool
270
+ - `@Did('execute')` runs **after** `execute()` completes successfully
271
+ - `@Will('validateInput')` runs before input validation — useful for input enrichment
272
+ - `@Did('validateOutput')` runs after output validation — useful for output transformation
273
+ - The hook has full access to `this` (the tool context) including `this.input`, `this.get()`, `this.fail()`
274
+
275
+ ### Available Stages for Tool Hooks
276
+
277
+ ```
278
+ parseInput → findTool → checkToolAuthorization → createToolCallContext
279
+ → validateInput → execute → validateOutput → finalize
280
+ ```
281
+
282
+ Any stage can have `@Will`, `@Did`, `@Stage`, or `@Around` hooks.