Skip to main content
Glama
index.ts9.26 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, InitializeRequestSchema, ListToolsRequestSchema, McpError, JSONRPCMessage, } from '@modelcontextprotocol/sdk/types.js'; import { mkdir } from 'fs/promises'; import path from 'path'; import os from 'os'; import { ContextConfig, Result, Tool } from './types.js'; import { StateManager } from './state/manager.js'; import { CreateProjectTool, GetProjectTool, CreateProjectCheckpointTool, RestoreProjectCheckpointTool } from './tools/project.js'; import { CreateWorkPackageTool, GetWorkPackageTool, UpdateWorkPackageProgressTool, UpdateWorkPackageStatusTool } from './tools/workpackage.js'; import { CreateTaskTool, UpdateTaskStatusTool, RecordFileChangeTool, CreateTaskCheckpointTool, RestoreTaskCheckpointTool } from './tools/task.js'; import { StartQAReviewTool, CompleteQAReviewTool, RequestFixesTool, AcceptWorkPackageTool } from './tools/qa.js'; // Initial synchronous debug output process.stderr.write('=== MCP Server Starting ===\n'); process.stderr.write(`Environment: DEBUG=${process.env.DEBUG}\n`); process.stderr.write(`Working Directory: ${process.cwd()}\n`); process.stderr.write(`Module URL: ${import.meta.url}\n`); process.stderr.write(`Entry Point: ${process.argv[1]}\n`); // Check stdin mode process.stderr.write('Stdin Status:\n'); process.stderr.write(` isTTY: ${process.stdin.isTTY}\n`); process.stderr.write(` readable: ${process.stdin.readable}\n`); process.stderr.write(` isPaused: ${process.stdin.isPaused()}\n`); // Enhanced stdin data logging process.stdin.on('data', (data) => { process.stderr.write('=== Received stdin data ===\n'); process.stderr.write('As string: ' + data.toString() + '\n'); process.stderr.write('As buffer: ' + data.toString('hex') + '\n'); process.stderr.write('Length: ' + data.length + '\n'); process.stderr.write('========================\n'); }); const DEBUG = process.env.DEBUG === '1'; // Always log important messages, debug for details const log = (msg: string) => process.stderr.write(`[MCP] ${msg}\n`); const debug = (msg: string, ...args: any[]) => { if (DEBUG) { process.stderr.write(`[DEBUG] ${msg}\n`); if (args.length > 0) { process.stderr.write(JSON.stringify(args, null, 2) + '\n'); } } }; // Log uncaught errors process.on('uncaughtException', (error) => { log(`Uncaught Exception: ${error.message}`); console.error(error); process.exit(1); }); process.on('unhandledRejection', (reason) => { log(`Unhandled Rejection: ${reason}`); console.error(reason); process.exit(1); }); export class ContextmgrServer { private server: Server; private config: ContextConfig; private stateManager!: StateManager; private registeredTools: Map<string, Tool> = new Map(); constructor(config: ContextConfig) { this.config = config; log('Creating server instance'); log('Setting up server capabilities'); this.server = new Server( { name: 'contextmgr-mcp', version: '1.0.0', }, { capabilities: { tools: {} // Will be populated during initialization }, } ); this.server.onerror = (error) => { log(`Error: ${error}`); debug('Error details:', error); }; // Debug pipe status process.stdout.on('error', (error) => { log(`Stdout error: ${error}`); debug('Stdout error details:', error); }); process.stdout.on('close', () => { log('Stdout closed'); }); process.stdin.on('close', () => { log('Stdin closed'); }); process.on('SIGINT', async () => { log('Shutting down...'); await this.server.close(); process.exit(0); }); log('Server instance created'); } private async ensureContextDir(): Promise<string> { const contextDir = path.join( this.config.projectRoot, this.config.contextDir || 'contextmgr' ); log(`Ensuring context directory: ${contextDir}`); await mkdir(contextDir, { recursive: true }); await Promise.all([ mkdir(path.join(contextDir, 'checkpoints'), { recursive: true }), mkdir(path.join(contextDir, 'temp'), { recursive: true }) ]); return contextDir; } private async registerTools(): Promise<void> { log('Registering tools with state manager'); const toolClasses = [ CreateProjectTool, GetProjectTool, CreateProjectCheckpointTool, RestoreProjectCheckpointTool, CreateWorkPackageTool, GetWorkPackageTool, UpdateWorkPackageProgressTool, UpdateWorkPackageStatusTool, CreateTaskTool, UpdateTaskStatusTool, RecordFileChangeTool, CreateTaskCheckpointTool, RestoreTaskCheckpointTool, StartQAReviewTool, CompleteQAReviewTool, RequestFixesTool, AcceptWorkPackageTool ]; log('Initializing tools'); debug('Tool classes:', toolClasses.map(tc => tc.name)); for (const ToolClass of toolClasses) { const tool = new ToolClass(this.stateManager); this.registerTool(tool); debug('Registered tool:', tool.name); } log(`Registered ${this.registeredTools.size} tools`); } private registerTool(tool: Tool): void { debug('Registering tool:', tool.name); this.registeredTools.set(tool.name, tool); } private setupRequestHandlers(): void { log('Setting up request handlers'); // Handle initialization this.server.setRequestHandler(InitializeRequestSchema, async (request) => { log(`Handling initialize request: ${JSON.stringify(request)}`); const response = { version: '1.0.0', capabilities: { tools: Object.fromEntries( Array.from(this.registeredTools.entries()).map(([name, tool]) => [ name, { description: tool.description, inputSchema: tool.inputSchema } ]) ) } }; log(`Sending initialize response: ${JSON.stringify(response)}`); return response; }); this.server.setRequestHandler(ListToolsRequestSchema, async () => { log('Handling list_tools request'); const tools = Array.from(this.registeredTools.values()).map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })); log(`Returning ${tools.length} tools`); debug('Tools response:', tools); return { tools }; }); this.server.setRequestHandler( CallToolRequestSchema, async (request) => { log(`Executing tool: ${request.params.name}`); try { const tool = this.registeredTools.get(request.params.name); if (!tool) { log(`Tool not found: ${request.params.name}`); throw new McpError( ErrorCode.InvalidParams, `Tool ${request.params.name} not found` ); } const result = await tool.handler(request.params.arguments); if (!result.success) { log(`Tool execution failed: ${result.error}`); throw new McpError(ErrorCode.InternalError, result.error || 'Unknown error'); } log('Tool execution succeeded'); debug('Tool result:', result.data); return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } catch (error) { log(`Tool execution error: ${error}`); if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, error instanceof Error ? error.message : 'Unknown error' ); } } ); log('Request handlers configured'); } public async initialize(): Promise<void> { log('Initializing server'); const contextDir = await this.ensureContextDir(); log('Context directory ready'); this.stateManager = new StateManager(contextDir); await this.stateManager.initialize(); log('State manager initialized'); await this.registerTools(); this.setupRequestHandlers(); log('Server initialization complete'); } public async start(): Promise<void> { log('Starting server'); await this.initialize(); const transport = new StdioServerTransport(); log('Created stdio transport'); try { // Debug the incoming messages transport.onmessage = (message: JSONRPCMessage) => { debug('Raw message received:', JSON.stringify(message)); }; await this.server.connect(transport); log('Server running on stdio'); } catch (error) { log(`Failed to start server: ${error}`); process.exit(1); } } } if (process.argv[1] === import.meta.url) { log('Starting in standalone mode'); const server = new ContextmgrServer({ projectRoot: process.cwd() }); server.start().catch(error => { log(`Fatal error: ${error}`); process.exit(1); }); }

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/docherty/contextmgr-mcp'

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