Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
PLUGIN_DEVELOPMENT_GUIDE.md11.7 kB
# DollhouseMCP Plugin Development Guide ## Overview This guide explains how to create plugins for DollhouseMCP, extending the platform with new element types and capabilities. Plugins enable you to add functionality without modifying the core codebase. ## Plugin Architecture ### Core Concepts DollhouseMCP uses a plugin system that allows dynamic registration of new element types. Each plugin: - Defines a new element type - Provides a manager for CRUD operations - Registers MCP tools for interaction - Maintains its own state and configuration ### Plugin Interface ```typescript interface IElementTypePlugin { // Unique identifier for the element type type: string; // Plugin version (semantic versioning) version: string; // Manager handling element operations manager: IElementManager; // MCP tools provided by this plugin tools: ToolDefinition[]; // Optional: Required license level requiredLicense?: 'community' | 'pro' | 'enterprise'; // Optional: Plugin metadata metadata?: { author: string; description: string; repository?: string; documentation?: string; }; } ``` ## Getting Started ### Step 1: Set Up Your Development Environment ```bash # Clone the DollhouseMCP repository git clone https://github.com/DollhouseMCP/mcp-server.git cd mcp-server # Install dependencies npm install # Create your plugin directory mkdir -p src/plugins/my-element-type ``` ### Step 2: Create Your Element Class ```typescript // src/plugins/my-element-type/MyElement.ts import { BaseElement } from '../../elements/BaseElement.js'; import { IElement } from '../../types/elements/index.js'; export interface MyElementMetadata extends IElementMetadata { // Add your custom metadata fields customField?: string; settings?: Record<string, any>; } export class MyElement extends BaseElement implements IElement { constructor(metadata: Partial<MyElementMetadata>) { super('my-element-type', metadata); // Initialize your element } // Implement required methods async activate(): Promise<void> { // Activation logic } async deactivate(): Promise<void> { // Deactivation logic } // Add custom methods async performAction(params: any): Promise<any> { // Custom functionality } } ``` ### Step 3: Create Your Element Manager ```typescript // src/plugins/my-element-type/MyElementManager.ts import { IElementManager } from '../../types/elements/index.js'; import { MyElement } from './MyElement.js'; export class MyElementManager implements IElementManager<MyElement> { async load(path: string): Promise<MyElement> { // Load element from file } async save(element: MyElement, path: string): Promise<void> { // Save element to file } async list(): Promise<MyElement[]> { // List all elements of this type } async find(predicate: (element: MyElement) => boolean): Promise<MyElement | undefined> { // Find element matching predicate } async validate(element: MyElement): Promise<ValidationResult> { // Validate element } } ``` ### Step 4: Define Your MCP Tools ```typescript // src/plugins/my-element-type/tools.ts import { ToolDefinition } from '../../server/tools/ToolRegistry.js'; export function getMyElementTools(): ToolDefinition[] { return [ { name: 'create_my_element', description: 'Create a new element of my type', inputSchema: { type: 'object', properties: { name: { type: 'string' }, description: { type: 'string' }, customField: { type: 'string' } }, required: ['name', 'description'] } }, { name: 'execute_my_element', description: 'Execute an action on my element', inputSchema: { type: 'object', properties: { name: { type: 'string' }, action: { type: 'string' }, params: { type: 'object' } }, required: ['name', 'action'] } } ]; } ``` ### Step 5: Create Your Plugin ```typescript // src/plugins/my-element-type/index.ts import { IElementTypePlugin } from '../../types/plugins.js'; import { MyElementManager } from './MyElementManager.js'; import { getMyElementTools } from './tools.js'; export class MyElementPlugin implements IElementTypePlugin { type = 'my-element-type'; version = '1.0.0'; manager = new MyElementManager(); tools = getMyElementTools(); metadata = { author: 'Your Name', description: 'Description of what your element type does', repository: 'https://github.com/yourusername/my-element-plugin', documentation: 'https://docs.example.com/my-element' }; // Optional: Initialization logic async initialize(): Promise<void> { // Set up any required resources } // Optional: Cleanup logic async cleanup(): Promise<void> { // Clean up resources } } // Export plugin factory export default function createPlugin(): MyElementPlugin { return new MyElementPlugin(); } ``` ### Step 6: Register Your Plugin ```typescript // In your application initialization import { ElementTypeRegistry } from './core/ElementTypeRegistry.js'; import createMyElementPlugin from './plugins/my-element-type/index.js'; const registry = ElementTypeRegistry.getInstance(); const myPlugin = createMyElementPlugin(); await registry.register(myPlugin); ``` ## Best Practices ### 1. Follow the Single Responsibility Principle Each plugin should focus on one element type and its related functionality. ### 2. Use TypeScript Strong typing helps prevent errors and improves developer experience. ### 3. Implement Comprehensive Validation ```typescript async validate(element: MyElement): Promise<ValidationResult> { const errors = []; if (!element.metadata.name) { errors.push('Name is required'); } if (element.metadata.name.length > 100) { errors.push('Name must be 100 characters or less'); } // Add more validation rules return { valid: errors.length === 0, errors }; } ``` ### 4. Handle Errors Gracefully ```typescript async performAction(params: any): Promise<any> { try { // Perform action return { success: true, result }; } catch (error) { logger.error('Action failed:', error); return { success: false, error: error.message, recovery: 'Suggested recovery action' }; } } ``` ### 5. Add Comprehensive Logging ```typescript import { logger } from '../../utils/logger.js'; async activate(): Promise<void> { logger.info(`Activating ${this.type} element: ${this.metadata.name}`); // Activation logic logger.debug('Activation details:', { /* context */ }); } ``` ### 6. Write Tests ```typescript // test/plugins/my-element-type/MyElement.test.ts describe('MyElement', () => { it('should create element with metadata', () => { const element = new MyElement({ name: 'Test Element', description: 'Test description' }); expect(element.metadata.name).toBe('Test Element'); }); it('should validate required fields', async () => { const element = new MyElement({}); const result = await element.validate(); expect(result.valid).toBe(false); expect(result.errors).toContain('Name is required'); }); }); ``` ## Advanced Topics ### State Persistence For elements that need to maintain state across sessions: ```typescript class StatefulElement extends BaseElement { private stateFile: string; async loadState(): Promise<any> { const statePath = path.join( this.getStateDir(), `${this.metadata.name}.json` ); if (await fs.pathExists(statePath)) { return await fs.readJson(statePath); } return this.getDefaultState(); } async saveState(state: any): Promise<void> { const statePath = path.join( this.getStateDir(), `${this.metadata.name}.json` ); await fs.ensureDir(this.getStateDir()); await fs.writeJson(statePath, state, { spaces: 2 }); } } ``` ### Inter-Element Communication For plugins that need to interact with other elements: ```typescript class CollaborativeElement extends BaseElement { async collaborateWith(otherElement: IElement): Promise<void> { // Check compatibility if (!this.isCompatible(otherElement)) { throw new Error(`Incompatible element type: ${otherElement.type}`); } // Exchange information const sharedContext = await this.createSharedContext(otherElement); // Perform collaborative action await this.executeCollaboration(sharedContext); } } ``` ### Performance Optimization For plugins handling large amounts of data: ```typescript class OptimizedManager implements IElementManager { private cache: Map<string, MyElement> = new Map(); async load(path: string): Promise<MyElement> { // Check cache first if (this.cache.has(path)) { return this.cache.get(path)!; } // Load from disk const element = await this.loadFromDisk(path); // Cache for future use this.cache.set(path, element); // Implement cache eviction if needed if (this.cache.size > 100) { this.evictOldestEntry(); } return element; } } ``` ## Plugin Distribution ### Publishing to npm ```json // package.json { "name": "@dollhousemcp/plugin-my-element", "version": "1.0.0", "description": "My custom element type for DollhouseMCP", "main": "dist/index.js", "types": "dist/index.d.ts", "peerDependencies": { "@dollhousemcp/core": "^1.0.0" }, "keywords": ["dollhousemcp", "plugin", "mcp"], "license": "MIT" } ``` ### Installation Users can install your plugin: ```bash npm install @dollhousemcp/plugin-my-element ``` ### Registration in User Configuration ```typescript // In user's configuration import createMyElementPlugin from '@dollhousemcp/plugin-my-element'; const plugins = [ createMyElementPlugin(), // Other plugins ]; ``` ## Community Guidelines ### Contributing Your Plugin 1. **Documentation**: Provide clear documentation with examples 2. **Tests**: Include comprehensive test coverage 3. **Examples**: Provide example elements and use cases 4. **Support**: Be responsive to issues and questions 5. **Versioning**: Follow semantic versioning 6. **License**: Choose an appropriate open source license ### Getting Help - Join our Discord community - Check existing plugins for examples - Open issues on GitHub for questions - Contribute to plugin documentation ## Example Plugins Check out these example plugins for reference: - **workflow-plugin**: Complex orchestration patterns - **function-plugin**: External function calling - **database-plugin**: Database connectivity - **api-plugin**: REST API integration ## Troubleshooting ### Common Issues **Plugin not loading** - Check that your plugin implements all required interfaces - Verify the plugin is registered correctly - Check logs for initialization errors **Tools not appearing** - Ensure tools are properly defined with schemas - Verify tool names are unique - Check that tools are returned from the plugin **State not persisting** - Verify state directory permissions - Check state serialization/deserialization - Ensure cleanup doesn't delete state unintentionally ## Conclusion Creating plugins for DollhouseMCP allows you to extend the platform with custom functionality while maintaining clean separation from the core. Follow these guidelines to create robust, maintainable plugins that benefit the entire community. For more examples and support, visit our [GitHub repository](https://github.com/DollhouseMCP/mcp-server) and join our community discussions. --- *Happy plugin development!*

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DollhouseMCP/DollhouseMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server