Claude Code Plugin Architecture and Extension Development
Claude Code provides a flexible plugin architecture that enables developers to extend its functionality through hooks, MCP servers, and custom slash commands. This guide covers the complete architecture and practical patterns for building extensions.
Architecture Overview
Claude Code’s extensibility is built on three core systems:
- Hook System - Event-driven extension points for injecting custom logic
- MCP (Managed Code Process) System - Integration framework for external tools and services
- Custom Slash Commands - User-defined commands stored as Markdown templates
Hook System
Available Hooks
Hooks allow you to intercept and modify Claude Code’s behavior at specific points:
UserPromptSubmit
Triggered when a user submits a prompt. Perfect for:
- Input validation and sanitization
- Context enrichment
- Custom preprocessing
interface UserPromptSubmitHook {
event: "UserPromptSubmit";
data: {
prompt: string;
cwd: string;
};
}PreToolUse
Validates and controls tool execution before Claude Code uses them:
- Security checks
- Rate limiting
- Tool parameter validation
interface PreToolUseHook {
event: "PreToolUse";
data: {
tool: string;
parameters: Record<string, any>;
};
}PreCompact
Manages conversation cleanup when context limits approach:
- Custom compaction strategies
- Context preservation logic
- Memory optimization
Stop/SubagentStop
Handles process completion and transcript processing:
- Result processing
- Cleanup operations
- Analytics collection
Implementing Custom Hooks
Here’s a practical example of implementing a security validation hook:
// security-hook.ts
import { Hook, HookResult } from '@claude-code/hooks';
export class SecurityValidationHook implements Hook {
async handle(event: HookEvent): Promise<HookResult> {
if (event.type === 'PreToolUse') {
const { tool, parameters } = event.data;
// Validate dangerous operations
if (tool === 'Bash' && parameters.command.includes('rm -rf')) {
return {
action: 'block',
message: 'Dangerous command detected and blocked'
};
}
// Log all tool usage for audit
await this.logToolUsage(tool, parameters);
}
return { action: 'continue' };
}
private async logToolUsage(tool: string, params: any) {
// Implementation for logging
}
}MCP (Managed Code Process) System
MCP Architecture
MCP servers enable integration with external services through standardized protocols:
graph LR A[Claude Code] -->|stdio/HTTP/SSE| B[MCP Server] B --> C[External Service] B --> D[Custom Tools] B --> E[Database]
Transport Protocols
- stdio - Direct process communication
- HTTP - RESTful API integration
- SSE (Server-Sent Events) - Real-time streaming
Creating an MCP Server
Here’s a complete example of an MCP server for database operations:
// database-mcp-server.ts
import { MCPServer, Tool, Transport } from '@claude-code/mcp';
class DatabaseMCPServer extends MCPServer {
private db: DatabaseConnection;
constructor() {
super({
name: 'database-tools',
version: '1.0.0',
transport: Transport.STDIO
});
this.registerTools();
}
private registerTools() {
this.addTool({
name: 'query_database',
description: 'Execute a database query',
parameters: {
query: { type: 'string', required: true },
database: { type: 'string', default: 'main' }
},
handler: this.queryDatabase.bind(this)
});
this.addTool({
name: 'schema_info',
description: 'Get database schema information',
parameters: {
table: { type: 'string' }
},
handler: this.getSchemaInfo.bind(this)
});
}
async queryDatabase(params: any) {
// Validate and sanitize query
const sanitized = this.sanitizeQuery(params.query);
const results = await this.db.execute(sanitized);
return {
success: true,
rows: results.rows,
count: results.count
};
}
async getSchemaInfo(params: any) {
// Implementation
}
}
// Start the server
const server = new DatabaseMCPServer();
server.start();MCP Configuration
Configure MCP servers in your project:
// .mcp.json
{
"servers": {
"database": {
"command": "node",
"args": ["./mcp-servers/database-mcp-server.js"],
"transport": "stdio",
"scope": "project"
},
"github": {
"url": "https://api.mycompany.com/mcp/github",
"transport": "http",
"auth": {
"type": "oauth",
"provider": "github"
},
"scope": "user"
}
}
}Scopes
- project: Shared within repository
- local: Local development only
- user: Global user configuration
Custom Slash Commands
Creating Commands
Place Markdown files in .claude/commands/:
<!-- .claude/commands/generate-component.md -->
---
title: Generate React Component
description: Creates a new React component with TypeScript
args:
name: Component name
type: Component type (functional/class)
---
Generate a new React component named {{name}} of type {{type}}.
Requirements:
- Use TypeScript
- Include proper props interface
- Add JSDoc comments
- Follow project conventions in src/components/
```bash
# Check existing component patterns
ls -la src/components/@src/components/Button.tsx - Use this as a reference
### Advanced Command Features
#### Namespacing
Create subdirectories for command organization:
.claude/commands/ ├── frontend/ │ ├── component.md # /frontend:component │ └── hook.md # /frontend:hook └── backend/ ├── api.md # /backend:api └── migration.md # /backend:migration
#### Dynamic Arguments
```markdown
---
args:
table: Database table name
fields: Comma-separated field names
---
Create a CRUD API for {{table}} with fields: {{fields}}
Best Practices
1. Hook Development
- Keep hooks lightweight - Don’t block the main thread
- Handle errors gracefully - Always return a valid response
- Use async operations - Leverage promises for I/O
- Implement timeout handling - Prevent hanging operations
2. MCP Server Guidelines
- Validate all inputs - Never trust external data
- Implement rate limiting - Protect against abuse
- Use connection pooling - Optimize resource usage
- Provide clear error messages - Help with debugging
3. Command Design
- Make commands discoverable - Use clear names and descriptions
- Provide examples - Show expected usage
- Handle edge cases - Validate arguments
- Follow conventions - Match project patterns
Security Considerations
Input Validation
Always validate and sanitize inputs:
function validateInput(input: string): string {
// Remove potential command injection
const sanitized = input.replace(/[;&|`$]/g, '');
// Validate against whitelist
if (!ALLOWED_PATTERNS.test(sanitized)) {
throw new Error('Invalid input pattern');
}
return sanitized;
}Permission Management
Implement proper permission checks:
class PermissionHook implements Hook {
async handle(event: HookEvent): Promise<HookResult> {
if (event.type === 'PreToolUse') {
const hasPermission = await this.checkPermission(
event.user,
event.data.tool
);
if (!hasPermission) {
return {
action: 'block',
message: 'Insufficient permissions'
};
}
}
return { action: 'continue' };
}
}Performance Optimization
Caching Strategies
Implement intelligent caching for MCP servers:
class CachedMCPServer extends MCPServer {
private cache = new LRUCache<string, any>({
max: 1000,
ttl: 1000 * 60 * 5 // 5 minutes
});
async handleRequest(request: MCPRequest) {
const cacheKey = this.getCacheKey(request);
// Check cache first
const cached = this.cache.get(cacheKey);
if (cached) {
return cached;
}
// Process request
const result = await this.processRequest(request);
// Cache result
this.cache.set(cacheKey, result);
return result;
}
}Connection Pooling
Optimize external service connections:
class ConnectionPool {
private pool: Connection[] = [];
private maxConnections = 10;
async getConnection(): Promise<Connection> {
// Reuse existing connection
const available = this.pool.find(c => !c.inUse);
if (available) {
available.inUse = true;
return available;
}
// Create new if under limit
if (this.pool.length < this.maxConnections) {
const conn = await this.createConnection();
this.pool.push(conn);
return conn;
}
// Wait for available connection
return this.waitForConnection();
}
}Testing Extensions
Unit Testing Hooks
import { describe, it, expect } from 'bun:test';
import { SecurityHook } from './security-hook';
describe('SecurityHook', () => {
it('should block dangerous commands', async () => {
const hook = new SecurityHook();
const result = await hook.handle({
type: 'PreToolUse',
data: {
tool: 'Bash',
parameters: { command: 'rm -rf /' }
}
});
expect(result.action).toBe('block');
});
});Integration Testing MCP Servers
describe('DatabaseMCPServer', () => {
let server: DatabaseMCPServer;
beforeEach(() => {
server = new DatabaseMCPServer();
server.start();
});
it('should execute queries', async () => {
const response = await server.call('query_database', {
query: 'SELECT * FROM users LIMIT 10'
});
expect(response.success).toBe(true);
expect(response.rows).toHaveLength(10);
});
});Real-World Examples
GitHub Integration
Example from claude-issue-triage.yml:
- name: Run Claude Code with GitHub MCP
uses: anthropics/claude-code-action@v1
with:
mcp-servers: |
github:
url: https://api.github.com/mcp
transport: http
auth:
type: oauth
token: ${{ secrets.GITHUB_TOKEN }}Custom Analysis Hook
class CodeAnalysisHook implements Hook {
async handle(event: HookEvent): Promise<HookResult> {
if (event.type === 'PreToolUse' && event.data.tool === 'Edit') {
// Analyze code changes
const analysis = await this.analyzeCode(
event.data.parameters.file_path,
event.data.parameters.new_string
);
// Add analysis to context
return {
action: 'continue',
context: {
codeAnalysis: analysis
}
};
}
return { action: 'continue' };
}
}Resources
- Official Claude Code Documentation
- MCP Protocol Specification
- Hook System API Reference
- Example MCP Servers
Next Steps
- Start with a simple hook implementation
- Build an MCP server for your most common tasks
- Create custom slash commands for your workflow
- Share your extensions with the community
The plugin architecture provides powerful extension points while maintaining security and performance. By following these patterns, you can create robust extensions that enhance Claude Code for your specific needs.