Documentation Index
Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
Use this file to discover all available pages before exploring further.
Creating Plugins
Build custom plugins to extend FrontMCP with cross-cutting capabilities like caching, authorization, logging, and more.
Nx users: Scaffold with nx g @frontmcp/nx:plugin my-plugin --project my-app. See Plugin Generator.
Plugin Architecture
FrontMCP plugins use the @Plugin decorator and typically extend DynamicPlugin. They can:
- Register providers — Services available to the plugin and exported to the host app
- Contribute tools, resources, and skills — Add capabilities when the plugin is attached
- Intercept flows via hooks — Run code before/after specific stages using
@ToolHook and @ListToolsHook
- Accept configuration — Via
init() for runtime customization
- Extend metadata — Add custom fields to tool metadata
Basic Plugin
import { DynamicPlugin, Plugin, ToolHook, FlowCtxOf } from '@frontmcp/sdk';
interface AuditPluginOptions {
logLevel?: 'info' | 'debug';
}
@Plugin({
name: 'audit',
description: 'Logs all tool executions',
})
export default class AuditPlugin extends DynamicPlugin<AuditPluginOptions> {
options: AuditPluginOptions;
constructor(options: AuditPluginOptions = {}) {
super();
this.options = { logLevel: 'info', ...options };
}
@ToolHook.Did('execute', { priority: 1000 })
async logExecution(flowCtx: FlowCtxOf<'tools:call-tool'>) {
const { tool, toolContext } = flowCtx.state;
if (!tool || !toolContext) return;
console.log(`[audit] Tool executed: ${tool.fullName}`);
}
}
Registering a Plugin
Attach plugins at the app level:
import { App } from '@frontmcp/sdk';
import AuditPlugin from './plugins/audit.plugin';
@App({
id: 'my-app',
name: 'My App',
plugins: [
// Option 1: Pass class directly (uses default options)
AuditPlugin,
// Option 2: Use init() with custom options
AuditPlugin.init({ logLevel: 'debug' }),
],
})
export default class MyApp {}
Adding Hooks
Plugins intercept flow stages using @ToolHook and @ListToolsHook decorators:
import { DynamicPlugin, Plugin, ToolHook, FlowCtxOf, FlowHooksOf } from '@frontmcp/sdk';
const ListToolsHook = FlowHooksOf('tools:list-tools');
@Plugin({
name: 'authorization',
description: 'Role-based access control for tools',
})
export default class AuthorizationPlugin extends DynamicPlugin {
// Runs BEFORE tool execution
@ToolHook.Will('execute', { priority: 900 })
async validateAccess(flowCtx: FlowCtxOf<'tools:call-tool'>) {
const { toolContext } = flowCtx.state;
if (!toolContext) return;
// Check authorization...
}
// Runs AFTER tool listing — filter unauthorized tools
@ListToolsHook.Did('findTools')
async filterTools(flowCtx: FlowCtxOf<'tools:list-tools'>) {
const { tools } = flowCtx.state.required;
// Filter tools based on user roles
const filteredTools = tools.filter((t) => this.isToolAllowed(t));
flowCtx.state.set('tools', filteredTools);
}
/** TODO: implement real role check */
private isToolAllowed(_tool: unknown): boolean {
return true;
}
}
Hook Timing
.Will(stage) — runs before the stage
.Did(stage) — runs after the stage
Priority
Lower numbers run first:
| Priority | Use Case |
|---|
| 100–500 | Critical security checks |
| 500–900 | Authorization, validation |
| 900–1000 | Standard plugin behavior |
| 1000+ | Logging, metrics |
Dynamic Providers
For plugins that create providers based on configuration:
import { DynamicPlugin, Plugin, ProviderType, ToolHook, FlowCtxOf } from '@frontmcp/sdk';
interface CachePluginOptions {
type: 'memory' | 'redis';
host?: string;
port?: number;
}
const CacheStoreToken = Symbol('CacheStore');
@Plugin({
name: 'cache',
description: 'Cache plugin for tool results',
providers: [
{
name: 'cache:memory',
provide: CacheStoreToken,
useValue: new MemoryCacheProvider(),
},
],
})
export default class CachePlugin extends DynamicPlugin<CachePluginOptions> {
static override dynamicProviders(options: CachePluginOptions): ProviderType[] {
if (options.type === 'redis') {
return [{
name: 'cache:redis',
provide: CacheStoreToken,
useValue: new RedisCacheProvider(options),
}];
}
return [];
}
@ToolHook.Will('execute', { priority: 950 })
async checkCache(flowCtx: FlowCtxOf<'tools:call-tool'>) {
const cacheStore = this.get(CacheStoreToken);
// Check cache and respond early if hit...
}
@ToolHook.Did('execute', { priority: 950 })
async storeCache(flowCtx: FlowCtxOf<'tools:call-tool'>) {
const cacheStore = this.get(CacheStoreToken);
// Store result in cache...
}
}
Plugins can add custom fields to tool metadata via global type augmentation:
declare global {
interface ExtendFrontMcpToolMetadata {
cache?: { ttl?: number } | true;
}
}
Tools can then use this metadata:
@Tool({
name: 'get-user',
inputSchema: { id: z.string() },
cache: { ttl: 3600 },
})
export default class GetUserTool extends ToolContext { /* ... */ }
Plugins can contribute tools and skills via the @Plugin decorator:
import { DynamicPlugin, Plugin } from '@frontmcp/sdk';
@Plugin({
name: 'devops',
description: 'DevOps tools and workflows',
tools: [DeployTool, RollbackTool],
skills: [DeployWorkflowSkill],
})
export default class DevOpsPlugin extends DynamicPlugin {}
Publishing Plugins
# Recommended package structure
my-plugin/
├── src/
│ ├── index.ts # Exports
│ ├── my-plugin.plugin.ts # Plugin class
│ └── my-plugin.types.ts # Types
├── package.json
└── README.md
{
"name": "@yourscope/frontmcp-plugin-myfeature",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"keywords": ["frontmcp", "frontmcp-plugin", "mcp", "plugin"],
"peerDependencies": {
"@frontmcp/sdk": "^0.4.0"
}
}
Next Steps
Plugin Guide
Full plugin API reference with hooks, scopes, and DynamicPlugin details
Create a Plugin
Step-by-step tutorial building a real-world plugin
Cache Plugin
Study the built-in cache plugin implementation
Community
Share your plugin with the community