Skip to main content
Glama
server.ts5.75 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { registerTools, handleToolCall } from './tools/index.js'; import { registerPrompts, handlePromptList, handlePromptGet } from './prompts/index.js'; import { validateProjectPath } from './core/path-utils.js'; import { WorkspaceInitializer } from './core/workspace-initializer.js'; import { ProjectRegistry } from './core/project-registry.js'; import { DashboardSessionManager } from './core/dashboard-session.js'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; export class SpecWorkflowMCPServer { private server: Server; private projectPath!: string; private projectRegistry: ProjectRegistry; private lang?: string; constructor() { // Get version from package.json const __dirname = dirname(fileURLToPath(import.meta.url)); const packageJsonPath = join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); // Get all registered tools and prompts const tools = registerTools(); const prompts = registerPrompts(); // Create tools capability object with each tool name const toolsCapability = tools.reduce((acc, tool) => { acc[tool.name] = {}; return acc; }, {} as Record<string, {}>); this.server = new Server({ name: 'spec-workflow-mcp', version: packageJson.version }, { capabilities: { tools: toolsCapability, prompts: { listChanged: true } } }); this.projectRegistry = new ProjectRegistry(); } async initialize(projectPath: string, lang?: string) { this.projectPath = projectPath; this.lang = lang; try { // Validate project path await validateProjectPath(this.projectPath); // Initialize workspace const __dirname = dirname(fileURLToPath(import.meta.url)); const packageJsonPath = join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); const workspaceInitializer = new WorkspaceInitializer(this.projectPath, packageJson.version); await workspaceInitializer.initializeWorkspace(); // Register this project in the global registry const projectId = await this.projectRegistry.registerProject(this.projectPath, process.pid); console.error(`Project registered: ${projectId}`); // Try to get the dashboard URL from session manager let dashboardUrl: string | undefined = undefined; try { const sessionManager = new DashboardSessionManager(); const dashboardSession = await sessionManager.getDashboardSession(); if (dashboardSession) { dashboardUrl = dashboardSession.url; } } catch (error) { // Dashboard not running, continue without it } // Create context for tools const context = { projectPath: this.projectPath, dashboardUrl: dashboardUrl, lang: this.lang }; // Register handlers this.setupHandlers(context); // Connect to stdio transport const transport = new StdioServerTransport(); // Handle client disconnection - exit gracefully when transport closes transport.onclose = async () => { await this.stop(); process.exit(0); }; await this.server.connect(transport); // Monitor stdin for client disconnection (additional safety net) process.stdin.on('end', async () => { await this.stop(); process.exit(0); }); // Handle stdin errors process.stdin.on('error', async (error) => { console.error('stdin error:', error); await this.stop(); process.exit(1); }); // MCP server initialized successfully } catch (error) { throw error; } } private setupHandlers(context: any) { // Tool handlers this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: registerTools() })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { return await handleToolCall(request.params.name, request.params.arguments || {}, context); } catch (error: any) { throw new McpError(ErrorCode.InternalError, error.message); } }); // Prompt handlers this.server.setRequestHandler(ListPromptsRequestSchema, async () => { try { return await handlePromptList(); } catch (error: any) { throw new McpError(ErrorCode.InternalError, error.message); } }); this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { try { return await handlePromptGet( request.params.name, request.params.arguments || {}, context ); } catch (error: any) { throw new McpError(ErrorCode.InternalError, error.message); } }); } async stop() { try { // Unregister from global registry try { await this.projectRegistry.unregisterProject(this.projectPath); console.error('Project unregistered from global registry'); } catch (error) { // Ignore errors during cleanup } // Stop MCP server await this.server.close(); } catch (error) { console.error('Error during shutdown:', error); // Continue with shutdown even if there are errors } } }

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/Pimzino/spec-workflow-mcp'

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