---
title: Creating Plugins
description: Build custom FrontMCP plugins using the DynamicPlugin API
icon: 'puzzle-piece'
keywords: ['frontmcp', 'mcp', 'model context protocol', 'plugin', 'plugins', 'extension', 'creating plugins', 'plugin development', 'typescript']
---

# Creating Plugins

Build custom plugins to extend FrontMCP with cross-cutting capabilities like caching, authorization, logging, and more.

<Tip>
**Nx users:** Scaffold with `nx g @frontmcp/nx:plugin my-plugin --project my-app`. See [Plugin Generator](/frontmcp/nx-plugin/generators/plugin).
</Tip>

## Plugin Architecture

FrontMCP plugins use the `@Plugin` decorator and typically extend `DynamicPlugin`. They can:

1. **Register providers** — Services available to the plugin and exported to the host app
2. **Contribute tools, resources, and skills** — Add capabilities when the plugin is attached
3. **Intercept flows via hooks** — Run code before/after specific stages using `@ToolHook` and `@ListToolsHook`
4. **Accept configuration** — Via `init()` for runtime customization
5. **Extend metadata** — Add custom fields to tool metadata

## Basic Plugin

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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...
  }
}
```

## Extending Tool Metadata

Plugins can add custom fields to tool metadata via global type augmentation:

```typescript
declare global {
  interface ExtendFrontMcpToolMetadata {
    cache?: { ttl?: number } | true;
  }
}
```

Tools can then use this metadata:

```typescript
@Tool({
  name: 'get-user',
  inputSchema: { id: z.string() },
  cache: { ttl: 3600 },
})
export default class GetUserTool extends ToolContext { /* ... */ }
```

## Contributing Tools and Skills

Plugins can contribute tools and skills via the `@Plugin` decorator:

```typescript
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

```bash
# Recommended package structure
my-plugin/
├── src/
│   ├── index.ts              # Exports
│   ├── my-plugin.plugin.ts   # Plugin class
│   └── my-plugin.types.ts    # Types
├── package.json
└── README.md
```

```json package.json
{
  "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

<CardGroup cols={2}>
  <Card title="Plugin Guide" icon="puzzle-piece" href="/frontmcp/extensibility/plugins">
    Full plugin API reference with hooks, scopes, and DynamicPlugin details
  </Card>
  <Card title="Create a Plugin" icon="plug" href="/frontmcp/guides/create-plugin">
    Step-by-step tutorial building a real-world plugin
  </Card>
  <Card title="Cache Plugin" icon="database" href="/frontmcp/plugins/cache-plugin">
    Study the built-in cache plugin implementation
  </Card>
  <Card title="Community" icon="users" href="https://github.com/agentfront/frontmcp/discussions">
    Share your plugin with the community
  </Card>
</CardGroup>
