bmad-method-mcp
Version:
Breakthrough Method of Agile AI-driven Development with Enhanced MCP Integration
960 lines (773 loc) ⢠27.1 kB
Markdown
# 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.