Skip to main content
Glama

BMAD MCP Server

by Dali1789
server.tsโ€ข8.89 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import express from 'express'; interface AgentState { id: string; status: "active" | "inactive"; activated_at: string; metadata?: any; } class BMADMCPServer { private server: Server; private agentStates: Map<string, AgentState> = new Map(); private app: express.Application; constructor() { this.server = new Server( { name: "bmad", version: "2.1.0" }, { capabilities: { tools: {}, resources: {} } } ); this.app = express(); this.setupMiddleware(); this.setupRoutes(); this.setupMCPHandlers(); this.setupErrorHandling(); } private setupMiddleware() { this.app.use(express.json()); this.app.use((_req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); }); } private setupRoutes() { // Health check for Railway this.app.get('/health', (_req, res) => { res.json({ status: 'healthy', service: 'bmad-mcp-server', version: '2.1.1', timestamp: process.hrtime()[0] + process.hrtime()[1] / 1e9, deployment: 'railway', activeAgents: this.agentStates.size }); }); // Agent states endpoint this.app.get('/agent-states', (_req, res) => { const states = Object.fromEntries(this.agentStates); res.json(states); }); // Tool call endpoint for HTTP clients this.app.post('/tools/call', async (req, res) => { try { const result = await this.handleToolCall(req.body); res.json(result); } catch (error) { res.status(500).json(this.createErrorResponse(error)); } }); } private setupMCPHandlers() { // For now, focus on HTTP endpoints which work reliably // MCP SDK stdio handlers can be added later console.log('[BMAD] MCP handlers setup - using HTTP endpoints for Railway'); } private async handleToolCall(params: any) { const { name, arguments: args } = params; // Normalize arguments - handle both {} and empty/undefined const normalizedArgs = args || {}; console.log(`[BMAD] Tool call: ${name}`, normalizedArgs); switch (name) { case "list_agents": return this.listAgents(normalizedArgs); case "bmad_activate_agent": return this.activateAgent(normalizedArgs); case "execute_task": return this.executeTask(normalizedArgs); case "get_agent_status": return this.getAgentStatus(normalizedArgs); default: throw new Error(`Unknown tool: ${name}`); } } private async listAgents(_args: any) { const agents = [ { id: "architect", name: "Winston", title: "System Architect", status: this.agentStates.get("architect")?.status || "available", capabilities: ["system-design", "architecture", "technical-planning"] }, { id: "developer", name: "Dev", title: "Senior Developer", status: this.agentStates.get("developer")?.status || "available", capabilities: ["coding", "implementation", "debugging"] }, { id: "analyst", name: "Ana", title: "Business Analyst", status: this.agentStates.get("analyst")?.status || "available", capabilities: ["analysis", "requirements", "documentation"] }, { id: "pm", name: "Patricia", title: "Project Manager", status: this.agentStates.get("pm")?.status || "available", capabilities: ["project-management", "coordination", "planning"] }, { id: "qa", name: "Quinn", title: "QA Engineer", status: this.agentStates.get("qa")?.status || "available", capabilities: ["testing", "quality-assurance", "validation"] } ]; return { content: [{ type: "text", text: JSON.stringify({ success: true, agents, total_agents: agents.length, active_agents: Array.from(this.agentStates.values()).filter(a => a.status === "active").length }, null, 2) }] }; } private async activateAgent(args: any) { const { agent_id, config } = args; if (!agent_id) { throw new Error("agent_id is required"); } // Check if agent exists const validAgents = ["architect", "developer", "analyst", "pm", "qa"]; if (!validAgents.includes(agent_id)) { throw new Error(`Invalid agent_id: ${agent_id}. Valid agents: ${validAgents.join(", ")}`); } // Idempotent activation - check if already active const existingState = this.agentStates.get(agent_id); if (existingState && existingState.status === "active") { return { content: [{ type: "text", text: JSON.stringify({ success: true, status: "already_active", agent_id, message: `Agent '${agent_id}' is already active`, activated_at: existingState.activated_at }, null, 2) }] }; } // Activate agent with new state const newState: AgentState = { id: agent_id, status: "active", activated_at: new Date().toISOString(), metadata: config || {} }; this.agentStates.set(agent_id, newState); console.log(`[BMAD] Agent activated: ${agent_id}`); return { content: [{ type: "text", text: JSON.stringify({ success: true, status: "activated", agent_id, message: `Agent '${agent_id}' successfully activated`, activated_at: newState.activated_at, config: newState.metadata }, null, 2) }] }; } private async executeTask(args: any) { const { agent_id, task, parameters } = args; if (!agent_id || !task) { throw new Error("agent_id and task are required"); } // Check if agent is active const agentState = this.agentStates.get(agent_id); if (!agentState || agentState.status !== "active") { throw new Error(`Agent '${agent_id}' is not active. Please activate first.`); } // Simulate task execution (replace with actual agent logic) const taskResult = { task_id: `task_${Date.now()}`, agent_id, task, parameters: parameters || {}, status: "completed", result: `Task '${task}' executed successfully by agent '${agent_id}'`, executed_at: new Date().toISOString() }; console.log(`[BMAD] Task executed:`, taskResult); return { content: [{ type: "text", text: JSON.stringify({ success: true, ...taskResult }, null, 2) }] }; } private async getAgentStatus(args: any) { const { agent_id } = args; if (!agent_id) { throw new Error("agent_id is required"); } const agentState = this.agentStates.get(agent_id); return { content: [{ type: "text", text: JSON.stringify({ success: true, agent_id, status: agentState?.status || "inactive", activated_at: agentState?.activated_at || null, metadata: agentState?.metadata || {} }, null, 2) }] }; } private createErrorResponse(error: any) { console.error(`[BMAD] Error:`, error); return { content: [{ type: "text", text: JSON.stringify({ success: false, error: true, message: error.message || "Unknown error occurred", type: error.constructor.name, timestamp: new Date().toISOString() }, null, 2) }] }; } private setupErrorHandling() { process.on('uncaughtException', (error) => { console.error('[BMAD] Uncaught Exception:', error); }); process.on('unhandledRejection', (reason, promise) => { console.error('[BMAD] Unhandled Rejection at:', promise, 'reason:', reason); }); } public async start() { const port = process.env.PORT || 3000; // Start HTTP server for Railway this.app.listen(port, () => { console.log(`[BMAD] HTTP Server running on port ${port}`); console.log(`[BMAD] Health check: http://localhost:${port}/health`); }); // Start MCP server for stdio transport const transport = new StdioServerTransport(); await this.server.connect(transport); console.log('[BMAD] MCP Server connected via stdio'); } } // Start server const server = new BMADMCPServer(); server.start().catch(console.error); export default BMADMCPServer;

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/Dali1789/bmad-mcp-server'

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