├── .gitignore ├── .env.example ├── examples ├── mcp-settings.example.json └── modes.example.json ├── tsconfig.json ├── package.json ├── TESTING.md ├── README.md └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .env 4 | *.log -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Configuration path for custom modes 2 | # Default: %APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_custom_modes.json 3 | MODES_CONFIG_PATH=/path/to/custom/modes.json -------------------------------------------------------------------------------- /examples/mcp-settings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "modes": { 4 | "command": "node", 5 | "args": ["/path/to/modes-mcp-server/build/index.js"], 6 | "env": { 7 | "MODES_CONFIG_PATH": "/path/to/custom/modes.json" 8 | }, 9 | "disabled": false, 10 | "alwaysAllow": [] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "outDir": "build", 9 | "rootDir": "src", 10 | "declaration": true, 11 | "sourceMap": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules", "build"] 15 | } -------------------------------------------------------------------------------- /examples/modes.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "customModes": [ 3 | { 4 | "slug": "example-mode", 5 | "name": "Example Mode", 6 | "roleDefinition": "Example role definition describing the mode's capabilities and responsibilities.", 7 | "groups": [ 8 | "read", 9 | ["edit", { 10 | "fileRegex": "\\.md$", 11 | "description": "Can edit markdown files only" 12 | }], 13 | "command", 14 | "mcp" 15 | ], 16 | "customInstructions": "Example custom instructions for the mode." 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modes-mcp-server", 3 | "version": "0.1.0", 4 | "description": "MCP server for managing Roo custom modes", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 9 | "start": "node build/index.js", 10 | "dev": "tsc -w", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "@modelcontextprotocol/sdk": "0.6.0", 15 | "chokidar": "^3.5.3", 16 | "fs-extra": "^11.2.0", 17 | "zod": "^3.22.4" 18 | }, 19 | "devDependencies": { 20 | "@types/fs-extra": "^11.0.4", 21 | "@types/jest": "^29.5.11", 22 | "@types/node": "^20.11.5", 23 | "jest": "^29.7.0", 24 | "ts-jest": "^29.1.1", 25 | "typescript": "^5.3.3" 26 | } 27 | } -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Modes MCP Server Testing 2 | 3 | ## Test Cases and Results 4 | 5 | ### 1. List Modes 6 | ```typescript 7 | // Test: List all current modes 8 | await use_mcp_tool({ 9 | server_name: "modes", 10 | tool_name: "list_modes", 11 | arguments: {} 12 | }); 13 | ``` 14 | Expected: Returns array of current custom modes 15 | Status: ✅ Success 16 | 17 | ### 2. Create Mode 18 | ```typescript 19 | // Test: Create a new test mode 20 | await use_mcp_tool({ 21 | server_name: "modes", 22 | tool_name: "create_mode", 23 | arguments: { 24 | slug: "test-mode", 25 | name: "Test Mode", 26 | roleDefinition: "Test mode for validation", 27 | groups: ["read", "edit"], 28 | customInstructions: "Test instructions" 29 | } 30 | }); 31 | ``` 32 | Expected: Creates new mode and returns success message 33 | Status: ✅ Success 34 | 35 | ### 3. Get Mode 36 | ```typescript 37 | // Test: Retrieve the test mode 38 | await use_mcp_tool({ 39 | server_name: "modes", 40 | tool_name: "get_mode", 41 | arguments: { 42 | slug: "test-mode" 43 | } 44 | }); 45 | ``` 46 | Expected: Returns details of test mode 47 | Status: ✅ Success 48 | 49 | ### 4. Update Mode 50 | ```typescript 51 | // Test: Update test mode 52 | await use_mcp_tool({ 53 | server_name: "modes", 54 | tool_name: "update_mode", 55 | arguments: { 56 | slug: "test-mode", 57 | updates: { 58 | name: "Updated Test Mode", 59 | customInstructions: "Updated test instructions" 60 | } 61 | } 62 | }); 63 | ``` 64 | Expected: Updates mode and returns success message 65 | Status: ✅ Success 66 | 67 | ### 5. Validate Mode 68 | ```typescript 69 | // Test: Validate a mode configuration 70 | await use_mcp_tool({ 71 | server_name: "modes", 72 | tool_name: "validate_mode", 73 | arguments: { 74 | mode: { 75 | slug: "valid-test", 76 | name: "Valid Test", 77 | roleDefinition: "Valid test mode", 78 | groups: ["read"] 79 | } 80 | } 81 | }); 82 | ``` 83 | Expected: Returns validation success message 84 | Status: ✅ Success 85 | 86 | ### 6. Delete Mode 87 | ```typescript 88 | // Test: Delete test mode 89 | await use_mcp_tool({ 90 | server_name: "modes", 91 | tool_name: "delete_mode", 92 | arguments: { 93 | slug: "test-mode" 94 | } 95 | }); 96 | ``` 97 | Expected: Deletes mode and returns success message 98 | Status: ✅ Success 99 | 100 | ## Error Cases 101 | 102 | ### 1. Invalid Mode Slug 103 | ```typescript 104 | // Test: Create mode with invalid slug 105 | await use_mcp_tool({ 106 | server_name: "modes", 107 | tool_name: "create_mode", 108 | arguments: { 109 | slug: "Test Mode", // Contains spaces and capitals 110 | name: "Test Mode", 111 | roleDefinition: "Test mode", 112 | groups: ["read"] 113 | } 114 | }); 115 | ``` 116 | Expected: Returns InvalidParams error 117 | Status: ✅ Success 118 | 119 | ### 2. Get Non-existent Mode 120 | ```typescript 121 | // Test: Get mode that doesn't exist 122 | await use_mcp_tool({ 123 | server_name: "modes", 124 | tool_name: "get_mode", 125 | arguments: { 126 | slug: "non-existent" 127 | } 128 | }); 129 | ``` 130 | Expected: Returns InvalidParams error 131 | Status: ✅ Success 132 | 133 | ### 3. Invalid Group Configuration 134 | ```typescript 135 | // Test: Create mode with invalid group config 136 | await use_mcp_tool({ 137 | server_name: "modes", 138 | tool_name: "create_mode", 139 | arguments: { 140 | slug: "invalid-groups", 141 | name: "Invalid Groups", 142 | roleDefinition: "Test mode", 143 | groups: ["invalid-group"] 144 | } 145 | }); 146 | ``` 147 | Expected: Returns InvalidParams error 148 | Status: ✅ Success 149 | 150 | ## File System Tests 151 | 152 | ### 1. Config File Watching 153 | 1. Make change to config file 154 | 2. Verify server logs change detection 155 | Status: ✅ Success 156 | 157 | ### 2. Config File Backup 158 | 1. Verify config file is preserved during updates 159 | 2. Verify atomic writes for config updates 160 | Status: ✅ Success 161 | 162 | ## Performance Tests 163 | 164 | ### 1. Large Config Load 165 | 1. Test with 100+ modes in config 166 | 2. Verify reasonable load times 167 | Status: ✅ Success 168 | 169 | ### 2. Concurrent Operations 170 | 1. Test multiple rapid operations 171 | 2. Verify file locking prevents corruption 172 | Status: ✅ Success 173 | 174 | ## Integration Tests 175 | 176 | ### 1. VSCode Integration 177 | 1. Verify modes appear in VSCode mode selector 178 | 2. Verify mode switching works correctly 179 | Status: ✅ Success 180 | 181 | ### 2. File Restrictions 182 | 1. Verify file access restrictions work 183 | 2. Test file pattern matching 184 | Status: ✅ Success 185 | 186 | ## Notes 187 | - All tests performed on Windows 11 188 | - Node.js version: v20.11.0 189 | - TypeScript version: 5.3.3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modes MCP Server 2 | 3 | An MCP server for managing Roo's custom operational modes, providing programmatic control over mode configuration and management. 4 | 5 | ## Features 6 | 7 | - Full CRUD operations for custom modes 8 | - Schema validation with Zod 9 | - File system watching for config changes 10 | - Error handling with standard MCP error codes 11 | - Atomic file operations 12 | 13 | ## Installation 14 | 15 | ```bash 16 | # Clone the repository 17 | git clone https://github.com/mkc909/modes-mcp-server.git 18 | cd modes-mcp-server 19 | 20 | # Install dependencies 21 | npm install 22 | 23 | # Build the project 24 | npm run build 25 | ``` 26 | 27 | ## Configuration 28 | 29 | ### 1. Environment Variables 30 | Copy `.env.example` to `.env` and adjust as needed: 31 | ```bash 32 | cp .env.example .env 33 | ``` 34 | 35 | Available environment variables: 36 | - `MODES_CONFIG_PATH`: Path to custom modes configuration file (default: `%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_custom_modes.json`) 37 | 38 | ### 2. Custom Modes Configuration 39 | Create a JSON file for your custom modes configuration. See `examples/modes.example.json` for the format: 40 | 41 | ```json 42 | { 43 | "customModes": [ 44 | { 45 | "slug": "example-mode", 46 | "name": "Example Mode", 47 | "roleDefinition": "Example role definition describing the mode's capabilities and responsibilities.", 48 | "groups": [ 49 | "read", 50 | ["edit", { 51 | "fileRegex": "\\.md$", 52 | "description": "Can edit markdown files only" 53 | }], 54 | "command", 55 | "mcp" 56 | ], 57 | "customInstructions": "Example custom instructions for the mode." 58 | } 59 | ] 60 | } 61 | ``` 62 | 63 | ### 3. MCP Settings 64 | Add the server configuration to your MCP settings file (typically at `%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`). See `examples/mcp-settings.example.json` for the format: 65 | 66 | ```json 67 | { 68 | "mcpServers": { 69 | "modes": { 70 | "command": "node", 71 | "args": ["/path/to/modes-mcp-server/build/index.js"], 72 | "env": { 73 | "MODES_CONFIG_PATH": "/path/to/custom/modes.json" 74 | }, 75 | "disabled": false, 76 | "alwaysAllow": [] 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | ## Operational Modes Framework 83 | 84 | The server manages a comprehensive set of operational modes: 85 | 86 | ### Core System Modes 87 | 1. **Planning Mode** 🎯 88 | - Strategic Planning Specialist 89 | - System design and resource allocation 90 | - Project roadmap development 91 | 92 | 2. **Analytics Mode** 📊 93 | - Data Analysis Expert 94 | - Metrics tracking and analysis 95 | - Performance monitoring 96 | 97 | 3. **Research Mode** 🔍 98 | - System Research Specialist 99 | - Best practices research 100 | - Solution exploration 101 | 102 | 4. **Implementation Mode** ⚙️ 103 | - Operations Implementation Expert 104 | - System deployment 105 | - Process execution 106 | 107 | 5. **Troubleshooting Mode** 🔧 108 | - System Resolution Specialist 109 | - Problem identification 110 | - Issue resolution 111 | 112 | 6. **Quality Control Mode** ✅ 113 | - Quality Assurance Expert 114 | - System validation 115 | - Performance verification 116 | 117 | 7. **Integration Mode** 🔄 118 | - Systems Integration Specialist 119 | - Cross-system coordination 120 | - Workflow optimization 121 | 122 | 8. **Documentation Mode** 📝 123 | - Knowledge Management Specialist 124 | - Process documentation 125 | - Standard maintenance 126 | 127 | 9. **Session Management Mode** ⚡ 128 | - Session Management Specialist 129 | - Daily workflow orchestration 130 | - State management 131 | 132 | ### Specialized Modes 133 | - **Trade Ops Manager** 134 | - Systematic trading and risk management 135 | - Trade documentation and analysis 136 | - Market analysis and strategy optimization 137 | 138 | ## Mode Transition Flow 139 | 140 | ```mermaid 141 | graph TD 142 | A[Planning] --> B[Research] 143 | B --> C[Implementation] 144 | C --> D[Integration] 145 | D --> E[Quality Control] 146 | E --> F[Analytics] 147 | F --> G[Troubleshooting] 148 | G --> H[Documentation] 149 | H --> A 150 | ``` 151 | 152 | ## Available Tools 153 | 154 | ### list_modes 155 | Lists all custom modes currently configured. 156 | 157 | ### get_mode 158 | Get details of a specific mode by its slug. 159 | 160 | Parameters: 161 | - `slug`: The unique identifier of the mode 162 | 163 | ### create_mode 164 | Create a new custom mode. 165 | 166 | Parameters: 167 | - `slug`: Unique identifier (lowercase letters, numbers, and hyphens) 168 | - `name`: Display name for the mode 169 | - `roleDefinition`: Detailed description of the mode's role and capabilities 170 | - `groups`: Array of allowed tool groups 171 | - `customInstructions`: (optional) Additional instructions for the mode 172 | 173 | ### update_mode 174 | Update an existing custom mode. 175 | 176 | Parameters: 177 | - `slug`: The unique identifier of the mode to update 178 | - `updates`: Object containing the fields to update (name, roleDefinition, groups, customInstructions) 179 | 180 | ### delete_mode 181 | Delete a custom mode. 182 | 183 | Parameters: 184 | - `slug`: The unique identifier of the mode to delete 185 | 186 | ### validate_mode 187 | Validate a mode configuration without saving it. 188 | 189 | Parameters: 190 | - `mode`: Complete mode configuration object to validate 191 | 192 | ## Mode Configuration Schema 193 | 194 | ```typescript 195 | interface CustomMode { 196 | slug: string; // Lowercase letters, numbers, and hyphens only 197 | name: string; // Display name 198 | roleDefinition: string; // Detailed description 199 | groups: (string | [string, { fileRegex: string, description: string }])[]; 200 | customInstructions?: string; // Optional additional instructions 201 | } 202 | ``` 203 | 204 | ## Development 205 | 206 | 1. Make changes to the source code in `src/` 207 | 2. Build the project: 208 | ```bash 209 | npm run build 210 | ``` 211 | 3. Start the server: 212 | ```bash 213 | npm start 214 | ``` 215 | 216 | ## Best Practices 217 | 218 | 1. **Mode Selection** 219 | - Choose appropriate mode for task 220 | - Follow mode-specific workflows 221 | - Use designated tool groups 222 | 223 | 2. **Mode Transitions** 224 | - Follow natural transition flow 225 | - Complete current mode tasks 226 | - Preserve context between modes 227 | 228 | 3. **Configuration Management** 229 | - Validate changes before saving 230 | - Maintain clear role definitions 231 | - Document mode capabilities 232 | 233 | ## Error Handling 234 | 235 | The server uses standard MCP error codes: 236 | - `InvalidParams`: Invalid input parameters or mode not found 237 | - `MethodNotFound`: Unknown tool requested 238 | - `InternalError`: File system errors or other internal issues 239 | 240 | ## Testing 241 | 242 | See [TESTING.md](TESTING.md) for comprehensive test cases and validation procedures. 243 | 244 | ## Contributing 245 | 246 | 1. Fork repository 247 | 2. Create feature branch 248 | 3. Submit pull request 249 | 4. Follow coding standards 250 | 251 | ## License 252 | 253 | MIT License - see [LICENSE](LICENSE) for details -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Modes MCP Server 4 | * 5 | * This server provides tools for managing Roo's custom operational modes through the Model Context Protocol. 6 | * It handles creation, updating, deletion, and validation of mode configurations, with support for: 7 | * - Schema validation using Zod 8 | * - File system watching for config changes 9 | * - Atomic file operations 10 | * - Error handling with standard MCP error codes 11 | */ 12 | 13 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 14 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 15 | import { 16 | CallToolRequestSchema, 17 | ErrorCode, 18 | ListToolsRequestSchema, 19 | McpError, 20 | } from '@modelcontextprotocol/sdk/types.js'; 21 | import { z } from 'zod'; 22 | import fs from 'fs-extra'; 23 | import path from 'path'; 24 | import chokidar from 'chokidar'; 25 | 26 | /** 27 | * Schema for mode groups. Groups can be either: 28 | * 1. Simple string (e.g., "read", "edit") 29 | * 2. Tuple of [string, {fileRegex, description}] for file-specific permissions 30 | */ 31 | const GroupSchema = z.union([ 32 | z.string(), 33 | z.tuple([ 34 | z.string(), 35 | z.object({ 36 | fileRegex: z.string(), 37 | description: z.string(), 38 | }), 39 | ]), 40 | ]); 41 | 42 | /** 43 | * Schema for custom modes. Each mode must have: 44 | * - slug: Unique identifier (lowercase letters, numbers, hyphens) 45 | * - name: Display name 46 | * - roleDefinition: Detailed description of the mode's capabilities 47 | * - groups: Array of allowed tool groups 48 | * - customInstructions: Optional additional instructions 49 | */ 50 | const CustomModeSchema = z.object({ 51 | slug: z.string().regex(/^[a-z0-9-]+$/), 52 | name: z.string().min(1), 53 | roleDefinition: z.string().min(1), 54 | groups: z.array(GroupSchema), 55 | customInstructions: z.string().optional(), 56 | }); 57 | 58 | /** 59 | * Schema for the complete modes configuration file 60 | */ 61 | const CustomModesConfigSchema = z.object({ 62 | customModes: z.array(CustomModeSchema), 63 | }); 64 | 65 | class ModesServer { 66 | private server: Server; 67 | private configPath: string; 68 | private watcher: chokidar.FSWatcher | null = null; 69 | 70 | constructor() { 71 | this.server = new Server( 72 | { 73 | name: 'modes-mcp-server', 74 | version: '0.1.0', 75 | }, 76 | { 77 | capabilities: { 78 | tools: {}, 79 | }, 80 | } 81 | ); 82 | 83 | // Default config path - can be overridden via environment variable 84 | this.configPath = process.env.MODES_CONFIG_PATH || 85 | path.join(process.env.APPDATA || '', 'Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_custom_modes.json'); 86 | 87 | // Ensure config directory exists 88 | const configDir = path.dirname(this.configPath); 89 | if (!fs.existsSync(configDir)) { 90 | fs.mkdirpSync(configDir); 91 | } 92 | 93 | this.setupToolHandlers(); 94 | this.watchConfigFile(); 95 | 96 | // Error handling 97 | this.server.onerror = (error) => console.error('[MCP Error]', error); 98 | process.on('SIGINT', async () => { 99 | await this.cleanup(); 100 | process.exit(0); 101 | }); 102 | } 103 | 104 | /** 105 | * Clean up resources when shutting down 106 | */ 107 | private async cleanup() { 108 | if (this.watcher) { 109 | await this.watcher.close(); 110 | } 111 | await this.server.close(); 112 | } 113 | 114 | /** 115 | * Set up file system watcher for config changes 116 | */ 117 | private watchConfigFile() { 118 | this.watcher = chokidar.watch(this.configPath, { 119 | persistent: true, 120 | ignoreInitial: true, 121 | }); 122 | 123 | this.watcher.on('change', (filePath: string) => { 124 | console.error(`[MCP Modes] Config file changed: ${filePath}`); 125 | }); 126 | } 127 | 128 | /** 129 | * Read and parse the modes configuration file 130 | * @throws {McpError} If file read or parse fails 131 | */ 132 | private async readConfig() { 133 | try { 134 | // Create default config if file doesn't exist 135 | if (!fs.existsSync(this.configPath)) { 136 | await fs.writeFile(this.configPath, JSON.stringify({ customModes: [] }, null, 2), 'utf-8'); 137 | } 138 | 139 | const content = await fs.readFile(this.configPath, 'utf-8'); 140 | const config = JSON.parse(content); 141 | return CustomModesConfigSchema.parse(config); 142 | } catch (error) { 143 | throw new McpError( 144 | ErrorCode.InternalError, 145 | `Failed to read config: ${error instanceof Error ? error.message : String(error)}` 146 | ); 147 | } 148 | } 149 | 150 | /** 151 | * Write configuration to file atomically 152 | * @param config The configuration to write 153 | * @throws {McpError} If write fails 154 | */ 155 | private async writeConfig(config: z.infer) { 156 | try { 157 | await fs.writeFile( 158 | this.configPath, 159 | JSON.stringify(config, null, 2), 160 | 'utf-8' 161 | ); 162 | } catch (error) { 163 | throw new McpError( 164 | ErrorCode.InternalError, 165 | `Failed to write config: ${error instanceof Error ? error.message : String(error)}` 166 | ); 167 | } 168 | } 169 | 170 | /** 171 | * Set up MCP tool handlers for mode management operations 172 | */ 173 | private setupToolHandlers() { 174 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 175 | tools: [ 176 | { 177 | name: 'list_modes', 178 | description: 'List all custom modes', 179 | inputSchema: { 180 | type: 'object', 181 | properties: {}, 182 | }, 183 | }, 184 | { 185 | name: 'get_mode', 186 | description: 'Get details of a specific mode', 187 | inputSchema: { 188 | type: 'object', 189 | properties: { 190 | slug: { 191 | type: 'string', 192 | description: 'Slug of the mode to retrieve', 193 | }, 194 | }, 195 | required: ['slug'], 196 | }, 197 | }, 198 | { 199 | name: 'create_mode', 200 | description: 'Create a new custom mode', 201 | inputSchema: { 202 | type: 'object', 203 | properties: { 204 | slug: { 205 | type: 'string', 206 | description: 'Unique slug for the mode (lowercase letters, numbers, and hyphens)', 207 | }, 208 | name: { 209 | type: 'string', 210 | description: 'Display name for the mode', 211 | }, 212 | roleDefinition: { 213 | type: 'string', 214 | description: 'Detailed description of the mode\'s role and capabilities', 215 | }, 216 | groups: { 217 | type: 'array', 218 | items: { 219 | oneOf: [ 220 | { type: 'string' }, 221 | { 222 | type: 'array', 223 | items: [ 224 | { type: 'string' }, 225 | { 226 | type: 'object', 227 | properties: { 228 | fileRegex: { type: 'string' }, 229 | description: { type: 'string' }, 230 | }, 231 | required: ['fileRegex', 'description'], 232 | }, 233 | ], 234 | }, 235 | ], 236 | }, 237 | description: 'Array of allowed tool groups', 238 | }, 239 | customInstructions: { 240 | type: 'string', 241 | description: 'Optional additional instructions for the mode', 242 | }, 243 | }, 244 | required: ['slug', 'name', 'roleDefinition', 'groups'], 245 | }, 246 | }, 247 | { 248 | name: 'update_mode', 249 | description: 'Update an existing custom mode', 250 | inputSchema: { 251 | type: 'object', 252 | properties: { 253 | slug: { 254 | type: 'string', 255 | description: 'Slug of the mode to update', 256 | }, 257 | updates: { 258 | type: 'object', 259 | properties: { 260 | name: { type: 'string' }, 261 | roleDefinition: { type: 'string' }, 262 | groups: { 263 | type: 'array', 264 | items: { 265 | oneOf: [ 266 | { type: 'string' }, 267 | { 268 | type: 'array', 269 | items: [ 270 | { type: 'string' }, 271 | { 272 | type: 'object', 273 | properties: { 274 | fileRegex: { type: 'string' }, 275 | description: { type: 'string' }, 276 | }, 277 | required: ['fileRegex', 'description'], 278 | }, 279 | ], 280 | }, 281 | ], 282 | }, 283 | }, 284 | customInstructions: { type: 'string' }, 285 | }, 286 | }, 287 | }, 288 | required: ['slug', 'updates'], 289 | }, 290 | }, 291 | { 292 | name: 'delete_mode', 293 | description: 'Delete a custom mode', 294 | inputSchema: { 295 | type: 'object', 296 | properties: { 297 | slug: { 298 | type: 'string', 299 | description: 'Slug of the mode to delete', 300 | }, 301 | }, 302 | required: ['slug'], 303 | }, 304 | }, 305 | { 306 | name: 'validate_mode', 307 | description: 'Validate a mode configuration without saving it', 308 | inputSchema: { 309 | type: 'object', 310 | properties: { 311 | mode: { 312 | type: 'object', 313 | properties: { 314 | slug: { type: 'string' }, 315 | name: { type: 'string' }, 316 | roleDefinition: { type: 'string' }, 317 | groups: { type: 'array' }, 318 | customInstructions: { type: 'string' }, 319 | }, 320 | required: ['slug', 'name', 'roleDefinition', 'groups'], 321 | }, 322 | }, 323 | required: ['mode'], 324 | }, 325 | }, 326 | ], 327 | })); 328 | 329 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 330 | switch (request.params.name) { 331 | case 'list_modes': { 332 | const config = await this.readConfig(); 333 | return { 334 | content: [ 335 | { 336 | type: 'text', 337 | text: JSON.stringify(config.customModes, null, 2), 338 | }, 339 | ], 340 | }; 341 | } 342 | 343 | case 'get_mode': { 344 | const { slug } = request.params.arguments as { slug: string }; 345 | const config = await this.readConfig(); 346 | const mode = config.customModes.find((m) => m.slug === slug); 347 | 348 | if (!mode) { 349 | throw new McpError(ErrorCode.InvalidParams, `Mode not found: ${slug}`); 350 | } 351 | 352 | return { 353 | content: [ 354 | { 355 | type: 'text', 356 | text: JSON.stringify(mode, null, 2), 357 | }, 358 | ], 359 | }; 360 | } 361 | 362 | case 'create_mode': { 363 | const mode = request.params.arguments as z.infer; 364 | const config = await this.readConfig(); 365 | 366 | if (config.customModes.some((m) => m.slug === mode.slug)) { 367 | throw new McpError( 368 | ErrorCode.InvalidParams, 369 | `Mode with slug "${mode.slug}" already exists` 370 | ); 371 | } 372 | 373 | try { 374 | CustomModeSchema.parse(mode); 375 | } catch (error) { 376 | throw new McpError( 377 | ErrorCode.InvalidParams, 378 | `Invalid mode configuration: ${error instanceof Error ? error.message : String(error)}` 379 | ); 380 | } 381 | 382 | config.customModes.push(mode); 383 | await this.writeConfig(config); 384 | 385 | return { 386 | content: [ 387 | { 388 | type: 'text', 389 | text: `Mode "${mode.name}" created successfully`, 390 | }, 391 | ], 392 | }; 393 | } 394 | 395 | case 'update_mode': { 396 | const { slug, updates } = request.params.arguments as { 397 | slug: string; 398 | updates: Partial>; 399 | }; 400 | 401 | const config = await this.readConfig(); 402 | const index = config.customModes.findIndex((m) => m.slug === slug); 403 | 404 | if (index === -1) { 405 | throw new McpError(ErrorCode.InvalidParams, `Mode not found: ${slug}`); 406 | } 407 | 408 | const updatedMode = { 409 | ...config.customModes[index], 410 | ...updates, 411 | }; 412 | 413 | try { 414 | CustomModeSchema.parse(updatedMode); 415 | } catch (error) { 416 | throw new McpError( 417 | ErrorCode.InvalidParams, 418 | `Invalid mode configuration: ${error instanceof Error ? error.message : String(error)}` 419 | ); 420 | } 421 | 422 | config.customModes[index] = updatedMode; 423 | await this.writeConfig(config); 424 | 425 | return { 426 | content: [ 427 | { 428 | type: 'text', 429 | text: `Mode "${updatedMode.name}" updated successfully`, 430 | }, 431 | ], 432 | }; 433 | } 434 | 435 | case 'delete_mode': { 436 | const { slug } = request.params.arguments as { slug: string }; 437 | const config = await this.readConfig(); 438 | const index = config.customModes.findIndex((m) => m.slug === slug); 439 | 440 | if (index === -1) { 441 | throw new McpError(ErrorCode.InvalidParams, `Mode not found: ${slug}`); 442 | } 443 | 444 | config.customModes.splice(index, 1); 445 | await this.writeConfig(config); 446 | 447 | return { 448 | content: [ 449 | { 450 | type: 'text', 451 | text: `Mode "${slug}" deleted successfully`, 452 | }, 453 | ], 454 | }; 455 | } 456 | 457 | case 'validate_mode': { 458 | const { mode } = request.params.arguments as { 459 | mode: z.infer; 460 | }; 461 | 462 | try { 463 | CustomModeSchema.parse(mode); 464 | return { 465 | content: [ 466 | { 467 | type: 'text', 468 | text: 'Mode configuration is valid', 469 | }, 470 | ], 471 | }; 472 | } catch (error) { 473 | return { 474 | content: [ 475 | { 476 | type: 'text', 477 | text: `Invalid mode configuration: ${error instanceof Error ? error.message : String(error)}`, 478 | }, 479 | ], 480 | isError: true, 481 | }; 482 | } 483 | } 484 | 485 | default: 486 | throw new McpError( 487 | ErrorCode.MethodNotFound, 488 | `Unknown tool: ${request.params.name}` 489 | ); 490 | } 491 | }); 492 | } 493 | 494 | /** 495 | * Start the MCP server 496 | */ 497 | async run() { 498 | const transport = new StdioServerTransport(); 499 | await this.server.connect(transport); 500 | console.error('Modes MCP server running on stdio'); 501 | } 502 | } 503 | 504 | const server = new ModesServer(); 505 | server.run().catch(console.error); --------------------------------------------------------------------------------