@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.
- package/LICENSE +201 -0
- package/README.md +135 -0
- package/catalog/TEMPLATE.md +49 -0
- package/catalog/adapters/create-adapter/SKILL.md +127 -0
- package/catalog/adapters/official-adapters/SKILL.md +136 -0
- package/catalog/auth/configure-auth/SKILL.md +250 -0
- package/catalog/auth/configure-auth/references/auth-modes.md +77 -0
- package/catalog/auth/configure-session/SKILL.md +201 -0
- package/catalog/config/configure-elicitation/SKILL.md +136 -0
- package/catalog/config/configure-http/SKILL.md +167 -0
- package/catalog/config/configure-throttle/SKILL.md +189 -0
- package/catalog/config/configure-throttle/references/guard-config.md +68 -0
- package/catalog/config/configure-transport/SKILL.md +151 -0
- package/catalog/config/configure-transport/references/protocol-presets.md +57 -0
- package/catalog/deployment/build-for-browser/SKILL.md +95 -0
- package/catalog/deployment/build-for-cli/SKILL.md +100 -0
- package/catalog/deployment/build-for-sdk/SKILL.md +218 -0
- package/catalog/deployment/deploy-to-cloudflare/SKILL.md +192 -0
- package/catalog/deployment/deploy-to-lambda/SKILL.md +304 -0
- package/catalog/deployment/deploy-to-node/SKILL.md +229 -0
- package/catalog/deployment/deploy-to-node/references/Dockerfile.example +45 -0
- package/catalog/deployment/deploy-to-vercel/SKILL.md +196 -0
- package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +60 -0
- package/catalog/development/create-agent/SKILL.md +563 -0
- package/catalog/development/create-agent/references/llm-config.md +46 -0
- package/catalog/development/create-job/SKILL.md +566 -0
- package/catalog/development/create-prompt/SKILL.md +400 -0
- package/catalog/development/create-provider/SKILL.md +233 -0
- package/catalog/development/create-resource/SKILL.md +437 -0
- package/catalog/development/create-skill/SKILL.md +526 -0
- package/catalog/development/create-skill-with-tools/SKILL.md +579 -0
- package/catalog/development/create-tool/SKILL.md +418 -0
- package/catalog/development/create-tool/references/output-schema-types.md +56 -0
- package/catalog/development/create-tool/references/tool-annotations.md +34 -0
- package/catalog/development/create-workflow/SKILL.md +709 -0
- package/catalog/development/decorators-guide/SKILL.md +598 -0
- package/catalog/plugins/create-plugin/SKILL.md +336 -0
- package/catalog/plugins/create-plugin-hooks/SKILL.md +282 -0
- package/catalog/plugins/official-plugins/SKILL.md +667 -0
- package/catalog/setup/frontmcp-skills-usage/SKILL.md +200 -0
- package/catalog/setup/multi-app-composition/SKILL.md +358 -0
- package/catalog/setup/nx-workflow/SKILL.md +357 -0
- package/catalog/setup/project-structure-nx/SKILL.md +186 -0
- package/catalog/setup/project-structure-standalone/SKILL.md +153 -0
- package/catalog/setup/setup-project/SKILL.md +493 -0
- package/catalog/setup/setup-redis/SKILL.md +385 -0
- package/catalog/setup/setup-sqlite/SKILL.md +359 -0
- package/catalog/skills-manifest.json +414 -0
- package/catalog/testing/setup-testing/SKILL.md +539 -0
- package/catalog/testing/setup-testing/references/test-auth.md +88 -0
- package/catalog/testing/setup-testing/references/test-browser-build.md +57 -0
- package/catalog/testing/setup-testing/references/test-cli-binary.md +48 -0
- package/catalog/testing/setup-testing/references/test-direct-client.md +62 -0
- package/catalog/testing/setup-testing/references/test-e2e-handler.md +51 -0
- package/catalog/testing/setup-testing/references/test-tool-unit.md +41 -0
- package/package.json +34 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +16 -0
- package/src/index.js.map +1 -0
- package/src/loader.d.ts +46 -0
- package/src/loader.js +75 -0
- package/src/loader.js.map +1 -0
- package/src/manifest.d.ts +81 -0
- package/src/manifest.js +26 -0
- 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.
|