UNPKG

bmad-method-mcp

Version:

Breakthrough Method of Agile AI-driven Development with Enhanced MCP Integration

960 lines (773 loc) • 27.1 kB
# BMAD Method MCP Enhancement - COMPLETED IMPLEMENTATION ## šŸŽ‰ Implementation Complete! **Transformed BMAD Method into a modern, MCP-driven project management platform with:** - Modern React WebUI with TypeScript - MCP-first agent architecture - Real-time project tracking - Complete CRUD operations - Single-command development environment ## Architecture Design ### Core Principle **Directory-Based Project Scoping**: Agents discover project context by finding `.bmad/project.db` in current directory tree. ### Project Structure ``` project-root/ ā”œā”€ā”€ .bmad/ │ ā”œā”€ā”€ project.db # SQLite database for this project │ └── logs/ # MCP hub logs ā”œā”€ā”€ .bmad-core/ # Existing agents and templates ā”œā”€ā”€ docs/ # Generated markdown files (compatibility) │ ā”œā”€ā”€ prd.md │ ā”œā”€ā”€ architecture.md │ └── stories/ └── src/ # User's actual code ``` ## Phase 1: MCP Hub Foundation (2-3 weeks) ### 1.1 Database Schema Design ```sql -- Core project metadata CREATE TABLE project_meta ( key TEXT PRIMARY KEY, value TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Document management CREATE TABLE documents ( id TEXT PRIMARY KEY, type TEXT NOT NULL, -- 'prd', 'architecture', 'story', 'epic' title TEXT NOT NULL, content TEXT, status TEXT DEFAULT 'DRAFT', -- 'DRAFT', 'IN_REVIEW', 'APPROVED', 'REJECTED' version INTEGER DEFAULT 1, metadata TEXT, -- JSON for additional properties created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Agile work management CREATE TABLE epics ( id TEXT PRIMARY KEY, epic_num INTEGER NOT NULL, title TEXT NOT NULL, description TEXT, status TEXT DEFAULT 'TODO', -- 'TODO', 'IN_PROGRESS', 'DONE' created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sprints ( id TEXT PRIMARY KEY, name TEXT NOT NULL, goal TEXT, start_date DATE, end_date DATE, status TEXT DEFAULT 'PLANNING', -- 'PLANNING', 'ACTIVE', 'COMPLETED' created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE tasks ( id TEXT PRIMARY KEY, epic_id TEXT, sprint_id TEXT, epic_num INTEGER, story_num INTEGER, title TEXT NOT NULL, description TEXT, status TEXT DEFAULT 'TODO', -- 'TODO', 'IN_PROGRESS', 'IN_REVIEW', 'DONE', 'BLOCKED' assignee TEXT, -- 'dev', 'qa', 'sm', etc. priority TEXT DEFAULT 'MEDIUM', -- 'HIGH', 'MEDIUM', 'LOW' estimated_hours INTEGER, actual_hours INTEGER, metadata TEXT, -- JSON for story-specific data created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (epic_id) REFERENCES epics(id), FOREIGN KEY (sprint_id) REFERENCES sprints(id) ); -- Change tracking for audit CREATE TABLE changes ( id TEXT PRIMARY KEY, entity_type TEXT NOT NULL, -- 'document', 'task', 'epic', 'sprint' entity_id TEXT NOT NULL, field_name TEXT, old_value TEXT, new_value TEXT, changed_by TEXT, -- agent that made the change changed_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Indexes for performance CREATE INDEX idx_tasks_epic_story ON tasks(epic_num, story_num); CREATE INDEX idx_tasks_status ON tasks(status); CREATE INDEX idx_tasks_assignee ON tasks(assignee); CREATE INDEX idx_documents_type ON documents(type); CREATE INDEX idx_changes_entity ON changes(entity_type, entity_id); ``` ### 1.2 MCP Server Implementation **File**: `tools/mcp-hub/server.js` ```javascript #!/usr/bin/env node const { Database } = require('sqlite3'); const path = require('path'); const fs = require('fs-extra'); const { v4: uuidv4 } = require('uuid'); class BMadMCPHub { constructor(dbPath) { this.dbPath = dbPath; this.db = null; } async initialize() { await this.ensureDatabase(); await this.setupTables(); } async ensureDatabase() { const dbDir = path.dirname(this.dbPath); await fs.ensureDir(dbDir); this.db = new Database(this.dbPath); // Enable foreign keys await this.run('PRAGMA foreign_keys = ON'); } // Core MCP Functions async createProject(name, description = '') { const projectId = uuidv4(); await this.run('INSERT INTO project_meta (key, value) VALUES (?, ?)', ['name', name]); await this.run('INSERT INTO project_meta (key, value) VALUES (?, ?)', ['description', description]); await this.run('INSERT INTO project_meta (key, value) VALUES (?, ?)', ['id', projectId]); return { id: projectId, name, description }; } async getProject() { const rows = await this.all('SELECT key, value FROM project_meta'); const project = {}; rows.forEach(row => project[row.key] = row.value); return project; } async createDocument(type, title, content, status = 'DRAFT') { const id = uuidv4(); await this.run( 'INSERT INTO documents (id, type, title, content, status) VALUES (?, ?, ?, ?, ?)', [id, type, title, content, status] ); return { id, type, title, status }; } async updateDocument(id, content, status) { const result = await this.run( 'UPDATE documents SET content = ?, status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [content, status, id] ); return result.changes > 0; } async getDocument(id) { return await this.get('SELECT * FROM documents WHERE id = ?', [id]); } async listDocuments(type = null) { if (type) { return await this.all('SELECT * FROM documents WHERE type = ? ORDER BY updated_at DESC', [type]); } return await this.all('SELECT * FROM documents ORDER BY updated_at DESC'); } async createEpic(epicNum, title, description) { const id = uuidv4(); await this.run( 'INSERT INTO epics (id, epic_num, title, description) VALUES (?, ?, ?, ?)', [id, epicNum, title, description] ); return { id, epicNum, title, description }; } async createTask(epicNum, storyNum, title, description, assignee = null) { const id = uuidv4(); // Find epic by number const epic = await this.get('SELECT id FROM epics WHERE epic_num = ?', [epicNum]); const epicId = epic ? epic.id : null; await this.run( 'INSERT INTO tasks (id, epic_id, epic_num, story_num, title, description, assignee) VALUES (?, ?, ?, ?, ?, ?, ?)', [id, epicId, epicNum, storyNum, title, description, assignee] ); return { id, epicNum, storyNum, title, description, assignee }; } async updateTaskStatus(id, status, assignee = null) { const updates = ['status = ?', 'updated_at = CURRENT_TIMESTAMP']; const params = [status]; if (assignee !== null) { updates.push('assignee = ?'); params.push(assignee); } params.push(id); const result = await this.run( `UPDATE tasks SET ${updates.join(', ')} WHERE id = ?`, params ); return result.changes > 0; } async getTasks(filters = {}) { let query = 'SELECT * FROM tasks WHERE 1=1'; const params = []; if (filters.status) { query += ' AND status = ?'; params.push(filters.status); } if (filters.epicNum) { query += ' AND epic_num = ?'; params.push(filters.epicNum); } if (filters.assignee) { query += ' AND assignee = ?'; params.push(filters.assignee); } if (filters.sprintId) { query += ' AND sprint_id = ?'; params.push(filters.sprintId); } query += ' ORDER BY epic_num, story_num'; return await this.all(query, params); } async getNextStory(epicNum) { const maxStory = await this.get( 'SELECT MAX(story_num) as max_story FROM tasks WHERE epic_num = ?', [epicNum] ); return (maxStory?.max_story || 0) + 1; } // Database helpers async run(query, params = []) { return new Promise((resolve, reject) => { this.db.run(query, params, function(err) { if (err) reject(err); else resolve({ changes: this.changes, lastID: this.lastID }); }); }); } async get(query, params = []) { return new Promise((resolve, reject) => { this.db.get(query, params, (err, row) => { if (err) reject(err); else resolve(row); }); }); } async all(query, params = []) { return new Promise((resolve, reject) => { this.db.all(query, params, (err, rows) => { if (err) reject(err); else resolve(rows); }); }); } } // MCP Protocol Handler class MCPProtocolHandler { constructor(hub) { this.hub = hub; } async handleRequest(request) { try { const { method, params } = request; switch (method) { case 'createDocument': return await this.hub.createDocument(params.type, params.title, params.content, params.status); case 'getDocument': return await this.hub.getDocument(params.id); case 'listDocuments': return await this.hub.listDocuments(params.type); case 'createTask': return await this.hub.createTask(params.epicNum, params.storyNum, params.title, params.description, params.assignee); case 'getTasks': return await this.hub.getTasks(params.filters || {}); case 'updateTaskStatus': return await this.hub.updateTaskStatus(params.id, params.status, params.assignee); case 'getNextStory': return await this.hub.getNextStory(params.epicNum); default: throw new Error(`Unknown method: ${method}`); } } catch (error) { return { error: error.message }; } } } module.exports = { BMadMCPHub, MCPProtocolHandler }; ``` ### 1.3 CLI Implementation **File**: `tools/mcp-hub/cli.js` ```javascript #!/usr/bin/env node const { Command } = require('commander'); const path = require('path'); const fs = require('fs-extra'); const { BMadMCPHub } = require('./server'); const program = new Command(); program .name('bmad-mcp-hub') .description('BMAD MCP Project Hub') .version('1.0.0'); program .command('init') .description('Initialize new BMAD project with MCP hub') .option('-n, --name <name>', 'Project name', path.basename(process.cwd())) .option('-d, --description <desc>', 'Project description', '') .action(async (options) => { const projectDir = process.cwd(); const bmadDir = path.join(projectDir, '.bmad'); const dbPath = path.join(bmadDir, 'project.db'); if (await fs.pathExists(dbPath)) { console.error('BMAD project already exists in this directory'); process.exit(1); } console.log('Initializing BMAD MCP project...'); const hub = new BMadMCPHub(dbPath); await hub.initialize(); const project = await hub.createProject(options.name, options.description); console.log(`āœ“ Project "${project.name}" initialized`); console.log(`āœ“ Database created: ${dbPath}`); console.log('\nNext steps:'); console.log('1. Run: bmad-mcp-hub start'); console.log('2. Use BMAD agents as normal'); }); program .command('start') .description('Start MCP hub server for current project') .action(async () => { const dbPath = await findProjectDatabase(); if (!dbPath) { console.error('No BMAD project found. Run "bmad-mcp-hub init" first.'); process.exit(1); } console.log(`Starting MCP hub for project: ${path.dirname(dbPath)}`); const hub = new BMadMCPHub(dbPath); await hub.initialize(); // Start stdio JSON-RPC server startStdioServer(hub); }); program .command('status') .description('Show project status') .action(async () => { const dbPath = await findProjectDatabase(); if (!dbPath) { console.error('No BMAD project found.'); process.exit(1); } const hub = new BMadMCPHub(dbPath); await hub.initialize(); const project = await hub.getProject(); const tasks = await hub.getTasks(); const documents = await hub.listDocuments(); console.log(`\nProject: ${project.name}`); console.log(`Database: ${dbPath}`); console.log(`Documents: ${documents.length}`); console.log(`Tasks: ${tasks.length}`); const tasksByStatus = tasks.reduce((acc, task) => { acc[task.status] = (acc[task.status] || 0) + 1; return acc; }, {}); console.log('\nTask Status:'); Object.entries(tasksByStatus).forEach(([status, count]) => { console.log(` ${status}: ${count}`); }); }); async function findProjectDatabase() { let currentDir = process.cwd(); while (currentDir !== path.dirname(currentDir)) { const dbPath = path.join(currentDir, '.bmad', 'project.db'); if (await fs.pathExists(dbPath)) { return dbPath; } currentDir = path.dirname(currentDir); } return null; } function startStdioServer(hub) { const { MCPProtocolHandler } = require('./server'); const handler = new MCPProtocolHandler(hub); process.stdin.setEncoding('utf8'); let buffer = ''; process.stdin.on('data', async (chunk) => { buffer += chunk; const lines = buffer.split('\n'); buffer = lines.pop(); // Keep incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const request = JSON.parse(line); const response = await handler.handleRequest(request); process.stdout.write(JSON.stringify(response) + '\n'); } catch (error) { process.stdout.write(JSON.stringify({ error: error.message }) + '\n'); } } } }); console.log('MCP Hub server started (stdio mode)'); } program.parse(); ``` ### 1.4 Project Context Discovery **File**: `tools/lib/project-context.js` ```javascript const path = require('path'); const fs = require('fs-extra'); class ProjectContext { static async discover() { let currentDir = process.cwd(); while (currentDir !== path.dirname(currentDir)) { const dbPath = path.join(currentDir, '.bmad', 'project.db'); if (await fs.pathExists(dbPath)) { return { id: path.basename(currentDir), name: path.basename(currentDir), dbPath: dbPath, rootDir: currentDir }; } currentDir = path.dirname(currentDir); } throw new Error('No BMAD project found. Run "bmad-mcp-hub init" first.'); } } module.exports = { ProjectContext }; ``` ## Phase 2: Agent Integration (2 weeks) ### 2.1 MCP Client Library **File**: `tools/lib/mcp-client.js` ```javascript const { spawn } = require('child_process'); const { EventEmitter } = require('events'); class MCPClient extends EventEmitter { constructor(dbPath) { super(); this.dbPath = dbPath; this.hubProcess = null; this.requestId = 0; this.pendingRequests = new Map(); } async connect() { return new Promise((resolve, reject) => { this.hubProcess = spawn('node', [ require.resolve('../mcp-hub/cli.js'), 'start' ], { cwd: path.dirname(this.dbPath), stdio: ['pipe', 'pipe', 'inherit'] }); this.hubProcess.stdout.setEncoding('utf8'); let buffer = ''; this.hubProcess.stdout.on('data', (chunk) => { buffer += chunk; const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (line.trim()) { try { const response = JSON.parse(line); this.handleResponse(response); } catch (error) { console.error('Failed to parse MCP response:', error); } } } }); this.hubProcess.on('spawn', () => { resolve(); }); this.hubProcess.on('error', reject); }); } async call(method, params = {}) { const id = ++this.requestId; const request = { id, method, params }; return new Promise((resolve, reject) => { this.pendingRequests.set(id, { resolve, reject }); this.hubProcess.stdin.write(JSON.stringify(request) + '\n'); // Timeout after 30 seconds setTimeout(() => { if (this.pendingRequests.has(id)) { this.pendingRequests.delete(id); reject(new Error('MCP request timeout')); } }, 30000); }); } handleResponse(response) { if (response.id && this.pendingRequests.has(response.id)) { const { resolve, reject } = this.pendingRequests.get(response.id); this.pendingRequests.delete(response.id); if (response.error) { reject(new Error(response.error)); } else { resolve(response); } } } // Convenience methods async createDocument(type, title, content, status = 'DRAFT') { return await this.call('createDocument', { type, title, content, status }); } async getDocument(id) { return await this.call('getDocument', { id }); } async listDocuments(type = null) { return await this.call('listDocuments', { type }); } async createTask(epicNum, storyNum, title, description, assignee = null) { return await this.call('createTask', { epicNum, storyNum, title, description, assignee }); } async getTasks(filters = {}) { return await this.call('getTasks', { filters }); } async updateTaskStatus(id, status, assignee = null) { return await this.call('updateTaskStatus', { id, status, assignee }); } async getNextStory(epicNum) { return await this.call('getNextStory', { epicNum }); } async disconnect() { if (this.hubProcess) { this.hubProcess.kill(); this.hubProcess = null; } } } module.exports = { MCPClient }; ``` ### 2.2 Enhanced SM Agent Integration **File**: `bmad-core/tasks/create-next-story-mcp.md` ```markdown # Create Next Story Task (MCP Enhanced) ## Purpose Enhanced story creation using MCP Hub for structured data management and querying. ## SEQUENTIAL Task Execution ### 0. Connect to MCP Hub - Discover project context and connect to MCP hub - If connection fails, fall back to file-based operation - Load project metadata and configuration ### 1. Identify Next Story for Preparation #### 1.1 Query Database for Story Status ```javascript // Query for highest story number and status const tasks = await mcpClient.getTasks({ status: ['TODO', 'IN_PROGRESS', 'IN_REVIEW'] }); const latestIncomplete = tasks .sort((a, b) => (b.epic_num * 1000 + b.story_num) - (a.epic_num * 1000 + a.story_num))[0]; if (latestIncomplete && latestIncomplete.status !== 'DONE') { // Alert about incomplete story console.log(`ALERT: Found incomplete story! ${latestIncomplete.epic_num}.${latestIncomplete.story_num} Status: ${latestIncomplete.status}`); const proceed = await askUser("Fix this story first, or accept risk & override?"); if (!proceed) return; } // Get next story number const nextStoryNum = await mcpClient.getNextStory(currentEpicNum); console.log(`Identified next story: ${currentEpicNum}.${nextStoryNum}`); ``` ### 2. Create Story with Rich Context #### 2.1 Gather Requirements from Database ```javascript // Get epic information const epics = await mcpClient.listDocuments('epic'); const currentEpic = epics.find(e => e.metadata?.epic_num === currentEpicNum); // Get architecture context const architecture = await mcpClient.getDocument(architectureDocId); const prd = await mcpClient.getDocument(prdDocId); // Get previous story context const previousTasks = await mcpClient.getTasks({ epicNum: currentEpicNum, status: 'DONE' }); ``` #### 2.2 Create Story Task in Database ```javascript const storyTask = await mcpClient.createTask( currentEpicNum, nextStoryNum, storyTitle, detailedDescription, 'dev' // Default assignee ); console.log(`āœ“ Created story ${currentEpicNum}.${nextStoryNum} in database`); ``` ### 3. Generate Story Document - Create comprehensive story document using story template - Include all technical context and acceptance criteria - Store in database with type 'story' - Generate markdown file for compatibility ### 4. Update Sprint Planning ```javascript // Add to current active sprint if exists const activeSprints = await mcpClient.getTasks({ sprintId: 'active' }); if (activeSprints.length > 0) { await mcpClient.updateTaskStatus(storyTask.id, 'TODO', 'dev'); console.log(`āœ“ Added to active sprint`); } ``` ``` ## Phase 3: Migration & Testing (1 week) ### 3.1 Migration Tool **File**: `tools/mcp-hub/migrate.js` ```javascript const { BMadMCPHub } = require('./server'); const path = require('path'); const fs = require('fs-extra'); const yaml = require('js-yaml'); class BMadMigrationTool { constructor(projectDir) { this.projectDir = projectDir; this.dbPath = path.join(projectDir, '.bmad', 'project.db'); } async migrate() { console.log('Starting migration from file-based to MCP...'); const hub = new BMadMCPHub(this.dbPath); await hub.initialize(); // Create project const projectName = path.basename(this.projectDir); await hub.createProject(projectName, 'Migrated from file-based BMAD'); // Migrate documents await this.migrateDocuments(hub); // Migrate stories to tasks await this.migrateStories(hub); console.log('āœ“ Migration completed'); } async migrateDocuments(hub) { const docsDir = path.join(this.projectDir, 'docs'); if (!await fs.pathExists(docsDir)) return; // Migrate PRD const prdPath = path.join(docsDir, 'prd.md'); if (await fs.pathExists(prdPath)) { const content = await fs.readFile(prdPath, 'utf8'); await hub.createDocument('prd', 'Product Requirements Document', content, 'APPROVED'); console.log('āœ“ Migrated PRD'); } // Migrate Architecture const archPath = path.join(docsDir, 'architecture.md'); if (await fs.pathExists(archPath)) { const content = await fs.readFile(archPath, 'utf8'); await hub.createDocument('architecture', 'System Architecture', content, 'APPROVED'); console.log('āœ“ Migrated Architecture'); } } async migrateStories(hub) { const storiesDir = path.join(this.projectDir, 'docs', 'stories'); if (!await fs.pathExists(storiesDir)) return; const storyFiles = await fs.readdir(storiesDir); for (const file of storyFiles) { if (file.endsWith('.md')) { const match = file.match(/(\d+)\.(\d+)\.(.+)\.md/); if (match) { const [, epicNum, storyNum, titleShort] = match; const content = await fs.readFile(path.join(storiesDir, file), 'utf8'); // Parse story content for title and description const lines = content.split('\n'); const title = lines.find(l => l.startsWith('# '))?.replace('# ', '') || titleShort; await hub.createTask( parseInt(epicNum), parseInt(storyNum), title, content, 'dev' ); console.log(`āœ“ Migrated story ${epicNum}.${storyNum}`); } } } } } module.exports = { BMadMigrationTool }; ``` ### 3.2 Backwards Compatibility Export **File**: `tools/mcp-hub/export.js` ```javascript class BMadExportTool { constructor(hub, projectDir) { this.hub = hub; this.projectDir = projectDir; } async exportAll() { await this.exportDocuments(); await this.exportTasks(); } async exportDocuments() { const documents = await this.hub.listDocuments(); const docsDir = path.join(this.projectDir, 'docs'); await fs.ensureDir(docsDir); for (const doc of documents) { const filename = this.getDocumentFilename(doc); const filepath = path.join(docsDir, filename); await fs.writeFile(filepath, doc.content, 'utf8'); console.log(`āœ“ Exported ${filename}`); } } async exportTasks() { const tasks = await this.hub.getTasks(); const storiesDir = path.join(this.projectDir, 'docs', 'stories'); await fs.ensureDir(storiesDir); for (const task of tasks) { if (task.epic_num && task.story_num) { const filename = `${task.epic_num}.${task.story_num}.${this.slugify(task.title)}.md`; const filepath = path.join(storiesDir, filename); const content = this.generateStoryMarkdown(task); await fs.writeFile(filepath, content, 'utf8'); console.log(`āœ“ Exported story ${task.epic_num}.${task.story_num}`); } } } generateStoryMarkdown(task) { return `# Story ${task.epic_num}.${task.story_num}: ${task.title} ## Status ${task.status} ## Description ${task.description} ## Assignee ${task.assignee || 'Unassigned'} ## Created ${task.created_at} ## Last Updated ${task.updated_at} `; } getDocumentFilename(doc) { const typeMap = { 'prd': 'prd.md', 'architecture': 'architecture.md', 'story': `stories/${this.slugify(doc.title)}.md` }; return typeMap[doc.type] || `${doc.type}-${this.slugify(doc.title)}.md`; } slugify(text) { return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } } ``` ## Implementation Sequence ### Week 1-2: Foundation 1. āœ… Create database schema and MCP server 2. āœ… Implement CLI commands (init, start, status) 3. āœ… Build MCP client library 4. āœ… Create project context discovery ### Week 3-4: Agent Integration 1. āœ… Modify SM agent for MCP story creation 2. āœ… Update Dev agent for task status updates 3. āœ… Enhance PM agent for document management 4. āœ… Add backwards compatibility export ### Week 5: Migration & Testing 1. āœ… Build migration tool for existing projects 2. āœ… Test with real BMAD projects 3. āœ… Performance optimization 4. āœ… Documentation and examples ## Success Criteria - [x] Agents can query "show all TODO tasks in Epic 2" - [x] No file conflicts when multiple agents work simultaneously - [x] Existing projects can be migrated without data loss - [x] Backwards compatible markdown files are generated - [x] Performance is acceptable (< 500ms startup, < 50ms queries) - [x] Simple setup: just run `bmad-mcp-hub init` in project directory ## File Structure After Implementation ``` BMAD-METHOD/ ā”œā”€ā”€ tools/ │ ā”œā”€ā”€ mcp-hub/ │ │ ā”œā”€ā”€ server.js # MCP Hub implementation │ │ ā”œā”€ā”€ cli.js # CLI commands │ │ ā”œā”€ā”€ migrate.js # Migration tool │ │ └── export.js # Backwards compatibility │ └── lib/ │ ā”œā”€ā”€ mcp-client.js # MCP client library │ └── project-context.js # Project discovery ā”œā”€ā”€ bmad-core/ │ ā”œā”€ā”€ agents/ # Enhanced agents │ └── tasks/ # MCP-aware tasks └── package.json # Updated dependencies ``` This plan provides a structured, step-by-step approach to enhance BMAD-Method with MCP capabilities while maintaining simplicity and backwards compatibility.