Skip to main content
Glama
index.ts18 kB
/** * CodeRide MCP Server * * Entry point for the MCP server that provides task management tools * Compatible with both STDIO and Smithery HTTP deployments */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'; import express from 'express'; import http from 'http'; import { z } from 'zod'; import { HeartbeatManager } from './utils/heartbeat.js'; import { BroadcastableStdioServerTransport } from './utils/stdio-transport.js'; // Import utilities import { logger } from './utils/logger.js'; import { tokenSecurityManager } from './utils/token-security.js'; import { BaseTool } from './utils/base-tool.js'; import { createApiConfig, ApiConfig, isProductionMode } from './utils/env.js'; import { createSecureApiClient } from './utils/secure-api-client.js'; // Import tools import { GetTaskTool } from './tools/get-task.js'; import { UpdateTaskTool } from './tools/update-task.js'; import { GetProjectTool } from './tools/get-project.js'; import { UpdateProjectTool } from './tools/update-project.js'; import { GetPromptTool } from './tools/get-prompt.js'; import { StartProjectTool } from './tools/start-project.js'; // New MCP tools import { ListProjectsTool } from './tools/list-projects.js'; import { ListTasksTool } from './tools/list-tasks.js'; import { NextTaskTool } from './tools/next-task.js'; // Configuration schema for Smithery export const configSchema = z.object({ CODERIDE_API_KEY: z.string().describe("CodeRide API key for authentication") }); /** * Create mock server for development/testing */ function createMockServer() { logger.info('Creating mock CodeRide MCP server for development'); const server = new Server( { name: 'CodeRide-Mock', version: '0.9.2', }, { capabilities: { tools: {}, }, } ); // Mock tools with professional descriptions matching production const mockTools = [ { name: 'start_project', description: "Retrieves the project details and the prompt for the very first task of a specified project using the project's unique slug (e.g., 'CRD'). This is useful for initiating work on a new project or understanding its starting point.", inputSchema: { type: 'object', properties: { slug: { type: 'string', pattern: '^[A-Za-z]{3}$' } }, required: ['slug'] }, handler: async (args: any) => ({ project: { slug: args.slug, name: `CodeRide Project ${args.slug}` }, task: { number: `${args.slug}-1`, title: 'Initialize Project Architecture', prompt: 'Set up the foundational architecture and development environment for this CodeRide project. Review project requirements and establish coding standards.' } }) }, { name: 'get_prompt', description: "Retrieves the specific instructions or prompt for a given task, identified by its unique task number (e.g., 'CRD-1'). This is typically used to understand the detailed requirements or context for an AI agent to work on the task.", inputSchema: { type: 'object', properties: { number: { type: 'string', pattern: '^[A-Za-z]{3}-\\d+$' } }, required: ['number'] }, handler: async (args: any) => ({ taskPrompt: `Implement the core functionality for task ${args.number}. Focus on clean, maintainable code following CodeRide best practices. Ensure proper error handling and comprehensive testing coverage.` }) }, { name: 'get_task', description: "Retrieves detailed information for a specific task using its unique task number (e.g., 'CRD-1').", inputSchema: { type: 'object', properties: { number: { type: 'string', pattern: '^[A-Za-z]{3}-\\d+$' } }, required: ['number'] }, handler: async (args: any) => ({ number: args.number, title: `Implement Feature Component`, description: 'Develop and integrate a new feature component following CodeRide architecture patterns and design system guidelines.', status: 'to-do', priority: 'high', agent: 'AI Development Assistant', agent_prompt: 'Focus on clean code architecture and comprehensive testing', context: 'Part of the core platform development initiative', instructions: 'Follow CodeRide coding standards and ensure proper documentation' }) }, { name: 'get_project', description: "Retrieves detailed information about a specific project using its unique 'slug' (three uppercase letters, e.g., 'CRD').", inputSchema: { type: 'object', properties: { slug: { type: 'string', pattern: '^[A-Za-z]{3}$' } }, required: ['slug'] }, handler: async (args: any) => ({ slug: args.slug, name: `CodeRide ${args.slug} Platform`, description: 'AI-native task management and development workflow platform designed for modern software teams.', projectKnowledge: { components: ['task-engine', 'ai-integration', 'workflow-automation'], technologies: ['TypeScript', 'React', 'Node.js', 'MCP'], architecture: 'microservices', patterns: ['dependency-injection', 'event-driven'] }, projectDiagram: 'graph TD\n A[AI Agent] --> B[Task Engine]\n B --> C[CodeRide API]\n C --> D[Project Management]\n D --> E[Workflow Automation]' }) }, { name: 'list_projects', description: "Lists all projects in the user workspace. No input parameters required as the workspace is automatically determined from the API key authentication.", inputSchema: { type: 'object', properties: {}, required: [] }, handler: async () => ({ projects: [ { id: '1', slug: 'CRD', name: 'CodeRide Core Platform', description: 'Main CodeRide platform development' }, { id: '2', slug: 'MCP', name: 'MCP Integration Suite', description: 'Model Context Protocol integration tools' }, { id: '3', slug: 'API', name: 'CodeRide API Gateway', description: 'Unified API gateway and authentication system' } ], totalCount: 3 }) }, { name: 'list_tasks', description: "Lists all tasks within a project using the project slug (e.g., 'CDB'). Returns tasks organized by status columns with their order and current status.", inputSchema: { type: 'object', properties: { slug: { type: 'string', pattern: '^[A-Za-z]{3}$' } }, required: ['slug'] }, handler: async (args: any) => ({ project: { slug: args.slug, name: `CodeRide ${args.slug} Platform` }, taskSummary: { totalTasks: 4 }, tasksByStatus: [ { status: 'to-do', tasks: [ { number: `${args.slug}-1`, title: 'Initialize Project Architecture', status: 'to-do' }, { number: `${args.slug}-2`, title: 'Implement Core API Endpoints', status: 'to-do' } ] }, { status: 'in-progress', tasks: [ { number: `${args.slug}-3`, title: 'Develop User Authentication', status: 'in-progress' } ] }, { status: 'completed', tasks: [ { number: `${args.slug}-4`, title: 'Setup Development Environment', status: 'completed' } ] } ] }) }, { name: 'next_task', description: "Retrieves the next task in sequence based on the current task number (e.g., CDB-23 → CDB-24). This is useful for finding the next task that needs to be done in a project workflow.", inputSchema: { type: 'object', properties: { number: { type: 'string', pattern: '^[A-Za-z]{3}-\\d+$' } }, required: ['number'] }, handler: async (args: any) => { const [slug, num] = args.number.split('-'); const nextNum = parseInt(num) + 1; return { currentTask: { number: args.number }, nextTask: { number: `${slug}-${nextNum}`, title: `Implement Advanced Features`, status: 'to-do', description: 'Build advanced functionality and optimization features for the CodeRide platform.' } }; } }, { name: 'update_task', description: "Updates an existing task's 'description' and/or 'status'. The task is identified by its unique 'number' (e.g., 'CRD-1'). At least one of 'description' or 'status' must be provided for an update.", inputSchema: { type: 'object', properties: { number: { type: 'string', pattern: '^[A-Za-z]{3}-\\d+$' }, description: { type: 'string' }, status: { type: 'string', enum: ['to-do', 'in-progress', 'done'] } }, required: ['number'] }, handler: async (args: any) => ({ number: args.number, title: `CodeRide Task ${args.number}`, description: args.description || 'Updated task with enhanced functionality and improved implementation approach.', status: args.status || 'in-progress', updateConfirmation: `Successfully updated task ${args.number} in CodeRide platform` }) }, { name: 'update_project', description: "Updates a project's knowledge graph data and/or its structure diagram (in Mermaid.js format). The project is identified by its unique 'slug'. At least one of 'project_knowledge' or 'project_diagram' must be provided for an update to occur.", inputSchema: { type: 'object', properties: { slug: { type: 'string', pattern: '^[A-Za-z]{3}$' }, project_knowledge: { type: 'object' }, project_diagram: { type: 'string' } }, required: ['slug'] }, handler: async (args: any) => ({ slug: args.slug, name: `CodeRide ${args.slug} Platform`, project_knowledge: args.project_knowledge || { updated: true, components: ['enhanced-ai-engine', 'advanced-workflow'], technologies: ['TypeScript', 'React', 'Node.js', 'MCP', 'AI/ML'] }, project_diagram: args.project_diagram || 'graph TD\n A[Enhanced AI Engine] --> B[Smart Task Management]\n B --> C[CodeRide Platform]\n C --> D[Advanced Analytics]', updateConfirmation: `Successfully updated CodeRide project ${args.slug} with enhanced architecture` }) } ]; // Register list-tools handler server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: mockTools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })) })); // Register call-tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const mockTool = mockTools.find(t => t.name === toolName); if (!mockTool) { throw new McpError(ErrorCode.MethodNotFound, `Mock tool '${toolName}' not found`); } try { const result = await mockTool.handler(request.params.arguments || {}); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`Error in mock tool ${toolName}`, error as Error); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `Mock error: ${(error as Error).message}` }) }], isError: true }; } }); return server; } /** * Create production server with real API integration */ function createProductionServer(smitheryConfig: z.infer<typeof configSchema>) { logger.info('Creating production CodeRide MCP server'); // Create clean API configuration const apiConfig = createApiConfig(smitheryConfig); // Create secure API client with dependency injection const secureApiClient = createSecureApiClient(apiConfig); const server = new Server( { name: 'CodeRide', version: '0.9.2', }, { capabilities: { tools: {}, }, } ); // Initialize real tools with dependency injection const tools: any[] = [ new StartProjectTool(secureApiClient), new GetPromptTool(secureApiClient), new GetTaskTool(secureApiClient), new GetProjectTool(secureApiClient), new UpdateTaskTool(secureApiClient), new UpdateProjectTool(secureApiClient), new ListProjectsTool(secureApiClient), new ListTasksTool(secureApiClient), new NextTaskTool(secureApiClient), ]; // Register each tool with the server tools.forEach(tool => { tool.register(server); }); // Register the list-tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: tools.map(tool => tool.getMCPToolDefinition()), }; }); // Register the call-tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const tool = tools.find(t => t.name === toolName); if (!tool) { throw new McpError(ErrorCode.MethodNotFound, `Tool '${toolName}' not found`); } try { // Validate input against schema with security checks const validatedInput = await tool.validateInput(request.params.arguments); // Execute the tool with secure wrapper const result = await tool.secureExecute(validatedInput); // Return successful response with compact JSON formatting return { content: [ { type: 'text', text: JSON.stringify(result), // Compact JSON format (no pretty-printing) }, ], }; } catch (error) { // Handle errors logger.error(`Error executing tool ${toolName}`, error as Error); // Determine if this is a validation error const isValidationError = error instanceof z.ZodError; // Return error response with compact JSON formatting return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: isValidationError ? `Validation error: ${(error as z.ZodError).message}` : (error as Error).message }), }, ], isError: true, }; } }); return server; } /** * Create and configure the MCP server * This function is used by Smithery for HTTP deployments */ export default function createServer({ config }: { config?: z.infer<typeof configSchema> } = {}) { // Determine if we have a valid API key const hasValidApiKey = config?.CODERIDE_API_KEY && config.CODERIDE_API_KEY !== 'dev-key-placeholder' && config.CODERIDE_API_KEY.startsWith('CR_API_KEY_'); // Create appropriate server based on configuration const server = hasValidApiKey ? createProductionServer(config!) : createMockServer(); // Set up error handler server.onerror = (error) => { logger.error('MCP Server error', error); }; const serverType = hasValidApiKey ? 'production' : 'mock'; logger.info(`Created ${serverType} CodeRide MCP server`); return server; } /** * STDIO server class for local/CLI usage * This maintains backward compatibility for existing STDIO deployments */ class CodeRideServer { private server: Server; private httpServer: http.Server; private heartbeatManager: HeartbeatManager; constructor() { const envApiKey = process.env.CODERIDE_API_KEY; const config = envApiKey ? { CODERIDE_API_KEY: envApiKey } : undefined; this.server = createServer({ config }); this.heartbeatManager = new HeartbeatManager(); const app = express(); app.get('/health', (req, res) => { res.status(200).send('OK'); }); this.httpServer = http.createServer(app); process.on('SIGINT', async () => { await this.shutdown(); process.exit(0); }); process.on('SIGTERM', async () => { await this.shutdown(); process.exit(0); }); } public async start(): Promise<void> { try { logger.info('Starting CodeRide MCP server'); const apiConfig = createApiConfig(); logger.info(`Using CodeRide API URL: ${apiConfig.CODERIDE_API_URL}`); const transport = new BroadcastableStdioServerTransport(); await this.server.connect(transport); this.heartbeatManager.add(transport); logger.info('CodeRide MCP server started and ready to receive requests'); } catch (error) { logger.error('Failed to start server', error as Error); process.exit(1); } } private async shutdown(): Promise<void> { logger.info('Shutting down CodeRide MCP server'); tokenSecurityManager.shutdown(); await this.server.close(); this.httpServer.close(); logger.info('Server shutdown complete'); } } // Only start STDIO server if this file is run directly (not imported by Smithery) // Use Node.js compatible approach that works in both ES modules and CommonJS const isMainModule = () => { try { // ES modules approach if (typeof import.meta !== 'undefined' && import.meta.url) { return import.meta.url === `file://${process.argv[1]}`; } // Fallback: check if this file is the main entry point return process.argv[1]?.endsWith('src/index.ts') || process.argv[1]?.endsWith('dist/index.js') || process.argv[1]?.endsWith('index.js'); } catch { // If all else fails, assume we're in STDIO mode if no HTTP context return !process.env.SMITHERY_HTTP_MODE; } }; if (isMainModule()) { const server = new CodeRideServer(); server.start().catch((error: Error) => { logger.error('Server start error', error); process.exit(1); }); }

Implementation Reference

Latest Blog Posts

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/PixdataOrg/coderide-mcp'

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