feat(wip): initial commits for sub-tasks 1,2,3 for task 23

This commit is contained in:
Ralph Khreish
2025-03-24 21:31:36 +00:00
parent 1d70a7c32c
commit f49684a802
10 changed files with 4476 additions and 44 deletions

366
mcp-server/src/index.js Normal file
View File

@@ -0,0 +1,366 @@
import { FastMCP } from "fastmcp";
import { z } from "zod";
import path from "path";
import fs from "fs/promises";
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { logger } from "../../scripts/modules/utils.js";
import MCPAuth from "./auth.js";
import MCPApiHandlers from "./api-handlers.js";
import ContextManager from "./context-manager.js";
// Load environment variables
dotenv.config();
// Constants
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DEFAULT_PORT = process.env.MCP_SERVER_PORT || 3000;
const DEFAULT_HOST = process.env.MCP_SERVER_HOST || "localhost";
/**
* Main MCP server class that integrates with Task Master
*/
class TaskMasterMCPServer {
constructor(options = {}) {
this.options = {
name: "Task Master MCP Server",
version: process.env.PROJECT_VERSION || "1.0.0",
...options,
};
this.server = new FastMCP(this.options);
this.expressApp = null;
this.initialized = false;
this.auth = new MCPAuth();
this.contextManager = new ContextManager();
// Bind methods
this.init = this.init.bind(this);
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
// Setup logging
this.logger = logger;
}
/**
* Initialize the MCP server with necessary tools and routes
*/
async init() {
if (this.initialized) return;
this.logger.info("Initializing Task Master MCP server...");
// Set up express for additional customization if needed
this.expressApp = express();
this.expressApp.use(cors());
this.expressApp.use(helmet());
this.expressApp.use(express.json());
// Set up authentication middleware
this.setupAuthentication();
// Register API handlers
this.apiHandlers = new MCPApiHandlers(this.server);
// Register additional task master specific tools
this.registerTaskMasterTools();
this.initialized = true;
this.logger.info("Task Master MCP server initialized successfully");
return this;
}
/**
* Set up authentication for the MCP server
*/
setupAuthentication() {
// Add a health check endpoint that doesn't require authentication
this.expressApp.get("/health", (req, res) => {
res.status(200).json({
status: "ok",
service: this.options.name,
version: this.options.version,
});
});
// Add an authenticate endpoint to get a JWT token using an API key
this.expressApp.post("/auth/token", async (req, res) => {
const apiKey = req.headers["x-api-key"];
if (!apiKey) {
return res.status(401).json({
success: false,
error: "API key is required",
});
}
const keyDetails = this.auth.validateApiKey(apiKey);
if (!keyDetails) {
return res.status(401).json({
success: false,
error: "Invalid API key",
});
}
const token = this.auth.generateToken(keyDetails.id, keyDetails.role);
res.status(200).json({
success: true,
token,
expiresIn: process.env.MCP_JWT_EXPIRATION || "24h",
clientId: keyDetails.id,
role: keyDetails.role,
});
});
// Create authenticator middleware for FastMCP
this.server.setAuthenticator((request) => {
// Get token from Authorization header
const authHeader = request.headers?.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return null;
}
const token = authHeader.split(" ")[1];
const payload = this.auth.verifyToken(token);
if (!payload) {
return null;
}
return {
clientId: payload.clientId,
role: payload.role,
};
});
// Set up a protected route for API key management (admin only)
this.expressApp.post(
"/auth/api-keys",
(req, res, next) => {
this.auth.authenticateToken(req, res, next);
},
(req, res, next) => {
this.auth.authorizeRoles(["admin"])(req, res, next);
},
async (req, res) => {
const { clientId, role } = req.body;
if (!clientId) {
return res.status(400).json({
success: false,
error: "Client ID is required",
});
}
try {
const apiKey = await this.auth.createApiKey(clientId, role || "user");
res.status(201).json({
success: true,
apiKey,
clientId,
role: role || "user",
});
} catch (error) {
this.logger.error(`Error creating API key: ${error.message}`);
res.status(500).json({
success: false,
error: "Failed to create API key",
});
}
}
);
this.logger.info("Set up MCP authentication");
}
/**
* Register Task Master specific tools with the MCP server
*/
registerTaskMasterTools() {
// Add a tool to get tasks from Task Master
this.server.addTool({
name: "listTasks",
description: "List all tasks from Task Master",
parameters: z.object({
status: z.string().optional().describe("Filter tasks by status"),
withSubtasks: z
.boolean()
.optional()
.describe("Include subtasks in the response"),
}),
execute: async (args) => {
try {
// In a real implementation, this would use the Task Master API
// to fetch tasks. For now, returning mock data.
this.logger.info(
`Listing tasks with filters: ${JSON.stringify(args)}`
);
// Mock task data
const tasks = [
{
id: 1,
title: "Implement Task Data Structure",
status: "done",
dependencies: [],
priority: "high",
},
{
id: 2,
title: "Develop Command Line Interface Foundation",
status: "done",
dependencies: [1],
priority: "high",
},
{
id: 23,
title: "Implement MCP Server Functionality",
status: "in-progress",
dependencies: [22],
priority: "medium",
subtasks: [
{
id: "23.1",
title: "Create Core MCP Server Module",
status: "in-progress",
dependencies: [],
},
{
id: "23.2",
title: "Implement Context Management System",
status: "pending",
dependencies: ["23.1"],
},
],
},
];
// Apply status filter if provided
let filteredTasks = tasks;
if (args.status) {
filteredTasks = tasks.filter((task) => task.status === args.status);
}
// Remove subtasks if not requested
if (!args.withSubtasks) {
filteredTasks = filteredTasks.map((task) => {
const { subtasks, ...taskWithoutSubtasks } = task;
return taskWithoutSubtasks;
});
}
return { success: true, tasks: filteredTasks };
} catch (error) {
this.logger.error(`Error listing tasks: ${error.message}`);
return { success: false, error: error.message };
}
},
});
// Add a tool to get task details
this.server.addTool({
name: "getTaskDetails",
description: "Get detailed information about a specific task",
parameters: z.object({
taskId: z
.union([z.number(), z.string()])
.describe("The ID of the task to get details for"),
}),
execute: async (args) => {
try {
// In a real implementation, this would use the Task Master API
// to fetch task details. For now, returning mock data.
this.logger.info(`Getting details for task ${args.taskId}`);
// Mock task details
const taskDetails = {
id: 23,
title: "Implement MCP Server Functionality",
description:
"Extend Task Master to function as an MCP server, allowing it to provide context management services to other applications.",
status: "in-progress",
dependencies: [22],
priority: "medium",
details:
"This task involves implementing the Model Context Protocol server capabilities within Task Master.",
testStrategy:
"Testing should include unit tests, integration tests, and compatibility tests.",
subtasks: [
{
id: "23.1",
title: "Create Core MCP Server Module",
status: "in-progress",
dependencies: [],
},
{
id: "23.2",
title: "Implement Context Management System",
status: "pending",
dependencies: ["23.1"],
},
],
};
return { success: true, task: taskDetails };
} catch (error) {
this.logger.error(`Error getting task details: ${error.message}`);
return { success: false, error: error.message };
}
},
});
this.logger.info("Registered Task Master specific tools");
}
/**
* Start the MCP server
*/
async start({ port = DEFAULT_PORT, host = DEFAULT_HOST } = {}) {
if (!this.initialized) {
await this.init();
}
this.logger.info(
`Starting Task Master MCP server on http://${host}:${port}`
);
// Start the FastMCP server
await this.server.start({
port,
host,
transportType: "sse",
expressApp: this.expressApp,
});
this.logger.info(
`Task Master MCP server running at http://${host}:${port}`
);
return this;
}
/**
* Stop the MCP server
*/
async stop() {
if (this.server) {
this.logger.info("Stopping Task Master MCP server...");
await this.server.stop();
this.logger.info("Task Master MCP server stopped");
}
}
}
export default TaskMasterMCPServer;